Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Solución de problemas de RPC14 min de lectura

Análisis profundo de la paginación de getSignaturesForAddress en Solana

Recorre el historial de firmas de una dirección de Solana sin vacíos, duplicados ni truncamientos silenciosos manejando correctamente los cursores, el commitment y la retención.

TL;DR

getSignaturesForAddress devuelve entradas de estado a nivel de firma ordenadas de más reciente a más antigua, no transacciones completas, por lo que un recorrido completo del historial es de dos fases: enumerar firmas con cursores before/until y luego obtener cada transacción. El tamaño máximo de página por llamada y la ventana de retención son propiedades del proveedor y del clúster, no constantes fijas del protocolo, por lo que un walker correcto debe tratar páginas cortas, páginas vacías y transacciones faltantes como condiciones límite esperadas en lugar de errores. Esta guía explica la mecánica de los cursores, un algoritmo sin vacíos, las interacciones con el commitment, el comportamiento del transporte WebSocket y un ejemplo ejecutable en Node.js que produce evidencia reproducible en tu propio endpoint.

Qué devuelve y qué omite getSignaturesForAddress

El método JSON-RPC de Solana getSignaturesForAddress toma una dirección más los parámetros opcionales before, until, limit, commitment y minContextSlot, y devuelve un arreglo de entradas de estado de firma ordenadas de más reciente a más antigua. Cada entrada incluye signature, slot, err, memo, blockTime y confirmationStatus. Este es el comportamiento documentado en la referencia oficial de métodos RPC de Solana (https://solana.com/docs/rpc/http/getsignaturesforaddress).

De manera crítica, el método omite deliberadamente la transacción en sí. Obtienes una firma y sus metadatos de estado, pero no las instrucciones, los logs ni las claves de cuenta. Por lo tanto, un recorrido completo del historial es de dos fases: primero enumerar firmas, luego obtener cada transacción con getTransaction o un método similar. La segunda fase debe tolerar una firma cuya transacción ya no sea recuperable, porque los nodos pueden podar datos de transacciones antiguas incluso cuando el índice de firmas todavía las lista.

Este diseño de dos fases es importante para los indexadores porque las fases tienen modos de falla diferentes. La enumeración de firmas es barata y está guiada por cursores; la recuperación de transacciones es más pesada, puede agotar el tiempo de espera y puede devolver null para una firma que el nodo ya no sirve. Tratar una transacción null como un error fatal detendrá un recorrido que por lo demás está sano.

  • Devuelve: signature, slot, err, memo, blockTime, confirmationStatus.
  • Omite: el cuerpo de la transacción, los logs y los datos de instrucciones.
  • Implicación: planifica una fase separada de getTransaction con su propia política de reintentos y omisiones.

Cómo funcionan realmente los cursores before y until

Tanto before como until toman una firma, no un slot ni un índice. El patrón práctico es establecer before en la última firma de la página anterior, lo que le indica al nodo que devuelva entradas estrictamente más antiguas que esa firma. Avanzas until solo como una condición de parada acotada, como una firma de checkpoint conocida que no quieres cruzar.

El error de truncamiento silencioso más común es reutilizar un until obsoleto entre páginas. Si estableces until una vez al inicio y nunca lo actualizas, cada página posterior se filtra contra ese mismo límite y el recorrido se detiene antes de tiempo sin error. El enfoque correcto es dejar until sin establecer para un recorrido completo, o avanzarlo deliberadamente solo cuando tengas la intención de detenerte en un punto específico.

Debido a que before es una firma, no un slot, debes registrar la última firma recibida, no el último slot. Los slots no son únicos por dirección; múltiples firmas para la misma dirección pueden compartir un slot. Las firmas son únicas, lo que las convierte en el único cursor seguro.

  • before: devuelve entradas estrictamente más antiguas que esta firma.
  • until: detente cuando se alcance esta firma; úsalo como una parada acotada, no como un filtro fijo.
  • Tipo de cursor: firma, nunca slot.

Tres formas en que falla el bucle de paginación ingenuo

Primero, usar limit como tamaño de página asumiendo que siempre devuelve una página completa. El parámetro limit es un máximo, no una garantía. Una página corta puede significar que la dirección simplemente tiene menos firmas restantes, o puede significar que la ventana solicitada quedó fuera de la retención. Si tu bucle solo se detiene en una página vacía, una página corta seguida de una página vacía puede ocultar un límite de retención.

Segundo, reiniciar el recorrido desde un slot guardado en lugar de una firma guardada. Debido a que los slots no son únicos por dirección, reanudar desde un slot puede omitir o duplicar firmas que comparten ese slot. Siempre persiste la última firma que procesaste correctamente.

Tercero, tratar una página vacía como prueba de que el historial terminó. Una página vacía puede significar solo que la ventana solicitada quedó fuera de la retención del nodo. La interpretación correcta es que una página vacía está agotada solo cuando el recorrido comenzó desde null, es decir, comenzaste en la firma más reciente y retrocediste hasta el verdadero final del historial retenido.

  • No asumas que limit devuelve una página completa.
  • No reanudes desde un slot; reanuda desde una firma.
  • No trates cada página vacía como el final del historial.

Un algoritmo de recorrido sin vacíos

Un walker robusto registra el cursor como la última firma recibida, detecta faltantes en el tamaño de página, verifica el orden monotónico de slots dentro del resultado combinado y marca una página vacía como agotada solo cuando el recorrido comenzó desde null. Esto te da una máquina de estados determinista en lugar de un bucle esperanzado.

Comienza con before sin establecer y until sin establecer. Solicita una página. Si la página está vacía y comenzaste desde null, marca agotado y detente. Si la página está vacía y no comenzaste desde null, marca un límite de retención y detente. Si la página no está vacía, agrega las entradas, establece before en la última firma y continúa.

Después de fusionar páginas, verifica que los slots sean monotónicamente no crecientes a medida que avanzas de más reciente a más antiguo. Una violación indica un error de cursor o una inconsistencia del proveedor y debe detener el recorrido en lugar de corromper silenciosamente tu índice.

  • Estado: cursor (última firma), startedFromNull (booleano), exhausted (booleano).
  • En página corta: registra pageSizeReturned y continúa a menos que esté vacía.
  • En página vacía: agotado solo si startedFromNull, de lo contrario límite de retención.
  • Verificación posterior a la fusión: los slots deben ser monotónicamente no crecientes.

Manejo del límite de retención de transacciones

Los nodos proveedores conservan datos de transacciones recientes pero pueden no servir transacciones muy antiguas. Un recorrido que cruza este límite debe distinguir 'esta firma es más antigua de lo que el nodo puede servir' de 'esta firma no existe'. El índice de firmas y el almacén de transacciones pueden tener ventanas de retención diferentes, por lo que una firma listada no garantiza una transacción recuperable.

El diseño correcto es persistir la última firma archivada correctamente para que cada recorrido futuro se reanude desde un punto conocido como bueno. Cuando getTransaction devuelve null para una firma que ya enumeraste, regístrala como archived-unavailable en lugar de reintentar indefinidamente. Esto mantiene el recorrido en movimiento y preserva un punto de reanudación limpio.

Para el historial a largo plazo, la paginación por sí sola no puede reconstruir datos que el clúster ya no retiene. Necesitas una estrategia de archivo: persistir las transacciones a medida que las recorres y tratar el recorrido de firmas como un mecanismo de descubrimiento en lugar de una garantía de recuperación. Consulta Datos históricos de Solana sobre RPC para obtener contexto sobre la retención.

  • La retención del índice de firmas y la retención de transacciones pueden diferir.
  • Persiste la última firma archivada correctamente como punto de reanudación.
  • Transacción null: márcala como archived-unavailable, no reintentes para siempre.

Walker ejecutable en Node.js con salida reproducible

El siguiente ejemplo recorre una dirección con un tamaño de página explícito contra una URL de endpoint pasada como argumento. Imprime pageIndex, pageSizeReturned, firstSignature, lastSignature, firstSlot, lastSlot y elapsedMs, y verifica que las páginas consecutivas nunca se superpongan y nunca omitan un slot. Ejecútalo contra tu propio endpoint y dirección para generar evidencia reproducible.

Pasa el endpoint RPC como primer argumento y la dirección como segundo. El script usa fetch, que está disponible en Node.js moderno. Ajusta PAGE_SIZE a un valor muy por debajo del máximo de tu proveedor.

Las aserciones son intencionalmente estrictas: lanzarán una excepción si una página se superpone con la página anterior o si los slots no son monotónicos. Ese es el punto. Quieres que el walker falle ruidosamente en lugar de producir silenciosamente un índice corrupto.

// walk.mjs
// Usage: node walk.mjs <RPC_URL> <ADDRESS> [PAGE_SIZE]
const RPC_URL = process.argv[2];
const ADDRESS = process.argv[3];
const PAGE_SIZE = Number(process.argv[4] || 100);

if (!RPC_URL || !ADDRESS) {
  console.error('Usage: node walk.mjs <RPC_URL> <ADDRESS> [PAGE_SIZE]');
  process.exit(1);
}

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: 1, method, params }),
  });
  const json = await res.json();
  if (json.error) throw new Error(JSON.stringify(json.error));
  return json.result;
}

