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

Monad 历史 RPC 与归档数据:查询过去的区块、余额和状态

了解如何通过 RPC 查询 Monad 历史数据:归档节点与全节点的区别、查询历史区块和状态的方法,以及一个可运行的 ethers 脚本,用于分页获取 eth_getLogs。

TL;DR

本指南介绍如何通过 RPC 查询 Monad 链的历史数据,涵盖归档节点与全节点的行为差异、用于历史读取的 JSON-RPC 方法,以及一个实用的 ethers 脚本,用于分页获取 eth_getLogs。同时涵盖常见故障与权衡,并推荐使用托管归档提供商获取深层历史数据。

直接回答:如何查询 Monad 历史数据

要通过 RPC 查询 Monad 历史数据,你需要一个保留历史状态和日志的端点。标准全节点只保留近期状态(已修剪),因此对于旧区块号的读取(例如在区块 1,000,000 处的 eth_getBalance 或大范围的 eth_getLogs),你必须使用支持归档的数据源。Monad 兼容 EVM,因此你可以使用与以太坊相同的 JSON-RPC 方法,但 Monad 的亚秒级出块时间意味着区块数量增长迅速,因此高效的分页至关重要。

本文是 OnFinality Learn 通用指南《访问历史区块链数据》的 Monad 专属配套文章。我们将介绍节点类型、确切的 RPC 方法,以及一个可运行的脚本,用于可靠地获取历史日志。

Monad 全节点与归档节点:保留的内容

Monad 文档区分了全节点和归档节点。全节点通常会修剪历史状态,仅保留近期状态(例如最近 128 个区块,但具体取决于实现,并非固定数字)。归档节点保留所有历史状态,允许在任何过去的区块进行查询。对于日志,两种节点类型都可以提供 eth_getLogs,但旧日志的可用性取决于节点的保留策略;某些提供商可能会在全节点上修剪日志。

Monad 官方文档也做了同样的全节点与归档节点区分,并指出历史状态由归档源提供(参见 Monad JSON-RPC 概述 及其引用的 Monad 节点/归档文档);但未指定确切的保留窗口。通常,如果你需要特定旧区块的状态(例如区块 N 的代币余额),则需要支持归档的数据源。对于日志,你可能可以在全节点上查询近期历史,但对于深层回填,归档源更安全。

由于 Monad 使用延迟执行和并行执行,区块生产速度很快——亚秒级。这意味着每小时产生数千个区块。对 1 天窗口的日志查询可能跨越 100,000 多个区块,如果不分页,可能会压垮节点。请务必查看提供商的文档以了解保留和速率限制;这些因提供商而异,并非标准化。

  • 全节点:仅保留近期状态;适用于当前读取和近期日志。
  • 归档节点:保留完整历史状态;对于旧区块的 eth_call、eth_getBalance、eth_getCode 是必需的。
  • 日志:可能在全节点上有限窗口内可用;归档节点通常保留所有日志。
  • Monad 的快速出块时间意味着历史范围很大;请相应规划分页。

用于历史读取的 JSON-RPC 方法

Monad 支持标准的以太坊 JSON-RPC 方法。对于历史数据,关键方法如下:

eth_getBlockByNumber:按编号获取区块,包括完整交易(如果请求)。使用区块标签 'earliest' 或十六进制区块号。

eth_getLogs:按地址和主题在区块范围内过滤日志。这是获取历史事件数据的主要方法。

eth_call:在特定区块执行调用以读取合约状态(例如 balanceOf)。对于旧区块需要归档节点。

eth_getBalance、eth_getCode、eth_getStorageAt:在给定区块标签下读取账户状态。

eth_getProof:获取特定区块的账户和存储证明,可用于无需信任的验证。

  • 区块标签:'latest'、'earliest'、'pending' 或十六进制区块号。
  • 对于 eth_getLogs,fromBlock 和 toBlock 是必需的;使用十六进制或标签。
  • eth_call 接受区块参数;对于历史状态使用十六进制区块号。
  • eth_getProof 在归档节点上可用,可用于交叉检查。

实际示例:使用 ethers.js 分页获取 eth_getLogs

下面是一个独立的 Node.js 脚本,用于在范围内分页获取 eth_getLogs,并处理超时和退避。将 RPC_URL 替换为你自己的端点(例如来自 Monad RPC 端点)。该脚本以 10,000 个区块为块获取日志,并打印计数和示例日志。

该脚本使用 ethers v6,并包含简单的重试机制,在 429 或超时错误时进行指数退避。它还会记录进度,以便你监控长时间的回填。

// Requires Node.js 18+ and ethers v6: npm install ethers
const { ethers } = require('ethers');

const RPC_URL = process.env.RPC_URL || 'https://rpc.monad.xyz'; // Replace with your endpoint
const provider = new ethers.JsonRpcProvider(RPC_URL);

// Configuration
const CONTRACT_ADDRESS = '0x...'; // Optional: filter by contract address
const FROM_BLOCK = 1_000_000; // Starting block (hex or number)
const TO_BLOCK = 1_100_000;   // Ending block
const CHUNK_SIZE = 10_000;    // Blocks per request
const MAX_RETRIES = 5;
const BASE_DELAY = 1000;      // ms

