换树为什么不能一步到位
以太坊的世界状态存在 Merkle Patricia 树里,节点分支按十六进制 nibble 展开,俗称十六叉树。社区长期想换成二叉树:二叉形态的默克尔证明更短、路径编码更整齐,无论为轻客户端还是为无状态验证都更友好。难点不在定义二叉树,而在把已有整棵状态换算过去:状态有几十吉字节的量级,全量重排树结构、重算每一个中间哈希,是一个要跑数小时甚至更久的后台任务——而链不能停,每一秒都有交易在改状态。这就形成迁移悖论:换算完成前,新写入写在旧树上,换算结果会被越落越远;换算期间不断追增量,又追不上。
EIP-2584(2020 年 4 月创建,状态 Stagnant)给出的解法是把两种树同时挂在一个协议里,用叠加层当缓冲。
三阶段的具体形状
第一阶段,从某个约定的分叉块起,区块头里出现两个状态根:旧十六叉底树的根照常维护,同时新增一个叠加二叉树的根。规则是”读旧写新”——所有账户与存储的新写入都进叠加二叉树,旧底树原则上只读。任何状态的当前值,按”叠加层优先、底树兜底”的规则解析。这一步的代价是每个块头多一个哈希、读路径多一跳,好处是换算任务和出块从此互不阻塞。
第二阶段,后台的换算任务把整棵十六叉树重排成等价的二叉形态,得到新底树的根。此后新写入继续进叠加层,节点开始以恒定速率把叠加层里的条目合并进新底树——每块迁固定的条目数,像流水线一样把缓冲层消化干净。
第三阶段,叠加层清空,区块头里删掉多余的根字段,迁移结束。
为什么”等速回填”是方案的心脏
第一阶段的缓冲层若没有出口,会随时间越积越大,读取永远要做两次查找;而靠”攒够了一口气全切”的新写入洪峰在状态规模面前完全不现实——这就是提案动机段落说的 catch-up 问题:全量翻译不可能在出块间隔的量级里完成。等速回填把不可能变成排期问题:换算完成的瞬间,缓冲层里有多少条目是确定的,按每块若干条目的速率,迁移完成的时间点也是确定的。工程上真正难的恒定速率数值本身还要防一个反噬:回填太猛,普通交易之外凭空多出一大笔共识必需的写放大;回填太慢,双读双写期被拉得和换算本身一样长,失去意义。
结局与价值
这条十六叉换二叉的路线后来被更晚的方案替代(统一二叉树一线的思路),提案停在 Stagnant。但”叠加双写、后台换算、等速回填、摘除字段”这套骨架没有过期——它是在线系统改存储格式的标准答案之一,值得在每个”状态迁移""历史过期”类提案里重新认脸。
快速问答
问:旧树和新树会算出不一样的根吗? 答:迁移期本来就有两个根并存,这正是设计的核心;等回填完成、旧字段摘除后,才要求所有节点算出同一个新根。
问:叠加层和快照是什么关系? 答:不是一回事。快照是某一刻状态的导出副本;叠加层是共识规则的一部分,所有节点在同一高度集合里都必须就它的内容达成一致。
问:读一个账户要查几棵树? 答:规则是叠加层命中就用命中结果,未命中落底树。写永远写叠加层(第一阶段),不会出现同一笔修改分叉到两棵树里。
问:为什么这类提案需要多年窗口? 答:全量换算要覆盖几百吉字节级别的存储,还要让所有客户端、所有归档服务都能在新协议下平滑切换,快不得。
三个数字各自卡住一段风险
读这条提案时,三个参数值得各自记住名字:第一阶段起点高度,决定双写缓冲开启的时点,必须给所有客户端留出实现窗口;转换完成的第二阶段起点,决定回填何时开始,太早会把还没换算完的节点甩在共识外;每块固定回填的条目数,决定过渡尾巴的长度,定小了过渡拖成马拉松,定大了每个块多背几百次读写、出块时间被悄悄抬高。三阶段迁移方案的工程评审,基本就围绕这三个数吵:第一个是公平问题,第二个是鲁棒问题,第三个是性能问题。这套检查清单同样适用于任何”大表在线换格式”的数据库迁移,只是以太坊把答案写进了共识规则。
风险提示:本文只做协议机制说明,不构成任何投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。