如何构建和部署 Reth 执行扩展(ExExs)

本文介绍了 Reth Execution Extensions (ExExs),它是一种运行在 Reth 执行客户端内部的插件,允许你实时处理区块链数据。文章详细阐述了 ExExs 的架构、生命周期,并提供了一个创建、构建和部署交易计数器 ExEx 的详细教程,展示了如何使用 systemd 服务在生产环境中部署 ExExs。

概述

Reth Execution Extensions (ExExs) 是在 Reth 执行客户端内部运行的插件,允许你在区块提交时实时处理区块链数据。与轮询 RPC 端点的外部应用程序不同,ExExs 接收来自以太坊执行引擎的直接通知,这使得它们对于索引和自定义处理逻辑非常有效。

本指南将引导你创建、构建和部署一个交易计数器 ExEx,该 ExEx 演示了构建自定义扩展所需的核心概念和模式。

你将学到什么

  • 了解 Reth ExExs 的架构和生命周期
  • 从头开始创建一个自定义交易计数器 ExEx
  • 将 ExExs 作为 systemd 服务部署到生产环境
  • 测试和监控 ExEx 性能

你需要什么

  • 一个正在运行的 Reth 节点 - 首先按照我们的 Reth 节点设置指南 进行操作
  • Rust 开发环境 (rustc 1.86+)
  • 带有 WSL 的 Linux、macOS 或 Windows
  • 已安装 Git
  • 熟悉 Rust 编程的基础知识
  • 了解以太坊区块结构和交易

重要提示:ExExs 不是单独的进程。它们编译到 Reth 二进制文件中并在其中运行,直接与执行引擎共享内存。这意味着你不能针对现有的 Reth 节点运行 ExEx,你必须重新构建包含你的 ExEx 的 Reth。

什么是 Reth Execution Extensions (ExExs)?

Reth Execution Extensions 是 Rust 模块,它们直接连接到 Reth 执行客户端的区块处理管道。当新的区块被提交、回滚或重新组织时,ExExs 会立即收到通知,并完全访问区块数据、交易、收据和状态更改。

ExEx vs 外部应用程序

方面 ExEx 外部 RPC 应用
延迟 ~1ms (直接内存) 50-500ms (网络 + 处理)
数据访问 完整的区块数据、收据、状态 受公开的 RPC 方法限制
资源使用 共享进程内存 单独的进程 + 网络
部署 重新构建 Reth 二进制文件 独立部署
可扩展性 单进程扩展 水平扩展

ExEx 架构和生命周期

ExExs 不是通过网络连接到 Reth 的单独程序。相反,ExExs 直接编译到同一个 Reth 二进制文件中,并与节点一起运行,共享内存和处理能力。这种集成是 ExExs 能够以如此低的延迟处理区块链数据的原因。

当你运行你的 ExEx 时,你实际上是在运行一个修改后的 Reth 版本,其中包括你的自定义逻辑。该节点正常处理区块,但现在它还会向在同一进程中运行的 ExEx 代码发送通知。以下是通知类型:

通知类型

ExExs 接收三种类型的通知:

  1. ChainCommitted:添加到规范链的新区块
  2. ChainReverted:由于链回滚而删除的区块
  3. ChainReorged:链重组(旧链退出,新链进入)

我们现在将介绍生命周期,以及我们将如何实现通知类型(特别是 ChainCommited)来计算区块中的交易。

ExEx 生命周期

// 1. 初始化
async fn exex_init<Node: FullNodeComponents>(
    ctx: ExExContext<Node>,
) -> eyre::Result<impl Future<Output = eyre::Result<()>>> {
    // 设置数据库、连接等。
    Ok(exex(ctx))
}

// 2. 主处理循环
async fn exex<Node: FullNodeComponents>(mut ctx: ExExContext<Node>) -> eyre::Result<()> {
    while let Some(notification) = ctx.notifications.try_next().await? {
        // 处理通知
        handle_notification(&notification).await?;

        // 发出完成信号以进行修剪
        if let Some(committed_chain) = notification.committed_chain() {
            ctx.events.send(ExExEvent::FinishedHeight(
                committed_chain.tip().num_hash()
            ))?;
        }
    }
    Ok(())
}

稍后你将看到,我们构建的交易计数器示例将仅使用 exex 函数,因为我们不需要随着链的进行而保持状态。但是,对于未来的 ExExs,当你需要执行以下操作时,可以使用 exex_init 函数:

  • 从数据库加载先前的状态
  • 连接到外部服务
  • 设置存储
  • 其他初始化逻辑

