EVM本质

**          EVM(Ethereum Virtual Machine)** 是以太坊区块链的核心组件之一,其本质是一个状态机,也可以理解为一种虚拟机。它为以太坊智能合约的执行提供了一个运行时环境,并确保智能合约在所有以太坊节点上以完全相同的方式执行。下面我们从定义、特点、工作原理和实现方式四个方面探讨 EVM 的本质与实现。

1. 什么是 EVM?

**      EVM 的定义**

     EVM 是以太坊智能合约的运行时环境,它本质上是一个去中心化的、完全独立的虚拟机,能在不同节点上以相同方式执行代码。EVM 是图灵完备的,支持条件判断、循环、函数调用等编程语言特性。

**     EVM 的特点**

          • 图灵完备: 支持复杂计算逻辑。

          • 去中心化: 所有节点执行代码都保证一致性,结果可验证。

          • 状态驱动: EVM 是一个状态机,状态的改变依赖于智能合约代码的执行。

          • 账户模型: 以太坊使用账户模型(而非比特币的 UTXO 模型)管理状态。

2. EVM 的工作原理

**     核心概念**

          1. 账户(Accounts)

               • EVM 有两种账户:

                    • 外部拥有账户(EOA):由私钥控制,用于发起交易。

                    • 合约账户:由代码控制,代码存储在账户内。

          2. 字节码(Bytecode)

               • 智能合约以字节码形式部署在以太坊网络上,EVM 通过解释和执行这些字节码来完成工作。

          3. Gas(燃料)

               • 为防止恶意代码消耗过多资源,EVM 的每一步操作都会消耗一定的 Gas。

               • Gas 是计算成本的度量单位,由发起者支付。

          4. 状态树

               • EVM 的状态存储在一个默克尔-帕特里夏树中,记录了账户余额、合约代码和存储。

**     执行过程**

          EVM 的执行过程分为以下几个步骤:

               1. 交易验证: 外部账户发起交易,节点检查交易合法性(如签名、余额是否足够支付 Gas)。

               2. 加载字节码: 将合约的字节码加载到 EVM 中。

               3. 指令执行:

                    • EVM 逐步执行字节码中的操作码(Opcode)。

                    • 每个操作码完成特定任务,如数学计算、条件跳转、存储读写等。

               4. 更新状态: EVM 根据指令执行结果更新账户状态(余额、存储数据等)。

               5. 消耗 Gas: 每一步操作消耗对应的 Gas,当 Gas 消耗完时,交易会回滚。

3. EVM 是如何实现的?

     EVM 的实现可以从以下几个方面进行拆解:

**     虚拟机架构**

     EVM 是一个栈式虚拟机:

          • 有一个大小固定的栈(最大深度 1024),用于保存操作数和中间结果。

          • 操作码以字节形式表示,每个操作码代表一种指令(如 ADD、SUB)。

          • 没有通用寄存器,所有计算都通过栈完成。

**     关键组件**

          1. 堆栈(Stack):

               用于存储操作数和指令执行的中间结果。

          2. 内存(Memory):

               是一段临时存储空间,交易执行结束后内存会被清空。

          3. 存储(Storage):

               是一个永久存储空间,与账户绑定,用于存储智能合约的数据。

          4. 指令集(Opcodes):

               EVM 提供了一系列操作码,比如算术操作(ADD、MUL)、逻辑操作(AND、OR)、条件跳转(JUMP)、存储操作(SSTORE、SLOAD)等。

**     实现方式**

          EVM 的实现通常分为以下几个步骤:

               1. 编译 Solidity 合约代码为字节码:

                    • 使用编译器(如 solc)将高级语言(如 Solidity)编译为字节码。

               2. 解析字节码:

                    • 节点接收到交易时,会加载智能合约的字节码到 EVM。

               3. 执行字节码:

                    • 按照字节码指令逐条执行,使用堆栈完成计算,操作内存和存储。

               4. 更新状态:

                    • 合约执行完成后,将新的账户状态写入状态树。

