Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Fiabilidad y consistencia14 min de lectura

Monitoreo de transacciones pendientes en Ethereum con eth_newPendingTransactionFilter

Un análisis práctico y profundo de eth_newPendingTransactionFilter: cómo funciona el filtro del pool de pendientes, por qué es frágil y cómo construir un monitor resiliente.

TL;DR

eth_newPendingTransactionFilter crea un filtro del lado del servidor que devuelve los hashes de las transacciones que entran al pool de pendientes de un nodo. A diferencia de eth_newFilter, que devuelve logs, y eth_newBlockFilter, que devuelve hashes de bloques, este filtro devuelve únicamente hashes de transacciones, por lo que los consumidores deben hacer un seguimiento con eth_getTransactionByHash o eth_getTransactionReceipt para conocer algo sobre la transacción. El filtro se consulta con eth_getFilterChanges, que es destructivo y avanza un cursor interno, y los ids de filtro son estado local del nodo que expira tras un breve periodo de inactividad. La advertencia crítica es que el método depende por completo de la política del txpool del nodo: los proveedores que deshabilitan o podan el pool público de pendientes devolverán nada o un subconjunto sesgado, por lo que cualquier monitor debe validarse contra su propio endpoint. Este artículo explica el mecanismo, proporciona ejemplos ejecutables en Node.js y muestra cómo medir el rendimiento y la resolubilidad de hashes pendientes por endpoint.

Qué hace realmente eth_newPendingTransactionFilter

eth_newPendingTransactionFilter crea un filtro del lado del servidor en el nodo que devuelve los hashes de las transacciones que entran al pool de pendientes del nodo. Es uno de los tres métodos de creación de filtros en la especificación Ethereum JSON-RPC, junto con eth_newFilter (que devuelve logs que coinciden con un filtro de tema/dirección) y eth_newBlockFilter (que devuelve hashes de nuevos bloques). El método no toma parámetros y devuelve un id de filtro como cadena hexadecimal.

El valor de retorno es un id de filtro, no un stream. El nodo mantiene una cola interna de hashes de transacciones pendientes y los expone solo cuando el cliente llama a eth_getFilterChanges con ese id. Este es un modelo pull: el cliente controla la cadencia de sondeo y el nodo acumula hashes entre sondeos. La especificación Ethereum JSON-RPC documenta el método y sus métodos de sondeo complementarios en eth_newPendingTransactionFilter.

Debido a que el filtro devuelve solo hashes, es un mecanismo de descubrimiento, no una fuente de datos. Para conocer el remitente, el nonce, el gas price, maxFeePerGas o el destinatario de una transacción pendiente, el consumidor debe emitir un eth_getTransactionByHash de seguimiento para cada hash. Esa segunda llamada es donde realmente reside la mayor parte de la latencia y el costo de un monitor de transacciones pendientes.

  • eth_newPendingTransactionFilter: devuelve hashes de transacciones pendientes (sin parámetros).
  • eth_newFilter: devuelve logs que coinciden con dirección/temas en un rango de bloques.
  • eth_newBlockFilter: devuelve hashes de nuevos bloques.
  • Los tres devuelven un id de filtro que debe sondearse con eth_getFilterChanges.

Semántica del cursor de eth_getFilterChanges y eth_getFilterLogs

eth_getFilterChanges es destructivo. Cada llamada devuelve solo los elementos acumulados desde el sondeo anterior y avanza un cursor interno, por lo que un cliente que sondea dos veces seguidas verá que la segunda llamada devuelve un array vacío si no llegaron nuevos hashes entre medio. Este es el comportamiento documentado en la especificación Ethereum JSON-RPC en eth_getFilterChanges. El cursor es por id de filtro y por nodo; no se comparte entre clientes ni conexiones.

eth_getFilterLogs no es válido para un filtro de transacciones pendientes. Ese método devuelve el conjunto completo de logs que coinciden con un filtro de logs y está pensado para eth_newFilter. Llamarlo contra un id de filtro de transacciones pendientes es indefinido o devuelve un error según el cliente. Para transacciones pendientes, eth_getFilterChanges es el único método de sondeo que aplica.

El cursor destructivo tiene una consecuencia práctica: si tu poller se cae o se reinicia, los hashes acumulados desde el último sondeo exitoso se pierden a menos que el nodo aún los conserve en la cola del filtro. Un monitor robusto debe tratar cada sondeo como un checkpoint y persistir o reenviar los hashes que recibe antes de hacer cualquier otra cosa.

  • eth_getFilterChanges devuelve solo elementos nuevos desde el último sondeo y avanza el cursor.
  • eth_getFilterLogs es para filtros de logs, no para filtros de transacciones pendientes.
  • Un poller que se cae pierde los hashes que llegaron entre el último sondeo y la caída.

