本指南介绍如何通过 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 中心 获取更多指南。