Flashbots:内存池的王者 🤖

  • noxx__
  • 发布于 2022-07-12 18:29
  • 阅读 12

本文深入探讨了 Mempool 及其在 Flashbots 竞拍中的作用,详细解释了 MEV Geth 的发展以及其对以太坊交易池的影响。文章从 Mempool 的定义开始,逐步分析了不同版本的 MEV Geth 及其如何优化交易的排序和处理,最后介绍了 MEV Boost 的概念。

所以你听说过 Mempool、Flashbots、Bundles 和 Searcher Auctions,但你真正理解这些东西是什么以及它们是如何运作的吗?

你理解它们的内部机制吗?

本文将通过 Flashbots 拍卖的视角深入探讨 Mempool。

读者将经历 MEV Geth 的开发历程,这是一个定制的以太坊客户端,使 Flashbots 能够改变 Mempool 的形状和结构。

MEV Geth 以及 Flashbots 中继提供了搜索者与矿工之间的门户,改变了 MEV 游戏的未来。

Mempool

让我们从故事的主角“ Mempool”开始讲起,甚至在第一个官方 MEV Geth 之前就已经存在。

Mempool(内存池)是一个较小的数据库,包含每个节点保留的未确认或待处理交易。当交易通过被包含在一个区块中而被确认时,它将从 Mempool 中移除。

这个定义是一个很好的开始。让我们继续讨论一些基本概念。

  • 一个节点例如 Geth(或 MEV Geth)保存着未确认或待处理交易的列表。

  • 这些交易通过节点的对等体进行通讯,或直接通过它们的 RPC 端点接收。

  • 每个节点都有自己的 Mempool,没有所谓的全局 Mempool。

  • 这意味着节点的 Mempool 可能会有所不同,这取决于它们的地理位置和所连接的对等体。

  • 节点对它们在 Mempool中保留的交易数量有一个限制,这样可以确保节点不被淹没。

Mempool 在 Geth 节点中通过一个 TxPool 结构表示,如下所示。

tx_pool.go

已高亮显示两个项目。第一个项目“locals”代表一组地址。如果 TxPool(Mempool)中的一笔交易来自这些地址之一,则它将被视为本地交易(你稍后会看到这很重要)。

第二个高亮显示的项目是“pending”,表示 TxPool 中所有当前可处理的交易。

你可能还会注意到第 242 行的“queue”字段,这表示在 TxPool 中但不具可处理性的交易。例如,一笔 nonce 不按顺序的 Tx,表示有另一笔来自同一地址的交易应该优先处理。

当考虑 MEV 时,接下来你需要问的是,这些交易如何从 TxPool 进入区块。

下面是执行此功能的 Geth 代码。你会看到,这些交易实际上被分为 localTxs 和 remoteTxs,并按其 gas 价格 / nonce 排序,而 localTxs 在选择上优先于 remoteTxs。

