Un checkpoint de Sui es un compromiso de solo anexado, con secuencia monotónica, sobre un conjunto de efectos de transacción, identificado por un número de secuencia y un digest, y que lleva su época. La superficie JSON-RPC expone una llamada de resumen por checkpoint (sui_getCheckpoint) que necesita un número de secuencia o digest exactos, y una llamada de paginación (sui_getCheckpoints / suix_getCheckpoints) que toma un cursor y un flag descending y devuelve un nextCursor. El servicio de ledger gRPC expone el mismo log como una suscripción de checkpoints con streaming de servidor que empuja cada checkpoint certificado con su contenido, eliminando la latencia de polling pero acoplando al consumidor a una única conexión. Un indexador de producción normalmente transmite para liveness y reconcilia con una lectura por cursor para corrección, persistiendo el último número de secuencia procesado para que los reinicios nunca omitan ni dupliquen.
A qué compromete realmente un checkpoint de Sui
Un checkpoint de Sui es la unidad de continuidad de datos: compromete un conjunto de efectos de transacción y se identifica por un número de secuencia monotónicamente creciente y un digest. Cada checkpoint también lleva su época, y cada época finaliza con un checkpoint marcado como el final de esa época. Esto convierte la cadena de checkpoints en la columna vertebral de la que cuelgan las épocas, los objetos (id + versión), los efectos de transacción y los eventos. La documentación para desarrolladores de Sui describe los checkpoints, las épocas y la referencia de la API con más detalle.
Los campos de resumen y los campos de contenido más pesados están separados en la forma de la respuesta. El resumen incluye el número de secuencia, el digest, la época, el flag de fin de época, el total de transacciones de la red y la lista de digests de transacciones. El contenido incluye los datos de ejecución y efectos de esas transacciones, por lo que el contenido por checkpoint se recupera por separado del resumen.
Como el número de secuencia es monotónico y el digest es un compromiso, puedes probar la continuidad comprobando que el número de secuencia de cada checkpoint es exactamente uno mayor que el anterior, y que la cadena de digests es consistente. Esta es la base para una indexación sin huecos. Para una visión más amplia de cómo se sirven los datos de Sui, consulta la página de la red Sui y el hub de aprendizaje de OnFinality.
- Número de secuencia: identificador monotónicamente creciente del checkpoint.
- Digest: compromiso criptográfico del contenido del checkpoint.
- Época: la época a la que pertenece este checkpoint.
- Flag de fin de época: marca el checkpoint final de una época.
- Total de transacciones de la red: recuento acumulado en este checkpoint.
- Lista de digests de transacciones: las transacciones incluidas en este checkpoint.
La superficie JSON-RPC: lecturas exactas vs lecturas paginadas
La superficie JSON-RPC tiene una llamada de resumen por checkpoint, sui_getCheckpoint, que toma un número de secuencia o un digest exactos y devuelve los campos de resumen más una sección de contenido separada. Esta es la llamada correcta cuando sabes exactamente qué checkpoint necesitas, por ejemplo al reconciliar un número de secuencia específico o verificar un digest.
También tiene una llamada de rango/paginación, sui_getCheckpoints (o suix_getCheckpoints según la generación del cliente), que toma un cursor y un flag descending y devuelve un nextCursor. Paginar por cursor en orden ascendente es seguro porque los límites de página son límites de secuencia. Recorrer hacia atrás con descending=true es la forma de rellenar desde la punta.
La distinción importa: sui_getCheckpoint necesita un número de secuencia exacto, mientras que sui_getCheckpoints pagina sobre números de secuencia con un cursor. Confundirlos lleva a solicitudes sin paginar de 'dame todo desde el génesis', que son un modo de fallo común. Para patrones de paginación relacionados, consulta Lectura de objetos, campos dinámicos y paginación en Sui y Consulta de eventos de Sui con suix_queryEvents.
- sui_getCheckpoint: número de secuencia o digest exactos, devuelve resumen + contenido.
- sui_getCheckpoints / suix_getCheckpoints: cursor + flag descending, devuelve nextCursor.
- La paginación ascendente por cursor es segura; los límites de página son límites de secuencia.
- descending=true es la ruta de relleno desde la punta.
El servicio de ledger gRPC y la suscripción de checkpoints
La superficie gRPC expone el mismo log como un servicio de ledger con una suscripción de checkpoints con streaming de servidor que empuja cada nuevo checkpoint (con contenido) a medida que se certifica. Esto elimina la latencia de polling porque no tienes que preguntar repetidamente; el servidor empuja cada checkpoint a medida que está disponible.
La contrapartida es que el stream acopla al consumidor a una única conexión. Si la conexión se cae, debes reconectar y reconciliar. Un indexador de producción normalmente transmite para liveness y reconcilia con una lectura por cursor para corrección, usando el digest como clave de idempotencia para que una reconexión no reproduzca duplicados.
Las fuentes primarias autorizadas para esto son la documentación para desarrolladores de Sui, la referencia de la API de Sui y el ledger_service.proto de sui-apis en GitHub, que define la suscripción de checkpoints. Cualquier cosa específica del cliente o del proveedor, como los límites de conexión o la retención, se documenta / varía según el proveedor. Para la selección de endpoints, consulta Proveedores y endpoints RPC de Sui (RPC Assistant).
- La suscripción con streaming de servidor empuja cada checkpoint certificado con contenido.
- Elimina la latencia de polling pero acopla al consumidor a una única conexión.
- Transmite para liveness, reconcilia con una lectura por cursor para corrección.
- Usa el digest como clave de idempotencia al reconectar.
Lector paginador ejecutable con cursor persistido
El siguiente ejemplo de Node.js pagina hacia adelante sobre checkpoints usando un cursor, persiste el último número de secuencia procesado en disco y verifica que el primer número de secuencia de la siguiente página sea igual al último número de secuencia de la página anterior más uno. También comprueba que la unión de todos los números de secuencia vistos no contenga ningún hueco por debajo de la punta.
Ejecútalo contra tu propio endpoint. El código usa una llamada JSON-RPC genérica; reemplaza el nombre del método por sui_getCheckpoints o suix_getCheckpoints según la generación de tu cliente. El archivo de persistencia es un simple archivo JSON para que puedas interrumpir el proceso a mitad de página y reiniciar desde el último número de secuencia persistido sin omitir ni duplicar.
const fs = require('fs');
const fetch = require('node-fetch');
const RPC_URL = process.env.SUI_RPC_URL || 'https://your-endpoint.example';
const STATE_FILE = './checkpoint-cursor.json';
function loadState() {
if (fs.existsSync(STATE_FILE)) {
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
}
return { lastSeq: null, seen: [] };
}
function saveState(state) {
fs.writeFileSync(STATE_FILE, JSON.stringify(state));
}
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 main() {
const state = loadState();
let cursor = state.lastSeq ? String(state.lastSeq) : null;
let descending = false;
while (true) {
const page = await rpc('sui_getCheckpoints', [cursor, descending, 50]);
const data = page.data || [];
if (data.length === 0) break;
for (const cp of data) {
const seq = Number(cp.sequenceNumber);
if (state.lastSeq !== null && seq !== state.lastSeq + 1) {
throw new Error(`Gap detected: expected ${state.lastSeq + 1}, got ${seq}`);
}
state.lastSeq = seq;
state.seen.push(seq);
}
saveState(state);
cursor = page.nextCursor;
if (!cursor) break;
}
const sorted = [...state.seen].sort((a, b) => a - b);
for (let i = 1; i < sorted.length; i++) {
if (sorted[i] !== sorted[i - 1] + 1) {
throw new Error(`Hole in seen sequence numbers: ${sorted[i - 1]} -> ${sorted[i]}`);
}
}
console.log('No holes below tip. Last sequence:', state.lastSeq);
}
main().catch((err) => { console.error(err); process.exit(1); });Prueba de reanudación: interrumpir a mitad de página y reiniciar
Para verificar la continuidad, interrumpe el proceso a mitad de página y reinicia. El lector debe reiniciar desde el último número de secuencia persistido sin omitir ni duplicar. Verifica que el primer número de secuencia de la siguiente página sea igual al último número de secuencia de la página anterior más uno, y que la unión de todos los números de secuencia vistos no contenga ningún hueco por debajo de la punta.
Esta prueba detecta el fallo común de reutilizar el cursor a través de un límite de época, donde un cursor de una época puede no ser válido en la siguiente. También detecta confundir la época de un checkpoint con su número de secuencia, y tratar el checkpoint de fin de época como uno ordinario. El checkpoint de fin de época sigue siendo un checkpoint con un número de secuencia, pero lleva el flag que cierra la época.
Si estás rellenando desde la punta, establece descending=true y recorre hacia atrás, luego invierte el orden antes de persistir. La misma aserción de continuidad se aplica a la inversa: cada número de secuencia anterior debe ser exactamente uno menos que el actual. Para patrones de reintento en torno a timeouts, consulta Timeouts de RPC de Sui y patrones de reintento fiables.
- Persiste lastSeq después de cada página, no después de cada checkpoint, para acotar la reproducción.
- Al reiniciar, reanuda desde lastSeq + 1 y verifica que el primer seq coincida.
- Comprueba la unión de números de secuencia vistos en busca de huecos por debajo de la punta.
- No reutilices un cursor a través de un límite de época.
Tabla de resultados: mide tu propio endpoint
Usa la tabla a continuación para registrar el comportamiento observado de tu propio endpoint. No confíes en cifras publicadas por proveedores; mide contra tu endpoint y completa los resultados. Esto mantiene la comparación honesta y reproducible.
Ejecuta el lector paginador anterior, luego registra el tamaño de página, el comportamiento observado de nextCursor y si el endpoint devuelve el contenido en la llamada de paginación o requiere una llamada separada a sui_getCheckpoint. Anota cualquier diferencia de limitación de tasa o retención, que se documenta / varía según el proveedor.
- Tamaño de página usado: ____
- Primer número de secuencia de la página: ____
- Último número de secuencia de la página: ____
- nextCursor devuelto: ____
- Contenido incluido en la llamada de paginación: sí / no
- Hueco detectado: sí / no
- Duplicados reproducidos al reconectar: sí / no
Fallos comunes y cómo evitarlos
Las solicitudes sin paginar de 'dame todo desde el génesis' son el fallo más común. Sobrecargan el endpoint y a menudo expiran. Pagina siempre con un cursor y un tamaño de página acotado.
Reutilizar el cursor a través de un límite de época es otro fallo. Un cursor está ligado al espacio de secuencia; cuando cambia la época, reancla desde un número de secuencia conocido. Confundir la época de un checkpoint con su número de secuencia lleva a errores de desfase de época en analítica.
Tratar el checkpoint de fin de época como uno ordinario puede romper la lógica de límites de época. Consumir el stream de checkpoints sin una clave de idempotencia (digest) significa que una reconexión reproduce checkpoints, causando duplicados. Consultar un número de secuencia antiguo exacto contra un endpoint con poda falla porque los datos ya no se retienen; usa un endpoint de archivo para lecturas históricas, como se describe en Nodos de archivo de Sui y RPC histórico.
- Evita solicitudes sin paginar de génesis a punta.
- Reancla los cursores en los límites de época.
- No confundas época con número de secuencia.
- Maneja explícitamente los checkpoints de fin de época.
- Usa el digest como clave de idempotencia al reconectar el stream.
- Usa endpoints de archivo para números de secuencia antiguos exactos.
Lista de verificación de solución de problemas
Recorre esta lista cuando tu indexador informe un hueco o un duplicado. Empieza por el archivo de estado persistido y confirma el último número de secuencia. Luego confirma que el primer número de secuencia de la siguiente página sea igual a lastSeq + 1.
Si ves duplicados, comprueba si el stream se reconectó sin una clave de idempotencia. Si ves un hueco, comprueba si se omitió una página debido a un timeout o un error de cursor. Si fallan números de secuencia antiguos exactos, comprueba si el endpoint poda y cambia a un endpoint de archivo.
- Confirma que el lastSeq persistido está presente y es legible.
- Verifica que el primer seq de la siguiente página == lastSeq + 1.
- Comprueba si hay digests duplicados tras la reconexión.
- Comprueba si hay páginas omitidas tras timeouts.
- Verifica la retención del endpoint para números de secuencia antiguos.
- Confirma el manejo de límites de época en tu lógica.
Limitaciones, supuestos y contrapartidas
Esta guía asume que tienes un endpoint JSON-RPC o gRPC de Sui que soporta lecturas de checkpoints y, opcionalmente, la suscripción al servicio de ledger. Los límites específicos del proveedor, las ventanas de retención y la disponibilidad de suscripciones se documentan / varían según el proveedor. OnFinality no afirma aquí cifras específicas de latencia, rendimiento o tasa.
El streaming ofrece menor latencia pero te acopla a una única conexión; el polling es más simple pero añade latencia y carga. El patrón recomendado es transmitir para liveness y reconciliar con una lectura por cursor para corrección. Esto asume que tu consumidor puede persistir el estado de forma duradera y puede tolerar una entrega al menos una vez con deduplicación basada en digest.
La prueba de continuidad se basa en el número de secuencia monotónico y el compromiso del digest. Si tu endpoint devuelve un cursor que omite números de secuencia, trátalo como un comportamiento específico del proveedor y reconcilia con una lectura exacta de sui_getCheckpoint para el rango faltante.
- Límites y retención del proveedor: se documenta / varía según el proveedor.
- Streaming: baja latencia, acoplamiento a una única conexión.
- Polling: simple, mayor latencia y carga.
- Recomendado: transmitir para liveness, reconciliar con lectura por cursor.
- La prueba de continuidad depende de secuencia monotónica y digest.
Próximos pasos: construye un indexador de Sui sin huecos
Empieza seleccionando un endpoint que soporte los métodos de checkpoint que necesitas. La página Proveedores y endpoints RPC de Sui (RPC Assistant) te ayuda a comparar opciones, y la página de la red Sui lista los detalles de la red.
Luego implementa el lector paginador con un cursor persistido, añade la prueba de reanudación y conecta la suscripción al ledger gRPC para liveness. Usa el digest como clave de idempotencia y reconcilia con una lectura por cursor al reconectar. Para el relleno histórico, usa un endpoint de archivo como se describe en Nodos de archivo de Sui y RPC histórico.
Para lectura relacionada, consulta Consulta de eventos de Sui con suix_queryEvents, Lectura de objetos, campos dinámicos y paginación en Sui y Timeouts de RPC de Sui y patrones de reintento fiables. Para opciones de servicio, consulta Precios de RPC y el servicio de API.
- Elige un endpoint con soporte de checkpoints y servicio de ledger.
- Implementa paginación con cursor persistido y la prueba de reanudación.
- Añade streaming gRPC para liveness con deduplicación por digest.
- Reconcilia con lecturas por cursor al reconectar.
- Usa endpoints de archivo para el relleno histórico.