El espacio de nombres de trazas estilo Parity en BNB Smart Chain expone trace_filter, trace_block, trace_transaction y trace_get, cada uno respondiendo a una pregunta diferente sobre llamadas internas, transferencias de valor, creaciones y autodestrucciones. Debido a que un solo bloque de BSC puede contener miles de objetos de traza, un rango amplio de trace_filter es materialmente más pesado que el rango equivalente de eth_getLogs y comúnmente es limitado o agotado por los proveedores. El espacio de nombres es opcional, por lo que muchos endpoints públicos devuelven el error JSON-RPC de método no encontrado (-32601) hasta que se sondea la capacidad. Este artículo muestra cómo detectar soporte de trazas, paginar ventanas de bloques fijas con un cursor persistido, deduplicar en (blockNumber, transactionHash, traceAddress), retroceder ante errores de rango demasiado grande y reparar los últimos bloques después de una reorganización. También separa el comportamiento documentado de las trazas de OpenEthereum de los límites específicos del proveedor y te proporciona una tabla de resultados para completar con tu propio endpoint.
El conjunto de métodos del espacio de nombres de trazas en BNB Smart Chain
BNB Smart Chain hereda el espacio de nombres de trazas de Parity/OpenEthereum, una familia de métodos que reportan eventos a nivel de ejecución en lugar de registros a nivel de recibo. Los cuatro métodos que más usarás son trace_filter, trace_block, trace_transaction y trace_get. Cada uno responde a una pregunta diferente, y elegir el incorrecto produce una respuesta silenciosamente incompleta en lugar de un error.
trace_filter acepta un rango de bloques más filtros opcionales fromAddress y toAddress y devuelve las trazas que coinciden. trace_block devuelve todas las trazas de un solo bloque. trace_transaction devuelve todas las trazas de una transacción, y trace_get devuelve una sola traza por su hash de transacción y ruta traceAddress. El origen histórico de estos métodos y sus campos de filtro y salida es la documentación del módulo de trazas de OpenEthereum (openethereum.github.io).
La especificación JSON-RPC de Ethereum (ethereum.org/en/developers/docs/apis/json-rpc) define los métodos estándar eth_* que se sitúan junto a este espacio de nombres, mientras que el espacio de nombres debug de geth (geth.ethereum.org) cubre los árboles de llamadas por transacción. Si estás decidiendo entre trace_transaction y debug_traceTransaction, consulta trace_transaction vs debug_traceTransaction y trace_call.
El espacio de nombres de trazas en sí no forma parte del conjunto base de métodos JSON-RPC de Ethereum; se originó con el cliente OpenEthereum, cuya documentación del módulo trace sigue siendo la referencia para trace_filter, trace_block y sus campos de salida. Los métodos ordinarios junto a los que se sitúa están definidos por la especificación JSON-RPC de Ethereum. Léelos juntos, porque la especificación explica la superficie estándar mientras que el módulo de trazas documenta la opcional.
- trace_filter — rango de bloques más fromAddress/toAddress opcionales; devuelve trazas coincidentes orientadas al exterior.
- trace_block — todas las trazas de un bloque; útil para indexación orientada a bloques.
- trace_transaction — todas las trazas de un hash de transacción.
- trace_get — una traza por hash de transacción y ruta traceAddress.
Cómo difieren las trazas de los registros y por qué los bloques de BSC son pesados
Un registro es emitido por un contrato y almacenado en el recibo; una traza registra cada llamada interna, transferencia de valor, creación y autodestrucción, cada una con un tipo de llamada y una ruta traceAddress que describe su posición en el árbol de llamadas. Eso significa que un solo bloque de BSC puede producir miles de objetos de traza incluso cuando el bloque contiene solo unos pocos cientos de transacciones, porque cada transacción puede ramificarse en muchas llamadas internas.
La consecuencia práctica es que un rango amplio de trace_filter es materialmente más pesado que el rango equivalente de eth_getLogs. La misma ventana de bloques que devuelve una carga útil de registros manejable puede devolver un orden de magnitud más de objetos de traza, por lo que un rango que tiene éxito para registros puede ser limitado o agotado para trazas. La estrategia de paginación para registros sigue aplicándose, pero con ventanas más pequeñas; la versión específica para registros se cubre en Escaneo de registros de BSC a escala: límites de rango de eth_getLogs.
Debido a que trace_filter solo cubre trazas orientadas al exterior, el detalle de llamadas internas requiere debug_traceTransaction. Si tu pregunta es '¿qué hizo esta dirección a nivel superior?', trace_filter es la herramienta correcta; si es '¿qué pasó dentro de esta transacción?', necesitas la familia debug.
Por qué el soporte de trazas varía según el endpoint y cómo sondearlo
El espacio de nombres de trazas es opcional. Muchos endpoints públicos no lo habilitan, y una llamada a trace_filter en dicho endpoint devuelve el error JSON-RPC de método no encontrado, código -32601, según lo definido por la especificación JSON-RPC 2.0 (jsonrpc.org/specification). Esto no es un fallo transitorio; reintentar no ayudará. Un cliente debe sondear la capacidad antes de confiar en el espacio de nombres.
El sondeo es una llamada barata a trace_block contra un bloque reciente, o un trace_filter con un rango de un bloque. Si la respuesta es un array de resultados, el espacio de nombres está habilitado. Si es un objeto de error con código -32601, el endpoint no expone trazas y debes conmutar por error a otro endpoint. El patrón general para errores de espacio de nombres está documentado en Método RPC no encontrado (-32601) y espacios de nombres de endpoints.
El comportamiento documentado de OpenEthereum define la semántica de los métodos; si un proveedor determinado habilita el espacio de nombres y qué rango limita varía según el proveedor. Trata la capacidad y los límites como específicos del proveedor y mide en lugar de asumir.
async function probeTraceSupport(url) {
const body = {
jsonrpc: '2.0',
id: 1,
method: 'trace_block',
params: ['latest']
};
const res = await fetch(url, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body)
});
const json = await res.json();
if (json.error && json.error.code === -32601) {
return { supported: false, reason: 'trace namespace not enabled' };
}
if (json.error) {
return { supported: false, reason: json.error.message };
}
return { supported: true, sample: json.result.length };
}Una estrategia de paginación para rangos de bloques grandes
El patrón seguro son ventanas de bloques fijas con un cursor persistido. Elige un tamaño de ventana, solicita trace_filter para [cursor, cursor + window - 1], procesa los resultados y luego avanza el cursor a cursor + window. Persiste el cursor después de cada ventana exitosa para que una interrupción se reanude desde el último bloque completado en lugar de desde el principio.
Deduplica en la tupla (blockNumber, transactionHash, traceAddress). Debido a que traceAddress es un array de ruta, dos trazas en la misma transacción pueden compartir un hash de transacción pero diferir en la ruta; la tupla es la identidad estable. Si reintentas una ventana después de un timeout, la deduplicación evita el doble conteo.
Ante un error de rango demasiado grande o timeout, divide la ventana a la mitad y reintenta el mismo cursor. Ante fallos repetidos, retrocede exponencialmente y considera cambiar a un proveedor con un límite mayor. Esto refleja el enfoque de eth_getLogs pero con ventanas más pequeñas para tener en cuenta la mayor carga útil de trazas por bloque. Para la reconciliación con cabeceras de bloque, consulta Reconciliación de indexador EVM bloque por bloque.
- Ventana fija, cursor persistido, avanza solo después de una ventana exitosa.
- Clave de deduplicación: (blockNumber, transactionHash, traceAddress).
- Ante rango demasiado grande: divide la ventana a la mitad, reintenta el mismo cursor.
- Ante fallos repetidos: retroceso exponencial, luego conmutación por error.
Escaneos orientados a direcciones con trace_filter
Cuando la pregunta es '¿qué hizo esta dirección?', trace_filter con fromAddress o toAddress la responde sin descargar cada traza de la cadena. El filtro se aplica en el lado del servidor, por lo que la respuesta contiene solo trazas donde la dirección aparece como remitente o destinatario de una llamada o transferencia orientada al exterior.
Las direcciones son hex sin distinción de mayúsculas y minúsculas. Una cadena con checksum o truncada no coincide silenciosamente con nada, devolviendo un resultado vacío en lugar de un error. Normaliza a minúsculas antes de enviar, y valida la longitud y los caracteres hex en el lado del cliente para que un filtro malformado falle ruidosamente en tu código en lugar de silenciosamente en el endpoint.
Debido a que trace_filter solo cubre trazas externas, una dirección que aparece únicamente como objetivo de llamada interna no aparecerá. Si necesitas apariciones internas, debes recurrir a debug_traceTransaction por transacción, que es mucho más costoso y debe reservarse para búsquedas específicas.
function normalizeAddress(addr) {
if (typeof addr !== 'string') throw new Error('address must be a string');
const hex = addr.toLowerCase();
if (!/^0x[0-9a-f]{40}$/.test(hex)) {
throw new Error('malformed address: ' + addr);
}
return hex;
}
async function scanAddress(url, address, fromBlock, toBlock) {
const body = {
jsonrpc: '2.0',
id: 1,
method: 'trace_filter',
params: [{
fromBlock: '0x' + fromBlock.toString(16),
toBlock: '0x' + toBlock.toString(16),
fromAddress: [normalizeAddress(address)]
}]
};
const res = await fetch(url, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body)
});
const json = await res.json();
if (json.error) throw new Error(json.error.message);
return json.result;
}Un paginador Node.js ejecutable con persistencia de cursor
El paginador a continuación detecta el soporte del espacio de nombres de trazas, pagina ventanas de trace_filter, persiste un cursor en un archivo JSON y se reanuda después de una interrupción. Divide la ventana a la mitad ante un error de rango demasiado grande y retrocede ante fallos repetidos. Ejecútalo contra tu propio endpoint y ajusta WINDOW al valor más grande que acepte tu proveedor.
El archivo de cursor almacena el siguiente bloque a solicitar. Al reiniciar, el paginador lo lee y continúa. La deduplicación se maneja con un Set con clave en la tupla de identidad, por lo que una ventana reintentada no cuenta doble. Esta es una referencia mínima; el código de producción debería agregar registro estructurado y una cola de mensajes muertos para ventanas que nunca tienen éxito.
const fs = require('fs');
const ENDPOINT = process.env.BSC_RPC_URL;
const CURSOR_FILE = './trace-cursor.json';
const START = Number(process.env.START_BLOCK || 0);
const END = Number(process.env.END_BLOCK || 0);
let WINDOW = Number(process.env.WINDOW || 50);
function loadCursor() {
if (fs.existsSync(CURSOR_FILE)) {
return JSON.parse(fs.readFileSync(CURSOR_FILE, 'utf8')).next;
}
return START;
}
function saveCursor(next) {
fs.writeFileSync(CURSOR_FILE, JSON.stringify({ next }));
}
async function rpc(method, params) {
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
});
return res.json();
}
async function traceWindow(from, to) {
return rpc('trace_filter', [{
fromBlock: '0x' + from.toString(16),
toBlock: '0x' + to.toString(16)
}]);
}
async function main() {
const probe = await rpc('trace_block', ['latest']);
if (probe.error && probe.error.code === -32601) {
throw new Error('trace namespace not enabled on this endpoint');
}
const seen = new Set();
let cursor = loadCursor();
while (cursor <= END) {
const to = Math.min(cursor + WINDOW - 1, END);
const json = await traceWindow(cursor, to);
if (json.error) {
if (/range|too large|timeout/i.test(json.error.message)) {
WINDOW = Math.max(1, Math.floor(WINDOW / 2));
console.warn('shrinking window to', WINDOW);
continue;
}
throw new Error(json.error.message);
}
for (const t of json.result) {
const key = t.blockNumber + ':' + t.transactionHash + ':' + JSON.stringify(t.traceAddress);
if (seen.has(key)) continue;
seen.add(key);
// process(t)
}
cursor = to + 1;
saveCursor(cursor);
console.log('processed through block', to, 'traces', json.result.length);
}
}
main().catch((e) => { console.error(e); process.exit(1); });Tabla de resultados para completar con tu propio endpoint
Los límites de los proveedores y la disponibilidad de trazas varían, así que mide contra el endpoint que realmente usas. Ejecuta el sondeo y el paginador anteriores, luego registra los valores a continuación. No asumas que los números de otro proveedor se aplican al tuyo.
Comienza con una ventana pequeña y auméntala hasta que encuentres un error de rango demasiado grande o timeout, luego registra el último valor exitoso. Repite en una altura de bloque ocupada y una tranquila, porque las trazas por bloque varían con la actividad de la red.
- Soporte de trazas presente: sí / no (del sondeo -32601).
- Rango más grande de trace_filter exitoso: ___ bloques.
- Trazas por bloque (altura ocupada): ___.
- Trazas por bloque (altura tranquila): ___.
- Reintentos antes del éxito: ___.
- Tamaño de ventana después del retroceso: ___ bloques.
Solución de problemas de fallos de trace_filter y trace_block
Un error -32601 significa que el espacio de nombres no está habilitado en ese endpoint. No es transitorio; conmuta por error a un endpoint que exponga trazas. Confirma el código de error en lugar del mensaje, ya que los mensajes varían según el proveedor.
Un error de rango demasiado grande o timeout significa que la ventana excede el límite del proveedor o que la respuesta tardó demasiado en construirse. Divide la ventana a la mitad y reintenta el mismo cursor. Si el error persiste con una ventana de un bloque, el bloque en sí puede ser demasiado pesado; considera trace_block para esa altura o un proveedor con un límite mayor.
Un resultado vacío de trace_filter generalmente significa un filtro de dirección malformado. Las direcciones son hex sin distinción de mayúsculas y minúsculas, por lo que una cadena con checksum o truncada no coincide silenciosamente con nada. Normaliza a minúsculas y valida la forma de 40 caracteres hex con prefijo 0x antes de enviar.
Para los últimos bloques, una reorganización puede invalidar trazas que ya procesaste. Rastrea el hash del bloque junto con el número, y ante una discrepancia de hash vuelve a solicitar ese bloque y los posteriores. El patrón de reconciliación se describe en Reconciliación de indexador EVM bloque por bloque.
- -32601: espacio de nombres no habilitado; conmuta por error, no reintentes.
- Rango demasiado grande: divide la ventana a la mitad, reintenta el mismo cursor.
- Resultado vacío: normaliza y valida el filtro de dirección.
- Reorganización: compara hashes de bloque, vuelve a solicitar desde el punto de bifurcación.
Limitaciones y compensaciones del espacio de nombres de trazas
El almacenamiento de trazas es más pesado que el de recibos, por lo que los proveedores pueden podar trazas en nodos de archivo o deshabilitar el espacio de nombres por completo en endpoints públicos. El comportamiento documentado de OpenEthereum define la semántica de los métodos, pero si un proveedor determinado retiene trazas, y por cuánto tiempo, varía según el proveedor.
trace_filter solo cubre trazas orientadas al exterior. El detalle de llamadas internas requiere debug_traceTransaction, que es más costoso y típicamente tiene límites de tasa más agresivos. Si tu pregunta necesita el árbol de llamadas completo, presupuesta para la familia debug en lugar de intentar reconstruirlo a partir de la salida de trace_filter.
Los rangos amplios son el principal riesgo operativo. Incluso en un endpoint que los acepta, una respuesta grande de trace_filter puede tardar en construirse y ser grande de transferir, por lo que ventanas más pequeñas con un cursor persistido son más confiables que una gran solicitud. Para la confiabilidad del endpoint y el comportamiento de timeout, consulta Confiabilidad y timeouts de RPC de BNB Smart Chain.
Próximos pasos para la indexación de trazas en producción
Comienza sondeando el soporte de trazas en los endpoints que pretendes usar, luego completa la tabla de resultados con tus propias mediciones. Elige un tamaño de ventana que tenga éxito de manera confiable en alturas ocupadas, no solo tranquilas, y persiste el cursor para que los reinicios sean baratos.
Si necesitas endpoints BSC gestionados con el espacio de nombres de trazas disponible, revisa Endpoints RPC de BNB Smart Chain (RPC Assistant) y la página de red de BNB Smart Chain. Para planificación de capacidad y costos, consulta Precios de RPC y el servicio API. Más guías de solución de problemas de RPC están recopiladas en el centro de aprendizaje de OnFinality.