worker.go & transaction.go

  1. “commitNewWork” 函数做了很多事情,包括从 TxPool 填充区块中的交易。

  2. 这一节从 TxPool 获取交易并将其分为本地和远程交易。

    1. 在第 954 行,你可以看到注释“用所有可用的待处理交易填充区块”,在这一行之后,我们通过 w.eth.TxPool().Pending(true) 设置“pending”变量。这返回一个地址与交易的键值映射。

    2. 第 967 行到 974 行显示了我们使用先前高亮显示的“locals”变量将交易分为本地和远程。我们通过本地地址循环并检查是否有任何待处理 Tx 是来自本地地址。如果匹配,这些交易将从 remoteTxs 中移除并添加到 localsTxs。(Locals 是节点可以在其配置中定义的一组地址,它们在 remote 交易中优先,并不会被节点丢弃。)

  3. 最终结果是待处理交易被分为来自 “locals” 地址的 localTxs 和来自其他所有人的 remoteTxs。

  4. 此区块表示 localTxs 被排序并提交到区块中。由于在代码中优先处理 localTxs,它们相较于 remoteTxs 具有优先权。 通过“NewTransactionsByPriceAndNonce”按 gas 价格和 nonce 对交易进行排序,然后通过“commitTransactions” 提交到区块。

  5. “NewTransactionsByPriceAndNonce” 接受一组 Tx,并按 gas 价格对其进行排序,并确保账户 nonce 不乱序。为此,它使用一个堆数据结构,我们可以在第 443 行看到它的初始化。

    1. heads 变量是一个 TxByPriceAndTime 数组,该数组在底层只是一个交易数组。它实现了堆接口,使我们能够使用堆调用来排序我们的数据。它表示“每个唯一账户的下一个交易(价格堆)”。要了解更多有关堆的内容,请参见 这里

    2. 每个账户的交易按 gas 价格排序,但同时保持 nonce 顺序。这意味着一笔高 gas 价格的交易如果 nonce 不是下一个,可能不会位于块的顶部。下面的 Geth 测试用例的输出演示了这一点。

image.png

  1. 如果多笔交易的价格相同,首先被看到的那笔交易将被优先处理,以避免针对特定订单的网络垃圾攻击(时间戳在代码中被捕获 这里)。
  1. 我们返回交易集以用于“commitTransactions” 函数将交易提交到区块。

  2. 与 remote 交易发生相同的过程,按 gas 价格和 nonce 排序并提交到区块。提交交易的数量受到区块 gas 限制的限制。当 TxPool 中的 Tx 数量超过节点限制时,远程交易可能会被节点丢弃。

这让我们回到了“优先级 Gas 拍卖”(PGA)的时候,搜索者和机器人会提高交易的 gas 价格以确保在区块最前面。

机器人会在~13.5秒的区块时间内不断竞标,以确保摆在区块最前面,从而首个捕捉链上机会(即执行清算)。

当你在拍卖中失败时,你仍然必须支付你的最高竞标的 gas 价格,因为你的交易仍会被执行,但会回滚。机器人可能会因此损失很多钱。

在 PGA 竞标时,机器人和搜索者必须遵循这些规则以提高给定交易的 gas 价格。

来源: https://www.blocknative.com/blog/speed-up-transactions

现在我们已经覆盖了原始 Geth,让我们看看首个官方 release 的 MEV Geth 的变化。

MEV-Geth v0.1 - 2021年3月31日

对于那些不知道的人,MEV Geth 是一个新的定制客户端,允许矿工接收 MEV Bundles。

“MEV Bundles” 表示提取价值的交易捆绑,且需要原子性执行。捆绑中的所有交易应该全部执行或全部不执行。

Flashbots 提供了一个中继,将搜索者与矿工连接。搜索者会向 Flashbots 中继提交捆绑,而中继会将其转发给矿工。该系统依赖于双方之间的信任程度。

MEV Geth 和 Flashbots 中继寻求将 PGA 移出对日常用户产生影响的链条,并移动到这个中继。

新的拍卖规则随即确定并提供给搜索者。失败的竞标不会被执行,节省了机器人的资金,也节省了以太坊的区块空间。

捆绑还引入了通过 coinbase.transfer( ) 直接支付给矿工的能力,而不是通过 gas 费用支付。

对于 v0.1,每个区块只能选择一个捆绑。

让我们看看为使这些 MEV Bundles 启用所做的客户端更改。

