El método eth_getLogs está especificado para aceptar un rango de bloques inclusivo, pero los proveedores imponen límites por solicitud no documentados que se manifiestan como errores definidos por la implementación o rechazos HTTP. Dado que el límite es en realidad un límite de costo, un filtro estrecho de dirección/tema puede cubrir un rango mucho más amplio que uno amplio. Este artículo enseña un algoritmo de chunking adaptativo portátil que reduce a la mitad el intervalo ante fallos de rango, distingue errores deterministas de errores transitorios y fusiona resultados con garantías de deduplicación y ordenamiento. También proporciona un método de reconciliación y un ejemplo ejecutable en Node.js para que puedas medir el comportamiento de tu propio endpoint.
Qué significan los argumentos de eth_getLogs para las consultas de rango
La especificación JSON-RPC de Ethereum define eth_getLogs como un método que devuelve un array de objetos de log que coinciden con un filtro. El objeto de filtro acepta fromBlock, toBlock, address, topics y blockHash. El rango es inclusivo: tanto fromBlock como toBlock se incluyen en el escaneo. La especificación establece que el filtro se evalúa contra un rango de bloques y que un filtro blockHash es mutuamente excluyente con fromBlock y toBlock. Si proporcionas blockHash, el nodo ignora cualquier parámetro de rango y devuelve logs solo para ese bloque único.
La cadena especial latest es un objetivo móvil. Se resuelve al head actual en el momento en que el nodo procesa la solicitud. Para escaneos históricos reproducibles, nunca uses latest como límite. En su lugar, resuelve el head actual una vez, almacena ese número de bloque y úsalo como el toBlock fijo para todo el escaneo. Esto hace que el escaneo sea determinista y te permite reconciliar contra un hash de bloque conocido más adelante.
Un rango demasiado grande no es un error de especificación. La especificación JSON-RPC 2.0 define códigos de error en el rango -32700 a -32600 para errores de parseo y solicitudes inválidas, pero deja el rango -32000 a -32099 para errores de servidor definidos por la implementación. Los proveedores usan este espacio para señalar que un rango excede su política. Algunos devuelven un código numérico como -32005 o -32000 con un mensaje como 'query returned more than 10000 results' o 'block range too large'. Otros rechazan en la capa HTTP con un estado 400 o 413 antes de llegar a la capa JSON-RPC. Por eso el mismo escaneo que funciona contra un endpoint falla contra otro con un mensaje diferente y, con frecuencia, el mismo código.
- fromBlock y toBlock son inclusivos; ambos extremos se escanean.
- blockHash es mutuamente excluyente con fromBlock y toBlock.
- latest es un objetivo móvil y no es adecuado para escaneos históricos reproducibles.
- Los límites de rango son política del proveedor, no errores de protocolo, y se manifiestan en el rango -32000 definido por la implementación o como rechazos HTTP.
Por qué el límite es un límite de costo, no un conteo de bloques
El límite por solicitud no es un conteo fijo de bloques. Es un límite de costo. El nodo debe ejecutar el filtro sobre cada bloque del rango y devolver cada log coincidente. El costo es proporcional al número de bloques escaneados más el número de logs devueltos. Un escaneo con un filtro estrecho de dirección y tema produce menos logs coincidentes y, por lo tanto, menor costo, incluso sobre un rango amplio. Un escaneo con un filtro amplio produce más logs y alcanza el límite antes.
Esto tiene una consecuencia práctica: un filtro estrecho puede cubrir un rango mucho más amplio que un filtro amplio. Si estás indexando un solo contrato con una firma de evento específica, es posible que puedas escanear decenas de miles de bloques en una sola solicitud. Si estás escaneando todos los logs de todas las direcciones, puedes estar limitado a unos pocos cientos de bloques. El límite está documentado o varía según el proveedor, y puede cambiar sin previo aviso. Nunca codifiques un conteo de bloques como una constante universal.
El modelo de costo también explica por qué ocurren los timeouts. Un nodo que ya está bajo carga puede tardar más en ejecutar una consulta de rango amplio y devolver un timeout o un error 5xx incluso si el rango está dentro del límite documentado del proveedor. Esto es un fallo transitorio, no un fallo determinista. Distinguir los dos es esencial para una estrategia de backoff correcta.
- El límite es un límite de costo basado en bloques escaneados y logs devueltos.
- Los filtros estrechos sobreviven a rangos más amplios que los filtros amplios.
- Los timeouts y errores 5xx son transitorios; los errores de rango son deterministas.
Algoritmo de chunking adaptativo con condiciones de parada explícitas
El objetivo es un algoritmo portátil que sobreviva al límite no documentado de cualquier proveedor. Comienza desde un span máximo configurado, ejecuta la consulta y clasifica el resultado. Las categorías de resultado son: éxito, rango demasiado grande, timeout o 5xx, y límite de tasa. Ante un fallo de rango, reduce el span a la mitad y reintenta la misma posición del cursor. Continúa hasta que el span llegue a un bloque. Si una consulta de un solo bloque aún falla con un error de rango, registra un fallo y avanza el cursor en un bloque para evitar un bucle infinito.
Ante un timeout o 5xx, no reduzcas el span a la mitad. La solicitud es capaz pero el nodo fue lento. Reintenta el mismo span después de un intervalo de backoff. Ante un límite de tasa (HTTP 429), respeta el encabezado Retry-After si está presente, o usa backoff exponencial. No reduzcas el span para errores transitorios, porque eso aumentaría innecesariamente el número de solicitudes y empeoraría el límite de tasa.
El algoritmo debe tener una condición de parada explícita y un registro de fallos explícito. Un error común es reintentar indefinidamente ante cualquier error. Eso convierte un error de rango determinista en un bucle infinito. En su lugar, después de un número configurable de reintentos para errores transitorios, registra el chunk como fallido y continúa. El registro de fallos debe incluir fromBlock, toBlock, span, código de error y mensaje de error para que puedas diagnosticar el comportamiento del endpoint más adelante.
async function adaptiveScan({ endpoint, address, topics, fromBlock, toBlock, maxSpan, maxRetries }) {
const results = [];
const failures = [];
let cursor = fromBlock;
let span = maxSpan;
while (cursor <= toBlock) {
const chunkTo = Math.min(cursor + span - 1, toBlock);
let attempt = 0;
let success = false;
while (attempt <= maxRetries) {
try {
const logs = await rpcCall(endpoint, 'eth_getLogs', [{
fromBlock: '0x' + cursor.toString(16),
toBlock: '0x' + chunkTo.toString(16),
address,
topics
}]);
results.push(...logs);
success = true;
break;
} catch (err) {
const code = err.code;
const message = (err.message || '').toLowerCase();
const isRangeError = code === -32005 || code === -32000 || message.includes('range') || message.includes('too large') || message.includes('more than');
const isRateLimit = err.status === 429;
const isTransient = err.status >= 500 || message.includes('timeout') || isRateLimit;
if (isRangeError && span > 1) {
span = Math.max(1, Math.floor(span / 2));
attempt = 0;
break;
}
if (isTransient && attempt < maxRetries) {
const delay = isRateLimit && err.retryAfter ? err.retryAfter * 1000 : Math.pow(2, attempt) * 1000;
await new Promise(r => setTimeout(r, delay));
attempt++;
continue;
}
failures.push({ fromBlock: cursor, toBlock: chunkTo, span, code, message: err.message });
success = true;
break;
}
}
if (!success) {
failures.push({ fromBlock: cursor, toBlock: chunkTo, span, code: 'unknown', message: 'retries exhausted' });
}
cursor = chunkTo + 1;
}
return { results, failures };
}Distinguir fallos deterministas de fallos transitorios
Un fallo determinista es reproducible. Si envías el mismo fromBlock, toBlock, address y topics al mismo endpoint, obtendrás el mismo error de rango cada vez. Un fallo transitorio es no determinista. Un timeout o un 429 puede tener éxito en el siguiente intento con los mismos parámetros. La estrategia de backoff no debe aplicarse a un fallo determinista, porque reintentar una solicitud imposible desperdicia tiempo y puede provocar límites de tasa.
Para clasificar un error, inspecciona tanto el código de error JSON-RPC como el estado HTTP. Los errores de rango suelen llegar como errores JSON-RPC con códigos en el rango -32000. Los timeouts y errores 5xx llegan como errores HTTP o como errores JSON-RPC con mensajes que contienen 'timeout' o 'gateway'. Los límites de tasa llegan como HTTP 429 con un encabezado Retry-After. Si el error es ambiguo, registra la respuesta completa y prueba los mismos parámetros de nuevo. Si el error se reproduce, trátalo como determinista.
La regla práctica: ante un error de rango, reduce el span a la mitad y reintenta el mismo cursor. Ante un timeout o 5xx, reintenta el mismo span después del backoff. Ante un 429, respeta Retry-After y reintenta el mismo span. Nunca reduzcas el span a la mitad por un error transitorio, porque eso aumenta el número de solicitudes y puede empeorar el límite de tasa.
- Fallo determinista: se reproduce de forma determinista con los mismos parámetros.
- Fallo transitorio: timeout, 5xx o 429; puede tener éxito al reintentar.
- Reduce el span a la mitad solo para errores de rango; usa backoff para errores transitorios.
Garantías de completitud y por qué no son automáticas
Un escaneo por chunks no produce automáticamente un conjunto de logs completo, ordenado y deduplicado. Dos chunks adyacentes pueden devolver legítimamente el mismo log cuando el cursor se superpone. Esto ocurre si avanzas el cursor por el tamaño del chunk en lugar de chunkTo + 1, o si el proveedor devuelve logs de un bloque que fue reescaneado. Debes deduplicar por la tupla (blockNumber, logIndex, transactionIndex) y ordenar por la misma tupla para producir un orden determinista.
Una reorganización de cadena puede invalidar un chunk que ya se había confirmado en el almacenamiento. Si una reorg elimina un bloque que contenía un log, tu log almacenado ahora queda huérfano. El método de reconciliación debe detectar esto. Un enfoque es almacenar el bloque procesado más alto por par dirección/tema y reescanear una profundidad de confirmación configurable detrás de la punta en cada pasada. Otro es verificar que los conteos totales de logs sean monótonamente crecientes y que ningún log haga referencia a un bloque que ya no sea canónico.
La completitud también requiere que nunca te salte un bloque. Si un chunk falla y avanzas el cursor sin registrar el fallo, tienes un hueco. El registro de fallos no es opcional. Es la evidencia de que existe un hueco y el punto de partida para un reescaneo dirigido. Para un tratamiento más profundo de la reconciliación, consulta Reconciliación de indexadores EVM bloque por bloque.
- Deduplica por (blockNumber, logIndex, transactionIndex).
- Ordena por la misma tupla para un ordenamiento determinista.
- Las reorgs pueden invalidar chunks confirmados; reescanea una profundidad de confirmación detrás de la punta.
- Nunca avances el cursor más allá de un chunk fallido sin registrar el fallo.
Método de reconciliación para demostrar que el escaneo está completo
La reconciliación es el proceso de demostrar que tus logs almacenados coinciden con lo que la cadena realmente contiene. No es una verificación única. Debe ejecutarse en cada pasada. El método tiene cuatro partes: volver a ejecutar una pequeña muestra de chunks y comparar, almacenar el bloque procesado más alto por par dirección/tema, verificar que los conteos totales de logs sean monótonamente crecientes y reescanear una profundidad de confirmación configurable detrás de la punta.
Volver a ejecutar una muestra de chunks es la verificación más sólida. Elige un conjunto aleatorio de chunks previamente procesados, vuelve a ejecutar la misma consulta eth_getLogs y compara los logs devueltos con lo que almacenaste. Si los conjuntos difieren, tienes un hueco o un log huérfano. El tamaño de la muestra puede ser pequeño, pero debe ser distinto de cero en cada pasada. Para una discusión más amplia de patrones de reconciliación, consulta Reconciliación de indexadores EVM bloque por bloque.
Almacenar el bloque procesado más alto por par dirección/tema te permite reanudar sin reescanear todo. También te permite detectar si una reorg ha movido la punta hacia atrás. Si el head actual es menor que tu bloque procesado más alto almacenado, ha ocurrido una reorg y debes reescanear desde el nuevo head menos la profundidad de confirmación. Verificar que los conteos totales de logs sean monótonamente crecientes detecta eliminaciones accidentales. Reescanear una profundidad de confirmación detrás de la punta detecta reorgs más profundas que un bloque.
- Vuelve a ejecutar una muestra aleatoria de chunks y compara resultados.
- Almacena el bloque procesado más alto por par dirección/tema.
- Verifica que los conteos totales de logs sean monótonamente crecientes.
- Reescanea una profundidad de confirmación configurable detrás de la punta en cada pasada.
Ejemplo ejecutable en Node.js para escaneos adaptativos por chunks
El siguiente script de Node.js realiza un escaneo adaptativo por chunks contra una URL de endpoint pasada como argumento. Imprime la salida por chunk incluyendo fromBlock, toBlock, span, status, logCount y elapsedMs. También verifica que el array de logs fusionado esté estrictamente ordenado y libre de duplicados. Ejecútalo con node scan.js <endpoint> <fromBlock> <toBlock> <address> <topic0>.
El script usa la función adaptiveScan de la sección anterior. Añade un paso de fusión que deduplica por (blockNumber, logIndex, transactionIndex) y ordena por la misma tupla. La aserción al final lanza un error si el array fusionado no está estrictamente ordenado o contiene duplicados. Esto te da una medición reproducible del comportamiento de tu endpoint.
Como el límite varía según el proveedor y la cadena, debes ejecutar este script contra cada endpoint que uses. La salida por chunk es tu evidencia. Registra el span en el que ocurre el primer error de rango, el código de error y el mensaje de error. Ese es el límite efectivo de tu endpoint para esa selectividad de filtro.
const https = require('https');
function rpcCall(endpoint, method, params) {
return new Promise((resolve, reject) => {
const url = new URL(endpoint);
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params });
const req = https.request({
hostname: url.hostname,
port: url.port || 443,
path: url.pathname,
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
}, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.error) {
const err = new Error(json.error.message);
err.code = json.error.code;
err.status = res.statusCode;
reject(err);
} else {
resolve(json.result);
}
} catch (e) {
const err = new Error('parse error: ' + data.slice(0, 200));
err.status = res.statusCode;
reject(err);
}
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
async function adaptiveScan({ endpoint, address, topics, fromBlock, toBlock, maxSpan, maxRetries }) {
const results = [];
const failures = [];
let cursor = fromBlock;
let span = maxSpan;
while (cursor <= toBlock) {
const chunkTo = Math.min(cursor + span - 1, toBlock);
let attempt = 0;
let success = false;
const start = Date.now();
while (attempt <= maxRetries) {
try {
const logs = await rpcCall(endpoint, 'eth_getLogs', [{
fromBlock: '0x' + cursor.toString(16),
toBlock: '0x' + chunkTo.toString(16),
address,
topics
}]);
results.push(...logs);
console.log(`from=${cursor} to=${chunkTo} span=${span} status=ok logs=${logs.length} ms=${Date.now() - start}`);
success = true;
break;
} catch (err) {
const code = err.code;
const message = (err.message || '').toLowerCase();
const isRangeError = code === -32005 || code === -32000 || message.includes('range') || message.includes('too large') || message.includes('more than');
const isRateLimit = err.status === 429;
const isTransient = err.status >= 500 || message.includes('timeout') || isRateLimit;
if (isRangeError && span > 1) {
console.log(`from=${cursor} to=${chunkTo} span=${span} status=range_error code=${code} msg=${err.message}`);
span = Math.max(1, Math.floor(span / 2));
attempt = 0;
break;
}
if (isTransient && attempt < maxRetries) {
const delay = isRateLimit && err.retryAfter ? err.retryAfter * 1000 : Math.pow(2, attempt) * 1000;
console.log(`from=${cursor} to=${chunkTo} span=${span} status=transient code=${code} retry=${attempt + 1} delay=${delay}ms`);
await new Promise(r => setTimeout(r, delay));
attempt++;
continue;
}
console.log(`from=${cursor} to=${chunkTo} span=${span} status=failed code=${code} msg=${err.message}`);
failures.push({ fromBlock: cursor, toBlock: chunkTo, span, code, message: err.message });
success = true;
break;
}
}
if (!success) {
failures.push({ fromBlock: cursor, toBlock: chunkTo, span, code: 'unknown', message: 'retries exhausted' });
}
cursor = chunkTo + 1;
}
return { results, failures };
}
function mergeLogs(logs) {
const seen = new Set();
const merged = [];
for (const log of logs) {
const key = `${log.blockNumber}:${log.logIndex}:${log.transactionIndex}`;
if (!seen.has(key)) {
seen.add(key);
merged.push(log);
}
}
merged.sort((a, b) => {
const bn = parseInt(a.blockNumber, 16) - parseInt(b.blockNumber, 16);
if (bn !== 0) return bn;
const li = parseInt(a.logIndex, 16) - parseInt(b.logIndex, 16);
if (li !== 0) return li;
return parseInt(a.transactionIndex, 16) - parseInt(b.transactionIndex, 16);
});
return merged;
}
(async () => {
const [endpoint, fromBlock, toBlock, address, topic0] = process.argv.slice(2);
const { results, failures } = await adaptiveScan({
endpoint,
address,
topics: [topic0],
fromBlock: parseInt(fromBlock),
toBlock: parseInt(toBlock),
maxSpan: 10000,
maxRetries: 3
});
const merged = mergeLogs(results);
console.log(`total logs=${results.length} merged=${merged.length} failures=${failures.length}`);
for (let i = 1; i < merged.length; i++) {
const prev = merged[i - 1];
const curr = merged[i];
const prevKey = `${prev.blockNumber}:${prev.logIndex}:${prev.transactionIndex}`;
const currKey = `${curr.blockNumber}:${curr.logIndex}:${curr.transactionIndex}`;
if (prevKey >= currKey) throw new Error('ordering violation at index ' + i);
}
console.log('ordering and deduplication assertions passed');
})();Tabla de resultados para medir tu propio endpoint
Como el límite varía según el proveedor y la cadena, debes medirlo tú mismo. Usa el script anterior para producir una tabla de resultados. Ejecútalo contra cada endpoint que uses, con el mismo filtro de dirección y tema, y registra el span en el que ocurre el primer error de rango. Registra también el código y el mensaje de error. Esta tabla se convierte en tu referencia para configurar maxSpan por endpoint.
La tabla debe tener columnas para endpoint, cadena, dirección, topic0, maxSpan intentado, primer span fallido, código de error, mensaje de error y notas. Rellénala con tus propias mediciones. No confíes en números publicados por otros proveedores, porque el límite puede cambiar sin previo aviso y difiere según la selectividad del filtro.
Si usas múltiples endpoints para redundancia, mide cada uno por separado. Un escaneo que funciona contra un endpoint puede fallar contra otro con un mensaje diferente y, con frecuencia, el mismo código. Persiste el maxSpan medido por endpoint para que tu escáner pueda adaptarse sin intervención manual.
- Endpoint: la URL RPC que estás probando.
- Cadena: el nombre de la red o el ID de cadena.
- Dirección y topic0: la selectividad del filtro usada para la prueba.
- MaxSpan intentado: el span inicial en tu configuración.
- Primer span fallido: el span en el que ocurrió el primer error de rango.
- Código y mensaje de error: los valores exactos devueltos por el endpoint.
- Notas: cualquier límite de tasa, timeout o comportamiento específico del proveedor observado.
| Endpoint | Chain | Address | topic0 | maxSpan attempted | First failing span | Error code | Error message | HTTP status | Elapsed ms |
|---|---|---|---|---|---|---|---|---|---|
| ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ |
| ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ |
| ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ |
| ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ | ____ |Integración con pipelines de producción
El chunking interactúa con el resto de un pipeline de producción de tres maneras. Primero, los resultados de cada chunk deben confirmarse por chunk para que un fallo no pierda todo el escaneo. Si almacenas en búfer todos los logs en memoria y confirmas al final, un fallo al 90% pierde todo. Confirma los logs de cada chunk y su posición de cursor en la misma transacción. Segundo, el paralelismo debe estar acotado para evitar activar límites de tasa. No excedas la concurrencia documentada del endpoint. Si el endpoint no documenta un límite de concurrencia, comienza con una o dos solicitudes paralelas y aumenta solo después de medir.
Tercero, el tamaño del chunk debe persistirse por endpoint porque el límite difiere entre proveedores. Almacena el maxSpan medido junto a la URL del endpoint. Cuando el escáner se inicie, lee el valor persistido y úsalo como span inicial. Si ocurre un error de rango, reduce el span a la mitad y actualiza el valor persistido. Esto hace que el escáner se autoajuste con el tiempo.
Para una discusión más amplia sobre selección de endpoints y concurrencia, consulta la Guía de endpoints RPC (RPC Assistant). Para solución de problemas específicos de timeout, consulta Cómo solucionar errores de timeout de RPC.
- Confirma los logs de cada chunk y la posición del cursor en la misma transacción.
- Acota el paralelismo a la concurrencia documentada del endpoint.
- Persiste el maxSpan medido por endpoint y actualízalo ante errores de rango.
Solución de problemas comunes de chunking
El fallo más común es un bucle de reintentos infinito ante un error de rango determinista. Si tu escáner sigue reduciendo el span a la mitad pero nunca llega a un bloque, verifica que tu condición de parada sea span > 1 y que avances el cursor después de un fallo. Otro fallo común es un hueco causado por avanzar el cursor más allá de un chunk fallido. Registra siempre el fallo y reescanealo más tarde.
Un segundo modo de fallo son las violaciones de ordenamiento después de la fusión. Esto ocurre cuando ordenas solo por blockNumber e ignoras logIndex y transactionIndex. Dos logs en el mismo bloque pueden tener el mismo blockNumber pero valores de logIndex diferentes. Ordena por la tupla completa. Un tercer modo de fallo son los logs duplicados de chunks superpuestos. Si avanzas el cursor por chunkTo + 1, no deberías ver duplicados, pero los proveedores pueden devolver logs de un bloque que fue reescaneado. Deduplica por la tupla completa.
Un cuarto modo de fallo es el límite de tasa causado por paralelismo ilimitado. Si ves respuestas HTTP 429, reduce el paralelismo y respeta Retry-After. No reduzcas el span a la mitad por un 429, porque eso aumenta el número de solicitudes y empeora el límite de tasa. Para más información sobre manejo de timeouts y límites de tasa, consulta Cómo solucionar errores de timeout de RPC.
- Bucle de reintentos infinito: verifica la condición de parada y el avance del cursor.
- Huecos: registra los fallos y reescanealos más tarde.
- Violaciones de ordenamiento: ordena por (blockNumber, logIndex, transactionIndex).
- Duplicados: deduplica por la tupla completa.
- Límites de tasa: reduce el paralelismo y respeta Retry-After.
Limitaciones y compensaciones
El límite varía según el proveedor y la cadena, a menudo no está documentado y puede cambiar sin previo aviso. Un algoritmo de chunking que funciona hoy puede necesitar ajustes mañana. Persistir el maxSpan medido por endpoint ayuda, pero no es una garantía. Debes monitorear nuevos códigos y mensajes de error.
El escaneo acotado por bloques no puede ver logs de bloques que fueron reorgados fuera. Si un log se emitió en un bloque que ya no es canónico, tu escaneo no lo devolverá. Este es el comportamiento correcto para un escaneo canónico, pero significa que tus logs almacenados pueden quedar obsoletos después de una reorg. El método de reconciliación aborda esto reescaneando una profundidad de confirmación detrás de la punta.
Un escaneo por chunks no es atómico. Lee diferentes bloques en diferentes momentos. Si necesitas una instantánea puntual, debes reconciliar contra un hash de bloque fijo. Usa eth_getBlockByNumber para resolver el hash del bloque head al inicio del escaneo y usa ese hash para cualquier consulta basada en blockHash. Para consultas de rango, almacena el número del bloque head y úsalo como el toBlock fijo. Esto te da una vista consistente de la cadena en ese bloque.
Para límites de rango específicos de BSC y escaneos grandes, consulta Límites de rango de eth_getLogs en BSC y escaneos grandes. Para filtrado de eventos y temas, consulta Filtrado de eventos y temas de eth_getLogs.
- El límite varía según el proveedor y la cadena, a menudo no está documentado y puede cambiar.
- El escaneo acotado por bloques no puede ver logs reorgados fuera.
- Un escaneo por chunks no es atómico; reconcilia contra un hash de bloque fijo para instantáneas.
Próximos pasos para escaneos de logs en producción
Comienza midiendo el límite efectivo de tu endpoint con el script anterior. Rellena la tabla de resultados para cada endpoint que uses. Luego configura tu escáner con el maxSpan medido e implementa el algoritmo de chunking adaptativo con condiciones de parada explícitas y registros de fallos. Añade el método de reconciliación a cada pasada.
Si estás evaluando proveedores, compara sus límites de concurrencia documentados y su comportamiento ante errores. La página de Precios de RPC y la página de Servicio de API describen las ofertas de OnFinality. Para una visión general de las redes EVM, consulta Red Ethereum. Para más guías de integración y desarrollo, consulta el Centro de aprendizaje de OnFinality.
Por último, trata el límite como un objetivo móvil. Vuelve a medir periódicamente y después de cualquier anuncio del proveedor. Persiste los valores medidos y actualízalos automáticamente cuando ocurra un error de rango. Esto mantiene tu escáner resiliente sin intervención manual.
- Mide el límite de tu endpoint y rellena la tabla de resultados.
- Implementa chunking adaptativo con condiciones de parada explícitas y registros de fallos.
- Añade reconciliación a cada pasada.
- Vuelve a medir periódicamente y persiste los valores medidos por endpoint.