分析Mempool(交易内存池),复盘 “黑色星期四”
Blocknative 已经找出了 MakerDAO 在 3 月 12 日和 13 日 时的清算活动乃是有人刻意为之的证据。这些证据是从 “Mempool”(交易内存池),即矿工打包区块时候的备选交易池,中来的。交易内存池是一个以太坊生态中经常被人忽略 —— 也不受大家重视 —— 的部分。
作为交易内存池分析专家, Blocknative 运营着一个由遍布全球且配置各不相同的 Geth 节点和 Parity(OpenEthereum)节点组成的网络。这一基础设施 使我们能部署实时的交易监控服务 ,我们可以捕捉、规范化和归档内存交易池的状态变化(如无专门操作,这些变化都是川流不息、转瞬即逝的)。Blocknative 在 “黑色星期四” 时捕捉到了 3000 万行数据,使我们能做一项开放式的研究;迄今为止,我们已经发现了数个似乎已被利用过的 “漏洞”。
虽然很多人都已写过 “黑色星期四” 的结果,但在本文中我们会首次披露 Blocknative 内存池存档中的数据,用新的数据和理论说话。我们也把分析要用的底层内存池交易数据集合开放出来了,供大家审阅(见下文)。
如果你是一位安全研究员,想了解更多我们的发现,或想发现其它潜在的交易池异常, 请联系我们 。
总结
Blocknative 的内存池法医报告显示,内存池中有三大因素影响到了 3 月 12 日和 13 日的事态:
- 被阻塞的交易 —— 内存池的拥堵极大地提高了交易卡壳率,让同一地址连续发出的多笔交易都被卡住、不能得到处理;
- 交易池 “压缩” —— 交易池中可上链部分(即被矿工认为 Gas 费用足够高的交易)比重的大幅减少,可能影响到了 Gas 价格的估计;
- “Hammerbot” —— 自动化的交易系统加剧了交易池堵塞,因此与交易池压缩效应相叠加;
背景: “黑色星期四” 概况
在 2020 年 3 月 12 日,密码学货币市场上出现了有史以来最大的抛售潮,几个小时里面,ETH 价格暴跌 43%,BTC 的价格跌掉了 39%。随着价格下跌,一个负反馈循环出现,多种 DeFi 合约内都开始出现流动性降低和强制清算活动。在此期间,这种下行的压力使得每一个尝试在网络上发送交易的个体都遇到了严酷的挑战。
如此堵塞情形的一个负面后果是 MakerDAO 债仓 清算活动中出现了 “0 价格拍卖” 现象。在 “黑色星期四” 期间的 3994 个清算拍卖中,有 1462 个(36.6%)债仓的担保品是被 0 价格拍走的。在大概 12 小时的时间里,锁在债仓中、总计 832 万美元的担保品被 0 价格拍走,没有让系统回收到一分钱。更多细节请看 MakerDAO 自己发布的《 2020 年 3 月 12 -13 日的价格暴跌及其对 MakerDAO 的影响 》一文。
1. 由交易池堵塞导致的交易卡壳
3 月 12 日时 ETH 价格的迅速变化导致用户大量发送交易,产生了交易池内部的拥堵。对价格波动自动作出反应的机器人的活动加剧了这一情形。
- 删除,或者说丢弃掉许多有效的交易;
- 拒绝,或者说无视掉许多有效的交易。
删掉一笔交易之后,该节点不会留下关于该笔交易的任何信息 ,比如发送地址和交易 nonce。因此,当同一个地址的一笔新的交易(使用新的一个 nonce)到达交易池,该节点会发现该地址已处理交易和这笔新交易之间有 nonce 空缺(nonce gap)(因为该新交易的上一笔交易还没得到处理),因此也不能处理这笔新交易。受此效果影响的交易就只能放到该节点的交易池中无法处理的队列中。 这些交易,无论所支付的交易手续费(即 Gas 费)有多高,都一概会被卡住、无法处理。
想了解更多细节,请看我们此前讲解交易排序的文章: 《节点是你通往内存池的网关》 。
与此同时,网络的堵塞导致进入内存池的 Gas Price 门槛随之迅速提高,因此最初的一些交易现在会因为 Gas Price 太低而被拒绝。而且,因为之前被删除的交易不能回到交易池中来,交易的 nonce 空缺问题又变得更严重了。事实上,一些节点实现会在一段时间内主动无视掉这些被拒绝的交易,以保护自己不受点对点网络中的泛滥攻击(spamming)影响。所以, nonce 空缺实际上会锁住这些受影响的地址,使得他们无法完成新的交易。
卡壳交易更有可能影响会发出许多新交易的地址,包括自动化交易系统、支付网络,甚至交易所。这些系统想回到正轨,但通常会加剧拥堵,因为越来越多交易被推迟处理。
有没有解决办法?你得主动发现自己何时开始遭遇卡壳。这可能有点年,因为你的交易可能仅在某些节点处是卡壳的,但并不是在所有节点处都面临卡壳。因此,你必须确定第一笔被丢弃、导致 nonce 空缺的交易,然后立即用可以得到打包的 Gas Price 加速 让这笔交易上链。最后,你还得继续监控一开始发现卡壳的交易,确保它从不能处理的队列中移回到了交易池的待打包部分中,并成功上链。如果你的交易还是卡壳,重复上述加速步骤,直到你可以确认所有导致 nonce 空缺的交易都已成功上链。当然,这也是我们开发并运营我们的 Notify API 的理由之一。
2. 压缩内存池中可上链的部分
网络阻塞的出现 —— 及其导致的交易卡壳 —— 使得交易池中可上链交易的比重迅速缩减。 我们管这叫 “交易池压缩” 。
矿工的激励分两部分:区块奖励和交易的 Gas Price;所以收益最大化需要打包 Gas Price 最高的那部分交易,挖矿时要根据交易池 —— 即候选交易(也可以说候选区块)—— 的情况(也就是各交易愿意给多高的 Gas Price)来决定打包哪些交易。
在交易池拥堵时,多种行为会导致交易池压缩,对矿工来说值得考虑的交易比重越来越小:
- 资源耗尽 :在某些节点实现中,卡壳交易数量的迅速上升会消耗掉可观的交易内存池资源。这又反过来导致节点处理有效 pending 交易的可用资源进一步减少。
- 不断升高的 Gas Price :ETH 价格的下跌导致许多交易变得 “高度紧急”,因为这些交易在构造时是希望能够赶在 ETH 进一步下跌时上链确认的。这使得个体用户也好、自动化机器人也好,都赶紧提高 Gas Price。因为不是所有参与者都这样密切关注着堵塞情形,待打包交易总体包含了反常比例的低价 —— 因此不可能被打包的 —— 交易。
- 反应慢半拍的定价算法 :交易池中待打包交易的 Gas Price 分布,以及近期被打包交易的 Gas Price,使得一些预测合适 Gas Price 的服务的测算出现扭曲。但这(些服务报出已低于实际情形的 Gas Price)就导致更多 Gas Price 过低的新交易出现,又进一步加剧了测算值与实际值的不一致。
在 3 月 12 日,我们的数据平台发现了交易手续费定价的明显偏离。在 3 月 13 日,交易池中部分交易的 Gas Price 与上链交易的 Gas Price 差额在可预料范围内, 但在 3 月 12 日,已挖出交易和未挖出交易的 Gas 出价简直是天壤之别 。
不能上链的交易要成为可以上链的交易,一般来说标准的操作就是提高 Gas Price。但是,在 3 月 12 日,这样做根本就没用。 因为,大量交易池资源被以几乎同样的 Gas Price 重发的交易消耗掉了 。
3. Hammerbot 交易导致内存池失真
我们的内存池存档数据暗示,机器人成功地提高了堵塞情形,并扭曲了交易池中交易的 Gas 价格分布,而且还没有导致交易手续费的相应提高。
这样做的净效果就是交易卡壳率的提高和 Gas Price 报告服务扭曲,结果是交易池一场,使天平偏向了某些特定的交易 —— 即,提高了清算 CDP 仓位的交易以 0 价格成功竞拍的几率。
机器人用本来就无意提交上链的交易捶打(hammer)交易池。 这些 Hammerbot 通过发送置换率极高的交易(不相应提高 Gas 价格但又不断重发)消耗掉了交易池的资源 。 但是,交易池还有一种设计,是要求重发交易至少要提高 10% 的 Gas 价格,本身就是用来防止此类行为的。那这些交易是如何实现置换的呢?
答案很简单:异常高的交易丢弃率导致节点 “失忆”(见上文)。
Hammerbot 等待着 —— 或者仅仅是预估 —— 自己发出的交易从交易内存池中丢弃,然后立即用相同(甚至更低)的 Gas 价格重发交易。因为节点 “忘记掉了” 之前被丢弃的交易,自然就尽职地把这些置换后的交易当成有效交易接了过来。当然,结果就是进一步的拥堵。
Hammerbot 用显然是 “自动化” 的方式让自己的交易变得畸形,每一次置换都包括了稍微更改过的合约输出。因此,每一笔 Hammerbot 交易都有一个独特的哈希值,可以绕过所有节点的点对点网络协议中的泛滥攻击过滤保护。
如下图中重点标出的粉色线所示,从 UTC 时间 3 月 12 日 9 点开始,我们的交易池数据平台发现根本不可能被打包的待打包交易数量急剧上升。35 分钟后,此类不可能上链的交易产生的速度翻了一倍,在 10 点之后才降为线性增长的模式。
从整体上来看,虽然进入交易池的交易数量急剧增加,交易池中还是有很大一个比例的交易 Gas Price 被人为压低了。
仅仅用你惯用的区块浏览器检视这笔交易并不能给出除了其区块确认信息以外的洞见。表面上来看,这笔交易平平无奇。但 Blocknative 的交易池数据平台检测到了这笔交易上链之前使用这个 nonce 值的 418 笔独特交易。这些交易都是在一个小时内出现的,也就是这些置换交易平均每 6.86 秒重发一次,而某些置换交易之间的时间差不超过 0.1 秒。
地址 | Nonce | 一分钟之内的置换交易笔数 |
---|---|---|
0xdd3b6ae71ff420375fefaa2448046a37beeed800 | 9211 | 22 |
0x704bf43e578b2b912584495467c3a2210deaec11 | 7307 | 20 |
0x56f0bdbdf48556ed41248021fbc8027f69c41f27 | 5004 | 19 |
0x4a1351523071ed88d20afd1d10cda75d1b34f4e7 | 6644 | 19 |
0x2a7139e98f47c2fc65aec0de9a3adb8ffd46206a | 10712 | 19 |
0x602bf7ba827a61d708614eb35284e79654c7e58f | 8458 | 18 |
0xe6919901cef07c15373feac6871046848efd4212 | 2745 | 17 |
0x58500f55ac86a3022703806a7415cac321cce2a1 | 2587 | 16 |
0x6861d397f7ff510a1ab4bb60434d8a9c4dd01240 | 5837 | 16 |
0x5cf2fa4e0c84e71fd2e4fa86d2fa64b7a50a6fc0 | 3067 | 15 |
0x3a711e39640b802d201391d14d5f2d3159f07957 | 2279 | 14 |
0x4769ad67bce2779d1374675069a3b78b8dafbea6 | 2671 | 14 |
0x97e7ba1f2db224dac7e0a812a071474e7c60819a | 9715 | 14 |
0x124f58b41fd43a498fe7041bc2d9d5813c4f80d7 | 2379 | 13 |
0x124f58b41fd43a498fe7041bc2d9d5813c4f80d7 | 2379 | 13 |
0x82b1e33c6465a9bedefd4af8a2c1cfc1c874bfbe | 2516 | 13 |
0x3a711e39640b802d201391d14d5f2d3159f07957 | 2354 | 12 |
0x95227df275141c9bbf679f695668a838a31459fc | 2435 | 12 |
0x5e4f7ce2607c39f4ec08355e2ea48e50f6f77bff | 9208 | 12 |
0x3046a9743a1b8d967f2ddb014e341b0eca41c191 | 6193 | 10 |
0xb123a59c4e3ff44e57b3113234fa6ebe804e996d | 2223 | 8 |
0xc1bfbc44536200b02f92aec4dee08ea390fa0535 | 2187 | 6 |
交易池漏洞对 MakerDAO 的影响
MakerDAO 的 担保债仓 (CDP)是用户生成稳定币 DAI 时托管被锁定的担保品 ETH 的智能合约。因为 ETH 的价格有波动,而 DAI 希望能保持 1 美元的价格,所以维持一个开放的 CDP 所需的担保品数量是不断变动的。
当 3 月 12 日 ETH 价格暴跌时,大量的 CDP 立即变成了担保不足的状态,需要被 强制清算 。系统为保证担保品清算时可以获得竞争性的市场价格,安排了折价机制:参与清算者可以以折价买到担保品 ETH;这就使得许多人都愿意运营多种多样的 Keeper 机器人 。清算活动的表现形式为 拍卖 。
任何 Keeper(看护者)都可以用 链上 出价参与拍卖,而一个看护者出价之后,另一个看护者想要与之竞争必须在 10 分钟之内发送竞拍价更高的交易。每当有新的竞拍交易到达,都会刷新 10 分钟的竞拍窗口期。如果 10 分钟之内系统没有收到更高的出价,则拍卖结束,最高出价者胜出。 [注意:在 “黑色星期四” 下午,MakerDAO 把竞拍窗口期从 10 分钟提高到了 6 小时。]
但是,当矿工节点因为网络哟难度和交易池压缩而以异常高的比例丢弃交易时,许多看护者的交易都被卡住了,因此根本没能及时让清算 CDP 的竞拍交易成功上链。 结果就是看护者们无法在由 0 价格竞拍交易开启的窗口中可靠地开展竞争,即使看护机器人正确地发现了 0 价格拍卖并发送出了更高出价的交易也没用。
最终,因为拥堵和实际手续费的混淆,看护者无法恰当地解读出 Gas Price 的实际上涨幅度,看护者的交易也因此被节点丢弃。接下来是产生 nonce 空缺、交易在使用默认交易池设置的节点处卡壳。 遭遇此种情形的看护者机器人为其他拍卖发出的所有后续竞拍交易会全部被卡住,让该看护者根本无法在 10 分钟的竞拍窗口内参与交易,最终让 0 价格竞拍交易得逞 。
下面我们用 1866 号拍卖作为例子来说明上述过程:
- 一个 “0 价格竞拍机器人” 在 UTC 时间 15:59:50 时以 200 Gwei 的价格发送一笔 0 价格竞拍交易。该交易在 26 秒后成功上链,10 分钟倒计时就此开始。
- 一个 “诚信看护者机器人” 在 16:08:01 时以 450 Gwei 的 Gas Price 发送一笔竞价交易。这笔交易是在 10 分钟内发出的 —— 之隔了 8 分 11 秒 —— 而且 Gas 价格还是最初那笔 0 价格竞拍交易的 2.5 倍。如果能在接下来的 1 分 49 秒内被打包到区块中,1866 号拍卖就会像大家预期的那样进行。但是,这笔竞拍交易因为这个好机器人之前发送的一笔交易被丢弃(也就是产生了 nonce 空缺)而被卡住了。
- 这个好机器人在 16:15:31 又发出一笔 4500 Gwei 的竞价交易。但是哪怕 Gas Price 提高了 10 倍也无济于事,因为这个地址被 nonce 空缺锁死了。再然后,等到竞价交易不再被卡的时候,已经过了 10 分钟的窗口期,交易自然就失败了。
Gas Price 的迅速上升、导致 Gas 价格估计失灵的交易池压缩,同样对其它价格信息传输机制造成了负面影响。这导致了定价方面的混乱,也有可能使得面临清算风险的 CDP 持有者推迟了添加担保品的决定。
总结
总结一下,我们对 “黑色星期四” 交易池状况的事后检验暗示了下述情况:
- 多种 Hammerbot 的活动导致挖矿节点的交易池饱和。这推高了以太坊网络的拥堵程度并使之不断上升。
- 发送许多以同样的 Gas Price 发出的置换交易在点对点 gossip 层和节点本身产生了巨大的开销。
- 正常交易的传播受到阻碍,大量的交易被挖矿节点丢弃或者拒绝。
- 这又反过来提高了出现 nonce 空缺和卡壳交易的几率,还扭曲了对 Gas Price 的估计。
- 虽然自动化的交易系统通常被设计成会自动提高交易的 Gas Price,许多交易系统并不能很好地处理 nonce 空缺问题 —— 甚至是完全束手无策。
为加速这个发现过程,我们现在把那两天的交易池存档数据开放给社区,我们希望这能促进对交易池在 “黑色星期四” 及以太坊网络其它类似事件中所扮演的角色的研究。
建议:保护好你自己,以及你的用户
交易池是区块链生态系统非常关键的一环,虽然它瞬息万变,常常被忽略。因此,交易池中藏着很多开发者和用户 “完全无知的未知之物”。
现在,我们并不知道有多少人在开发这样的利用交易池弱点的技术 —— 只是显然有人在利用交易池的特点。我们也不知道有多少这样的弱点存在 —— 只是那些复杂的利用方法似乎已经在现实中证明了其有效性。
因此,我们建议所有交易、所有协议、所有钱包供应商和交易员:
- 持续监控交易池的情况 ,注意(1)发现交易池开始变得拥堵的时机;(2)交易丢弃率和拒绝率的变化。
- 理解交易 nonce 排序的细微差别 。即使合理构造的交易也可能被卡在网络的某个角落。
- 主动观察被卡壳的交易 ,并且要知道加速先前的哪一笔交易能使发送交易的地址脱困。
- 基于交易池中对矿工有吸引力的部分来计算 Gas Price 。还需要知道你所依赖的 Gas Price 报告服务所用的算法。
- 在高度拥堵期间,不要假设交易待打包的情形是可以预测的 。先防范,你要监控每一笔交易,了解每一个细节,包括 Gas 是否充分,交易是不是被丢弃、被卡壳,会不会被人抢跑(front-running),等等。
……
非常感谢评议本文初稿的各位,包括: Sarah Baker-Mills 、 Dmitriy Berenzon 、 Spencer Bogart 、 Nic Carter 、 Hsin-Ju Chuang 、 Tomasz Drwięga , Andy Gray 、 Hudson Jameson 、 Jon Kol 、 Calvin Liu 、 Justin Mart 、 Gavin McDermott 、 Taylor Monahan 、 Andra Nicolau 、 Charlie Noyes 、 Simona Pop 、 Alex Pruden 、 Austin Roberts 、 Cuy Sheffield 、 Larry Sukernik 、 Chris Whinfrey ,等等。我们非常感谢你们的反馈、洞见和指导。
(完)
原文链接: https://blog.blocknative.com/blog/mempool-forensics 作者: Blocknative 翻译: 阿剑