现在我们对 Reth 的 ExExs 有了更好的了解,让我们开始本指南的技术编码部分。

项目设置

在本指南中,我们将演示构建一个交易计数器 ExEx,该 ExEx 跟踪和计算每个区块的交易数量。这将是一个简单的例子,但它将成为构建未来 ExExs 的基础。

重要提示:在继续之前,请确保你有一个正在运行并已同步的 Reth 全节点。在本指南中,我们将使用已同步的 Hoodi 节点。

前提条件检查

首先,验证你的环境:

## 检查 Rust 版本(需要 1.86+)
rustc --version

## 检查你是否有一个正在运行的 Reth 节点
systemctl status reth.service

## 验证 Git 安装
git --version

你还需要确保你的 Reth 节点已同步并位于链的顶端(例如,检查 eth_syncing,或者从你的本地节点获取最新的区块并与区块浏览器进行比较)

创建 ExEx 项目

  1. 创建一个新的 Rust 项目:
mkdir transaction-counter-exex
cd transaction-counter-exex
  1. 之后,通过创建 Cargo.toml 文件来初始化 Cargo 项目:
touch Cargo.toml

然后将以下代码包含在该文件中:

[package]
name = "transaction-counter-exex"
version = "0.1.0"
edition = "2021"
rust-version = "1.86"
license = "MIT OR Apache-2.0"
publish = false

[dependencies]
## Core Reth dependencies
reth = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.6.0" }
reth-exex = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.6.0" }
reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.6.0" }
reth-node-api = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.6.0" }
reth-tracing = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.6.0" }
reth-primitives = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.6.0" }

## Ethereum primitives
alloy-primitives = { version = "1.0", default-features = false }
alloy-consensus = { version = "1.0", default-features = false }

## Async and utilities
futures = "0.3"
eyre = "0.6"

[dev-dependencies]
tokio = { version = "1.0", features = ["full"] }

核心依赖项包括用于 ExEx 框架的 reth-exex,用于以太坊特定功能的 reth-node-ethereum 和用于日志记录的 reth-tracing。 Alloy 库提供标准的以太坊类型,例如地址和区块结构,而 futures 处理接收区块链通知所需的异步流处理,而 eyre 在出现问题时提供更好的错误消息。 dev-dependencies 部分中的 tokio 依赖项仅用于测试,并提供异步运行时。

  1. 然后,创建源 ( src) 目录和 main.rs 文件:
mkdir src
touch src/main.rs

实现交易计数器 ExEx

现在让我们实现一个交易计数器,该计数器按类型对交易进行分类。

src/main.rs 中,添加以下代码:

use futures::{Future, TryStreamExt};
use reth_exex::{ExExContext, ExExEvent, ExExNotification};
use reth_node_api::{BlockBody, FullNodeComponents};
use reth_node_ethereum::EthereumNode;
use alloy_consensus::BlockHeader;
use reth_tracing::tracing::info;

##[derive(Debug, Default, Clone)]
struct TransactionCounter {
    /// Total number of transactions processed
    total_transactions: u64,
    /// Number of blocks processed
    total_blocks: u64,

    /// FUTURE USAGE EXAMPLES BELOW
    /// Number of ETH transfers (transactions with no input data)
    /// eth_transfers: u64,
    /// Number of contract calls (transactions with input data to existing contracts)
    /// contract_calls: u64,
}

/// The initialization logic of the ExEx is just an async function.
async fn exex_init<Node: FullNodeComponents>(
    ctx: ExExContext<Node>,
) -> eyre::Result<impl Future<Output = eyre::Result<()>>> {
    Ok(exex(ctx))
}