**     主流实现**

          • Go Ethereum (Geth): 以太坊的官方 Go 实现。

          • OpenEthereum (Parity): 高性能的以太坊客户端。

          • evmone: 专注于优化 EVM 执行速度的独立实现。

     这些实现中,EVM 作为核心模块,采用不同的优化策略以提升效率。

4. EVM 的局限与扩展

**     局限性**

     (1). 性能:

               因为是解释型虚拟机,EVM 的执行效率较低,尤其在面对高计算量任务时。

     (2). 复杂性:

               智能合约的代码需要为 Gas 成本优化,增加了开发复杂性。

     (3). 存储成本高:

               合约存储的数据会永久保存在区块链中,成本昂贵。

**     扩展与改进**

     (1). EVM 优化:

               • JIT 编译器(Just-In-Time Compilation): 动态将字节码编译为机器码。

               • 优化指令集: 简化复杂操作,降低 Gas 消耗。

     (2). 新虚拟机(如 eWASM):

               • eWASM 是以太坊 2.0 的未来虚拟机,实现基于 WebAssembly(WASM),支持更高性能和多语言开发。

     (3). Layer 2 技术:

               • 通过将部分计算移到链下(如 Rollups),减轻主链的负担。

总结

     EVM 的本质是一个基于栈式架构的虚拟机,负责解释和执行智能合约的字节码。它为以太坊提供了去中心化的计算环境和一致性保障。在 EVM 的支持下,以太坊能够承载图灵完备的智能合约和去中心化应用(DApps)。

     尽管存在性能与成本方面的挑战,但通过优化与扩展,EVM 仍然是区块链技术的重要基石,推动着去中心化应用的创新与发展。

疑问

1、如果是外部账户发起一个转账的操作,是否需要通过EVM?

    外部账户发起的转账操作需要通过 EVM,但其具体执行过程相对简单,与智能合约的复杂逻辑执行相比,只是 EVM 的一种基本操作。下面详细说明为什么以及如何通过 EVM 完成外部账户的转账。

外部账户转账的基本流程

1. 转账的发起
  外部账户(EOA,Externally Owned Account)由用户控制,通过私钥生成。发起转账操作时,用户需要签名并发送一笔交易。

  交易主要包含以下内容:
  •发送方地址(From): 发起交易的外部账户地址。
  •接收方地址(To): 交易的目标地址,可以是另一个外部账户,也可以是一个合约账户。
  •转账金额(Value): 转账的以太币数量。
  •Gas 限制(Gas Limit): 为交易执行设定的最大 Gas 消耗。
  •Gas 价格(Gas Price): 每单位 Gas 的价格,用于计算交易费用。
  •签名(Signature): 用发送方的私钥对交易签名,证明交易的合法性。

2. 节点验证交易
  节点在接受到交易后,会首先验证其合法性:
    1.确保发送方账户中余额足够支付转账金额及 Gas。
    2.验证交易的签名是否正确。
    3.检查交易的 nonce 是否正确(防止交易重放)。
3. EVM 执行转账
  如果交易合法,节点会调用 EVM 来更新区块链状态:
    1.加载交易数据到 EVM:
       •EVM 从交易数据中提取 FromTo 地址、转账金额等信息。
       •确认 To 地址是否为外部账户或合约账户。
    2.更新状态树:
       •如果 To 是外部账户,EVM 会直接将转账金额从 From 的余额中扣除,并添加到 To 的余额中。
       •如果 To 是合约账户,EVM 会触发合约代码的执行(例如调用 fallback 或 receive 函数)。
    3.消耗 Gas:
       •即使只是简单的转账操作,也需要支付 Gas(最小值是 21,000 Gas)。
       •Gas 费用从 From 的账户中扣除,作为奖励发给矿工。

外部账户转账与 EVM 的关系