async function walk() {
  let before = null;
  let pageIndex = 0;
  let startedFromNull = true;
  let previousLastSignature = null;
  let previousFirstSlot = null;
  const seen = new Set();

  while (true) {
    const t0 = Date.now();
    const params = [ADDRESS, { limit: PAGE_SIZE }];
    if (before) params[1].before = before;
    const page = await rpc('getSignaturesForAddress', params);
    const elapsedMs = Date.now() - t0;

    const pageSizeReturned = page.length;
    const firstSignature = page[0]?.signature ?? null;
    const lastSignature = page[page.length - 1]?.signature ?? null;
    const firstSlot = page[0]?.slot ?? null;
    const lastSlot = page[page.length - 1]?.slot ?? null;

    console.log(JSON.stringify({
      pageIndex,
      pageSizeReturned,
      firstSignature,
      lastSignature,
      firstSlot,
      lastSlot,
      elapsedMs,
    }));

    if (pageSizeReturned === 0) {
      if (startedFromNull) console.log('exhausted: true');
      else console.log('retention boundary reached');
      break;
    }

    for (const entry of page) {
      if (seen.has(entry.signature)) {
        throw new Error('overlap detected: ' + entry.signature);
      }
      seen.add(entry.signature);
    }

    if (previousFirstSlot !== null && firstSlot > previousFirstSlot) {
      throw new Error('slot ordering violation: ' + firstSlot + ' > ' + previousFirstSlot);
    }

    previousLastSignature = lastSignature;
    previousFirstSlot = firstSlot;
    before = lastSignature;
    startedFromNull = false;
    pageIndex += 1;
  }
}

