以太坊 EVM 与状态模型:账户、世界状态、Gas 与存储

以太坊底层执行模型深度解析:EOA/合约双账户模型、世界状态与 Patricia Trie、Gas 定价机制与 EIP-1559、EVM 调用栈与 ABI 编码、storage/memory/calldata 三种数据位置、precompile 预编译合约。附 Opcode 语义与 gas 成本表。

导语:以太坊是一台"世界计算机"

如果说比特币是"可编程的钱",以太坊就是可编程的状态。它维护一个全球共享的"世界状态"(world state),任何节点都可以读取,而交易可以原子地修改它。

EVM(Ethereum Virtual Machine)是这台世界计算机的 CPU:它执行智能合约字节码,消耗 gas 计量成本,最终把世界状态从 S → S'。

一句话总结:以太坊的状态模型是"账户余额 + 合约存储"的键值世界,EVM 是用 gas 计费的确定性执行器——读懂状态树和 gas,就读懂了以太坊性能与安全的一切。


1. 账户模型:两类账户,一个世界状态

1.1 EOA 与合约账户

属性EOA(外部账户)合约账户(Contract)
创建者私钥控制交易 CREATE / CREATE2
地址由公钥哈希得出由创建者地址 + nonce 哈希得出
能否主动发起交易✅❌(只能被调用)
私钥有无
存储只有余额有 code 和 storage
触发方式签名交易被交易或其他合约 CALL
# 账户对象的概念结构(以太坊黄皮书状态模型)
class Account:
    nonce: int          # 交易计数(EOA)/ 创建合约计数(合约)
    balance: int        # Wei(1 ETH = 10^18 wei)
    storage_root: bytes # storage 树的根(只对合约账户非空)
    code_hash: bytes    # 合约字节码哈希(只对合约账户非空)

1.2 地址生成规则

EOA 地址:keccak256(pubkey)[-20:]  (取后 20 字节)
合约地址(CREATE):keccak256(rlp([sender, nonce]))[-20:]
合约地址(CREATE2):keccak256(0xff ++ sender ++ salt ++ keccak256(init_code))[-20:]

1.3 世界状态:Patricia Trie

所有账户被打包进一棵 Merkle Patricia Trie,根哈希(state root)进入区块头:

世界状态 = State Trie(账户地址 → 账户数据)
每个合约的 storage 也是一棵 Trie(storage slot → 值)

区块头包含三个根:
  state_root      → 账户状态树根
  transactions_root → 交易树根
  receipts_root   → 收据树根

任何一个账户余额变化 → state_root 变化 → 区块头变化

这棵树的代价:每个合约存储槽位都是永久状态,没有"删除"——这正是状态膨胀(state bloat)问题的根源。

一句话总结:以太坊没有"删数据"的概念,任何历史状态修改都永久影响世界状态树——账户和存储是永生的,这既是"状态无国界"的民主承诺,也是资源与扩展性的长期负担。


2. 交易与状态转移

2.1 交易的结构

EIP-1559 之后的交易(type 0x02):
  chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas,
  gas_limit, to, value, data, access_list, signature

type 0x00(Legacy):gas_price, gas_limit, to, value, data, v, r, s

2.2 状态转移函数

σ' = Υ(σ, T)    // 状态转移

执行流程(简化):
1. 校验 nonce、签名、余额 ≥ value + gas_limit × max_fee
2. 扣减余额(预付 gas 上限)
3. 若 to 为空 → CREATE:部署合约字节码
   否则 → CALL:加载合约 code,进入 EVM 执行
4. 执行后:
   - 实际消耗 gas 返还剩余(EIP-1559 下优先退 priority fee 外的部分)
   - 若 gas 耗尽 → 状态回滚(REVERT),已扣 gas 不退
   - 成功 → 状态持久化,emit 事件进收据

2.3 nonce 与重放保护

EOA 的 nonce 从 0 开始,每笔交易 +1。
交易必须携带"当前正确的 nonce",否则被拒。

防止:同一笔交易被重复广播(重放攻击)
     链上乱序依赖

一句话总结:交易的 nonce 保证了每笔签名的"一次性";状态转移函数是纯函数——同样的输入交易在任何节点执行都得到同样结果,这正是"确定性"的来源。


