# eth_getLogs 區塊跨度上限與分段查詢

> Source: https://docs.blockvectra.com/zh-hant/guides/getlogs-block-range/

## 直接答案

單次 `eth_getLogs` 的上限是目標鏈在 [GET /v1/chains](https://api.blockvectra.com/v1/chains) 中的 `max_logs_block_range`（HyperEVM 為 1,000 個區塊），區塊數按 `toBlock − fromBlock + 1` 計算。超限回傳 HTTP 200、JSON-RPC `-32602` 與 `error.data.reason: logs_range_too_large`，且 `retryable: false`（見[錯誤目錄](https://docs.blockvectra.com/en/errors/#logs_range_too_large)）。將區間分成 `[from, min(from + max − 1, end)]`，成功後從上一段末尾加一繼續。

將以下程式碼儲存為 `logs-minimal.mjs`，設定環境變數 `BLOCKVECTRA_API_KEY`、合約地址 `LOG_ADDRESS` 及已確認的區塊範圍 `FROM_BLOCK`、`TO_BLOCK`，然後執行 `node logs-minimal.mjs`（Node.js 24 或以上）。用 `CHAIN` 選擇目標鏈；預設使用 `robinhood_mainnet`。

```js
const { BLOCKVECTRA_API_KEY: key, LOG_ADDRESS: address, FROM_BLOCK, TO_BLOCK } = process.env;
if (!key || !/^0x[0-9a-f]{40}$/i.test(address ?? '')) throw new Error('Set BLOCKVECTRA_API_KEY and LOG_ADDRESS');
if (![FROM_BLOCK, TO_BLOCK].every(value => /^(0x[0-9a-f]+|[0-9]+)$/i.test(value ?? ''))) {
  throw new Error('Set FROM_BLOCK and TO_BLOCK to nonnegative block numbers');
}
const start = BigInt(FROM_BLOCK), end = BigInt(TO_BLOCK);
if (start > end) throw new Error('FROM_BLOCK must not exceed TO_BLOCK');
const chainSlug = process.env.CHAIN ?? 'robinhood_mainnet';
const chainsUrl = 'https://api.blockvectra.com/v1/chains';
const catalogResponse = await fetch(chainsUrl, { signal: AbortSignal.timeout(15_000) });
if (!catalogResponse.ok) throw new Error(`Chains HTTP ${catalogResponse.status}`);
const catalog = await catalogResponse.json();
const chain = catalog.chains.find(item => item.chain === chainSlug);
if (!chain || !Number.isSafeInteger(chain.max_logs_block_range) || chain.max_logs_block_range <= 0) {
  throw new Error('Missing or invalid max_logs_block_range');
}
const matches = pattern => pattern.endsWith('*') ? 'eth_getLogs'.startsWith(pattern.slice(0, -1)) : pattern === 'eth_getLogs';
if (!chain.methods?.allow?.some(matches) || chain.methods?.deny?.some(matches)) {
  throw new Error('eth_getLogs is unavailable on this chain');
}
const max = BigInt(chain.max_logs_block_range);
const rpcUrl = new URL(`./${chainSlug}`, chainsUrl).href;
const hex = value => `0x${value.toString(16)}`;
for (let from = start; from <= end;) {
  const to = from + max - 1n < end ? from + max - 1n : end;
  const response = await fetch(rpcUrl, {
    method: 'POST', redirect: 'error', signal: AbortSignal.timeout(15_000),
    headers: { 'Content-Type': 'application/json', 'x-api-key': key },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_getLogs',
      params: [{ address, fromBlock: hex(from), toBlock: hex(to) }] }),
  });
  const body = await response.json();
  if (!response.ok || body.error || !Array.isArray(body.result)) {
    throw new Error(`RPC HTTP ${response.status}: ${JSON.stringify(body.error ?? 'Invalid result')}`);
  }
  console.log(JSON.stringify({ fromBlock: hex(from), toBlock: hex(to), result: body.result }));
  from = to + 1n;
}
```

每行輸出一個已成功區段；任何 HTTP 或 JSON-RPC 錯誤都會終止範例，不會跳過失敗區段。429 處理見下方批次與速率限制說明。

## eth\_getLogs 的區塊跨度上限

在呼叫 JSON-RPC 方法 `eth_getLogs` 時，單次請求的區塊跨度按照 `toBlock − fromBlock + 1` 計算，不能超過目標鏈透過 `GET /v1/chains` 公布的 `max_logs_block_range` 上限。

該限制因鏈而異——各鏈的參數透過公開端點 `GET /v1/chains` 發布（鏈清單見[支援的鏈](https://docs.blockvectra.com/en/chains/)）。該端點免驗證、不計費。在編寫用戶端程式碼時，請在執行時透過該端點動態讀取目標鏈的 `max_logs_block_range`，切勿將上限數值寫死在程式碼中。

過濾參數 `fromBlock` 與 `toBlock` 省略或為 `null` 時，按 `latest` 處理。

## 各鏈 eth\_getLogs 上限

以下為 [GET /v1/chains](https://api.blockvectra.com/v1/chains) 公布的各鏈 `max_logs_block_range`；「未公布」不表示無限制。呼叫前同時核對該鏈的 `methods.allow` 與 `methods.deny`，其中 deny 優先；區塊跨度上限不代表結果數量或查詢耗時上限。

| 鏈 | 鏈識別碼 | max_logs_block_range（區塊數） |
| --- | --- | --- |
| Arbitrum One | `arb_mainnet` | 1,000 |
| Base | `base_mainnet` | 1,000 |
| BNB Smart Chain | `bsc_mainnet` | 1,000 |
| Ethereum | `eth_mainnet` | 1,000 |
| Ethereum Sepolia | `eth_sepolia` | 1,000 |
| HyperEVM | `hyperevm_mainnet` | 1,000 |
| Polygon | `polygon_mainnet` | 1,000 |
| Robinhood Chain | `robinhood_mainnet` | 1,000 |
| Robinhood Chain Testnet | `robinhood_testnet` | 1,000 |

## 常見報錯原文

先區分區塊跨度、結果數量與查詢耗時；同一個 JSON-RPC 錯誤碼可能代表不同問題。

| 報錯原文 / 識別代號                                                                         | 出處                                                                                                                                                  | 處理方式                                                                      |
| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `eth_getLogs block range too large: max <N> blocks`；`-32602`；`logs_range_too_large` | [BlockVectra 錯誤目錄](https://docs.blockvectra.com/en/errors/#logs_range_too_large)                                                                                                | `<N>` 是該鏈的 `max_logs_block_range`；縮小跨度後重發，原樣重試無效。                         |
| `query block range exceeds server limit, narrow your filter: <N>`                   | [Erigon eth\_getLogs 原始碼](https://github.com/erigontech/erigon/blob/9e603d74f60c21ca793a03a7ce373de19aa3fdc7/rpc/jsonrpc/eth_receipts.go#L343-L347) | `<N>` 是該節點的區塊範圍上限；縮小查詢區間後重發。                                              |
| `query returns too many logs, narrow your filter: <N>`                              | [Erigon eth\_getLogs 原始碼](https://github.com/erigontech/erigon/blob/9e603d74f60c21ca793a03a7ce373de19aa3fdc7/rpc/jsonrpc/eth_receipts.go#L439-L442) | `<N>` 是該節點的結果數量上限；縮小區間，並用 `address`、`topics` 收窄過濾。即使只查一個區塊，也可能需要更精確的過濾條件。 |

以上報錯範本中的 `<N>` 在實際回應中替換為端點上限。第三方原文對應其自身的端點與限制，措辭可能隨用戶端版本變化；使用 BlockVectra 時，以 `/v1/chains` 和 `error.data.reason` 為準。

## 超出跨度上限的表現

當單次請求的區塊跨度 `toBlock − fromBlock + 1` 超過該鏈的 `max_logs_block_range` 時，請求回傳 HTTP 200 與 JSON-RPC 錯誤：

* **錯誤碼**：`-32602`
* **錯誤訊息**：`eth_getLogs block range too large: max <N> blocks`
* **計費狀態**：不計費。

### 範例請求

以下請求僅在區塊跨度大於目標鏈目前的 `max_logs_block_range` 時超限：

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_getLogs",
  "params": [
    {
      "fromBlock": "0x45a2409",
      "toBlock": "0x45a27f1"
    }
  ]
}
```

### 範例回應

超出上限時回傳的錯誤回應範例：

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "eth_getLogs block range too large: max <N> blocks"
  }
}
```

其中 `<N>` 為該鏈的 `max_logs_block_range`（透過 `GET /v1/chains` 發布）。

在包含多個呼叫的批次請求中，如果其中某個 `eth_getLogs` 跨度過大，該呼叫同樣會在對應位置回傳上述 `-32602` 錯誤，且該呼叫不計費。

## 分段查詢實作

當需要檢索跨越較長區間的日誌時，應先讀取目標鏈的 `max_logs_block_range`，再將目標區間按照 `[from, from + max - 1]` 劃分為若干段閉區間，按順序逐段請求並合併結果。

以下範例以 `robinhood_mainnet` 為例示範分段查詢流程：

**cURL**

```bash
export BLOCKVECTRA_API_KEY="rgw_your_api_key"

# 1. 從公開端點讀取目標鏈的 max_logs_block_range（免驗證、不計費）
curl -s "https://api.blockvectra.com/v1/chains"

# 2. 發起單次合規請求：區塊跨度（toBlock - fromBlock + 1）不超過該鏈的 max_logs_block_range
curl -s "https://api.blockvectra.com/v1/robinhood_mainnet" \
  -H 'Content-Type: application/json' \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_getLogs",
    "params": [{
      "address": "0x1111111111111111111111111111111111111111",
      "fromBlock": "0x45a2409",
      "toBlock": "0x45a246c"
    }]
  }'
```


  **TypeScript**

```ts
const CHAIN = "robinhood_mainnet";
const RPC_ENDPOINT = "https://api.blockvectra.com/v1/robinhood_mainnet";

// 1. 從公開端點 GET /v1/chains 讀取該鏈允許的最大區塊跨度（免驗證、不計費）
const chainsUrl = new URL("/v1/chains", RPC_ENDPOINT);
const chainsRes = await fetch(chainsUrl);
const { chains } = (await chainsRes.json()) as {
  chains: Array<{ chain: string; max_logs_block_range: number }>;
};

const targetChain = chains.find((c) => c.chain === CHAIN);
if (!targetChain) {
  throw new Error(`Chain ${CHAIN} not found`);
}

const maxRange = targetChain.max_logs_block_range;

// 2. 按 [from, from + max - 1] 切段循序查詢並合併結果（閉區間包含兩端）
const fromBlock = 0x45a2409;
const toBlock = 0x45a2900;

const allLogs: unknown[] = [];
let cur = fromBlock;

while (cur <= toBlock) {
  const chunkEnd = Math.min(cur + maxRange - 1, toBlock);

  const res = await fetch(RPC_ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.BLOCKVECTRA_API_KEY!,
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "eth_getLogs",
      params: [
        {
          address: "0x1111111111111111111111111111111111111111",
          fromBlock: "0x" + cur.toString(16),
          toBlock: "0x" + chunkEnd.toString(16),
        },
      ],
    }),
  });

  const body = (await res.json()) as {
    result?: unknown[];
    error?: { code: number; message: string };
  };

  if (body.error) {
    throw new Error(`eth_getLogs error ${body.error.code}: ${body.error.message}`);
  }

  if (body.result) {
    allLogs.push(...body.result);
  }

  cur = chunkEnd + 1;
}

console.log(`擷取到 ${allLogs.length} 筆日誌（跨多個區塊）`);

// npx tsx example.mts
```


  **Python**

```python
import os
from urllib.parse import urljoin
import requests

CHAIN = "robinhood_mainnet"
RPC_ENDPOINT = "https://api.blockvectra.com/v1/robinhood_mainnet"

# 1. 從公開端點 GET /v1/chains 讀取該鏈允許的最大區塊跨度（免驗證、不計費）
chains_url = urljoin(RPC_ENDPOINT, "/v1/chains")
chains_res = requests.get(chains_url)
chains_res.raise_for_status()

chains = chains_res.json().get("chains", [])
target_chain = next((c for c in chains if c["chain"] == CHAIN), None)
if not target_chain:
    raise RuntimeError(f"Chain {CHAIN} not found")

max_range = target_chain["max_logs_block_range"]

# 2. 按 [from, from + max - 1] 切段循序查詢並合併結果（閉區間包含兩端）
from_block = 0x45a2409
to_block = 0x45a2900

all_logs: list = []
cur = from_block

while cur <= to_block:
    chunk_end = min(cur + max_range - 1, to_block)

    res = requests.post(
        RPC_ENDPOINT,
        headers={
            "Content-Type": "application/json",
            "x-api-key": os.environ["BLOCKVECTRA_API_KEY"],
        },
        json={
            "jsonrpc": "2.0",
            "id": 1,
            "method": "eth_getLogs",
            "params": [
                {
                    "address": "0x1111111111111111111111111111111111111111",
                    "fromBlock": hex(cur),
                    "toBlock": hex(chunk_end),
                }
            ],
        },
    )
    res.raise_for_status()
    body = res.json()

    if "error" in body:
        err = body["error"]
        raise RuntimeError(f"eth_getLogs error {err.get('code')}: {err.get('message')}")

    all_logs.extend(body.get("result", []))
    cur = chunk_end + 1

print(f"擷取到 {len(all_logs)} 筆日誌（跨多個區塊）")
```


## 批次請求注意事項

若考慮將切分後的多個分段呼叫打包進單個 JSON-RPC 批次請求，需要遵循批次與突發上限規則：

* **批次大小限制**：批次請求支援 1～100 個呼叫；超過 100 個呼叫會被直接拒絕，回傳 HTTP 200 與錯誤碼 `-32600 batch too large: max 100 calls`（不計費）。
* **單請求突發上限**：若單次請求內所有呼叫的 CU 權重之和超過該 key 的突發容量（`burst_cu`），請求會被拒絕並回傳 HTTP 429 `-32022 request cost <N> CU exceeds burst capacity <M> CU`（不計費）；請拆分成更小的批次。
* **權杖桶容量不足**：若單請求滿權重之和未超過突發上限，但目前權杖桶內可用容量不足，服務回傳 HTTP 429 與錯誤碼 `-32005 rate limit exceeded`（帶 `Retry-After`），詳見[哪些情況不扣費：錯誤碼與計費規則](https://docs.blockvectra.com/en/guides/billing-rules/)。

因此，在大跨度日誌查詢時，建議採用循序逐段請求；若使用批次請求，應嚴格控制批內呼叫數量，避免滿權重之和超出突發上限。

## 相關指南與計費規則

* 參閱 [eth\_getLogs 方法參考](https://docs.blockvectra.com/en/api/json-rpc/methods/eth_getLogs/)以取得過濾參數、回傳值與 CU 權重。
* 參閱 [logs\_range\_too\_large 錯誤參考](https://docs.blockvectra.com/en/errors/#logs_range_too_large)以取得錯誤詳情與建議處理動作。
* 關於 `eth_getLogs` 與 Data API 轉帳端點（地址轉帳與代幣轉帳）的功能定位、覆蓋範圍及最終性差異，請參閱[節點近況與已索引歷史：何時使用 eth\_getLogs，何時使用轉帳 API](https://docs.blockvectra.com/en/guides/logs-vs-transfers/)。
* 關於計算單位（CU）、每小時結算與不計費錯誤回應的完整說明，請參閱[哪些情況不扣費：錯誤碼與計費規則](https://docs.blockvectra.com/en/guides/billing-rules/)。

## 下一步

* [瀏覽資料集目錄](https://blockvectra.com/en/data/)，查看 BlockVectra 索引的全部資料集。
* [查看免費方案與定價](https://blockvectra.com/en/pricing/#free)，確認你的帳戶包含的內容。
* [登入控制台](https://console.blockvectra.com/login/?next=%2Fkeys%2F)建立 API key。
