# eth_getLogs 블록 범위 제한 및 청크 분할 쿼리

> Source: https://docs.blockvectra.com/ko/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)]`로 분할하고, 성공 후에는 이전 청크의 끝에 1을 더한 블록부터 진행하세요.

이를 `logs-minimal.mjs`로 저장하고 `BLOCKVECTRA_API_KEY`, 컨트랙트 주소 `LOG_ADDRESS`, 확정된 블록 윈도우 `FROM_BLOCK`과 `TO_BLOCK`을 설정한 후 Node.js 24 이상에서 `node logs-minimal.mjs`를 실행하세요. `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`로 계산되며 대상 체인에 고시된 `max_logs_block_range`를 초과할 수 없습니다.

이 제한은 체인마다 다릅니다. 체인별 파라미터는 퍼블릭 엔드포인트인 `GET /v1/chains`([지원 체인](https://docs.blockvectra.com/en/chains/)에 체인 목록 제공)를 통해 제공됩니다. 이 엔드포인트는 인증이 필요하지 않으며 비용이 청구되지 않습니다. 클라이언트 애플리케이션을 개발할 때는 코드에 블록 범위 제한을 하드코딩하지 말고 런타임에 이 엔드포인트를 동적으로 조회하세요.

필터 필드인 `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. Read max_logs_block_range from the public chains endpoint (unauthenticated, unbilled)
curl -s "https://api.blockvectra.com/v1/chains"

# 2. Make a single compliant request within the chain's max_logs_block_range (toBlock - fromBlock + 1)
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. Fetch max_logs_block_range from the public GET /v1/chains endpoint (unauthenticated, unbilled)
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. Query chunks sequentially over [from, from + max - 1] and aggregate results (both endpoints inclusive)
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(`Fetched ${allLogs.length} logs across blocks`);

// 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. Fetch max_logs_block_range from the public GET /v1/chains endpoint (unauthenticated, unbilled)
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. Query chunks sequentially over [from, from + max - 1] and aggregate results (both endpoints inclusive)
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"Fetched {len(all_logs)} logs across blocks")
```


## 배치 요청 시 고려사항

분할된 여러 청크 쿼리를 단일 JSON-RPC 배치 요청으로 묶는 것을 고려 중이라면 배치 크기 및 버스트 용량 규칙을 유의해야 합니다:

* **배치 크기 제한**: 배치 요청은 1회에서 100회까지의 호출을 허용합니다. 100회를 초과하는 호출을 제출하면 HTTP 200과 오류 코드 `-32600 batch too large: max 100 calls`로 거부됩니다(미과금).
* **단일 요청 버스트 용량**: 한 요청 내 모든 호출의 CU 가중치 합계가 키의 버스트 용량(`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/)을 참조하세요.

따라서 대규모 로그 쿼리를 실행할 때는 순차적 청크 분할 쿼리를 사용하는 것이 좋습니다. 배치를 사용할 경우 전체 가중치의 합이 버스트 용량 내에 유지되도록 배치당 호출 수를 충분히 작게 유지하세요.

## 관련 가이드 및 과금 규칙

* 필터 파라미터, 반환값 및 CU 가중치는 [eth\_getLogs 메서드 레퍼런스](https://docs.blockvectra.com/en/api/json-rpc/methods/eth_getLogs/)를 참조하세요.
* 오류 세부사항 및 권장 조치는 [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 생성.
