Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南阅读约 14 分钟

追踪 Base 跨链事件:存款日志与提款证明

学习使用 RPC 解析 Base 上的 L1->L2 存款日志和 L2->L1 提款证明,包含可运行示例和故障排查。

TL;DR

要追踪 Base 上的跨链活动,您必须同时读取以太坊(L1)和 Base(L2)上的日志。L1->L2 存款由一笔调用 OptimismPortal 的交易发起,该交易会发出 TransactionDeposited 事件;op-node 会从中派生出一笔存款交易,该交易会出现在 Base 区块中,并带有自己的 L2 哈希和回执。对于 L2->L1 提款,您从 L2 上的 MessagePassed 事件开始,然后使用输出根和争议游戏在 L1 上进行证明和最终确认。本指南解释了事件结构,提供了可运行的 RPC 示例来解析和验证它们,并包含故障排查清单。

直接回答:如何追踪 Base 跨链事件

要追踪 Base 上的跨链活动,您必须同时读取以太坊(L1)和 Base(L2)上的日志。L1->L2 存款由一笔调用 OptimismPortal 的交易发起,该交易会发出 TransactionDeposited 事件;op-node 会从中派生出一笔存款交易,该交易会出现在 Base 区块中,并带有自己的 L2 哈希和回执。对于 L2->L1 提款,您从 L2 上的 MessagePassed 事件开始,然后使用输出根和争议游戏在 L1 上进行证明和最终确认。本指南解释了事件结构,提供了可运行的 RPC 示例来解析和验证它们,并包含故障排查清单。

关键点在于切勿混淆 L1 存款交易哈希与由此产生的 L2 交易哈希。L1 哈希标识的是门户调用;L2 哈希由存款派生而来,并出现在后续的 Base 区块中。您可以通过查询 L2 回执并检查其状态来确认包含情况。对于提款,过程涉及三个阶段:在 L2 上发起,在 L1 上证明,以及在 L1 上最终确认。每个阶段都会发出不同的事件,您可以使用 RPC 日志进行监控。

本指南是 OnFinality 学习中心 的一部分,并补充了我们的 Base 网络 RPC 端点(RPC 助手)Base 上的 OP-Stack 最终性与安全/已最终确认区块 文章。

机制:OP-Stack 上的 L1 到 L2 存款

在像 Base 这样的 OP-Stack 链上,L1->L2 存款是一个两步过程。首先,用户在以太坊上的 OptimismPortal 合约上调用 depositTransaction 函数。这会发出一个 TransactionDeposited 事件,其字段编码了存款信息。其次,op-node(共识客户端)从该事件中派生出一笔存款交易,并将其包含在 Base 区块中。由此产生的 L2 交易具有自己的哈希和回执,与 L1 交易不同。

TransactionDeposited 事件在 OptimismPortal 接口中定义。其索引字段包括 from、to、version 和 opaqueData(后者在较新版本中未被索引)。opaqueData 包含 L2 交易的 mint、value、gasLimit 和 calldata。要解析它,您需要正确的 ABI,并且必须知道期望的索引字段数量——旧版本具有不同的索引。

有关详细的协议规范,请参阅 OP Stack 存款规范Base 文档 也描述了链特定参数。集成时,请始终根据当前合约 ABI 进行验证,因为协议会不断演进(例如,Ecotone 升级更改了某些事件结构)。

  • L1 交易哈希不是 L2 交易哈希。
  • 存款在 op-node 处理 L1 区块后出现在 Base 区块中。
  • 您可以通过扫描门户地址上的 TransactionDeposited 日志来追踪存款。
  • L2 回执的 status 字段确认执行是否成功。

机制:L2 到 L1 提款与证明

从 Base 到以太坊的提款更为复杂。在 L2 上,用户在 L2CrossDomainMessenger 上调用 withdrawTransaction(或在较新接口中为 initiateWithdrawal),这会发出 MessagePassed 事件。该事件包含提款哈希、nonce、发送者、目标和数据。在 L1 上完成证明和最终确认之前,提款尚未最终确定。

提款发起后,包含该提款的 L2 输出根必须在 L1 上被提议。在当前 Superchain 模式下,这是通过 DisputeGame 合约完成的。中继者提交证明,证明提款已包含在提议的输出根中,并且在争议窗口过后,提款才能被最终确认。在旧系统中,输出提议者直接将输出根提交给 OptimismPortal。