交易排序

  1. TxPool Geth 的 Mempool 实现,如我们之前所见。

  2. 在 TxPool 中增加了一个名为“mevBundles”的新字段,它是一个 mevBundle 类型的数组。MevBundle 由 4 个元素组成

    1. “txs” - 捆绑中的交易列表。每个交易都是签名和 RLP 编码的。

    2. “blockNumber” - 捆绑可以被执行的确切区块号。

    3. “minTimestamp” - 捆绑可以被执行的最早区块时间戳(包含)。

    4. “maxTimestamp” - 捆绑可以被执行的最晚区块时间戳(包含)。

  3. “CommitNewWork” 是我们之前看到的同样功能,将所有待处理交易填充到区块中。

  4. “Local Transactions”和“Remote Transactions”部分负责将每组 Tx 排序并提交到区块。你会注意到这两个部分都在“Flashbots Bundles”部分下,这意味着捆绑的交易会优先于并放在区块顶部。

  5. 这一部分处理来自 Flashbots 的“mevBundles”。第 1148 行从 TxPool 中抓取所有捆绑。“findMostProfitableBundle”函数(我们将在下一部分看到)用于确定最具盈利的捆绑。捆绑信息在节点上记录,然后通过第 1155 行的“commitBundle” 提交到区块。

那么 Flashbots 如何确定最具盈利的捆绑呢?

最具盈利的捆绑

早些时候我们说过,“新的拍卖规则已确定并可供搜索者使用”。

下面是新的拍卖规则,虽然看起来复杂,但我可以向你保证其实并不复杂。

来源: https://docs.flashbots.net/flashbots-auction/miners/mev-geth-spec/v01

让我们通过图示来理解。

  • “s” 是“调整后的 gas 价格”,这是我们想要最大化以赢得拍卖的价格。

  • Delta coinbase 是捆绑中通过直接支付即 coinbase.transfer( ) 支付给矿工的总 ETH。

  • 捆绑中所有交易的 gas 费用总和(gas price * gas used)代表捆绑的总 gas 费用。

  • 捆绑中使用的所有 gas 的总和。

  • 将这两个值相除会给出捆绑的每单位 gas 的 ETH 价格(调整后的 gas 价格)。

  • 为了最大化这个值,我们需要:

    • 通过 coinbase.transfer( ) 或 Gas 来支付更多的钱给矿工,从而增加分子。我们可以通过降低利润边际或寻找更有利可图的机会来做到这一点。

    • 通过减少捆绑的使用 gas 来减少分母,我们可以通过对合约进行 gas 优化来实现这一点。

好吧,让我们看看 MEV Geth 中如何实现这个计算。我们从上一个例子中的“findMostProfitableBundle”函数开始。

  1. “findMostProfitableBundle” 函数循环遍历捆绑,以确定“maxBundle”(最具盈利性促销),并返回该捆绑以及记录在 Geth 节点上的其他一些数据。

  2. 每个捆绑都会经过“computeBundleGas” 函数处理。该函数返回“totalEth”,其代表“coinbaseDiff”(矿工的余额增加了多少),和“totalGasUsed”,这是捆绑中交易消耗的 gas。

  3. 我们通过循环遍历捆绑中的每笔交易并运行“ApplyTransaction”来计算 gas 消耗,这使我们能够模拟交易并获得包含“GasUsed”的交易收据。通过对每笔交易的“GasUsed”进行求和,我们能够得到整个捆绑的“totalGasUsed”。更新后的状态将用于循环中的下一笔交易,因而能更准确地表示真实区块中的情况。

    1. 请注意,“totalGasUsed” 代表我们“调整后的 gas 价格”计算中的分母(底部),它决定了 Flashbots 拍卖的胜者。
  4. 在第 1245 行,我们声明一个变量“coinbaseBalanceBefore”,该变量在模拟任何交易之前获取 coinbase(矿工)地址的余额。第 1254 行声明“coinbaseBalanceAfter”,其在所有交易应用到状态后获取 coinbase 余额。第 1255 行设置“coinbaseDiff”,它将“coinbaseBalanceBefore”从“coinbaseBalanceAfter”中减去,以得到矿工的利润。该值用于设定 totalEth 并返回。

    1. 这个值考虑了 coinbase.transfer( ) 的支付以及在“ApplyTransaction”函数中进行的以 gas 方式的支付。这意味着“coinbaseDiff” 代表了我们“调整后的 gas 价格”计算的整个分子(顶部)。
  5. “调整后的 Gas Price”计算,由于我们现在具备了分子和分母,我们只需进行计算。在第 1221 行声明一个变量“mevGasPrice”,我们将“totalEth”除以“totalGasUsed”,从而得到我们的“调整后的 Gas Price”。

  6. 一旦计算出“调整后的 Gas Price”,将比较每个捆绑与当前的最大值(第 1222 - 1227 行),这确保了返回的捆绑具有最高的“调整后的 Gas Price”(“mevGasPrice”)。