Ciclo de vida del filtro, expiración y recreación

Los ids de filtro son estado local del nodo. No son portables entre nodos, entre reinicios ni entre conexiones balanceadas que caen en backends diferentes. Un filtro creado en un nodo no puede sondearse en otro, y un filtro creado antes de un reinicio del nodo no sobrevivirá a este. Esto es consistente con el ciclo de vida general de filtros descrito en el artículo Ciclo de vida de eth_newFilter y getFilterChanges en Ethereum.

Los filtros inactivos se podan tras un breve periodo. El timeout exacto depende del cliente y no está fijado por la especificación; los clientes comunes podan filtros que no se han sondeado durante unos minutos. Por lo tanto, un monitor de larga duración debe sondear con la frecuencia suficiente para mantener vivo el filtro, y debe estar preparado para recrear el filtro cuando un sondeo devuelva un error de id de filtro desconocido.

La ruta de recreación es sencilla: capturar el error, llamar de nuevo a eth_newPendingTransactionFilter y reanudar el sondeo con el nuevo id. El hueco entre el sondeo fallido y la creación del nuevo filtro es una ventana en la que se pueden perder hashes pendientes. No hay forma de recuperar esos hashes del nodo a posteriori; la única mitigación es mantener el intervalo de sondeo corto en relación con el timeout de expiración.

  • Los ids de filtro son locales del nodo y no sobreviven a reinicios ni cambios de nodo.
  • Los filtros inactivos se podan tras un timeout dependiente del cliente.
  • Ante un error de id desconocido, recrea el filtro y acepta un pequeño hueco.

La dependencia del txpool: por qué los resultados varían según el proveedor

El filtro de transacciones pendientes lee del pool de transacciones del nodo (txpool). Si el nodo no mantiene un pool público de pendientes, o si poda o bloquea transacciones por comisión o por tipo, el filtro devolverá nada o un subconjunto sesgado. Esta es la advertencia más importante para cualquiera que construya un monitor de transacciones pendientes. El comportamiento está documentado por cliente, pero en la práctica varía según el proveedor y debe tratarse como 'documentado / varía según el proveedor'.

Algunos proveedores deshabilitan por completo el pool público de pendientes para reducir la presión de memoria y evitar exponer datos de transacciones no confirmadas. Otros mantienen un pool acotado y expulsan primero las transacciones de baja comisión. Un monitor que funciona contra un endpoint puede devolver cero hashes contra otro. El artículo El pool de transacciones de Ethereum y el namespace txpool cubre el namespace txpool y sus métodos de inspección, que son útiles para validar qué contiene realmente un nodo dado.

La implicación práctica es que un monitor de transacciones pendientes debe validarse contra su propio endpoint antes de confiar en él. No asumas que un filtro que devuelve hashes en un nodo de desarrollo local se comportará igual en un endpoint RPC alojado. Mide primero y luego decide si el endpoint es adecuado para el caso de uso.

  • El filtro lee del txpool del nodo; sin pool no hay hashes.
  • La poda por comisión o por tipo produce un subconjunto sesgado.
  • Valida el endpoint antes de confiar en el monitor en producción.

Sondeo con eth_newPendingTransactionFilter: un ejemplo ejecutable en Node.js

El ejemplo siguiente usa JSON-RPC en bruto sobre fetch. Crea un filtro de transacciones pendientes, sondea eth_getFilterChanges en un intervalo, resuelve cada hash con eth_getTransactionByHash e imprime el remitente, el nonce, gasPrice y maxFeePerGas. También recrea el filtro cuando el nodo devuelve un error de id de filtro desconocido.

Reemplaza RPC_URL con tu endpoint. El intervalo de sondeo está fijado en 2 segundos, lo suficientemente corto para mantener vivos la mayoría de los filtros, pero aún deja una ventana en la que se pueden perder ráfagas. Ajústalo según el comportamiento de expiración que observes en tu endpoint.

const RPC_URL = process.env.RPC_URL || 'https://your-endpoint.example';
let filterId = null;

async function rpc(method, params = []) {
  const res = await fetch(RPC_URL, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method, params })
  });
  const json = await res.json();
  if (json.error) throw new Error(json.error.message);
  return json.result;
}

async function createFilter() {
  filterId = await rpc('eth_newPendingTransactionFilter');
  console.log('created filter', filterId);
}

async function poll() {
  if (!filterId) await createFilter();
  let hashes;
  try {
    hashes = await rpc('eth_getFilterChanges', [filterId]);
  } catch (err) {
    console.warn('filter lost, recreating:', err.message);
    filterId = null;
    return;
  }
  for (const hash of hashes) {
    try {
      const tx = await rpc('eth_getTransactionByHash', [hash]);
      if (!tx) continue;
      console.log({
        hash,
        from: tx.from,
        nonce: tx.nonce,
        gasPrice: tx.gasPrice,
        maxFeePerGas: tx.maxFeePerGas
      });
    } catch (err) {
      console.warn('resolve failed', hash, err.message);
    }
  }
}