证明步骤在 OptimismPortal 上调用 proveWithdrawalTransaction,传递提款哈希、证明和输出根证明。最终确认调用 finalizeWithdrawalTransaction。两个步骤都会发出事件:L1 上的 WithdrawalProven 和 WithdrawalFinalized,以及消息执行时 L2 上的 RelayedMessage。

有关权威详细信息,请参阅 OP Stack 提款规范。阶段名称和合约地址会随升级而变化,因此请始终检查最新的部署工件。

可运行示例:解析存款日志并验证 L2 回执

以下 Node.js 脚本使用 ethers.js 连接到 L1 和 L2 端点。它扫描 OptimismPortal 上特定 from 地址的 TransactionDeposited 事件,然后查询派生交易的 L2 回执。您必须用自己的端点 URL 和合约地址替换示例中的占位符(有关 Base 端点,请参阅 Base 网络 RPC 端点(RPC 助手))。

该脚本演示了如何按地址和主题过滤日志、解码事件数据,然后使用 depositCount 或区块号查找相应的 L2 交易。在实践中,您可能会使用通过 WebSocket 监听实时日志的中继者或索引器,但此示例使用 eth_getLogs 以简化。

const { ethers } = require('ethers');

// Replace with your endpoints and addresses
const L1_RPC = 'https://eth-mainnet.example.com';
const L2_RPC = 'https://base-mainnet.example.com';
const PORTAL_ADDRESS = '0x...'; // OptimismPortal on L1

const portalAbi = [
  'event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData)'
];

async function main() {
  const l1Provider = new ethers.JsonRpcProvider(L1_RPC);
  const l2Provider = new ethers.JsonRpcProvider(L2_RPC);
  const portal = new ethers.Contract(PORTAL_ADDRESS, portalAbi, l1Provider);

  // Example: scan last 1000 blocks for deposits from a specific address
  const fromAddress = '0x...'; // depositor address
  const latestBlock = await l1Provider.getBlockNumber();
  const fromBlock = latestBlock - 1000;

  const filter = portal.filters.TransactionDeposited(fromAddress);
  const logs = await l1Provider.getLogs({
    ...filter,
    fromBlock,
    toBlock: latestBlock
  });

  for (const log of logs) {
    const parsed = portal.interface.parseLog(log);
    console.log('L1 deposit log:', log.transactionHash);
    console.log('Depositor:', parsed.args.from);
    console.log('Target:', parsed.args.to);
    console.log('Version:', parsed.args.version);
    // opaqueData is bytes; you need to decode further to get mint, value, gasLimit, data
    // For simplicity, we just log the raw data
    console.log('OpaqueData:', parsed.args.opaqueData);

    // Wait for the deposit to be included in an L2 block
    // In practice, you would poll or use a relayer's depositCount
    // Here we just wait a few seconds and then query the L2 receipt
    // You need to derive the L2 tx hash from the deposit event; this is non-trivial
    // For demonstration, we assume you have a mapping from depositCount to L2 tx hash
    // Instead, we show how to check the L2 receipt for a known L2 hash
    const l2TxHash = '0x...'; // derived from deposit
    const receipt = await l2Provider.getTransactionReceipt(l2TxHash);
    if (receipt) {
      console.log('L2 receipt status:', receipt.status); // 1 = success
    } else {
      console.log('L2 transaction not found yet');
    }
  }
}

main().catch(console.error);

可运行示例:读取提款证明与最终确认

对于提款,您需要监控 L2 上的 MessagePassed 事件,然后跟踪 L1 上的证明和最终确认。以下脚本演示了如何从 L2CrossDomainMessenger 查询 MessagePassed 日志,然后检查 OptimismPortal 上的 WithdrawalProven 和 WithdrawalFinalized 事件。

此示例已简化;在实际集成中,您将使用提款哈希来关联跨链事件。MessagePassed 事件包含提款哈希作为索引字段,您可以使用它来过滤 L1 日志。

const { ethers } = require('ethers');

const L1_RPC = 'https://eth-mainnet.example.com';
const L2_RPC = 'https://base-mainnet.example.com';
const L2_MESSENGER = '0x...'; // L2CrossDomainMessenger on Base
const PORTAL = '0x...'; // OptimismPortal on L1

const l2MessengerAbi = [
  'event MessagePassed(uint256 indexed nonce, address indexed sender, address indexed target, uint256 value, uint256 gasLimit, bytes data, bytes32 withdrawalHash)'
];
const portalAbi = [
  'event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to, uint256 timestamp)',
  'event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success)'
];

