账户模型与状态机:以太坊的世界状态(师生对话实录)
Web3 区块链系列 · 阶段 2 · 以太坊核心 · 第 10/57 篇
上一篇:《比特币的取舍与现状:为什么需要以太坊》 · 下一篇:《交易全解:类型、签名与 EIP-1559 费用》
学习大纲:《Web3 区块链学习总纲》
写在前面
阶段 1 结尾那张对比表里写着:「以太坊 = 账户模型,世界状态机」。这两句话我都能背,但到底是什么意思?而且听说 2025 年的 Pectra 升级把 EOA 和合约账户的边界弄模糊了——模糊在哪?
继续对话老办法:AI 当老师我当学生,每课一个概念,有问题就打断。这堂课上来就查主网真实账户,结果当场抓到一个「EOA 带代码」的现行——比任何教案都精彩。
课程路线图:
① 世界状态:一张地址 → 账户的表 → ② EOA vs 合约账户 → ③ 状态机:交易是转移函数 → ④ 实查:vitalik 的账户有代码?! → ⑤ EIP-7702:边界的模糊 → ⑥ 状态增长
环境:WSL2 Ubuntu-22.04 + Foundry 1.7.1(foundryup 安装,3.1 篇有完整安装实录);主网数据走公共 RPC(ethereum-rpc.publicnode.com,2026-08-26 实查)。官方文档:ethereum.org — Accounts。
第 1 课:世界状态——一张「地址 → 账户对象」的表
🧑🏫 老师:
先立地基。1.1 篇说过比特币的世界是「零钱集合」;以太坊的世界是一张巨大的键值表:
世界状态(World State)
地址 → 账户对象 {
nonce: 该地址发过多少笔交易(防重放 + 合约计数)
balance: 余额(wei)
storageRoot: 这个账户的存储树根(合约专属,EOA 为空)
codeHash: 代码哈希(合约专属,EOA 为空)
}两个要点先钉住:
- 表里只有账户,没有「交易」。交易是事件,账户是结果——账本的本体是这张表(怎么高效存储与证明它,是 2.5 篇 MPT 的主题);
- 「你的 ETH」不是链上的一个文件,就是这张表里你地址旁的一个数字。转账 = 改两行数字,就这么朴素。
这张表叫世界状态(world state),而「区块链」在以太坊语境里的本体是区块头的链——每块都拍下当时世界状态的一张「指纹照」(stateRoot)。谁想验证「某时刻某账户余额」,沿着状态树出示证明即可(0.2 篇默克尔证明在以太坊的全面升级版)。
一句话收口:世界状态 = 地址 → {nonce, balance, storageRoot, codeHash} 的全局表;账本的本体是它,区块只是它的定期快照链。
第 2 课:EOA 与合约账户——谁在花钱,谁在干活
🧑🎓 学生: 比特币只有一种「锁定脚本」,以太坊的账户怎么分了两种?
🧑🏫 老师:
按「四个字段里有没有后两个」分:
| EOA(外部账户) | CA(合约账户) | |
|---|---|---|
| 控制方式 | 一把私钥(0.3/0.4 篇那套) | 代码(部署时写死) |
| nonce | 已发交易计数 | 已创建合约计数 |
| balance | ✅ 有(能收钱) | ✅ 有(也能收钱!) |
| storage | ❌ 空 | ✅ 有(合约的状态变量住这) |
| code | ❌ 空(经典设定) | ✅ 有 |
| 谁能「发起」交易 | 只有 EOA 能(签名启动一切) | 不能自发——只能被调用后运行 |
分工记住一句话:EOA 是「人」,CA 是「程序」;程序自己不会醒,永远由人(或另一个程序,追根溯源还是人)触发。你在浏览器点一次「确认」,就是一次 EOA 签名——它可能是直接转账,也可能是去踩某个合约的函数,那笔交易引发的合约调用可以像多米诺一样传递一长串。
「只有 EOA 能发起交易」这个设定是双刃剑:它让权限模型清晰(签名=授权),但也让 EOA 能力贫乏(没有多签、没有批量、没有社交恢复)——这个不满积攒了七年,最终变成第 5 课的 EIP-7702。
一句话收口:EOA = 钥匙控制的人,CA = 代码控制的程序;世界状态的变更永远由某个 EOA 的签名启动。
第 3 课:状态机——交易是转移函数
🧑🎓 学生: 「世界计算机」到底怎么个「计算」法?
🧑🏫 老师:
把前两课拼起来就是答案。以太坊是一台确定性状态机:
S(当前世界状态)
│
TX(一笔交易,按序执行)
│ 执行 = EVM 跑一遍(2.3 篇)
▼
S'(新世界状态)
形式化写法:S' = ST(S, TX)三个配套性质,每个都值得咀嚼:
- 确定性:同样的 S 和 TX,在任何机器上跑出同一个 S'——这是几千个节点能对账的前提(1.3 篇「共识层少说话」的以太坊版:EVM 里没有随机数、没有时钟、没有网络,只有交易里带来的信息);
- 原子性:一笔交易要么全部生效、要么全部回滚(2.3 篇的 out-of-gas 回滚就是它)——没有「改了一半」的中间态;
- 有序性:同一状态、不同顺序的交易会得到不同结果——所以交易顺序本身是共识的一部分(这也是 MEV 的温床,6.5 篇)。
而「智能合约」在这台机器里的身份就清楚了:它不是「跑在以太坊上的程序」,它就是状态转移函数的一部分——你的交易 TX 里带一段 calldata 指向某合约的某函数,ST 执行时就把那个函数跑一遍,它对 storage 的修改都算进 S'。
对照比特币收个尾:比特币的状态转移是「销毁若干 UTXO、生成若干 UTXO」,结构简单到不需要「通用计算机」;以太坊把 TX 里的 data 交给任意代码解释——交易从「指令」升级成了「程序调用」。
一句话收口:以太坊 = 确定性状态机:S' = ST(S, TX);合约是这个转移函数里可编程的那部分;确定性换共识、原子性换安全、有序性催生 MEV。
第 4 课:实查主网——vitalik 的账户居然有代码?!
🧑🏫 老师:
该动手了。用 cast(Foundry 的链交互工具,3.1 篇详细讲)查主网真实账户。先查两个「教科书样本」——vitalik 的 EOA 和 USDC 合约:
RPC=https://ethereum-rpc.publicnode.com
VITALIK=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
USDC=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
cast balance $VITALIK --rpc-url $RPC --ether # EOA 余额
cast nonce $VITALIK --rpc-url $RPC # EOA nonce
cast code $VITALIK --rpc-url $RPC # EOA 代码本机实跑(2026-08-26):
===== EOA(vitalik.eth)=====
balance: 6.642178165221340300 ETH
nonce: 5956
code: 0xef01005a7fc11397e9a8ad41bf10bf13f22b0a(长度 49)
===== 合约账户(USDC)=====
balance: 0.000000000000000000 ETH
nonce: 1
code: 0x60806040526004361061006d576000357c010000000000000000000000…(长度 4375)停!按第 2 课的表格,EOA 的 code 应该是空的——但 vitalik 的账户里躺着 49 个字节,还以 0xef01 开头?!再取个对照样本,著名的「死地址」(只收不发的 EOA):
cast code 0x000000000000000000000000000000000000dEaD --rpc-url $RPC
# → 0x ←纯空,符合教科书vitalik 的不是普通 EOA 了。那 49 个字节是什么?0xef01 开头——这正是 EIP-7702 的委托设计ator 标记。天赐的教材,下一课。
(顺带把 USDC 的读数也对齐:nonce=1 是它作为「合约创建者」的计数;code 4375 字节才是它作为合约的本体;balance 为 0 说明它账户上一枚 ETH 都不留——收款逻辑由合约自己管。)
一句话收口:cast 三件套(balance/nonce/code)一查,教科书表格当场被主网推翻一半——实查永远比背表有意思。
第 5 课:EIP-7702——EOA 的「临时借壳」
🧑🎓 学生: 所以 7702 到底干了什么?EOA 有代码了,那 EOA 和合约账户的边界还在吗?
🧑🏫 老师:
EIP-7702(随 Pectra 升级,2025-05-07 上线)给交易加了一个授权列表(authorization list):EOA 签名声明「我委托地址 X 的代码代表我执行」。生效后,这个 EOA 的 code 字段变成:
0xef01 00 <被委托合约的 20 字节地址>
vitalik 的:0xef0100 5a7fc11397e9a8ad41bf10bf13f22b0a…
└── 一个代理合约的地址它不是把合约代码抄进 EOA——只是放了一个「此人有委托」的路标。效果上,这个 EOA 现在能做经典 EOA 做不到的事:
- 批量:一笔交易里完成「approve + swap + 转账」一串动作(省 L1 次数 = 省钱);
- 赞助 gas:让别人(paymaster)替你付这笔交易的钱;
- 合约级权限:会话密钥、限额、社交恢复——Web2 用户不用再理解「私钥丢了就全没了」。
三个边界必须说准,别把 7702 神化:
- 委托可撤销:再发一笔带新授权列表(指向零地址)的交易即恢复纯 EOA——「临时借壳」,不是永久改性;
- 签名权仍归私钥:委托的是「执行逻辑」,不是「钥匙」——被盗的场景依然是私钥泄露,7702 没有改变这一点(反而给了攻击者「给受害者 EOA 挂恶意委托」的新攻击面——钓鱼网站骗你签 7702 授权,6.3 篇的安全课);
- 与 ERC-4337 并行:4337 是「外挂」的账户抽象(纯合约账户 + bundler,8.6 篇),7702 是「原生改造」——两条路线互补,都以「让账户变聪明」为目标。
现在收回第 2 课的表格,把 7702 后的正确版写上:EOA 与 CA 的形式边界还在(谁能发起交易没变),能力边界模糊了(EOA 可以临时获得合约的执行逻辑)。比特币用 P2PK→P2PKH→…→Taproot 换了五次锁(1.3 插问 3);以太坊的账户模型也在走自己的演进路。
一句话收口:7702 = EOA 用签名「借」一份合约逻辑:code 字段放委托路标、可撤销、钥匙仍是私钥;形式边界还在,能力边界模糊。
插问 1:nonce 到底防什么?EOA 和合约的 nonce是一回事吗?
🧑🎓 学生: 账户对象里那个 nonce,跟比特币 UTXO 防双花的「花掉即删」是一回事吗?为什么 vitalik 的是 5956、USDC 的却是 1?
🧑🏫 老师:
好问题,同名不同义,正好对照着记:
EOA 的 nonce = 已发交易的计数器,作用就是账户模型版的「防双花」:每笔交易必须带上 nonce = 账户当前值,节点核对无误才执行,然后 nonce+1。1.1 篇插问 2 的「UTXO 花掉即删」在这里变成「nonce 只涨不回」——同一笔(同 nonce)交易只能上链一次,重放直接被拒。vitalik 的 5956 就是「这个人至今发过 5956 笔交易」;0.3 篇插问 2 说的「防重放靠交易里的 nonce」,就是它。
合约账户的 nonce = 已创建合约的计数器,跟它自己发不发交易无关(合约不能发起交易)——USDC 的 1 表示它当年用 CREATE 创建过 1 个子合约。这个计数器的用途藏在地址计算里:CREATE 出来的合约地址 = hash(创建者地址, 创建者nonce)——3.8 篇 CREATE2 预计算地址时它还要出场。
顺带一个实务彩蛋:EOA 的第一笔交易 nonce 是 0——你在浏览器看到某个地址 nonce 巨大,就知道它是只老鸟;查一个地址「有没有入场过」,看 nonce 是否 > 0 就行(比查余额灵——转账可以转走,nonce 只增不减)。
一句话收口:EOA 的 nonce 防重放(只涨不回),合约的 nonce 数创建(算地址用)——同名两物,都是「只增计数器」这同一件武器在不同战场的部署。
第 6 课:状态增长——世界状态表越来越胖怎么办
🧑🎓 学生: 最后一个隐患我一直在想:那张「全世界的账户表」,只会越来越大吧?
🧑🏫 老师:
会,而且这是以太坊最现实的长期工程压力之一,状态增长(state growth):
世界状态只增不减的原因:
├── 账户只多不少(没有「注销账户」;SELFDESTRUCT 早已名存实亡)
├── 合约 storage 槽一旦写过就占着(清零也要 gas、且历史证明仍需保留)
└── 每个新用户、每个新合约、每笔 DeFi 操作都在往表里加行/加槽
代价:
├── 全节点的磁盘与同步时间持续上涨(数百 GB 量级)
├── 状态访问越来越慢 → SLOAD 计费被迫上调(历史多次)
└── 「自己跑节点」的门槛升高 → 去中心化压力(1.4 插问 1 的同款困境)对照着记:比特币的状态是「可收缩」的——UTXO 花掉即删,账本只留「活着的钱」(1.1 篇对比表那行的深意);以太坊的状态是「只增」的——这是「可编程 + 有状态」的代价,1.4 篇「三堵墙」表格里「状态只增不减」那一格的兑现。
社区的应对方向(了解级):无状态化/状态租赁的长期路线(节点不存全量状态、交易自带证明;或对占用状态收费)——多年研究仍未完全落地,是以太坊路线图上比扩容更难啃的骨头。你现在只需要记住这个问题的存在和为什么必然:阶段 3 写合约时,「少碰 storage」不只是省钱,也是在给全网减负。
一句话收口:UTXO 花掉即删、账户状态只增不减——可编程的代价是状态膨胀;写合约少碰 storage,既是省钱也是公德。
小结
- 世界状态:地址 → {nonce, balance, storageRoot, codeHash};账本本体是它,区块是快照链。
- 两类账户:EOA 钥匙控制、CA 代码控制;只有 EOA 能发起交易。
- 状态机:S' = ST(S, TX);确定性换共识、原子性换安全、有序性催 MEV。
- 实查翻案(主网 2026-08-26):vitalik 的「EOA」带着
0xef01…的 49 字节 code——教科书表格当场被推翻一半。 - EIP-7702:签名委托合约逻辑,可撤销、钥匙仍是私钥、与 4337 互补;形式边界还在、能力边界模糊。
- nonce 两个身份:EOA 防重放(5956=发过 5956 笔),合约数创建(算地址用)。
- 状态增长:只增不减是账户模型的代价——少碰 storage 是省钱也是公德。
验收清单(做完再进下一篇):
思考题:7702 委托生效期间,这个 EOA 的 storageRoot 还是空的吗?(提示:委托路标在 code 字段——执行时 EVM 把调用「转发」给被委托合约的代码,但状态用的是谁的家?答案在 2.5 篇存储槽里找。)
下一篇:《交易全解:类型、签名与 EIP-1559 费用》——本地起一条链,亲手发一笔 1559 交易、故意把 nonce 用错,把交易的一生拆到字段级。
本篇实验命令(可照抄)
RPC=https://ethereum-rpc.publicnode.com
# EOA vs 合约账户四字段对照(第 4 课)
cast balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --rpc-url $RPC --ether
cast nonce 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --rpc-url $RPC
cast code 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --rpc-url $RPC # ← 7702 委托活标本
cast nonce 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --rpc-url $RPC # USDC
cast code 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --rpc-url $RPC | head -c 60
# 纯 EOA 对照(code 为 0x):
cast code 0x000000000000000000000000000000000000dEaD --rpc-url $RPC参考资料
- ethereum.org — Accounts(EOA/合约账户、7702)
- EIP-7702 — Set EOA account code(第 5 课,
0xef0100设计ator 格式) - Ethereum Yellow Paper — World State(账户对象四字段的形式化定义)
- 状态增长与无状态路线:ethereum.org/roadmap/statelessness(第 6 课)
- 本机:WSL2 Ubuntu-22.04 + Foundry 1.7.1(cast);主网数据 2026-08-26 实查