(async () => {
  await createFilter();
  setInterval(poll, 2000);
})();

Resolver hashes pendientes y detectar transacciones atascadas

Una vez que tienes un hash pendiente, eth_getTransactionByHash devuelve el objeto de transacción si el nodo aún lo tiene. Si la transacción ha sido minada, la misma llamada puede devolver la transacción minada o null según la política de retención del nodo. Para confirmación, usa eth_getTransactionReceipt, que devuelve null mientras la transacción está pendiente. El artículo eth_getTransactionReceipt null y sondeo de recibos pendientes cubre en detalle el caso de recibo nulo.

Una transacción atascada se puede detectar correlacionando un hash pendiente con un hueco de nonce posterior en el txpool. Si rastreas el nonce más alto visto para un remitente y luego observas que un nonce inferior nunca aparece en sondeos posteriores, la transacción en ese nonce probablemente está atascada. El namespace txpool (txpool_content, txpool_inspect) puede confirmarlo en nodos que lo exponen, pero muchos endpoints alojados no lo hacen.

Un monitor práctico mantiene un mapa por remitente de nonces vistos y marca un hueco cuando se observa un nonce superior sin el inferior. Esto es heurístico, no autoritativo: la transacción de nonce inferior puede simplemente haber sido expulsada del pool, o el nodo puede no haberla visto en absoluto. Trata la señal como un aviso para investigar, no como una prueba.

  • eth_getTransactionByHash resuelve un hash pendiente a un objeto de transacción.
  • eth_getTransactionReceipt devuelve null mientras la transacción está pendiente.
  • Un hueco de nonce en el txpool es una señal heurística de transacción atascada.

Sondeo frente a eth_subscribe('newPendingTransactions')

La suscripción WebSocket eth_subscribe('newPendingTransactions') envía los hashes pendientes al cliente a medida que llegan, en lugar de requerir que el cliente sondee. Esto elimina el intervalo de sondeo como fuente de hashes perdidos y elimina el problema de expiración del filtro, porque la suscripción está ligada a la conexión en lugar de a un id de filtro podado. La contrapartida es que el cliente debe mantener una conexión WebSocket persistente y manejar la lógica de reconexión.

El enfoque de sondeo es más sencillo de operar sobre HTTP, funciona a través de proxies y balanceadores de carga que no admiten conexiones de larga duración, y es más fácil de razonar para procesamiento por lotes. El enfoque de suscripción es mejor para monitoreo de baja latencia y para cargas de trabajo que pueden tolerar una conexión persistente. El artículo Logs de eth_subscribe en Ethereum frente a sondeo WebSocket compara ambos modelos para logs; las mismas contrapartidas aplican a transacciones pendientes.

Ninguno de los dos enfoques cambia la dependencia subyacente del txpool. Una suscripción a newPendingTransactions en un nodo sin pool público de pendientes también devolverá nada. La elección entre sondear y suscribirse es sobre transporte y latencia, no sobre la completitud del pool.

  • eth_subscribe envía hashes; eth_newPendingTransactionFilter requiere sondeo.
  • Las suscripciones evitan la expiración de filtros pero requieren una conexión persistente.
  • Ambos dependen de la política del txpool del nodo para lo que realmente es visible.

Una tabla de resultados reproducible para tu endpoint

Debido a que el comportamiento del pool de pendientes varía según el proveedor, la única forma confiable de saber qué hará un endpoint es medirlo. La tabla siguiente es una plantilla: complétala contra tu propio endpoint, usando el mismo script y la misma ventana de observación. No compares números entre endpoints a menos que la ventana y las condiciones de red sean comparables.

Ejecuta el monitor durante al menos diez minutos durante un periodo de actividad de red normal. Registra el número de hashes pendientes devueltos por minuto, la proporción de esos hashes que se resuelven a un objeto de transacción mediante eth_getTransactionByHash, si el primer sondeo tras la creación devuelve algo, y cuánto sobrevive el filtro sin sondeo. La última fila se mide creando un filtro y deliberadamente no sondeándolo hasta que ocurra un error.

  • Hashes pendientes por minuto: cuenta los resultados de eth_getFilterChanges en una ventana fija.
  • Proporción resoluble: fracción de hashes que devuelven un eth_getTransactionByHash no nulo.
  • Comportamiento del primer sondeo tras la creación: ¿el primer sondeo devuelve hashes o un array vacío?
  • Intervalo de expiración del filtro: tiempo desde la creación hasta el error de id de filtro desconocido sin sondeo.
  • Completitud del pool: compara contra un segundo endpoint o un nodo local si está disponible.

