从账本到状态机
我们通常用“分布式账本”的类比来描述像比特币这样的区块链,它使用密码学的基本工具来实现去中心化的货币。而以太坊也有自己的本土加密货币并同样遵循着分布式账本规则,但同时它也支持更强大的功能“智能合约”,这也是以太坊可编程的关键,因此以太坊除了是一个分布式账本之外,还是一个状态机器,可以根据预定义的规则(智能合约)在不同区块之间更改。
组件

-
程序计数器 (Program Counter, PC):记录当前正在执行的指令在字节码中的位置。
-
栈 (Stack):用于存放操作数。栈项的大小为 256 位,最大深度为 1024 层。
-
内存 (Memory):一个按字节寻址的连续字节数组,初始为空,执行时按 32 字节(Word)为单位动态扩展。
-
剩余 Gas (gasAvailable):当前上下文中可供使用的计算资源总量。
-
存储 (Storage):这是持久化状态的一部分(属于全局状态 σ),不像栈和内存那样在执行结束后消失
EVM 如何工作
EVM 运行在以太坊的每个节点上,当部署一个智能合约时,合约代码会被编译成字节码,然后通过网络广播到所有节点。EVM 在每个节点上执行这些字节码,确保所有节点对交易和合约状态达成共识。
和 Java 的 JVM 不同,EVM 不是一个需要单独下载的独立软件,而是嵌入在以太坊客户端中的运行时环境。开发者可以通过以下方式获取 EVM 环境:
- 安装以太坊客户端 :如 ethereumjs-vm(js 实现)、evnone(c++实现)、revm(rust 实现) 或 Py-EVM(python 实现),这些客户端都内置了 EVM。
- 使用开发框架 :如 Hardhat 或 Truffle,它们在本地启动一个模拟的 EVM 环境,方便开发和测试。
- 在线 IDE :如 Remix IDE,无需本地安装,直接通过浏览器连接到远程 EVM 实例。
比如用 Solidity 编写的合约,.sol 编译为 EVM 字节码(十六进制操作码)和 ABI 应用二进制接口。每个操作码执行会消耗燃料,具体看EVM 操作码

交易执行的三阶段
一笔交易进入 EVM 到产生结果,通常经历三个阶段:
-
第一阶段:检查点与预扣费: 验证交易合法性后,系统会增加发送者的 Nonce,并从其余额中扣除预付成本 (upfrontCost),以建立执行环境。
-
第二阶段:消息标准化: EVM 会根据交易类型(合约创建或消息调用)将交易内容统一封装成一个“消息(Message)”结构。
-
合约创建:计算新合约地址,初始化账户(Nonce=1,Balance 注入,Storage 为空)。
-
消息调用:根据目标地址获取代码并开始执行。
- 第三阶段:主执行循环,这是 EVM 最核心的运算过程,通过一个递归或循环的解释器来运行代码,步骤如下:
-
获取指令 (Fetch):根据 programCounter 从字节码中读取当前的操作码 (Opcode)。
-
Gas 计算:计算当前指令的基础 Gas 成本,以及是否涉及内存扩展成本。如果剩余 Gas 不足,则触发异常停机。
-
操作执行:
例如执行 ADD(加法)时,会从栈中弹出两个值,相加后将结果压回栈。
执行 PUSH 指令时,会将字节码中的数据加载到栈中。
执行 JUMP(跳转)指令时,会修改 programCounter 到指定位置,前提是该位置必须是合法的跳转目标(JUMPDEST)。
- 更新状态:更新 programCounter 到下一条指令的位置
预编译合约 Precompiled Contracts
预编译合约是以太坊中一类特殊的合约,它们并不存储 EVM 字节码,而是将特定的复杂计算逻辑直接集成在以太坊客户端的底层代码中,位于以太坊地址空间中极其靠前的特定地址(1 到 9 号地址)。当以太坊执行层发现交易的目标地址属于这个集合时,它不会启动普通的虚拟机指令解析流程,而是将执行权重定向到客户端实现的“原生函数(Native Functions)”。
根据目前的规格,这些预编译合约主要负责处理高强度的密码学运算,包括:
-
1号地址:ECDSA 公钥恢复(用于验证签名)。
-
2号与3号地址:SHA-256 和 RIPEMD-160 哈希运算。
-
4号地址:标识函数(Identity function)。
-
5至8号地址:椭圆曲线运算(alt_bn128),常用于零知识证明。
-
9号地址:BLAKE2 压缩函数。
预编译合约比普通字节码效率更高
预编译合约比普通字节码效率更高,主要源于其执行机制的本质区别:
-
免除了解释器的开销:普通的智能合约需要在 EVM 的主执行循环中运行,经历“取指(Fetch)、译码(Decode)、执行(Execute)”的过程,而预编译合约直接运行编译后的机器码,跳过了虚拟机的解释环节。
-
由于这些操作直接在底层高效运行,协议可以为这些复杂的密码学任务设置相对较低且固定的 Gas 消耗,而如果用普通的 EVM 指令去实现同样的逻辑,其 Gas 消耗将高到无法在单个区块内完成。
-
预编译合约通常是无状态的(Stateless),它们仅根据输入产生输出,不涉及复杂的合约存储状态修改