3. Gas 机制:计算资源的定价

3.1 为什么需要 Gas

EVM 是图灵完备的,理论上可以无限循环。如果没有 gas,一个恶意合约可以让全网络节点卡死。Gas 就是防停顿保险:每条指令都有固定成本,账户余额不足则执行终止。

3.2 常见 Opcode 的 Gas 成本

操作Gas说明
STOP0终止
ADD/SUB3算术
SLOAD2100(冷)/ 100(热)读 storage 槽(热槽指本次交易已访问)
SSTORE22100(新值)/ 5000(覆盖)/ 29000(冷槽首次)写 storage
CALL2600(冷地址)+ 其余合约调用
SHA330 + 6/wordKeccak 哈希
MLOAD/MSTORE3内存操作
BALANCE2600(冷)查余额
扩展内存3/word + 增长部分按二次方内存按访问字节扩展

关键结论:storage 是链上最昂贵的资源。 一次 SSTORE 的成本是内存操作的上万倍——所以合约优化几乎都围绕"减少 storage 读写"。

3.3 EIP-1559:燃烧与动态 base fee

交易费用 = max_fee_per_gas × gas_used
实际支付 = (base_fee + priority_fee) × gas_used

base_fee:由区块填充度动态调整
  填充度 > 50% → base_fee 上调(最多 12.5%/区块)
  填充度 < 50% → base_fee 下调

base_fee 被燃烧(销毁),priority_fee(小费)归矿工
对比Pre-EIP-1559(旧)EIP-1559(现)
费用模型用户出价 gas_price,矿工择优base_fee(协议定价)+ 可选小费
费用可见性波动大,需竞价base_fee 可预测
费用去向全部给矿工base_fee 燃烧 + 小费给矿工
网络拥塞响应滞后区块级快速调整

3.4 gas limit 与 gas 耗尽

区块 gas limit:~30M(2024 年量级,随硬分叉上调)
交易 gas limit:由用户设定,必须是"最小可行的上限"

gas 耗尽 = OOG(Out of Gas):
  - 执行立即回滚(已做的状态修改全部撤销)
  - 已支付的 gas 不退还(防 DoS 成本已发生)

一句话总结:Gas 是 EVM 的经济引擎——storage 读写贵、内存便宜、基础费用随拥堵动态燃烧;理解 gas 成本表,是写"不亏钱"合约的第一课。


4. EVM 执行模型:调用栈与数据位置

4.1 调用栈与上下文

EVM 执行帧(Stack Frame):
  - 栈(stack):最多 1024 个 256 位字,压入/弹出
  - memory(memory):易失字节数组,随访问增长,按字计价
  - storage(storage):持久化键值,账户私有,最贵
  - calldata(calldata):调用输入字节,只读

调用栈:CALL 嵌套最多 1024 层(超过 → 栈深度错误)
执行上下文:gas、address、origin、caller、value、calldata、returndata

4.2 三种数据位置的 Solidity 视角

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract StorageDemo {
    uint256 public stateVar;   // ← 默认 storage:持久化、最贵

    function f(uint256[] calldata input) external {
        // calldata:外部输入,只读、便宜(0.8 起外部参数默认 calldata)

        uint256 local = input[0];   // ← memory:局部变量,每次调用重新分配
        // 注意:状态变量 local 若不指定位置,引用类型默认 storage
    }

    function g() external pure {
        uint256[3] memory arr = [uint256(1), 2, 3];  // memory 数组
        bytes memory buf = new bytes(64);            // memory 动态数组
    }
}

4.3 函数选择器与 ABI 编码

// 函数选择器 = keccak256("transfer(address,uint256)") 的前 4 字节
bytes4 selector = bytes4(keccak256("transfer(address,uint256)"));
// 0xa9059cbb

// calldata 编码(ABI):
//   selector(4) || 参数 ABI 编码(32 字节对齐、动态类型偏移表)
// ethers.js 编码对比
const iface = new ethers.Interface(['function transfer(address,uint256)']);
iface.encodeFunctionData('transfer', [addr, 1000n]);
// => '0xa9059cbb' + 0000...地址(32B) + 0000...1000(32B)

