本文是讲解我在go-ethereum客户端中发现的Bug系列的第二篇。如果你还不了解它,请看第一篇。
这篇文章要讲的bug位于Geth客户端的状态下载器内,它可以用来下载器,使之不能与主网正确同步。攻击者可以利用这个bug给以太坊区块链设置陷阱、任意触发硬分叉。
同步
当你想运行一个以太坊节点的时候,首先必须同步上整个网络,即,下载和计算构建最新区块时刻的区块链状态所需的所有数据。根据用户自身的需要,同步方式可以在安全性和速度之间有所取舍,所以Geth支持两种同步模式:完全同步和快速同步。
顾名思义,完全同步就是独立地执行完对以太坊区块链的整个同步过程。这就意味着,你的Geth节点会下载和验证每个区块的工作量证明,此外,它还会计算区块内的每一条事务;由此,节点可以在本地生成区块链的最新状态,而无需信任其它节点。这种模式更安全,但速度上有很大牺牲。Geth的完全同步可能要花几天乃至几周不等的时间。
但是,有些用户可能不想等上几周。也许他们的时间很紧,又或者,他们并不觉得这种牺牲是值得的。因此,Geth提供了一个模式:在近期的某个区块之前的所有链数据,都用更快的方法来同步,只有pivot区块之后的区块链,才使用更慢的完全同步算法。在快速同步模式中,Geth会下载区块,但仅随机选取一些区块来验证工作量证明,而不是每个区块都验证;而且,它也将不再自己执行事务,而是从网络中的其它节点处直接下载状态树,以此获得最终的区块链状态。
当然,Geth也不会盲目相信其他节点发回的状态树数据,因为一个恶意的节点也可以声称某个账户只有一点点钱。要理解Geth如何能辨别收到的数据正确与否,我们先要理解默克尔帕特里夏树。
默克尔帕特里夏树
默克尔帕特里夏树是Geth客户端中的一种关键数据结构,它是默克尔树和帕特里夏树两者的结合。
简而言之,帕特里夏树会基于数据的前缀将数据存到一个树状结构中。相较于其它技术来说,帕特里夏树本身非常适合存储相似的数据,尽管在速度上可能有所牺牲。下面来看看,多个以r开头的单词是如何存到一棵帕特里夏树里的。
Aave新提案投票拟在以太坊合并前关闭ETH借贷市场:金色财经报道,Aave发起新的提案投票,计划在以太坊合并前关闭ETH借贷市场,此外当ETH利用率达到100%时,将借款APR从103%提升到1000%。该投票将于9月3日12:15结束。[2022/8/30 12:57:10]
-图源:https://commons.wikimedia.org/w/index.php?curid=2118795-
接着来说说默克尔树。在一棵默克尔树上,每个叶节点是数据的哈希值,每个非叶节点是它的两个子节点的哈希值。如果用户知道了默克尔树的默克尔根,并且想要确认某个数据是否存储在这棵树里,他只需要用到这棵树上的一条路径,这条路径所涉及的节点数量只跟叶节点数量的对数成正比。如下图所示,假设用户要证明L2存储在这棵树里,他只需提供Hash0-0和Hash1。接着,验证者生成Hash0-1、Hash0和TopHash,再将TopHash与其所预期的默克尔根进行比较。
-图源:https://commons.wikimedia.org/w/index.php?curid=18157888-
默克尔帕特里夏树将帕特里夏树基于前缀的存储结构与默克尔树相结合,创造出了一种新的数据结构,不仅支持密码学验证方式,而且还能保持良好的运行时性能。
Compound启动116号提案,将在以太坊上初始化USDC市场:8月19日消息,DeFi借贷协议Compound发推称,116号提案开始启动,将在以太坊上初始化USDC市场,具体投票将在2天内开始。此前报道,Compound 即将推出过渡至多链的新版本,将首先在以太坊上部署USDC市场。[2022/8/19 12:36:00]
在默克尔帕特里夏树中,键和值都是任意的字节串。要想获得一个键的值,我们首先要将这个键转换成一个十六进制字符序列,即,将每个字节变成两个十六进制字符。然后,我们要先根据序列中的第一个字符,向根节点查询下一个节点是什么;得到此子节点后,再根据第二个字符向下查询节点,依次类推,直至找到最后一个节点,获得最后一个字符。
在下面这个例子中,我们可以看到默克尔帕特里夏树实际上包含三种不同的节点。扩展节点是经过优化的,负责存储一连串字符。在下图所示案例中,根节点存储了所有以a7开头的键,无需使用两个不同的节点来代表a和7。分支节点包含每个可能字符的指针以及一个额外的空档来存储当前节点的值。最后,叶节点的key-end字段必然与其所存储的key的后缀相匹配1。
状态树
既然我们已经知道默克尔帕特里夏树是如何运作的了,我们可以开始探究什么是全局状态树。
区块链的绝大部分数据都存储在全局状态树中。虽然将状态树作为独一无二的实体包含在每个区块内这个设想看似便利,但实际上每个区块都要复制完整的状态树是极其低效的,因为每个区块之间的状态树只有细微差别。Geth采用了不同的方案。你可以想象一下,Geth维护了一个MPT节点池。每个区块的状态树只是整个池的子集。每当有新的区块被挖出或导入,就会有新的MPT节点被添加到池中。
Jack Dorsey:对建立在以太坊上的项目不感兴趣:金色财经报道,对于V神”不反对马斯克收购推特,只反对恶意收购“的评论,Twitter联合创始人兼Block首席执行官JackDorsey表示赞同,并称:“我不认为任何个人或机构都应该拥有社交媒体,或者更普遍的媒体公司。它应该是一个开放且可验证的协议。一切都是朝着那个方向迈出的一步。”随后,由社交媒体网站BitClout的创始人创立的区块链DeSo向JackDorsey推荐其项目,JackDorsey回复到:“如果你在以太坊上构建,你至少有一个(或者不是很多)单点故障,因此对我来说并不感兴趣。”尽管DeSo继续推荐并介绍其没有构建在以太坊上,JackDorsey回复道“该项目的基础是单点故障”(decrypt)[2022/4/20 14:34:53]
要想识别节点池中的根节点,我们必须查询区块头。每个区块都包含一个指向stateRoot的字段,该字段指向MPT的根节点。这样一来,Geth就可以使用我们上文描述的算法查询账户信息,如任意地址的nonce或余额。
请注意,如果是合约的话,账户状态将包含一个非空的storageRoot字段和codeHash字段。storageRoot字段指向另一个根节点。但是,此时所涉及的MPT的用途是存储该合约的存储项数据;该MPT会将存储空档作为键,将原始数据作为值。
Risk Harbor Core V2已在以太坊主网上线:1月26日,DeFi风险管理市场Risk Harbor宣布,Risk Harbor Core V2已在以太坊主网上线。
基于区块链的风险管理协议受到可用承保资金短缺的严重限制。Risk Harbor Core V2利用保险库(Vault)解决承保资金不足的问题。Vault持有资金,可用于同时对多个协议提供保护。Risk Harbor 还开发了一款针对风险管理市场定制的自动做市商(AMM)。AMM以编程方式理解风险,并相应地制定价格保护。
未来,Risk Harbor将很快部署更多Vault和新功能,包括Arbitrum Vault、Avalanche Vault、Polygon Vault、仪表板和分析功能。[2022/1/27 9:17:28]
为了将MPT存储在磁盘上,Geth选择使用LevelDB作为数据库。然而,LevelDB是只支持字符串到字符串映射的键值数据库,MPT不是字符串到字符串映射。为了解决这个问题,Geth将每个节点编写成键值对,从而实现全局状态树的扁平化:将节点的哈希值作为键,将序列化的节点作为值。这样一来,Geth就能查询任意区块的状态树,因为区块头中的stateRoot字段就是键,可以用来查找LevelDB中的序列化MPT节点。
树混乱
因此,假设你启动了一个Geth节点,并使用快速同步模式连接到网络。Geth将快速下载所有区块数据,但是不执行任何事务。不久之后,你将得到一个没有状态信息的区块链。此时,Geth通过状态下载器从pivot区块的stateRoot开始进行同步。
USDC Treasury在以太坊链上增发近2000万枚USDC:Whale Alert数据显示,北京时间9月6日03:05,USDC Treasury在以太坊网络上增发19999996枚USDC。哈希值为:0x9b2b63b1e5ddf165f73cee0b4fc776009e705fcf2702146ecf54e07d815e8822。[2020/9/6]
来源:https://github.com/ethereum/go-ethereum/blob/87c463c47aa6d10a55a4b860bab3d53199814d01/trie/sync.go#L246-L255
状态下载器会向对等节点请求与MPT节点键对应的MPT节点数据。收到结果后,状态下载器会对节点数据进行哈希计算,验证得到的哈希值是否与节点键相同。如果相同,状态下载器就知道这个MPT节点是正确的,然后就会发送更多请求,请求该MPT节点的每个子节点。
来源:https://github.com/ethereum/go-ethereum/blob/87c463c47aa6d10a55a4b860bab3d53199814d01/trie/sync.go#L205-L218
如果遇到叶节点,账户的代码和状态树将排队等待被获取。
来源:https://github.com/ethereum/go-ethereum/blob/87c463c47aa6d10a55a4b860bab3d53199814d01/core/state/sync.go#L31-L39
请注意同步子树和原始条目之间的区别。虽然二者都下载任意的数据块,但是如果同步器预期要同步的是原始树,它会将该数据解析为树节点,并开始同步其子节点。另一方面,如果同步器预期要同步的是原始条目,它会将数据块写入数据库并终止。
来源:https://github.com/ethereum/go-ethereum/blob/87c463c47aa6d10a55a4b860bab3d53199814d01/trie/sync.go#L183-L197
另外还要注意的是,Geth想要多次向同一个节点发送请求的情况并不少见。例如,如果两个合约存储相同的数据,那它们的stateRoot可能相同,也有可能出现两个账户拥有同样的账户状态。在这些情况下,Geth不想让网络充斥这些请求,因此会将它们合并。
来源:https://github.com/ethereum/go-ethereum/blob/87c463c47aa6d10a55a4b860bab3d53199814d01/trie/sync.go#L246-L255
然而,同步器不会合并请求的raw属性。这意味着,如果已经有了一个未决的原始条目请求,但是同步器又安排了一个具有相同哈希值的子树请求,后者将被合并,最终结果还是只有一个原始条目请求。
请记住,原始条目请求不会为了同步子节点而处理节点。这意味着,如果我们能以某种方式在原始条目和子树节点之间引发冲突,我们就能让Geth同步一个不完整的状态树。另外,遇到一个本地不存在的树节点时,Geth不会退出,这等于是假设,如果下载器报告同步成功,这种情况就不会发生。这就意味着,缺少一个树节点的Geth节点在行为上与其它完全同步树的节点截然不同。
那么,如何引发冲突呢?事实证明非常简单:我们只需用我们控制的某个合约的序列化状态根部署另一个合约,因此该合约代码的哈希值必定与状态根哈希值相同,这就意味着如果合约代码先同步,另一个合约的状态树不会被全部下载下来。
综上
如果有人利用这个漏洞作恶,需完成要多个步骤并等待很长时间。
首先,我们部署一个合约。这个合约应该有一个独一无二的stateRoot,从而避免与这个stateRoot相关的MPT节点提前同步。
现在我们就大功告成了。当新的Geth节点使用快速同步加入网络时,它们会先请求Exploit合约,同步其状态子树及代码。当Exploit合约的代码被同步时,它会创建一个看起来与Discrepancy的状态根请求完全相同的原始条目请求,但它不会被当作子树请求处理。这意味着,该节点永远不会下载Discrepancy的状态trie,因此未来读取magic的请求将返回0而非1。
经过足够长的时间后,我们要做的就是调用Hardfork.hardfork(discrepancy)。每个正确同步整个网络的节点都会看到一个回滚交易,而每个使用快速同步加入网络的Geth节点都会看到一个成功的交易。这将导致一个区块产生两个不同的状态根,也就是说我们可以随心所欲地触发链分裂。
Geth团队通过处理PR#21039中的树读取错误快速解决了该攻击,然后通过区分PR#21080中的代码部分和树部分完全修复了这个漏洞。
结论
这是一个非常有趣的漏洞,它可以让攻击者在以太坊网络上设置一个“炸弹”,并随时引爆,从而导致所有使用快速同步的Geth节点从主网中分叉。这个陷阱利用的是Geth的同步和数据存储代码中极其复杂的逻辑,这或许是它很长时间来都没有引起人们注意的原因。
敬请期待本系列的第三篇也是最后一篇文章。在这篇文章中,我们将探索Geth客户端的最新bug,具体细节不便透露。
脚注
从技术层面来说,Geth中的值节点不包含后缀。你可以将其理解成一个后面跟着值节点的扩展节点。
实际上,Geth使用的是“安全的trie”,即,通过SHA-3算法对所有键进行哈希计算,从而确保所有键都是固定长度。
原文链接:https://samczsun.com/booby-trapping-the-ethereum-blockchain/
作者:samczsun
翻译&校对:闵敏&阿剑
BTC减半倒计时|金色财经挖矿收益播报:金色财经报道,据OKEx矿池数据显示,下一次BTC减半日期预计为2020年5月12日,今天距此还有55天。BTC当前块高621915,下一次减半块高630000.
1900/1/1 0:00:00从链上数据回顾上半年行情、展望下半年市场。作者|?DILUTION-PROOF,由Odaily星球日报译者Katie辜编译.
1900/1/1 0:00:00原标题:《609监管日:多省禁挖矿、搜索封交易所、统一抓捕背后逻辑与未来发展如何》吴说作者|ColinWu本期编辑|ColinWu6月9日近期监管政策集中释放,背后逻辑与未来发展如何?1、新疆昌吉等地发文.
1900/1/1 0:00:006月9日,萨尔瓦多成功批准通过了将比特币作为法定货币的提案,该国成为首个采用比特币作为法币的主权国家,而此次事件也被法新社列为了货币史上的第14次重大事件.
1900/1/1 0:00:00上周BTC和ETH的期货交易量周环比下跌都超过了20%,期货日均未平仓量均保持基本稳定,期货市场整体还维持着多空相持局面。ETH的期权交易量周环比大跌40.06%.
1900/1/1 0:00:00据TheBlock6月8日消息,媒体TheInformation援引知情人士称,BlockFi在E轮融资中筹集了约5亿美元,由DanLoeb经营的对冲基金ThirdPoint和总部位于伦敦的风险投资公司Hedosophia共同领投.
1900/1/1 0:00:00