这是 MEV Geth 的第一个版本,但还有更多内容,在接下来的几个月中,v0.2 被发布。让我们看看有什么改进。

MEV-Geth v0.2 - 2021年4月12日 → 2021年6月16日

在 v0.2 中有多个改进,但我们将关注三个主要更改。调整后的 gas 价格计算的更改,捆绑合并以及对客户端中 MevBundle 结构的调整。

调整后的 Gas Price

对此计算的更改很小。涉及到从捆绑中去除已经在 TxPool 中的交易的 gas 支付。这些交易表示为

这样做的目的在于防止“捆绑填充”,即搜索者用 TxPool 中高 gwei 的 Tx 填充它们的捆绑,以提高其“调整后的 gas 价格”分数。

  1. 我们重新检查“computeBundleGas”,以查看计算的变化。

  2. 如之前所述,我们循环遍历捆绑中的 Tx,运行“ApplyTransaction”来模拟 Tx,并查看状态的变化。

  3. 首先我们将“txInPendingPool” 设置为 false。

    1. 然后,我们从捆绑 Tx 的地址获取 TxPool 中所有待处理交易。

    2. 获取捆绑 Tx 的 nonce。

    3. 对于 TxPool 中与该地址关联的每笔交易检查 nonce 是否匹配。

    4. 如果匹配,将 txInPendingPool 设置为 true,并跳出循环。

  4. 如果交易不在待处理池中,将该 Tx 的相关 gasFees 添加到整个捆绑的 gas 费用中。(这实际上相当于从已存在于 TxPool 中的 Tx 中减去 gas 费用)。

  5. 将调整后的 gas 价格计算与代码进行比较,我们可以看到

    1. “ethSentToCoinbase” 映射到 “delta coinbase” - 支付给 coinbase 地址的款项。

    2. “gasFees” 映射到“捆绑的总 gas 费用” - 这代表了捆绑中所有 Tx 的 gas 费用,减去已经在内存池中的 Tx 所产生的 gas 费用。

捆绑合并

在 v0.1 中,每个区块只能包含 1 个捆绑,而在 v0.2 增加了捆绑合并功能,使我们可以合并 3 个捆绑以最大化利润。让我们看看如何实现。

  1. “commitNewWork” 处理填充区块交易。

  2. 和之前一样,我们有 Flashbots 捆绑部分、本地 Tx 部分和远程 Tx 部分。“Flashbots Bundle”部分已经与 v0.1 改变,从前有一个新的函数叫 generateFlashbotsBundle。

  3. “generateFlashbotsBundle” 采用一个捆绑数组,模拟它们、排序并合并它们。让我们细看一下。

  4. 我们有一个新的结构体被称为 “simulatedBundle”,该结构体包含原始捆绑以及我们的“调整后的 gas 费用”计算所需的关键数据,如 totalGasUsed、totalEth 等。第 1217 行的 simulateBundles 函数接受一个捆绑并对其进行模拟,以验证其不回退,并计算捆绑 gas。

  5. 模拟的捆绑根据“mevGasPrice”(调整后的 gas 价格)被排序。

  6. 这些已排序的捆绑传递给“mergeBundles”函数。在该函数开始位置,声明了一个名为“finalBundle”的变量,这代表着交易集。该“mergeBundles”函数是为了使多个捆绑可以被纳入一个区块而设计的。

  7. 这一部分执行在一个捆绑循环内,每个捆绑又通过“computeBundleGas”进行模拟,但未对链顶状态进行,也没有根据订单生成;而是根据前一个捆绑离开的链状态进行模拟。如果“computeBundleGas”返回错误或“mevGasPrice”低于 floorGasPrice(他们在 v0.2 中引入的新固定下限 参考),则状态/gasPool 将重置为上一个循环中的状态。使用“continue”将让程序重新回到循环的开头,进入下一项,即意味着该捆绑不被包含。

  8. 如果等捆绑通过了“computeBundleGas”检查,则将其 Tx 添加到“finalBundle”,并更新一些关键/值,如 totalGasUsed、totalEth 等。最后,计数器增加,默认最大合并量为 3 个捆绑。这个“maxMergeBundles”值平衡了利润与节点性能,可在配置中进行更新。

  9. 这一部分检查计数器,当计数器达到“maxMergeBundles”时,从 for 循环中跳出。

  10. 一些变量(包括“finalBundle”)将返回,而该值将返回到 generateFlashbotsBundle,在“commitNewWork”函数的第 1146 行中,“bundleTxs” 将设置为“finalBundle”。此值随后将在第 1155 行提交到区块。

回滚 Tx 哈希

另一个小的改动是对客户端代码中定义的 MevBundle 结构的调整。增加了一个附加字段 RevertingTxHashes,表示允许在交易收据上返回状态 0(回滚)的 Tx 哈希列表。

允许回滚交易的理念似乎有些奇怪。作为搜索者,你应该已经模拟了捆绑并确保它能在区块顶部执行,所以我们为什么还需要它呢?

这是在 v0.1 中提交单一捆绑时的有效假设,但现在我们已经在节点上启用了捆绑合并。

突然之间,当你的捆绑被包含时,你无法确保链状态。你可能想标记某些交易,比如清算交易,以允许回滚,以防在你之前包含了另一个捆绑。

如果你的捆绑可以通过一次回滚交易来获取利润,将其列入清单是有意义的。否则,该捆绑将被节点丢弃,因此搜索者没有什么风险。

需要执行多个交易的策略,如夹击,可能不允许回滚,因为你可能会遇到一个交易腿回滚而另一个不回滚的情况,从而使你暴露在外。

接下来,我们将直接跳至 v0.4 版本,在此版本中引入了 Megabundles。对于感兴趣的用户来说,v0.3 的内容涉及使 MEV Geth 兼容 EIP-1559,你可以在 这里 阅读更多信息。

MEV-Geth v0.4 - 2021年9月27日 → 2022年1月10日

如果你查看 v0.2 中的“mergeBundles”实现,你会看到它在合并捆绑的过程中非常幼稚和局限。

其原因在于,你不想在软件(MEV Geth)中实现一次彻底改变所需硬件(CPU MEM)特点的更改。

理想情况下,你希望捆绑合并在具有优化硬件和算法的情况下。 这并不合理在每个节点上都应用。

Megabundles 使这成为可能,它们是我们在 MEV Boost 中将看到的“完整区块”捆绑的前身。

Megabundles 引入的一项有趣变化是通过利润而非 gas 价格对捆绑进行排名的能力。

想象一下 2 个捆绑,一个为你带来 1 ETH 的利润,调整后的 gas 价格为 100,另一个为你带来 10 ETH 的利润,调整后的 gas 价格为 80。这些数字是夸张的,但作为矿工,你会选择 10 ETH 的捆绑。你不知道自己何时会得到区块,因此你希望在你轮到的时候最大化利润。

请参见 Bert Miller 的这条推文以获得更多背景。

Megabundles 的线程 🧵👇🏻

价值主张很简单:我们会进行计算,找到一个最大化利润的捆绑。

让我们看看它是如何工作的。

Megabundles

  1. 回到 TxPool 结构(Mempool),我们可以看到有一个名为 Megabundles 的新字段,这是一个地址到“MevBundles”的映射。该地址表示捆绑来自的中继。

  2. sendMegabundleArgs 结构体与 MevBundle 结构非常相似,增加了一个附加字段 RelaySignature。这是来自中继的特定 Megabundle 的签名。

  3. RecoverRelayAddress 接收 RelaySignature 并确定其签名的地址。

  4. 受信的中继地址通过 MinerTrustedRelaysFlag 进行维护。通过 Megabundle 签名恢复的地址将与受信的中继地址进行交叉引用,以确保仅接受来自受信中继的 Megabundles。

为什么 Megabundles 具有这种额外的安全性,即节点检查中继地址以确保其来自可信的中继。让我们看看 Megabundle 被提交到区块的实现,以帮助我们理解其原因。

提交 Megabundle

Megabundles 是中继合并多个捆绑以最大化利润的结果。

它们旨在卸下 MEV Geth 节点的计算负载。它们通过消除节点合并和排序捆绑的需求来实现这一点。为了使客户端不执行这些计算,双方之间需要有一定程度的信任。

让我们看看在提交 Megabundle 到区块时会跳过哪些步骤。

  1. “commitNewWork” 处理填充区块交易。

  2. 有一个 if 语句检查接收到的捆绑是否为 Megabundle。如果是,它将从 TxPool 中获取 Megabundle 并提交到区块。在第 1220 行的代码中,节点会选择不是 Megabundle 的情况,从而获得常规的捆绑并进行合并,如我们之前所见。

  3. Megabundle 被提交到区块,注意没有“simulateBundles”、“mergeBundles”或“computeBundleGas”的过程。这减少了节点所需的计算量。

  4. “commitBundle” 进而调用“commitTransaction”。

  5. “commitTransaction” 将交易应用于状态,验证其不会回滚,更新利润,并返回接收记录。

我们可以看到 Megabundles 的实现侧重于速度和性能。

在 v0.5 中,速度和性能进一步提高,通过处理一个 Megabundle 来实现该机会,而这个 Megabundle 便是比到目前为止最佳的已知区块。如果你想了解更多的代码更改,请查看 这里

在 MEV Geth v0.6 中,客户端的最新版本,引入了私人交易。这使得用户可以发送交易,这些交易将在与远程交易一起捆绑,但在一段时间内不会被发送到节点对等体。

这些私人交易不会被发送到“ Mempool”,而“ Mempool” 意味着其他 Geth 节点的 TxPool。

一般化的前沿交易者将在 Mempool 中看到它们的交易。实现盈利的通知。虽然 Flashbot 绑定有效保护免受这些交易的干扰,因为它们并未发送到 Mempool,但它们记录在 Flashbots API 中。

搜索者常常会从这些记录的交易中寻找机会。搜索者可能希望通过将其与其他远程交易放在一起,来伪装他们的 Tx。

MEV Boost

正如我之前提到的,Megabundles 是完全区块构建和 MEV Boost 的前身。

在 ETH 2 中,出现了区块生成者的概念。一个专门致力于生成优化区块的实体。

在这个新世界中,验证者(而不是矿工)将拥有区块排序权。他们可以选择接收来自构建器的完整区块,以便为其分配的时隙进行提交。

这些区块将是被遮盖的 - 验证者在将区块发布到网络之前将无法看到交易。这防止了验证者窃取MEV,为双方之间减少了所需的信任。

MEV Boost 将成为连接验证者与中继的边际容器,它将连接到“区块构建者”的网络。

如果你想了解更多, 这里 是个不错的起点。

今天就到这里,希望你喜欢这篇文章。

  • 原文链接: noxx.substack.com/p/flas...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
noxx__
noxx__
江湖只有他的大名,没有他的介绍。