为什么需要通过 EVM?
    1.统一的状态管理:
     •EVM 是以太坊的核心状态管理工具,所有账户的余额变化都由EVM处理并记录在状态树中。
    2.状态可验证性:
     •EVM 确保每个节点对转账操作的执行结果一致,这是去中心化的基础。
    3.合约账户的兼容性:
     •即使是简单的转账,目标地址可能是合约账户,必须通过 EVM 执行以决定如何处理该交易。

EVM 的角色
    •外部账户对外部账户:
      •仅更新两者的余额,EVM 执行过程非常简单。
    •外部账户对合约账户:
      •如果目标账户是合约,EVM 会加载并执行目标合约中的代码。


外部账户转账的特殊情况
1. 转账到合约账户
    如果目标账户是一个智能合约,EVM 不仅会更新余额,还会执行以下操作:
    •调用智能合约的 receive() 函数(如果定义了此函数)。
    •如果未定义 receive(),则调用 fallback() 函数。
2. 转账失败
    可能的原因:
    •发送方余额不足。
    •Gas 不够,导致交易执行中途失败。
    •目标地址的合约逻辑导致交易被回滚(例如触发 revert 或 require 条件失败)。
在这些情况下,EVM 会回滚所有状态更新,但仍然会扣除发送方的 Gas 费用。


总结
  即使是外部账户之间的简单转账,仍需要通过 EVM 执行。这是因为 EVM 是以太坊的统一状态管理工具,负责:
      1.验证交易的合法性。
      2.计算转账结果(余额更新)。
      3.确保所有节点对结果达成共识。
  对于转账到合约账户的情况,EVM 的作用更加显著,它需要加载并执行智能合约代码,决定如何处理这笔交易。通过 EVM,以太坊实现了强一致性和智能合约的无缝交互。

2、Geth(Go Ethereum)中的EVM实现的本质是一个Go 的协程还是什么?

   在 Geth (Go Ethereum) 中,EVM(以太坊虚拟机)的实现本质上并不是一个 Go 的协程,而是通过 Go 的常规程序执行模型来执行智能合约的字节码,尽管 Geth 使用 Go 协程(goroutines)来处理并发和任务调度。

EVM 的执行本质
   EVM 在 Geth 中的实现是一个 解释型虚拟机,通过解析和逐步执行智能合约的字节码(opcode)来更新以太坊状态(账户余额、存储、合约状态等)。它是一个栈式虚拟机,其中使用栈来保存操作数,并根据操作码(opcode)执行不同的操作。EVM 执行过程中的每一个指令(例如加法、条件跳转、存储操作等)都涉及计算和状态更新。

EVM 和 Go 协程的区别:
   1.EVM 执行模型:
    •解释型执行: EVM 是通过逐条解释和执行字节码(智能合约的字节码)来实现的。这些字节码操作包括算术运算、存储读写、内存操作等。每个操作的执行消耗 Gas,而整个智能合约的执行可以理解为对状态树的操作。
    •栈式结构: EVM 使用栈来进行计算,每个指令操作从栈中取出相应的操作数并进行计算,更新栈上的结果。
    •EVM 本身并没有直接实现并发执行,也没有依赖 Go 协程来执行合约的字节码。它是单线程执行的,每个指令按顺序依次执行。
    
   2.Go 协程的作用:
    •Geth 在处理 以太坊节点的网络交互、交易处理、区块同步、RPC 请求 等任务时,广泛使用 Go 的协程(goroutines)来实现并发操作。Go 协程用于并行处理多个事务,提升吞吐量和性能。
    •EVM 执行本身并不会依赖 Go 协程,但在 Geth 的整体架构中,EVM 执行是作为一个函数调用存在的,而其他并发操作(如区块验证、交易的异步处理)会通过 Go 协程并行处理。