Solución de problemas de modos de fallo comunes

El fallo más común es un conjunto de resultados vacío. Si eth_getFilterChanges devuelve consistentemente un array vacío, lo primero que hay que comprobar es si el endpoint mantiene un pool público de pendientes. Prueba eth_newPendingTransactionFilter en un segundo endpoint y compara. Si ambos devuelven nada, la red puede simplemente estar tranquila, o ambos endpoints pueden deshabilitar el pool.

El segundo fallo común es un error de id de filtro desconocido. Esto significa que el filtro fue podado o que el nodo se reinició. La solución es recrear el filtro y reanudar el sondeo. Si esto ocurre con frecuencia, acorta el intervalo de sondeo o cambia a una suscripción WebSocket. Si ocurre inmediatamente después de la creación, es posible que el endpoint no soporte el método en absoluto.

El tercer fallo es una alta tasa de hashes no resolubles. Esto ocurre cuando el nodo expulsa transacciones del pool más rápido de lo que tu poller puede resolverlas, o cuando el pool está acotado y se descartan transacciones de baja comisión. Reducir el intervalo de sondeo y resolver hashes en paralelo puede ayudar, pero la causa subyacente es la política del pool. La Guía de nodos RPC de Ethereum (RPC Assistant) cubre la selección de endpoints y las comprobaciones de capacidades que pueden ayudarte a elegir un proveedor adecuado.

  • Resultados vacíos: comprueba si el endpoint tiene un pool público de pendientes.
  • Id de filtro desconocido: recrea el filtro y acorta el intervalo de sondeo.
  • Hashes no resolubles: expulsión del pool o pool acotado; reduce el intervalo de sondeo.

Limitaciones y contrapartidas del monitoreo de transacciones pendientes

El método no hace streaming. Los hashes se acumulan entre sondeos, y una ráfaga de transacciones que llega y se mina dentro de un solo intervalo de sondeo puede no observarse nunca. Esto es inherente al modelo pull y no puede eliminarse por completo acortando el intervalo, porque la cola de filtros del nodo está acotada y el cursor es destructivo.

Los ids de filtro no son portables. No pueden compartirse entre nodos, entre reinicios ni entre conexiones que pueden caer en backends diferentes. Un monitor que asume un id de filtro estable a través de un pool balanceado fallará de forma intermitente. El patrón seguro es tratar el filtro como efímero y recrearlo ante cualquier error.

El pool público de pendientes no es una representación confiable del estado pendiente global. Refleja lo que un solo nodo ha visto y ha elegido conservar. Diferentes nodos ven subconjuntos diferentes, y los proveedores aplican políticas distintas. Para cualquier aplicación que necesite una visión completa o autoritativa de las transacciones pendientes, el pool de pendientes es la fuente equivocada. Úsalo para descubrimiento y heurísticas, no para contabilidad.

  • Sin streaming: las ráfagas pueden perderse entre sondeos.
  • Los ids de filtro son locales del nodo y no portables.
  • El pool público de pendientes es una visión parcial y dependiente de la política.

Próximos pasos: elegir un endpoint y construir un monitor resiliente

Antes de construir un monitor de producción, valida el endpoint. Ejecuta la tabla de resultados anterior, confirma que el endpoint devuelve hashes pendientes a una tasa útil y confirma que el filtro sobrevive a tu intervalo de sondeo previsto. Si el endpoint no mantiene un pool público de pendientes, considera una suscripción WebSocket o un proveedor diferente. La Guía de nodos RPC de Ethereum (RPC Assistant) y el centro de aprendizaje de OnFinality son buenos puntos de partida para comparar capacidades de endpoints.

Para producción, envuelve el monitor en un supervisor que recree el filtro ante errores, persista los hashes antes de resolverlos y emita métricas de tasa de hashes pendientes, resolubilidad y recreaciones de filtro. Trata el monitor como una capa de descubrimiento de mejor esfuerzo, no como una fuente de verdad. Combínalo con sondeo de recibos para las transacciones que realmente te importan.

Si estás evaluando proveedores, las páginas de precios de RPC y servicio de API describen los planes y endpoints disponibles. La página de la red Ethereum lista las redes Ethereum soportadas. Elige un endpoint que documente su comportamiento de pool de pendientes y mide tú mismo antes de comprometerte con un diseño que dependa de él.

  • Valida el endpoint con la tabla de resultados antes de construir.
  • Supervisa el monitor: recrea filtros, persiste hashes, emite métricas.
  • Trata el monitoreo de pendientes como descubrimiento de mejor esfuerzo, no como fuente de verdad.

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar