以太坊的状态树里曾经躺着一批“四无账户”:没有余额、没有代码、没有nonce、也不该有任何存储。它们本身不存任何值,却占着状态树的节点。2016 年 11 月 22 日,Spurious Dragon 硬分叉在主网区块 2675000 处执行了 EIP-161,协议从此获得了一条新规则:被交易“触碰”到的空账户要顺手删掉。这篇讲清空账户从哪来、为什么删、删了之后以太坊学到了什么。
一分钱不花,怎么污染全网状态
2016 年 9 月到 10 月,以太坊连续遭遇两波拒绝服务攻击。攻击者的武器不是漏洞,而是一个便宜到离谱的组合:用极低的手续费反复触发合约创建类操作,尤其是 CREATE 到确定性地址和自毁(SELFDESTRUCT)。每执行一次,链上就多一个空账户。账户里没有以太,攻击不偷任何人的钱,但它让所有全节点和之后每一个同步者都必须在状态树里为这些空壳记账。这就是状态膨胀最纯粹的形态:单个行为的成本极低、收益为零,损害却由全网长期均摊。协议先以 Tangerine Whistle 分叉给这类昂贵操作码重新定价止血,再把“清库存”留给了第二轮。
协议怎么定义“空”,什么时候删
EIP-161 给出的判定是可机械执行的(“空状态对象”的定义由同批的 EIP-158 给出):余额为零、nonce 为零、没有合约代码,三个条件同时满足即空账户(若账户存有非零存储项则另当别论,那已经不算干净的空壳)。删除不是某个时刻的一次性大扫除,而是一条嵌入式规则:任何一笔交易只要“触碰”到一个满足条件的空账户——比如向它转账再转出、或在它上面执行一次调用——协议就顺手把它从状态树里移除。真正执行大规模清理的方式很务实:社区按预设顺序对当年攻击造出来的空账户逐个发起调用交易,每碰到一个删一个,所以清理本身也花了真实的时间和链上空间,而不是凭空发生的。
这次分叉还带了什么
Spurious Dragon 是一组变更的打包:EIP-155 把链 ID 编进交易签名,给重放攻击上锁;EIP-160 上调 EXP 操作码的计价,堵住算力型拖延攻击;EIP-170 给合约字节码设定了 24576 字节的上限,防止超大代码被以固定成本反复读取。空账户清理只是其中一环,但它是第一次把“状态的长期大小”当作协议要管的问题,而不只是节点运维的问题。
一条直觉算术
假设每个空账户在状态树和数据库里占几十字节的净开销,几十万账户听起来不多,但要乘上“全网每个全节点、每个未来同步者”这个系数:一次总花费不足几美元的写入,变成了永久摊在所有人磁盘和带宽上的负债。这正是状态膨胀和一次性数据的不同——转账历史进了历史区块就不再改变内存状态,而账户是每次都出现在状态里的活数据。
空账户在状态树里到底占什么
状态树不是“有余额的账户清单”,而是一张按地址哈希排序的字典:每个被注册进树的键都对应一个节点,节点的值为余额、nonce、代码哈希、存储根四项组成的编码。空账户的四项全为空或零,但键本身仍在树上,树的分支结构也为它保留了路径。同步一个节点时,这些空路径会被完整搬运一遍;每次状态修改,涉及路径上的哈希要重算;每次围绕状态的密码学证明(比如轻客户端查账户),树越胖证明越长。换句话说,空账户的伤害不体现在任何一笔账目上,而是抬高所有与状态相关的操作的固定成本——这就是为什么协议宁可付出一次硬分叉的协调成本,也要把它从规则层面删掉。
常见误区
一是认为“余额为零等于账户不存在”:协议层面,零值、零nonce、无代码三者齐全才被视作可删的空账户,任何非零项都会让它继续留在树里。二是把这次事件理解为“以太坊出了漏洞被偷币”:从头到尾没有资产损失,损害形态是节点资源与同步成本。三是以为清理之后状态膨胀就结束了:它只是被管住了,创建新账户的价格、自毁语义、脏状态处理在后续每轮升级里继续被调整,空账户清理是这条长期战线的第一个里程碑,不是终点站。
风险提示:本文为协议机制历史科普,不构成任何投资建议;文中区块高度与提案内容以以太坊官方博客的 Spurious Dragon 公告和 EIP 仓库当期文本为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。