如何理解 Geth 中的 EVM 执行:
    1.EVM 执行过程:
     •在 Geth 中,EVM 执行是通过一个名为 evm 的 Go 结构体来管理的。evm 结构体负责存储智能合约的执行上下文,如当前的状态、Gas 使用情况、内存、栈等。
     •当一个智能合约的调用被发起时,Geth 会初始化一个 evm 实例,加载合约字节码,并逐条解析并执行字节码操作。每执行一条指令时,EVM 会更新相关的状态(例如合约的存储、账户的余额等)。
    2.Go 协程的作用:
     •在 EVM 执行过程中,Geth 可能会启动 Go 协程来处理其他的异步任务,比如发送和接收网络请求、异步验证交易、与其他节点同步区块等。这样可以充分利用多核 CPU 和提高并发处理能力。
     •例如,在执行合约时,Geth 可能会通过 Go 协程并行处理与外部世界的交互(如网络请求),但这些操作并不会直接影响 EVM 执行本身。EVM 主要负责合约的字节码解释和状态更新。

总结
•EVM 本身在 Geth 中并不是通过 Go 协程来执行的,它是一个单线程的解释型虚拟机,逐条执行智能合约字节码。
•Go 协程在 Geth 中用于并发处理其他任务,如交易处理、区块同步和网络交互等,但 EVM 的执行过程是顺序化的,不依赖协程。

3、执行合约需要计算资源,消耗 Gas。Gas 的费用由调用者支付,按执行的指令数量计算。那么不同的指令消耗的gas一样吗?

   不同的指令在 EVM(以太坊虚拟机) 中消耗的 Gas 费用是不一样的。每个指令或操作的计算成本取决于其复杂性,执行时所需的计算资源、内存和存储需求。因此,EVM 为每个操作设定了不同的 Gas 消耗。

Gas 费用的基础
 •Gas Limit(Gas 限制): 交易发起者设定的最大 Gas 消耗限制,防止某些操作过度消耗资源。
 •Gas Price(Gas 价格): 每单位 Gas 的价格,决定了交易执行的费用。Gas 价格通常由用户设置,并由矿工决定接受与否。


不同指令的 Gas 消耗
EVM 中的每个指令(操作码,Opcode)对应着不同的计算或存储操作,因此消耗的 Gas 也不同。以下是一些常见指令及其 Gas 消耗:
  1. 算术操作
    •ADD, MUL, SUB, DIV 等:简单的数学计算(如加法、减法、乘法、除法)通常消耗少量 Gas,约为 3 Gas。

  2. 存储操作
    •SLOAD(加载存储):从合约的存储中加载一个值。每次加载操作需要 200 Gas。
    •SSTORE(存储操作):向合约存储写入一个新值。它的 Gas 消耗较高,分为以下两种情况:
    •如果原存储位置的值发生变化(从非零变为零),则消耗 5,000 Gas。
    •如果原存储位置的值发生变化(从零变为非零),则消耗 20,000 Gas。
    写入存储是 Gas 消耗最昂贵的操作之一,因为存储空间有限且昂贵。

3. 内存操作
    •MSTORE / MLOAD(内存存取):
    •内存的读写相对便宜。每次读取和写入内存大约消耗 3 Gas。
    •但是内存操作会消耗更多的 Gas,尤其是当内存的大小增加时。

4. 数据操作
    •CALL(调用其他合约):调用其他合约的成本较高。Gas 消耗取决于被调用合约的复杂度和调用的数据量。
    •每次合约调用大致消耗 700 Gas(用于执行调用过程中的标准操作),如果涉及到传递大量数据,则会增加消耗。

5. 条件跳转和逻辑
    •JUMP / JUMPI(跳转指令):跳转到代码中的特定位置或条件跳转。每次跳转操作消耗 8 Gas。
    •EQ, GT, LT, ISZERO 等(逻辑比较):这些逻辑操作通常消耗 3 Gas。

