导语:以太坊是一台"世界计算机"
如果说比特币是"可编程的钱",以太坊就是可编程的状态。它维护一个全球共享的"世界状态"(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 | 说明 |
|---|---|---|
STOP | 0 | 终止 |
ADD/SUB | 3 | 算术 |
SLOAD | 2100(冷)/ 100(热) | 读 storage 槽(热槽指本次交易已访问) |
SSTORE | 22100(新值)/ 5000(覆盖)/ 29000(冷槽首次) | 写 storage |
CALL | 2600(冷地址)+ 其余 | 合约调用 |
SHA3 | 30 + 6/word | Keccak 哈希 |
MLOAD/MSTORE | 3 | 内存操作 |
BALANCE | 2600(冷) | 查余额 |
| 扩展内存 | 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 |
|---|---|---|---|
0x01 | ecrecover | 从签名恢复公钥/地址 | 3000 |
0x02 | sha256 | SHA-256 | 60 + 12/word |
0x03 | ripemd160 | RIPEMD-160 | 600 + 120/word |
0x04 | identity | 数据拷贝 | 15 + 3/word |
0x05 | modexp | 大整数模幂(RSA 风格) | 动态 |
0x06 | bn128Add | BN254 曲线点加 | 150 |
0x07 | bn128Mul | BN254 曲线点乘 | 6000 |
0x08 | bn128Pairing | BN254 配对(zk-SNARK 验证) | 45000 + 34000/对 |
0x09 | blake2F | BLAKE2b 压缩函数(BIP-340 用) | 动态 |
0x0a | kzg_point_eval | EIP-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-1559 | base fee 燃烧 | 费用机制改革 |
| EIP-4844 | Blob 交易(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. 总结
- 账户模型:EOA(私钥控制)与合约账户(代码+存储),共同构成世界状态 Trie
- Gas 机制:每条指令明码标价,storage 最贵,EIP-1559 动态燃烧
- EVM 执行:栈式指令、三种数据位置、确定性状态转移、可回滚
- 预编译合约:原生密码学操作,zk-Rollup 验证的成本底座
延伸阅读:
- Solidity 智能合约开发入门 — 在 EVM 上写程序的语法层
- Layer2 扩容:Rollup 架构 — 把执行搬到链下、数据留在 L1
- 共识算法深入 — PoS 与终局性如何保障状态转移
- 跨链互操作 — 不同状态机之间的消息传递
- 区块链-web3 专题 — EVM 之上的 DApp 全栈开发
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。