/// An ExEx that counts transactions and blocks.
async fn exex<Node: FullNodeComponents>(mut ctx: ExExContext<Node>) -> eyre::Result<()> {
    let mut counter = TransactionCounter::default();

    while let Some(notification) = ctx.notifications.try_next().await? {
        match &notification {
            ExExNotification::ChainCommitted { new } => {
                // Count blocks and transactions in the committed chain
                for block in new.blocks_iter() {
                    counter.total_blocks += 1;

                    // Count transactions in this block
                    let tx_count = block.body().transactions().len() as u64;
                    counter.total_transactions += tx_count;

                    info!(
                        block_number = block.number(),
                        block_hash = %block.hash(),
                        tx_count,
                        total_transactions = counter.total_transactions,
                        total_blocks = counter.total_blocks,
                        "Processed block" // 处理的区块
                    );
                }

                info!(
                    committed_chain = ?new.range(),
                    total_transactions = counter.total_transactions,
                    total_blocks = counter.total_blocks,
                    "Processed committed chain" // 处理的已提交链
                );
            }
            ExExNotification::ChainReorged { old, new } => {
                info!(
                    from_chain = ?old.range(),
                    to_chain = ?new.range(),
                    "Received reorg" // 收到重组
                );
                // For simplicity, we just log reorgs
                // In production, you might want to adjust counters
            }
            ExExNotification::ChainReverted { old } => {
                info!(reverted_chain = ?old.range(), "Received revert" ); // 收到还原
                // For simplicity, we just log reverts
            }
        }

        if let Some(committed_chain) = notification.committed_chain() {
            ctx.events
                .send(ExExEvent::FinishedHeight(committed_chain.tip().num_hash()))?;
        }
    }

    Ok(())
}

fn main() -> eyre::Result<()> {
    reth::cli::Cli::parse_args().run(|builder, _| async move {
        let handle = builder
            .node(EthereumNode::default())
            .install_exex("TransactionCounter", exex_init)
            .launch()
            .await?;

        handle.wait_for_node_exit().await
    })
}

保存该文件。让我们回顾一下上面的代码。

此代码创建一个简单的 Reth ExEx,用于实时计数交易和区块。 TransactionCounter 结构包含两个计数器:total_transactionstotal_blocks

exex 函数运行一个无限循环,侦听区块链通知。当它收到 ChainCommitted 事件时,它会遍历每个新区块,使用 block.body().transactions().len() 计算该区块中的交易,添加到运行总计中,并记录结果。它还通过记录来处理重组和还原事件。

该代码遵循 Reth 的标准 ExEx 模式, exex_init 返回主处理,而 main 使用节点构建器将 ExEx 安装为 TransactionCounter 并启动 Reth 节点。在处理完每个链后,它会将 FinishedHeight 事件发送回 Reth 以发出完成信号。

构建 ExEx

  1. 编译你的 ExEx:
cargo check

如果你遇到编译错误,请确保所有导入均正确,并且你的 Rust 版本为 1.86+。

  1. 在发布模式下构建以进行生产:
cargo build --release

注意:初始构建可能需要 10-15 分钟,因为它会将整个 Reth 代码库与你的 ExEx 一起编译。

生产部署

Systemd 服务设置

对于生产部署,你将希望将你的 ExEx 作为 systemd 服务运行。这可确保自动启动、故障时重新启动以及正确的日志记录。

  • 新的 ExEx 服务
  • 替换现有服务

创建新服务

如果你想将 ExEx 作为单独的服务运行(建议用于测试):

## 创建服务文件
sudo nano /etc/systemd/system/reth-exex.service

添加以下配置:

> 注意:将 /path/to/transaction-counter-exex 替换为你的实际项目目录路径,将 YOUR_USERNAME 替换为你的系统用户名,并将 /path/to/jwtsecret 替换为你的 JWT 密钥文件位置。

[Unit]
Description=Reth Ethereum Client with Transaction Counter ExEx
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=YOUR_USERNAME
Group=YOUR_USERNAME
ExecStart=/path/to/transaction-counter-exex/target/release/transaction-counter-exex node --chain hoodi --http --http.addr 0.0.0.0 --http.port 8545 --authrpc.addr 127.0.0.1 --authrpc.port 8551 --authrpc.jwtsecret /path/to/jwtsecret --full
WorkingDirectory=/path/to/transaction-counter-exex
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=reth-exex
KillMode=mixed
KillSignal=SIGINT
TimeoutStopSec=30

## Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=false
ReadWritePaths=/path/to/transaction-counter-exex
ReadWritePaths=/path/to/.local/share/reth
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true

[Install]
WantedBy=multi-user.target

部署该服务:

## 重新加载 systemd
sudo systemctl daemon-reload

## 停止现有的 Reth 服务(如果正在运行)
sudo systemctl stop reth.service

## 启动 ExEx 服务
sudo systemctl start reth-exex.service
sudo systemctl enable reth-exex.service

## 监控日志
sudo journalctl -u reth-exex.service -f