async function getLogsWithRetry(filter) {
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    try {
      return await provider.getLogs(filter);
    } catch (error) {
      if (error.code === 'SERVER_ERROR' || error.code === 'TIMEOUT' || error.code === 429) {
        const delay = BASE_DELAY * Math.pow(2, attempt);
        console.log(`Retry ${attempt + 1} after ${delay}ms: ${error.message}`);
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
  throw new Error('Max retries exceeded');
}

async function main() {
  const allLogs = [];
  let from = FROM_BLOCK;
  while (from <= TO_BLOCK) {
    const to = Math.min(from + CHUNK_SIZE - 1, TO_BLOCK);
    console.log(`Fetching logs from ${from} to ${to}...`);
    const filter = {
      fromBlock: from,
      toBlock: to,
      address: CONTRACT_ADDRESS || undefined,
    };
    const logs = await getLogsWithRetry(filter);
    allLogs.push(...logs);
    console.log(`Found ${logs.length} logs in this chunk.`);
    from = to + 1;
  }
  console.log(`Total logs fetched: ${allLogs.length}`);
  if (allLogs.length > 0) {
    console.log('Sample log:', JSON.stringify(allLogs[0], null, 2));
  }
}

main().catch(console.error);

预期输出与验证

运行脚本时,你会看到进度行和最终计数。示例日志将显示标准的以太坊日志结构:地址、主题、数据、区块号、交易哈希等。使用这些来验证你的查询。

要验证正确性,你可以交叉检查已知事件。例如,如果你正在查询代币转账,你可以将总计数与区块浏览器中相同范围的事件列表进行比较。请注意,区块浏览器可能具有不同的索引,因此可能存在轻微差异。

填写下表以记录你的端点的行为。

| Chunk Range | Logs Count | Time Taken (s) | Errors/Retries |
|-------------|------------|----------------|----------------|
| 1,000,000-1,010,000 | ... | ... | ... |
| 1,010,001-1,020,000 | ... | ... | ... |
| ... | ... | ... | ... |
| Total | ... | ... | ... |

常见故障与修复

在 Monad 上查询历史数据时,你可能会遇到几个问题。以下是最常见的问题及解决方法。

错误:'header not found' 或 'missing trie node' — 这表明节点没有历史状态。你需要一个归档节点。如果你使用的是公共端点,请切换到提供归档数据的提供商。

错误:'query returned more than 10000 results' — 许多提供商限制了 eth_getLogs 的结果数。减小你的块大小或缩小范围。上面的脚本使用 10,000 个区块,但对于密集日志,你可能需要将其降低到 1,000 甚至 100。

超时错误 — Monad RPC 端点有文档记录的超时时间(参见 Monad RPC 超时和重试)。脚本包含重试逻辑,但你可能需要在提供商设置中增加超时时间。

速率限制(HTTP 429)— 提供商强制执行每 IP 速率限制。脚本会退避,但对于大型回填,请考虑使用专用端点或分散请求时间。参见 Monad RPC 速率限制和 429

提供商对大型日志范围的行为各不相同:许多 JSON-RPC 网关按结果数量而不是区块数量限制单个 eth_getLogs 响应,这就是为什么在 Monad 这样的高吞吐量链上,按固定区块窗口分页(如上面的示例)是安全模式。

  • 始终使用归档端点进行历史状态读取。
  • 如果出现 'result too large',请减小块大小。
  • 对 429 和超时实现指数退避。
  • 查看提供商文档以了解具体限制。

权衡与限制

查询历史数据存在固有的权衡。归档节点运行成本更高,并且对于深度查询通常具有更高的延迟。全节点对于当前数据更快,但无法提供旧状态。

对于需要在任意过去区块获取代币余额的 dApp,你必须使用归档节点。对于日志回填,如果范围较近,你通常可以使用全节点,但对于完整历史,归档是必要的。

Monad 的快速出块时间意味着即使几天的日志也可能有数百万个区块。这使得完整回填在 RPC 调用和时间方面成本高昂。对于非常大的回填,请考虑使用数据索引服务,但对于中等范围,上面的脚本有效。

提供商特定的速率限制和保留策略各不相同。请务必查看提供商的文档。要比较提供商,请使用 RPC 助手 或查阅 Monad RPC 提供商列表

  • 归档节点:成本更高,深度查询较慢,但对于历史状态是必需的。
  • 全节点:快速,但仅保留近期状态;日志可能被修剪。
  • 大型日志范围:使用分页并考虑索引服务。
  • 提供商策略:始终验证保留和速率限制。

后续步骤与进一步阅读

对于生产环境,请考虑使用托管归档 RPC 提供商,以避免自己运行归档节点的开销。要选择和比较具体的 Monad 提供商(包括它们是否提供支持归档的历史端点),请使用 Monad RPC 端点 RPC 助手页面Monad RPC 提供商列表

有关 Monad RPC 性能和可靠性的更多信息,请参阅我们的指南:Monad RPC 延迟与优化Monad RPC 超时和重试Monad RPC 速率限制和 429。你还可以浏览 Monad 主网网络页面 获取端点详细信息。

如果你需要选择提供商,请使用 Monad RPC 助手 比较选项。并重新访问通用的 OnFinality Learn 中心 获取更多指南。

永远不用担心基础设施

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

开始