4.4 错误回滚与 revert

REVERT(0xFD)→ 状态全部回滚,剩余 gas 退还,可携带错误消息(revert reason)
INVALID(0xFE)→ 等价于 revert,无错误消息
错误选择器:Error(string) = 0x08c379a0;Panic(uint256) = 0x4e487b71(0.8 算术溢出/除零)

一句话总结:EVM 是一个"栈式虚拟机 + 三段式存储(stack/memory/storage)+ 可回滚执行"的组合——理解数据位置与 gas 的直接挂钩,是 Solidity 优化的最核心技能。


5. 预编译合约(Precompiles)

5.1 为什么需要预编译

某些密码学操作(如椭圆曲线配对、SHA-256)用 EVM 字节码实现 gas 成本高得离谱。以太坊把固定地址的"内置合约"写入节点实现,用原生代码执行,收费远低于 EVM 模拟。

5.2 常见预编译合约(主网地址)

地址名称功能典型 gas
0x01ecrecover从签名恢复公钥/地址3000
0x02sha256SHA-25660 + 12/word
0x03ripemd160RIPEMD-160600 + 120/word
0x04identity数据拷贝15 + 3/word
0x05modexp大整数模幂(RSA 风格)动态
0x06bn128AddBN254 曲线点加150
0x07bn128MulBN254 曲线点乘6000
0x08bn128PairingBN254 配对(zk-SNARK 验证)45000 + 34000/对
0x09blake2FBLAKE2b 压缩函数(BIP-340 用)动态
0x0akzg_point_evalEIP-4844 KZG 承诺验证50000

5.3 使用场景

// 使用 ecrecover 验证链下签名(Meta 交易/智能钱包)
function recoverSigner(bytes32 digest, uint8 v, bytes32 r, bytes32 s)
    public pure returns (address) {
    return ecrecover(digest, v, r, s);
}

// 注意:ecrecover 对 msg.hash 需先做 EIP-191/712 前缀
// EIP-712 结构化数据签名是当前标准

zk-Rollup 的链上验证器(验证证明)正是调用 bn128Pairing——预编译合约的效率直接决定了 zk-Rollup 的验证成本。

一句话总结:预编译合约是以太坊为"原生密码学操作"预留的优化后门——它们把配对、模幂、哈希等重计算下沉到节点原生实现,是 zk-Rollup 得以在 L1 上低成本验证证明的关键。


6. EVM 升级与状态模型的演进

提案内容对状态/执行的影响
EIP-2929冷/热存储定价提高状态访问成本,激励无状态性
EIP-1559base fee 燃烧费用机制改革
EIP-4844Blob 交易(Proto-Danksharding)引入 Blob 数据暂存,Rollup 数据可用性成本大降
Verkle Tree状态树从 MPT 迁移到 Verkle为无状态客户端与轻验证铺路
状态过期/EIP-7732状态管理重构应对状态膨胀的长期方案探索

EIP-4844 之后,Rollup 把交易数据放进 blob(有效期约 18 天)而非永久 calldata——数据可用性(DA)成本降低一个数量级,这是 2024 年 L2 费用骤降的根源。

一句话总结:EVM 与状态模型在持续演进——从 gas 定价(EIP-2929)、费用机制(1559)、数据层(4844)到状态树(Verkle),每一次升级都在回答同一个问题:如何让这台世界计算机更便宜、更可验证、更去中心化。


7. 总结

  1. 账户模型:EOA(私钥控制)与合约账户(代码+存储),共同构成世界状态 Trie
  2. Gas 机制:每条指令明码标价,storage 最贵,EIP-1559 动态燃烧
  3. EVM 执行:栈式指令、三种数据位置、确定性状态转移、可回滚
  4. 预编译合约:原生密码学操作,zk-Rollup 验证的成本底座

延伸阅读:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「blockchain」更多文章

  1. 跨链互操作:跨链桥、轻客户端验证与信任假设
  2. 共识算法深入:PoW/PoS、Casper FFG、Tendermint 与分叉规则
  3. Layer2 扩容:Rollup 架构、数据可用性与跨 L2 桥