测试交易计数器 ExEx

让我们验证我们的交易计数器是否能够准确地中继信息。为此,请打开一个新的终端窗口并运行以下命令:

journalctl -u reth-exex.service --since="1 minutes ago" | grep -E "(TransactionCounter|Processed block)"

此命令将返回过去一分钟内处理的块。请注意,如果你使用的是 Hoodi 以外的网络,则可能需要更新此设置。

你应该会看到如下所示的响应,表明我们的 TransactionCounter 正在运行。

2025-09-18T15:20:01.947825Z  INFO exex{id="TransactionCounter"}: Processed block block_number=1238975 block_hash=0x99acbc7b5892f32afbdce65871a7ee8691789179eaf20eed8643776b91b40eb7 tx_count=39 total_transactions=10135 total_blocks=259
Sep 18 15:20:01 ip-172-31-6-79.us-east-2.compute.internal reth-exex[2796328]: 2025-09-18T15:20:01.947862Z  INFO exex{id="TransactionCounter"}: Processed committed chain committed_chain=1238975..=1238975 total_transactions=10135 total_blocks=259

Explorer link

接下来,运行下面的 cURL 命令从你的 Reth 节点获取最新的区块,我们将使用它进行测试。

curl -s -X POST -H "Content-Type: application/json" \
    --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
    http://localhost:8545

如果你在与 8545 不同的端口上运行,请根据需要进行更新。

你将收到类似于 {"jsonrpc":"2.0","id":1,"result":"0x12e7bf"} 的响应。我们需要将十六进制输出转换为十进制。我们可以使用以下命令来完成:

echo $((0xYourHex))

通过该响应,让我们再次调用 cURL 以解析出我们的测试区块中的交易数量(例如,)。

curl -s -X POST -H "Content-Type: application/json" \
    --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0xYourHex",false],"id":1}' \
    http://localhost:8545 | jq '.result.transactions | length'

响应显示为 39,我们可以通过查看该特定区块号的 explorer link 来确认我们的 ExEx 正在准确地记录信息。

通过 QuickNode 提供的替代解决方案

如果你不想处理管理自己的节点或构建端到端管道的麻烦。请查看下面的一些强大的解决方案和 API:

Streams

Streams 将区块链数据直接推送到你的目标位置,无需复杂的 ETL 管道。它提供实时和历史数据,并具有内置的重组处理功能。

主要特点:

  • 实时和历史数据传输
  • 用于自定义逻辑的 JavaScript 过滤器
  • 多个目的地 (Webhooks, S3, PostgreSQL)
  • 自动重组处理
  • 故障排除功能,允许你在发生错误时配置重试、等待时间和暂停机制
  • 在少于 250 行代码中 构建完整的索引器

Webhooks

使用 QuickNode Webhooks 为链上事件设置一键式提醒,直接传送到你的服务器。

主要特点:

  • 实时传输:实时接收对你重要的事件
  • 引导式向导:选择一个链,选择一个无代码模板或编写你自己的过滤器,设置你的 URL,就完成了
  • 压缩:使用 gzip 压缩有效负载以减少有效负载大小并提高延迟
  • 在本 技术指南 中了解如何开始使用

Marketplace

无需构建和管理自定义基础设施,你可以探索 QuickNode Marketplace 以利用已经构建并可以使用的大多数自定义 API,例如:

最后的想法

恭喜!你已成功学习如何构建、部署和管理 Reth Execution Extensions。 ExExs 提供了一种强大的方式来扩展 Reth 的功能,同时保持高性能和可靠性。

无论你是在构建分析平台、MEV 策略、索引服务还是自定义业务逻辑,ExExs 都为高性能区块链应用程序奠定了基础。

下一步

  • 探索 official Reth ExEx examples 以获取更多模式
  • 加入 Reth Discord 社区以获取支持和讨论
  • 考虑将你的 ExEx 模式贡献回生态系统
  • 尝试使用高级功能,例如状态访问和自定义 RPC 方法

订阅我们的 newsletter 以获取更多关于以太坊基础设施和开发的文章和指南。如果你有任何反馈,请随时通过 X 与我们联系。你始终可以在我们的 Discord 社区服务器上与我们聊天,其中包含你将遇到的最酷的一些开发人员!

我们 ❤️ 反馈!

Let us know 如果你有任何反馈或对新主题的请求。我们很乐意听取你的意见。

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

0 条评论

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