walk().catch((err) => {
  console.error('walk failed:', err.message);
  process.exit(1);
});

Medición de tu propio endpoint: guía para la tabla de resultados

Debido a que el tamaño máximo de página, la retención y los límites de tasa son propiedades del proveedor y del clúster, debes medirlos contra tu propio endpoint en lugar de asumir valores. Ejecuta el walker anterior con un tamaño de página pequeño y registra la salida. Luego repite con un tamaño de página más grande y compara.

Completa la tabla a continuación con tus propias observaciones. El objetivo es identificar el tamaño de página en el que comienzan a aparecer páginas cortas o tiempos de espera agotados, y el punto en el que una página vacía indica retención en lugar de agotamiento.

No trates ninguna ejecución individual como definitiva. El comportamiento del proveedor puede cambiar sin previo aviso, y el mismo endpoint puede comportarse de manera diferente bajo carga. Repite la medición en diferentes momentos y registra la varianza.

  • Columnas: pageSizeRequested, pageSizeReturned, elapsedMs, shortPage?, emptyPage?, notas.
  • Ejecuta al menos tres tamaños de página: pequeño, mediano, cerca del máximo sospechado.
  • Registra si la página vacía final siguió a una página completa o a una página corta.
| pageSizeRequested | pageSizeReturned | elapsedMs | shortPage? | emptyPage? | notes |
| --- | --- | --- | --- | --- | --- |
| ____ | ____ | ____ | ____ | ____ | ____ |
| ____ | ____ | ____ | ____ | ____ | ____ |
| ____ | ____ | ____ | ____ | ____ | ____ |
| ____ | ____ | ____ | ____ | ____ | ____ |

Niveles de commitment y páginas provisionales

El parámetro commitment interactúa con el recorrido porque los historiales confirmed y finalized divergen cerca de la punta. Un recorrido confirmed puede incluir firmas que luego desaparecen del historial finalized si se resuelve una bifurcación. Un indexador debe recorrer contra el nivel de commitment que realmente necesita y tratar las páginas más recientes como provisionales.

Si necesitas datos finalized, recorre con commitment finalized y acepta que las firmas más recientes pueden no estar disponibles todavía. Si necesitas datos de baja latencia, recorre con confirmed y prepárate para reconciliar la punta más tarde. Mezclar niveles de commitment dentro de un mismo recorrido produce resultados inconsistentes.

Para un tratamiento más profundo de cómo el commitment afecta la confirmación, consulta Niveles de commitment de Solana y confirmación de transacciones.

  • Los historiales confirmed y finalized divergen cerca de la punta.
  • Recorre contra el nivel de commitment que realmente necesitas.
  • Trata las páginas más recientes como provisionales y reconcilia más tarde.

Transporte WebSocket y seguridad del cursor

getSignaturesForAddress es una llamada normal de solicitud/respuesta, no una suscripción. Sobre un transporte WebSocket, se comporta igual que sobre HTTP; la ruta WebSocket se trata de reutilización de conexión en lugar de semántica de push. No hay un flujo de firmas iniciado por el servidor para este método.

