本文介绍了Base链通过引入Flashblocks技术,将区块时间从2秒缩短到200毫秒,实现了亚秒级的交易预确认。文章详细阐述了Flashblocks的原理、sequencer架构的演变,以及交易在Base主网上的生命周期,并深入探讨了为确保系统可靠性和性能所做的优化措施,例如启用构建器故障转移、优化状态根计算等。
为了构建一个面向全球经济的开放网络,我们专注于通过亚美分、亚秒级的交易使 Base Chain 更快、更便宜。今年早些时候,我们在 Base 主网上推出了 Flashblocks,通过将有效区块时间从 2 秒减少到仅仅 200 毫秒,使得 Base 速度提升了 10 倍。
该功能于 7 月 16 日上线,结束了从测试网到主网为期四个月的开发之旅。我们公开构建,并希望分享更多关于 Flashblocks 的技术细节、我们在旅程中遇到的挑战以及我们学到的经验。如果你正在寻找关于将你的应用程序或基础设施提供商与 Flashblocks 集成的支持,我们建议你查看这篇博客文章和常见问题解答页面。
以前,Base 每 2 秒生成一个新区块。通过 Flashblocks(使用 Flashbots 构建),构建者在该 2 秒窗口内流式传输 200 毫秒的子区块,从而提供亚秒级预确认,使交易感觉即时。
每个区块有 10 个 Flashblocks,其中每个 Flashblock (flashblock_i) 最多可以包含常规区块总 gas 预算的 i/10。一系列 Flashblocks 被组合起来以重新创建一个完整的区块。
现在我们已经介绍了 Flashblocks 是什么,让我们深入了解我们如何在 Base Chain 中实现它们。
作为 Flashblocks 发布的一部分,我们在排序器中添加了几个新的基础设施组件。为了更好地理解这些变化,我们分享了 Flashblocks 前后排序器架构的样子。
在 Base,我们运营一个高可用性排序器系统,拥有五个排序器实例。
每个排序器由以下基础设施组件组成:
Op-node: 标准的 op-stack 共识层 (CL) 软件
Op-geth: 标准的 op-stack 执行层 (EL) 软件
Op-conductor: 高可用性控制组件,带有用于领导者选举的 raft 共识
一个排序器实例充当“领导者”,负责通过构建区块并通过 P2P 将其传播到其他节点来进行排序。其余四个排序器充当同步链的 follower Base 节点。如果当前领导者在指定阈值内停止区块生产,则会发生领导权转移。
通过 Flashblocks,我们在高可用性系统中的每个单独排序器中集成了几个新组件,以允许添加更多自定义区块构建功能。这些包括:
Rollup-boost (CL<>EL Engine API 代理)
为什么 Flashblocks 需要它: 通过在 Engine API 调用上添加受控拦截点,无需修改 CL 即可与 EL 共享 Flashblocks。它将风险本地化,并允许我们进行协议外试验
它解锁了什么: 用于未来区块构建演进(例如,多构建器)的稳定接口,无需重构 CL/EL
Op-rbuilder(协议外构建器,@ 200 毫秒 cadence)
为什么 Flashblocks 需要它: 产生亚秒级 Flashblocks,与 EL 解耦
它解锁了什么: 用于新区块构建机制的可插拔构建器表面(例如,MEV 感知策略、跨多个构建器的拍卖)
Websocket proxy (Flashblocks 流扇出)
Node-reth(公开预确认的 RPC 表面)
rollup-boost 和 op-rbuilder 最初由 Flashbots 构建并目前由其维护。而在 Base 方面,我们构建并维护 websocket proxy 和 node-reth,并且根据我们的需求对 rollup-boost 和 op-rbuilder 进行了特定更改。在下面的可靠性部分中会更详细地解释。
鉴于这些变化,现在提交到 Base 的交易如何进行?
让我们追踪一下当用户向 mainnet-base.org 发送 eth_sendRawTransaction
时,交易的旅程。在 Flashblocks 前后,此流程相似,但区块构建算法有所修改。该请求经过以下阶段:
该请求首先到达我们的 DNS 提供商,以解析 mainnet-base.org。
从那里,它被路由到路由软件 Proxyd 的负载均衡器
Proxyd 然后将请求路由到私有 mempool
mempool 接收请求并将交易作为待处理交易插入其 txpool 中
mempool 维护与 EL(op-geth、op-rbuilder)的 P2P 连接,确保所有待处理交易都在 EL 中同步以进行区块构建
在每个区块构建循环中,EL 从其 txpool 中选择交易
在 Flashblocks 上线后,我们使用 op-rbuilder 作为区块构建器。在 op-rbuilder 中,正如之前的博客文章中所解释的那样,以下因素决定何时选择交易
交易费用:对于每个 200 毫秒的区块构建循环,交易按其费用排序,与 op-geth 相同
交易 gas 限制和剩余 gas 可用量:对于每个 flashblock FB_j,最多可以使用 j/10 的总区块 gas 限制。因此,交易必须具有足够的费用,并且保持在该 flashblock 的可用 gas 范围内
捕获上述因素的 Flashblock 区块构建的伪代码如下
FUNCTION BuildFlashblocks(pending_transactions, total_block_gas_limit):
flashblocks = []
block_gas_used = 0
FOR j FROM 0 TO MAX_FLASHBLOCKS_PER_BLOCK:
NEXT_TIME = MONOTONIC_NOW() + 150ms // 下一个区块开始时间
current_flashblock_gas_limit = (j / 10) * total_block_gas_limit
// 按费用(降序)对未决交易进行排序
sorted_transactions = SORT_BY_FEE_DESC(pending_transactions)
// 根据可用的 gas 选择交易
selected_transactions = TOP_TRANSACTIONS_WITHIN_GAS_LIMIT(sorted_transactions, current_flashblock_gas_limit, block_gas_used)
// 执行交易
executed_info, gas_used = EXECUTE_TX(selected_transactions)
block_gas_used += gas_used
// 通过计算区块哈希和状态根来构建 flashblock
flashblock = BUILD_BLOCK(flashblock, executed_info)
// 在构建下一个闪存块之前,等待剩余的时间
WAIT_UNTIL(NEXT_TIME)
END FOR
RETURN flashblocks
END FUNCTION
有关 op-rbuilder 中的完整实现,请参见此处。
一旦交易被选择并挖掘,它将作为流式传输到 websocket proxy 的 flashblock 数据的一部分包含在内,RPC 节点会监听该数据。RPC 节点缓存 flashblock 数据。可以在此处找到数据流的确切定义,我们也在我们的文档页面中更彻底地解释了它。
当调用 flashblocks 支持的 RPC 方法时,它会从缓存中检索数据并在可用时返回它。(例如,eth_getTransactionReceipt)
在 Flashblocks 的开发过程中,我们进行了一些改进,以确保系统具有高可用性,并且可以始终以 200 毫秒的速度生成 Flashblocks。
随着 op-rbuilder 的集成,有必要将构建器健康状况纳入 op-conductor 的健康检查中。我们启用了 healthcheck to rollup-boost,它验证构建器是否在链的顶端保持同步。如果构建器滞后,op-conductor 将启动领导权转移。
这显着提高了 Flashblocks 的可用性,因为 Flashblocks 不会因不健康的构建器而经历长时间的中断。但是,缺点是如果所有构建器都关闭,op-conductor 将无法选出健康的领导者,从而导致链停止。然而,所有构建器同时关闭的可能性很小,使其成为一个合理的折衷方案。
在最初的设计中,为每个 flashblock 计算状态根。这是因为很难确定哪个 flashblock 最终将成为构建器需要返回给 op-node 的最终确定区块。没有状态根,op-node 无法将该区块提升为最终确定区块。
虽然这种方法对于 Base Sepolia 来说运行正常,但 Base Mainnet 上的高交易量使得每个状态根计算平均花费大约 130 毫秒。这使得总区块构建时间接近 200 毫秒,难以构建超过 9 个 flashblock,从而可能降低吞吐量。
为了解决这个问题,我们没有让构建器为每个 flashblock 计算状态根,而是修改了 rollup-boost 以从构建器获取交易,然后使用额外的 FCU 使用 op-geth 构建具有状态根的区块。(非常感谢 Flashbots 的 Ferran 提供的建议!)
由于此更改,P50 Flashblock 构建时间从 150 毫秒大幅降至仅 10 毫秒,从而消除了此问题作为启动障碍。
将我们的构建器从 op-geth(基于 Geth)迁移到 op-rbuilder(基于 Reth)意味着从使用 Geth 的交易池实现转移到 Reth 的交易池实现。
在启动后不久,我们观察到一些异常行为:Coinbase 零售 staking 团队的交易停止被包含,导致大量交易积压。
然后,我们深入研究了 Geth 和 Reth 交易池实现之间的差异,并确定了以下区别:
Pending: 没有 nonce 间隙的下一个 nonce 交易。
Queued: 具有 nonce 间隙的交易。
Pending: 费用高于基础费用的下一个 nonce 交易。
Queued:
Basefee subpool:低于基础费用的交易,即使它们是下一个 nonce。
具有 nonce 间隙的交易。
因此,如果一个交易是下一个 nonce 交易,但费用低于基础费用,则它将保持 Geth 的 pending 状态,但会被 Reth 排队。一旦达到交易池生存期值,排队的交易将被删除,并且 Geth 不会重新广播这些交易,因为它始终假设 pending 交易已成功 P2P'd。
为了解决这个问题,我们构建了 Mempool-rebroadcaster。此工具检查 Geth 和 Reth txpool 之间的差异,并在它们之间重新广播交易。这确保了即使交易从 Reth 中删除,它仍然会被再次拾取。
在我们的初始实现中,构建器将 flashblock 直接发送到 websocket proxy。由于构建器不了解 op-node 分配的实际区块构建时间,这可能会导致尾部 flashblock 重组:尾部 flashblock 已流式传输出去,但未包含在实际区块中。
Flashbots 团队添加了一种新机制,即构建器不流式传输到 websocket proxy,而是流式传输到 rollup-boost,然后 rollup-boost 流式传输到 websocket proxy。它的工作方式如下
Op-node 将 FCU 发送到 rollup-boost,rollup-boost 将 FCU 代理到 op-rbuilder 以启动区块构建
在每个区块构建循环中,op-rbuilder 将 flashblock 流式传输到 rollup-boost,rollup-boost 将 flashblock 存储在一个名为 best_payload
的变量中
在区块构建结束时,op-node 将 get_payload 发送到 rollup-boost,rollup-boost 返回 best_payload
,并清除 best_payload
如果 op-rbuilder 继续发送具有相同 payload_id
的 flashblock,则它将被拒绝
这大大提高了我们的重组率,将其从 0.2% 降低到有效为 0:
我们目前正在添加新的 Flashblocks 感知 RPC:eth_call
和 eth_estimateGas
。我们的实现涉及让节点复制最新的状态,在其之上回放 Flashblock 流,并针对该覆盖层执行请求。这使得模拟和 gas 估算反映预确认状态。该功能正在积极开发中,将在验证和测试后发布。
Websocket 是交易者和托管自己的 RPC 节点的团队的低延迟主干。我们的目标是以高可用性和反压控制来运营公共 websocket。但是,值得注意的是,应用程序应尽量避免依赖 websocket,因为 RPC 提供稳定的 RPC 行为并在 Flashblocks 关闭时自动故障转移到常规区块。
启用自定义构建器 (op-rbuilder) 作为 Flashblocks 发布的一部分,为更多的区块构建创新铺平了道路,有可能为 Base 用户释放更多的区块空间容量和效率,甚至为开发人员提供独特的功能。要了解任何计划的区块构建更改的最新信息,请关注我们的文档和状态页面。
报告问题:打开一个 GitHub issue
问题 / 反馈:加入 Discord
如果你有兴趣帮助我们构建一个能够提高创新、创造力和自由的全球经济,我们正在招聘,并期待收到你的来信。
在社交媒体上关注我们,以了解最新信息:X(X 上的 Base 团队)| Farcaster | Discord
- 原文链接: blog.base.dev/flashblock...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!