async function main() {
  const l2Provider = new ethers.JsonRpcProvider(L2_RPC);
  const l1Provider = new ethers.JsonRpcProvider(L1_RPC);
  const l2Messenger = new ethers.Contract(L2_MESSENGER, l2MessengerAbi, l2Provider);
  const portal = new ethers.Contract(PORTAL, portalAbi, l1Provider);

  // Get recent MessagePassed events
  const latestL2Block = await l2Provider.getBlockNumber();
  const filter = l2Messenger.filters.MessagePassed();
  const logs = await l2Provider.getLogs({
    ...filter,
    fromBlock: latestL2Block - 1000,
    toBlock: latestL2Block
  });

  for (const log of logs) {
    const parsed = l2Messenger.interface.parseLog(log);
    const withdrawalHash = parsed.args.withdrawalHash;
    console.log('MessagePassed on L2:', log.transactionHash);
    console.log('Withdrawal hash:', withdrawalHash);

    // Check L1 for proof and finalization events
    const proofFilter = portal.filters.WithdrawalProven(withdrawalHash);
    const proofLogs = await l1Provider.getLogs({
      ...proofFilter,
      fromBlock: 0,
      toBlock: 'latest'
    });
    console.log('Proof events found:', proofLogs.length);

    const finalFilter = portal.filters.WithdrawalFinalized(withdrawalHash);
    const finalLogs = await l1Provider.getLogs({
      ...finalFilter,
      fromBlock: 0,
      toBlock: 'latest'
    });
    console.log('Finalization events found:', finalLogs.length);
  }
}

main().catch(console.error);

跨链事件追踪故障排查清单

在集成跨链事件追踪时,您可能会遇到几个常见问题。使用此清单来诊断问题。

首先,验证您是否使用了正确的链版本门户地址。Base 经历了升级,门户地址可能会发生变化。始终从官方 Base 文档 或 OP Stack 超级链注册表获取最新部署。

其次,确保您的事件解码与当前 ABI 匹配。TransactionDeposited 中的索引字段数量随时间发生了变化。如果您看到乱码数据,请检查 ABI 版本。

第三,如果您看到 L1 存款事件但没有相应的 L2 交易,请等待下一个派生区块。存款是异步处理的;L2 区块可能不会立即生成。您可以轮询 L2 端点以获取预期的交易哈希。

第四,扫描历史日志时,请注意区块范围限制。许多提供商将 eth_getLogs 限制在特定范围内(例如 10,000 个区块)。如果您需要更早的事件,请将扫描分块为更小的范围。这是一种有据可查的方法;有关速率限制的更多信息,请参阅 监控 RPC 端点和节点健康

最后,对于提款,请记住“已证明”不等于“已最终确认”。争议窗口必须经过才能最终确认。如果您看到 WithdrawalProven 事件但没有 WithdrawalFinalized,可能只是在等待窗口期结束。

  • 链版本的门户地址错误。
  • ABI 或索引字段数量不正确。
  • 存款尚未包含在 L2 区块中。
  • eth_getLogs 的区块范围过大。
  • 混淆“已证明”和“已最终确认”。

限制与协议升级

跨链事件追踪受协议限制和升级的影响。L2 上的存款最终性遵循安全层头部,该头部可能落后于最新区块。提款输出仅在争议窗口过后在 L1 上成熟,这可能需要数天时间。这些时间限制由协议定义,并可能随升级而变化。

例如,从输出根系统到争议游戏(故障证明)的过渡改变了提款的证明方式。请始终参考当前的 OP Stack 规范 和最新的合约 ABI。不要在没有验证的情况下依赖硬编码的地址或事件签名。

使用 RPC 提供商时,请注意速率限制和区块范围上限因提供商而异。OnFinality 的 RPC 定价 页面记录了我们的服务,但对于其他提供商,请查看其文档。对于生产系统,请考虑使用专用的 API 服务 来处理高吞吐量。

后续步骤与进一步阅读

既然您了解了如何追踪跨链事件,您就可以构建中继器、索引器或钱包集成。首先为以太坊和 Base 设置可靠的 RPC 端点。OnFinality 提供 Base 网络 RPC 端点(RPC 助手)Base 网络 RPC 端点(RPC 助手) 供生产使用。

要加深对 Base 最终性模型的理解,请阅读 Base 上的 OP-Stack 最终性与安全/已最终确认区块。有关处理 RPC 错误,请参阅 Base RPC 超时和重试。如果您需要历史数据,请查阅 Base 归档节点和历史状态

有关一般 RPC 最佳实践,请探索 OnFinality 学习中心监控 RPC 端点和节点健康

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始