Un bucle ingenuo de resuscripción puede reiniciar un recorrido desde el cursor incorrecto. Si tu conexión WebSocket se cae y te reconectas sin preservar la última firma, puedes comenzar de nuevo desde la firma más reciente y duplicar trabajo, o peor, reanudar desde un cursor en memoria obsoleto que ya no coincide con el estado del servidor.

Persiste el cursor fuera del ciclo de vida de la conexión. Trata el WebSocket como un detalle de transporte, no como una fuente de verdad para el estado del recorrido. Para la selección de endpoints y orientación sobre transporte, consulta la Guía de endpoints RPC (RPC Assistant).

  • No es una suscripción: no hay semántica de push para este método.
  • Persiste el cursor fuera del ciclo de vida de la conexión.
  • Al reconectar, reanuda desde la última firma persistida.

Elección de un tamaño de página por debajo del máximo del proveedor

El parámetro limit debe elegirse muy por debajo del máximo del proveedor. Las páginas más pequeñas acotan el tamaño de la respuesta y el radio de impacto de fallas, y los reintentos se vuelven baratos. Un límite de página completa maximiza la probabilidad de un tiempo de espera agotado que cuesta la página entera.

Si una página agota el tiempo de espera, pierdes el trabajo de esa página y debes reintentar desde el mismo cursor. Con una página más pequeña, el reintento es más rápido y el riesgo de tiempos de espera repetidos es menor. Esta es una compensación entre el número de solicitudes y la confiabilidad por solicitud.

Para patrones de tiempo de espera y reintento que complementan esta guía, consulta Tiempos de espera y reintentos en RPC de Solana.

  • Páginas más pequeñas: menor radio de impacto, reintentos más baratos.
  • Límite de página completa: mayor riesgo de tiempo de espera por solicitud.
  • Ajusta el tamaño de página según tus propias mediciones del endpoint.

Limitaciones, compensaciones y solución de problemas

La retención, el tamaño máximo de página y los límites de tasa son propiedades del proveedor y del clúster que varían. Los cambios no documentados en ellos rompen los recorridos largos. Un recorrido de firmas no puede reconstruir el historial que el clúster ya no retiene, por lo que el historial a largo plazo requiere una estrategia de archivo en lugar de una estrategia de paginación.

Los modos de falla comunes incluyen: un recorrido que se detiene antes de tiempo porque until se reutilizó entre páginas; un recorrido que duplica firmas porque se reanudó desde un slot; un recorrido que trata una página vacía como agotamiento cuando en realidad alcanzó la retención; y un recorrido que se estanca porque getTransaction devuelve null para una firma antigua.

Para solucionar problemas, registra pageIndex, pageSizeReturned, firstSignature, lastSignature, firstSlot, lastSlot y elapsedMs para cada página. Compara páginas consecutivas en busca de superposición y monotonicidad de slots. Si un recorrido se detiene inesperadamente, verifica si until estaba establecido y si la última página estaba vacía o era corta.

Para la migración de métodos obsoletos que pueden aparecer en walkers más antiguos, consulta Migración de métodos RPC obsoletos de Solana. Para detalles de endpoints específicos de la red, consulta Redes de Solana.

  • La retención, el tamaño de página y los límites de tasa varían según el proveedor y el clúster.
  • La paginación no puede recuperar datos que el clúster ya no retiene.
  • Registra métricas por página para diagnosticar paradas tempranas y superposiciones.
  • Distingue el límite de retención del agotamiento verdadero.

Próximos pasos para indexadores en producción

Pasa de un recorrido puntual a un indexador duradero persistiendo la última firma archivada correctamente, programando recorridos periódicos y reconciliando la punta provisional contra un nivel de commitment finalized. Trata el recorrido de firmas como un mecanismo de descubrimiento y la obtención de transacciones como el paso de archivo.

Elige un endpoint que coincida con tus requisitos de retención y tasa. Revisa Precios de RPC y el Servicio de API para comprender las restricciones del lado del proveedor, y usa el Centro de aprendizaje de OnFinality para guías adyacentes sobre datos históricos, tiempos de espera y commitment.

Finalmente, valida tu walker contra tu propia dirección y endpoint usando la guía de la tabla de resultados anterior. La evidencia reproducible de tus propias mediciones es la única base confiable para ajustar el tamaño de página y la política de reintentos.

  • Persiste la última firma archivada como punto de reanudación.
  • Programa recorridos periódicos y reconcilia la punta.
  • Valida contra tu propio endpoint antes de producción.

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