6. 其他操作
    •CREATE(创建合约):创建新合约会消耗大量 Gas,因为它不仅需要执行创建过程,还要存储合约的字节码。创建合约的 Gas 消耗大约为 32,000 Gas。
    •SELFDESTRUCT(销毁合约):销毁合约会释放存储空间,因此消耗的 Gas 取决于销毁时释放的存储量。基本消耗为 5,000 Gas,但如果销毁的合约释放了大量存储,则会根据释放量增加Gas。


Gas 费用的设定依据
EVM 中的 Gas 费用是根据以下因素设定的:
    1.操作的计算复杂性: 比如加法和乘法,乘法需要更多计算资源,因此 Gas 更高。
    2.存储与数据操作: 存储操作(SSTORE, SLOAD)通常比计算操作消耗更多 Gas,因为存储空间的管理和更新涉及更多资源。
    3.状态变更: 如果执行操作导致合约状态(存储)发生改变,通常会消耗更多 Gas。

Gas 费用的优化
开发者可以通过优化智能合约的代码来减少 Gas 消耗。常见的优化方式包括:
    •减少存储操作: 存储写入和读取消耗 Gas,避免不必要的存储更新。
    •使用合适的算法: 在计算密集型操作中,使用更加高效的算法来减少计算步骤。
    •批量处理: 尽量减少合约的外部调用次数,可以减少每次调用的 Gas 开销。
    •优化内存使用: 避免过多的内存分配,合理使用内存空间。

总结
  在 EVM 中,不同的指令消耗的 Gas 费用是不同的。Gas 费用主要取决于操作的复杂性、计算资源需求、存储更新等因素。开发者需要注意优化智能合约的 Gas 消耗,以减少交易费用并提高执行效率。在开发和部署智能合约时,理解各类操作的 Gas 费用是非常重要的。

4、EVM中加载的字节码是一串十六进制的编码,怎么转化为指令呢?

    在 EVM(以太坊虚拟机) 中,加载的字节码(也就是智能合约的字节码)是一串十六进制的编码,这些字节码实际上是由 EVM 指令(即 操作码,Opcodes)构成的。每个 EVM 操作码是一个固定大小的字节,代表着一个特定的指令。EVM 执行这些字节码时,会逐条解析它们,依照每个指令的含义执行相关操作。

字节码转化为 EVM 指令的过程
1.字节码的结构
    •EVM 的字节码由一系列的操作码(Opcode)和操作数(Operands)构成。每个操作码通常是一个字节(8位),也有些操作码需要额外的字节来表示更复杂的操作。
    •例如,EVM 中的字节码可以包含以下内容:
        •操作码(Opcode):表示特定的操作,如加法、存储、跳转等。每个操作码对应 EVM 中的一个指令。
    •操作数(Operands):某些指令需要额外的参数来完成操作,比如跳转目标地址、存储位置等。
2.如何加载字节码
    •当智能合约部署到以太坊网络时,它会生成一个字节码,通常由编译器(如 Solidity 编译器)生成。这段字节码被存储在区块链上,并通过交易的方式传递给以太坊节点。
    •在执行智能合约时,EVM 会加载这段字节码,并开始逐条解析它。每个字节对应一个操作码或参数。
3.字节码到指令的转化
    •EVM 的指令集(即操作码)是固定的,每个操作码对应一个特定的操作。例如:
        •0x01:加法操作(ADD)
    •0x02:乘法操作(MUL)
    •0x03:存储操作(SSTORE)
    •这些字节在 EVM 中逐条解释并执行,每个字节码根据其位置决定其含义。
    •对于更复杂的操作(如存储操作、跳转等),可能需要附带额外的操作数。例如,SSTORE 操作需要指定存储位置和要存储的值。

4.如何执行操作
    •以太坊节点通过加载字节码并逐条解释这些操作码,最终执行合约逻辑。EVM 通过解析字节码的每个操作码(Opcode),并根据操作码对应的行为更新智能合约的状态。

字节码示例与指令映射
    假设我们有一个简单的字节码示例:
0x60606040523415600e57600080fd5b6040516020806101f8833981016040528080516101008152602001806101006020028101906101006040525b505b60008060009054906101000a900460ff169056fea26469706673582212200893f1d716c4240c4a75ffcfcb1e3a1c9fa49a2d407b7b840efad44058091f1d64736f6c634300060c0033

    这个字节码中的每个字节都是操作码(opcode)和参数的组合,EVM 会依次执行它们。让我们分解前几个字节来看看它们如何对应指令:
    •0x60(PUSH1):这个操作码是 PUSH 指令,它用于将常量推入栈中。PUSH1 表示推送 1 字节的数据。
    •0x60(PUSH1):继续推送 1 字节数据。
    •0x40(MSTORE):MSTORE 操作码,用于将栈顶的值存储到内存中。
    •0x52(SSTORE):SSTORE 操作码,用于将栈顶的值存储到合约的存储中。

具体流程
  1.加载字节码:
    EVM 从区块链中加载合约的字节码。假设字节码在合约存储中,EVM 会将其读取到内存中。
  2.逐条解析指令:
    EVM 会逐条解析字节码中的每个操作码。每个操作码代表一个特定的指令,如 ADD, MUL, SLOAD, SSTORE 等。
  3.执行指令:
    每个操作码在 EVM 中都有一个对应的实现。例如,PUSH1 会将下一个字节(或一组字节)推入 EVM 的栈中,ADD 会弹出栈顶的两个元素,执行加法并将结果重新压入栈中。
  4.处理操作数:
    某些操作指令会使用附加的字节作为参数,这些字节被解析并传递给相应的操作。例如,PUSH1 后面跟着的字节会被推入栈中,SSTORE 需要一个位置和一个值来更新存储。
  5.继续执行:
    这个过程会持续进行,直到字节码执行完毕或由于错误导致交易失败。

总结
  •字节码到指令的转化是通过解析字节码中的每个操作码来实现的。每个操作码代表一个 EVM 指令,EVM 会逐条执行这些指令并根据其定义更新合约的状态。
  •字节码中的每个字节对应一个操作码,操作码后可能跟随参数,EVM 会根据这些参数来执行具体操作。
  •EVM 是一个解释型虚拟机,它通过逐条解析和执行字节码,最终完成智能合约的执行。

5、外部账户调用智能合约中的函数时,EVM的执行流程是什么样的呢?结合操作码说明

    外部账户(EOA)调用智能合约中的函数时,以太坊虚拟机(EVM)的执行流程涉及多个步骤。整个过程可以分为 交易的构建与发送、合约函数的选择、EVM 执行指令 三个阶段,并结合操作码进行详细说明。

1. 外部账户构建并发送交易
  外部账户通过签名构建交易并发送至以太坊网络:
    •发送交易信息:
    •to: 目标合约地址。
    •data: 包含函数选择器(4 字节的哈希)和函数参数的编码数据。
    •value: 发送的以太币金额(如果有)。
    •gasLimit: 最大 Gas 消耗。
    •gasPrice: 每单位 Gas 的价格。

2. EVM 的执行流程
  在交易抵达目标智能合约后,EVM 开始执行。以下是主要的流程步骤:

2.1 交易入队和函数选择
  (1).合约地址的校验与字节码加载:
      •EVM 首先检查交易的目标地址是否对应一个合约。
      •如果目标地址是一个合约,EVM 会从状态存储中加载该合约的字节码。
  (2).解析输入数据:
      •输入数据 data 被解析为函数选择器(前 4 字节)和剩余参数。
      •根据函数选择器,EVM 在合约字节码中查找对应函数的入口点。
   相关操作码:
    •CALLDATALOAD:从输入数据中加载指定位置的数据。
    •CALLDATASIZE:获取输入数据的长度。
    •CALLDATACOPY:将输入数据复制到内存。

2.2 字节码指令的执行
   
   EVM 是基于堆栈架构的虚拟机,操作字节码时,每条指令依次执行并操作堆栈、内存和存储。
   以下是关键执行阶段及对应操作码说明:
     (a) 函数参数的解析与校验
        •EVM 根据函数选择器读取参数,并验证是否满足 ABI 编码的格式和函数要求。
      •CALLDATALOAD:将参数加载到栈中。
      •ISZERO:检查输入是否为零。
      •JUMPI:条件跳转指令,用于验证输入合法性。

     (b) 合约逻辑的执行
        •EVM 执行函数逻辑,通常包括以下操作:
    (1).数学计算:
        •操作码示例:
          •ADD:栈顶两个数相加。
          •MUL:栈顶两个数相乘。
          •DIV:栈顶两个数相除。
          •MOD:取模运算。
    (2).内存与存储操作:
        •操作码示例:
          •MLOAD:从内存中读取数据。
          •MSTORE:将数据存入内存。
          •SLOAD:从合约存储中读取数据。
          •SSTORE:将数据存入合约存储。
    (3).条件与循环:
        •操作码示例:
          •EQ:比较栈顶两个值是否相等。
          •LT、GT:比较大小。
          •JUMP、JUMPI:跳转指令,用于实现条件逻辑和循环。

      (c) 可能的外部调用
    如果函数中包含对其他合约的调用(如 transfer 或 call),EVM 会生成一个子调用上下文,执行调用目标的字节码。
      •操作码示例:
              •CALL:发起外部合约调用。
          •DELEGATECALL:代理调用,不更改存储上下文。
          •STATICCALL:静态调用,不允许修改状态。
      (d) 返回值或错误处理
        函数执行完成后,返回值通过内存区域返回调用方。如果执行失败,则触发回滚。
      •操作码示例:
        •RETURN:返回值。
        •REVERT:回滚操作,并返回错误消息。
        •NVALID:无效操作码,导致失败。
        •STOP:正常结束执行。

2.3 Gas 消耗的计算
  EVM 每执行一条操作码都会消耗对应的 Gas,Gas 消耗根据操作码的复杂性不同而不同。
    •示例:
        •ADD、MUL 等基本算术操作消耗较少的 Gas。
        •SSTORE(写入存储)等操作消耗大量 Gas。
        •如果 Gas 消耗超过 gasLimit,交易会被终止,并触发回滚。

3. 示例:外部账户调用智能合约函数
假设一个合约包含以下函数:
pragma solidity ^0.8.0;

contract Example {
    uint256 public value;

    function setValue(uint256 _value) public {
        value = _value;
    }
}

调用 setValue(42) 的流程如下:
    (1).外部账户发送交易:
    •to: 合约地址。
    •data: 0x60fe47b1(函数选择器) + 000000000000000000000000000000000000000000000000000000000000002a(参数 42 的编码)。
    (2).EVM 执行流程:
    •读取输入数据:
        •CALLDATALOAD 提取函数选择器和参数。
    •加载函数入口点:
        •查找 setValue 对应的逻辑。
    •执行逻辑:
        •参数 42 被写入存储:
        •操作码 SSTORE 执行,将数据存入存储。
    •结束执行:
        •返回结果(空返回值)。

4. 总结
    EVM 的执行流程严格按照操作码逐条执行,通过堆栈、内存、存储三部分实现指令操作。外部账户调用智能合约时,EVM 从输入数据解析函数,加载字节码并依次执行对应的指令,直到返回或终止。了解操作码及其作用是深入理解智能合约运行机制的重要基础。

6、如果函数中包含对其他合约的调用(如 transfer 或 call),EVM 会生成一个子调用上下文,执行调用目标的字节码。这个子调用是指开启一个新的线程吗?

以太坊虚拟机(EVM)中的子调用并不是开启一个新的线程,而是通过在同一执行上下文中创建一个新的调用栈帧来实现的。这是一种同步的调用方式,所有的子调用操作都发生在当前线程(或当前执行上下文)内,而非多线程或并行执行。

EVM 子调用的原理
1.堆栈模型:
   •EVM 是基于堆栈的虚拟机,每次调用另一个合约时,会在调用栈中创建一个新的栈帧(frame),用于保存当前的执行状态(例如调用者的地址、剩余 Gas 等)。
   •子调用完成后,控制权返回到调用者,并继续执行调用者的后续指令。
2.同步调用:
   •子调用是同步的,意味着调用者会等待子调用完成后,才会继续执行自己的逻辑。
   •如果子调用失败,调用者会立即感知到,并根据失败的结果决定下一步操作(如回滚或忽略错误)。

调用的主要操作码
  EVM 提供以下操作码来支持子调用,这些操作码会触发新的调用栈帧:
1.CALL:
    •用于执行另一个合约的代码,同时可以传递 ETH。
    •调用时,会扣除调用者的 Gas,分配给被调用者。
    •如果调用失败(例如 Gas 不足或代码异常),调用者会得到失败结果。

2.DELEGATECALL:
    •用于代理调用,被调用者的代码会在调用者的上下文中执行(不改变存储)。
    •被常用于代理模式(例如 OpenZeppelin 的代理合约)。

3.STATICCALL:
    •类似于 CALL,但禁止在被调用者中修改状态(例如写入存储、发送 ETH)。

4.CREATE:
    •用于部署一个新的合约。
    •创建合约时,EVM 会分配一个新的调用栈帧,执行合约的构造函数。

5.CREATE2:
    •类似于 CREATE,但允许通过特定的计算规则指定新合约的地址。


调用过程的同步性

调用栈管理
  •每次子调用时,EVM 会将当前执行状态(例如程序计数器、堆栈、内存等)保存到调用栈中,并进入新的执行上下文。
  •调用完成后,EVM 会弹出调用栈,恢复到上一级的状态。

同步调用的特性
  •调用者会等待子调用完成,无论成功还是失败,然后处理返回值或错误。
  •没有并行性:EVM 的设计是单线程的,所有操作是顺序执行的,子调用不会导致并行处理。

示例:合约调用的同步性
  假设有两个合约 A 和 B:
合约 A:
pragma solidity ^0.8.0;

contract A {
    function callB(address _b) public {
        B(_b).doSomething();
    }
}
合约 B:
pragma solidity ^0.8.0;

contract B {
    function doSomething() public {
        // 执行逻辑
    }
}

当外部账户调用 A.callB() 时:
    1.EVM 执行 callB 函数,并进入 B.doSomething 的执行上下文。
    2.EVM 在执行 B.doSomething 的过程中,会暂停 A.callB 的执行,直到 B.doSomething 完成。
    3.B.doSomething 完成后,返回控制权到 A.callB。

整个过程是同步的,没有开启新的线程。

子调用 vs 线程
  EVM 的设计决定了它不支持多线程或并发操作:
  1.单线程模型:
      •以太坊是一个全局共享状态的区块链,所有交易按顺序执行,确保状态的确定性和一致性。
      •如果 EVM 使用多线程,可能会导致竞态条件(Race Condition)或状态不一致问题。
  2.Gas 消耗的限制:
      •EVM 的 Gas 模型确保每次交易在有限的计算资源内完成。多线程可能导致资源难以精确计量。
  3.去中心化要求:
      •多线程需要更复杂的协调机制,这与以太坊的去中心化架构设计目标相冲突。

总结
  EVM 中的子调用是通过调用栈帧管理实现的同步调用,而不是开启新的线程。每次子调用会暂时中断调用者的执行,直到子调用完成后才返回。通过这种方式,EVM 确保了交易的确定性和状态的一致性,同时避免了多线程可能带来的复杂性和潜在问题。