state_getKeysPaged es una primitiva de paginación que itera hacia adelante, con alcance de prefijo y sin cursor para el almacenamiento de Substrate. Devuelve hasta count claves de almacenamiento codificadas en hexadecimal que comienzan con un prefijo dado, comenzando después de un startKey exclusivo, evaluado en un bloque específico. Para iterar un mapa de almacenamiento completo, se pasa el hash twox128 de los nombres del pallet y del elemento de almacenamiento como prefijo, se reingresa la última clave devuelta como startKey y se detiene cuando la página es más corta que count. Debido a que no se garantiza que el orden sea lexicográfico, no se debe asumir un cursor monotónico del lado del cliente; en su lugar, se debe confiar en el orden de iteración propio del nodo y fijar el bloque para evitar mezclar estados. Esta guía cubre la firma del método, la construcción de claves, ejemplos ejecutables en Node.js, la agrupación de valores y métodos de verificación reproducibles.
Firma del método y semántica de paginación
El método state_getKeysPaged acepta cuatro parámetros: prefix (cadena hexadecimal), count (entero), startKey (cadena hexadecimal, opcional) y at (hash de bloque, opcional). Devuelve un arreglo de hasta count claves de almacenamiento codificadas en hexadecimal que comienzan con prefix, iniciando la página en startKey (exclusivo) y evaluadas en el bloque at. Si se omite startKey, la iteración comienza en la primera clave que coincide con el prefijo. El método está documentado en la referencia JSON-RPC de Substrate de polkadot.js y forma parte de la API JSON-RPC de Substrate.
La paginación se controla reingresando la última clave devuelta como startKey en la siguiente llamada. Se continúa hasta que el resultado sea más corto que count o esté vacío. Este es un patrón sin cursor: el nodo no mantiene estado del lado del servidor entre llamadas, por lo que se debe gestionar el bucle de iteración uno mismo. Debido a que el método tiene alcance de prefijo, solo devuelve claves que comparten el prefijo de bytes exacto que se proporciona. Esto lo hace ideal para iterar un único mapa de almacenamiento, pero también significa que un prefijo incorrecto devolverá nada o se extenderá a elementos de almacenamiento no relacionados.
El parámetro at fija la consulta a un bloque específico. Si se omite, el nodo evalúa en el último bloque, que puede cambiar entre llamadas. Para una iteración consistente a través de múltiples páginas, siempre se debe pasar el mismo hash de bloque. Este es un comportamiento documentado: la lectura de estado solo es consistente en el bloque que se pasa. Para obtener más información sobre lecturas específicas de bloques, consulta Extrínsecos y eventos de Polkadot RPC en un bloque.
prefix: prefijo de clave de almacenamiento codificado en hexadecimal (por ejemplo, hash twox128 del pallet y del elemento).count: número máximo de claves a devolver por página.startKey: clave codificada en hexadecimal después de la cual comenzar (exclusiva); omitir para la primera página.at: hash de bloque en el que evaluar; omitir para el último (no recomendado para iteración).
Construcción del prefijo de clave de almacenamiento correcto
Una clave de almacenamiento de Substrate se construye a partir de los hashes twox128 del nombre del pallet y del nombre del elemento de almacenamiento, concatenados. Para los mapas de almacenamiento, la clave del mapa se codifica en SCALE y se agrega a continuación. El prefijo que se pasa a state_getKeysPaged debe ser los bytes exactos que preceden a la clave del mapa. Para un mapa como System.Account, el prefijo es twox128('System') ++ twox128('Account'). Esto son 32 bytes (16 + 16). Pasar un prefijo más corto (por ejemplo, solo el hash del pallet) iterará todos los elementos de almacenamiento de ese pallet, lo cual puede no ser intencional.
La documentación de almacenamiento de Substrate explica que twox128 es un hash no criptográfico que se utiliza por su velocidad y baja probabilidad de colisión. Los nombres del pallet y del elemento son cadenas ASCII. Se pueden calcular estos hashes usando @polkadot/util-crypto o cualquier implementación de twox128. El prefijo resultante es una cadena hexadecimal sin el prefijo 0x cuando se pasa al método RPC? En realidad, el RPC espera una cadena hexadecimal con prefijo 0x. Siempre se debe verificar la longitud del prefijo: para un mapa, debe ser de 32 bytes (64 caracteres hexadecimales más 0x).
Si se usa accidentalmente un prefijo más amplio, se pueden recuperar claves de otros elementos de almacenamiento del mismo pallet. Por ejemplo, usar solo twox128('System') devolvería claves para Account, Events, BlockHash y otros. Esto rara vez es lo deseado. Para limitar a un solo mapa, siempre se deben concatenar ambos hashes. Se puede decodificar la metadata del runtime para confirmar los nombres exactos de los elementos de almacenamiento y sus prefijos; consulta Substrate state_getMetadata y versiones del runtime.
const { xxhashAsHex } = require('@polkadot/util-crypto');
function storageMapPrefix(pallet, item) {
const palletHash = xxhashAsHex(pallet, 128);
const itemHash = xxhashAsHex(item, 128);
return palletHash + itemHash.slice(2); // remove 0x from second hash
}
const prefix = storageMapPrefix('System', 'Account');
console.log('Prefix:', prefix); // 0x... (32 bytes)Control de la paginación sin huecos ni bucles
Para iterar un mapa completo, se comienza con startKey omitido (o null). Se llama a state_getKeysPaged(prefix, count, startKey, at). Si la longitud del arreglo devuelto es igual a count, se establece startKey en la última clave del arreglo y se repite. Si la longitud es menor que count, se ha llegado al final. Si el arreglo está vacío, el mapa está vacío o el prefijo es incorrecto. Este bucle es seguro porque startKey es exclusivo: el nodo devuelve claves estrictamente posteriores a la clave dada en su orden de iteración interno.
Fundamentalmente, no se debe asumir que las claves se devuelven en orden lexicográfico. El trie de almacenamiento de Substrate es un Merkle Patricia Trie, y el orden de iteración está determinado por la estructura del trie, que no está garantizado que esté ordenada por los bytes de la clave sin procesar. Un cursor del lado del cliente que asuma un orden monotónico (por ejemplo, comparar claves con > o <) tendrá errores. En su lugar, siempre se debe usar la última clave devuelta como el siguiente startKey, independientemente de su valor. Este es el patrón documentado en la referencia de polkadot.js.
Si se necesita reanudar la iteración más tarde, se puede almacenar la última clave y el hash de bloque. Sin embargo, si la cadena ha avanzado, el estado en ese bloque puede ya no estar disponible en nodos podados. Para iteraciones largas, considera fijar a un bloque finalizado reciente y completar la iteración dentro de la ventana de poda del nodo. Para obtener más información sobre consultas de estado, consulta Cambios de almacenamiento de Polkadot state_queryStorageAt.
- Comenzar con
startKeyomitido onull. - Repetir mientras la longitud devuelta === count.
- Establecer
startKeyen la última clave de la página anterior. - Detenerse cuando la longitud < count o esté vacío.
- Nunca ordenar ni comparar claves del lado del cliente; confiar en el orden del nodo.
Decodificación de las claves devueltas y lectura de valores
Cada clave devuelta es una cadena hexadecimal. Para decodificar la clave del mapa, se debe eliminar el prefijo conocido (el hash de 32 bytes del pallet+elemento) y luego decodificar en SCALE los bytes restantes según el tipo de clave del mapa. El tipo de clave se define en la metadata del runtime. Por ejemplo, System.Account usa AccountId32 como clave, que son 32 bytes. Después de eliminar el prefijo, se tiene la clave codificada en SCALE. Se puede usar @polkadot/types para crear un tipo a partir de la metadata y decodificarlo.
Una vez que se tienen las claves decodificadas, se pueden leer los valores correspondientes. Se puede llamar a state_getStorage para cada clave individualmente, pero eso es ineficiente para mapas grandes. En su lugar, usa state_queryStorageAt con un arreglo de claves y un hash de bloque. Este método devuelve los valores de todas las claves en un solo viaje de ida y vuelta, lo que es mucho más rápido y reduce el número de llamadas RPC. El método está documentado en la referencia de polkadot.js.
Al agrupar, se debe tener en cuenta el tamaño máximo de la solicitud. Algunos proveedores limitan el número de claves por llamada a state_queryStorageAt. Una práctica común es agrupar en fragmentos de 100 a 500 claves. También se puede usar state_getStorage para lecturas individuales si solo se necesitan unos pocos valores. Para profundizar en las lecturas por lotes, consulta Cambios de almacenamiento de Polkadot state_queryStorageAt.
const { ApiPromise, WsProvider } = require('@polkadot/api');
const { xxhashAsHex } = require('@polkadot/util-crypto');
async function iterateMap(pallet, item, pageSize = 100) {
const api = await ApiPromise.create({ provider: new WsProvider('wss://rpc.polkadot.io') });
const prefix = xxhashAsHex(pallet, 128) + xxhashAsHex(item, 128).slice(2);
const at = (await api.rpc.chain.getHeader()).hash;
let startKey = null;
let allKeys = [];
let page;
do {
page = await api.rpc.state.getKeysPaged(prefix, pageSize, startKey, at);
allKeys.push(...page);
if (page.length > 0) startKey = page[page.length - 1];
} while (page.length === pageSize);
console.log(`Total keys: ${allKeys.length}`);
// Batch read values
const values = await api.rpc.state.queryStorageAt(allKeys, at);
console.log(`Values fetched: ${values.length}`);
await api.disconnect();
}
iterateMap('System', 'Account').catch(console.error);Diferencias de tipo de almacenamiento y versión del nodo
El método state_getKeysPaged históricamente aceptaba un parámetro storageKind (por ejemplo, 'value' para el trie principal, 'child' para tries hijos). En los nodos modernos de Substrate, este parámetro a menudo está obsoleto o se reemplaza por métodos separados para almacenamiento hijo. El comportamiento exacto está documentado / varía según el proveedor y la versión del nodo. Se debe consultar la documentación RPC del nodo o la referencia de polkadot.js para la versión que se está utilizando.
Si se trabaja con tries hijos (por ejemplo, almacenamiento de contratos), es posible que se necesite usar state_getChildKeysPaged o similar. La semántica del prefijo sigue siendo la misma, pero el trie es diferente. Siempre se debe verificar la firma del método con la metadata del nodo. Para la decodificación de metadata del runtime, consulta Substrate state_getMetadata y versiones del runtime.
En caso de duda, prueba con un tamaño de página pequeño y un mapa conocido. Si el método devuelve un error sobre un parámetro desconocido, es posible que el nodo no admita storageKind. En ese caso, omítelo y usa el valor predeterminado (trie de valores).
Alcance de la iteración a un solo mapa vs. prefijos más amplios
El prefijo que se pasa determina el alcance. Un prefijo completo twox128 de pallet+elemento limita el alcance exactamente a un mapa de almacenamiento. Un prefijo solo de pallet (twox128 del nombre del pallet) limita el alcance a todos los elementos de almacenamiento de ese pallet. Un prefijo más corto (por ejemplo, los primeros 16 bytes) puede extenderse a otros pallets si el hash colisiona, aunque las colisiones de twox128 son extremadamente improbables. El enfoque más seguro es usar siempre el prefijo completo de 32 bytes para un mapa específico.
Si intencionalmente se desea iterar todo el almacenamiento de un pallet, se puede usar el hash del pallet como prefijo. Sin embargo, hay que tener en cuenta que las claves devueltas incluirán diferentes elementos de almacenamiento, y se necesitará decodificar el nombre del elemento de cada clave desde el prefijo para saber a qué mapa pertenece. Esto rara vez es necesario y puede ser propenso a errores.
Para verificar el prefijo, se puede llamar a state_getKeysPaged con un count pequeño e inspeccionar las claves devueltas. Todas deben comenzar con el prefijo. Si no lo hacen, el prefijo es incorrecto. También se puede usar state_getMetadata para listar todos los elementos de almacenamiento y sus prefijos.
- Prefijo de mapa completo: twox128(pallet) ++ twox128(item) — 32 bytes.
- Prefijo de todo el pallet: twox128(pallet) — 16 bytes.
- Verificar siempre que las claves devueltas comiencen con el prefijo.
- Usar la metadata para confirmar los nombres exactos de los elementos.
Método reproducible para detectar una iteración incompleta
Para asegurar que la iteración esté completa, se necesita una forma independiente de verificar el número total de claves. Un método es comparar el recuento de claves paginadas con un total confiable de otra fuente, como un explorador de bloques o un recuento de descendientes si el nodo lo expone. Por ejemplo, System.Account tiene un número conocido de cuentas que se puede contrastar con un explorador de bloques. Si el recuento es significativamente menor, la iteración puede haberse detenido antes debido a un error o un límite de velocidad.
Otro método es ejecutar la iteración dos veces con diferentes tamaños de página (por ejemplo, 100 y 500) y comparar los conjuntos de claves resultantes. Si difieren, la lógica de iteración es defectuosa. También se puede calcular una suma de verificación de todas las claves (por ejemplo, SHA-256 de las claves ordenadas concatenadas) y compararla entre ejecuciones. Esta es una forma reproducible de detectar huecos o duplicados.
Para una verificación más rigurosa, se puede usar el método state_getKeysPaged con un count muy grande (por ejemplo, 10000) en un mapa pequeño y comparar el resultado con el resultado paginado. Si coinciden, la paginación es correcta. Ten en cuenta que los valores grandes de count pueden ser rechazados por algunos proveedores, así que úsalos con precaución.
- Comparar el recuento paginado con un total confiable (por ejemplo, un explorador de bloques).
- Ejecutar con diferentes tamaños de página y comparar los conjuntos de claves.
- Calcular una suma de verificación de todas las claves y compararla entre ejecuciones.
- Usar una sola página grande como referencia para mapas pequeños.
Tabla de resultados: medir contra tu propio endpoint
Debido a que la latencia, el rendimiento y los límites de velocidad varían según el proveedor, se debe medir el rendimiento de state_getKeysPaged contra tu propio endpoint. La siguiente tabla proporciona una plantilla para registrar tus mediciones. Complétala con tus propios resultados. No confíes en benchmarks genéricos; tus condiciones de red y los límites de tu proveedor importan.
Para recopilar datos, ejecuta el ejemplo de Node.js anterior con diferentes tamaños de página y registra el tiempo que tarda cada iteración completa. Usa un cronómetro o console.time. También registra el número de llamadas RPC realizadas (que es igual al número de páginas). Si encuentras errores HTTP 429, anota el tamaño de página y el momento en que ocurrió el error. Esto te ayudará a ajustar el tamaño de página y la estrategia de retroceso.
Para un sistema en producción, considera usar un servicio de API dedicado o un plan de precios de RPC que coincida con tu volumen de solicitudes esperado. Los endpoints de red Polkadot de OnFinality admiten state_getKeysPaged y se pueden usar para pruebas. Respeta siempre los límites de velocidad e implementa retroceso exponencial.
- Tamaño de página: número de claves por solicitud.
- Claves totales: número total de claves en el mapa.
- Número de páginas: total de llamadas RPC.
- Tiempo total: tiempo de reloj de pared para la iteración completa.
- Errores: cualquier error 429 o de tiempo de espera encontrado.
Limitaciones y compensaciones de state_getKeysPaged
La exclusividad de startKey y la semántica de orden están documentadas / varían según el cliente. Aunque el patrón general es consistente, algunas implementaciones de nodos pueden tener diferencias sutiles en cómo manejan el inicio exclusivo o el orden de iteración. Siempre prueba contra tu nodo objetivo. La referencia de polkadot.js es la fuente autorizada para la API de JavaScript, pero el comportamiento subyacente del RPC está definido por el nodo de Substrate.
Iterar un mapa grande con muchas páginas pequeñas es lento. Cada página es un viaje de ida y vuelta separado, por lo que un endpoint con límite de velocidad devolverá HTTP 429 si se exceden sus límites. Esta es una compensación fundamental: los tamaños de página más grandes reducen el número de viajes de ida y vuelta pero aumentan el tamaño de la respuesta y el uso de memoria. Se debe encontrar un equilibrio que funcione para tu proveedor y red.
La lectura de estado solo es consistente en el bloque que se pasa. Si se omite at o se usan bloques diferentes entre páginas, se pueden mezclar estados de diferentes bloques, lo que lleva a una vista inconsistente. Siempre fija at a un único hash de bloque para toda la iteración. Además, un prefijo inestable o una actualización del runtime que cambie el nombre de un pallet o elemento de almacenamiento invalidará por completo el prefijo. Se debe volver a derivar el prefijo a partir de la nueva metadata después de una actualización. Para obtener más información sobre versiones del runtime, consulta Substrate state_getMetadata y versiones del runtime.
- Orden no garantizado; no asumir orden lexicográfico.
- Muchas páginas pequeñas = muchos viajes de ida y vuelta = riesgo de límite de velocidad.
- La consistencia del estado requiere fijar
ata un solo bloque. - Las actualizaciones del runtime pueden cambiar los prefijos; volver a derivarlos de la metadata.
- Los tamaños de página grandes pueden alcanzar los límites de tamaño de respuesta.
Solución de problemas comunes de iteración
Si se recibe un arreglo vacío en la primera llamada, verifica el prefijo. Puede ser incorrecto o el mapa puede estar vacío. Usa state_getMetadata para verificar los nombres del pallet y del elemento. Si se recibe un error sobre parámetros no válidos, asegúrate de que las cadenas hexadecimales estén formateadas correctamente con el prefijo 0x y que count sea un entero positivo.
Si la iteración se detiene antes de tiempo, verifica si estás comparando la longitud de la página con count correctamente. Un error común es detenerse cuando la página está vacía, pero si el tamaño del mapa es un múltiplo exacto de count, la última página estará llena y la siguiente llamada devolverá vacío. Debes detenerte cuando la longitud de la página sea menor que count, no cuando sea cero. Además, asegúrate de estar actualizando startKey a la última clave de la página anterior.
Si encuentras errores HTTP 429, reduce el tamaño de página o agrega un retraso entre solicitudes. Implementa retroceso exponencial. Si estás usando un endpoint compartido, considera actualizar a un plan dedicado. Para obtener más información sobre las mejores prácticas de RPC, consulta la Guía de RPC de Polkadot (RPC Assistant).
- Primera página vacía: verifica el prefijo y la existencia del mapa.
- Detención temprana: asegúrate de que la condición del bucle sea
length === count. - Errores 429: reduce el tamaño de página, agrega retroceso o actualiza el plan.
- Resultados inconsistentes: fija
ata un único hash de bloque. - Prefijo no válido después de una actualización: vuelve a derivarlo de la nueva metadata.
Próximos pasos: integrar la iteración en tu aplicación
Ahora que comprendes la mecánica, puedes integrar state_getKeysPaged en tu aplicación. Comienza escribiendo un pequeño script para iterar un mapa conocido, como System.Account, y verifica el recuento con un explorador de bloques. Luego, adapta el patrón a tu mapa de almacenamiento específico. Usa el centro de aprendizaje de OnFinality para obtener más guías sobre métodos RPC de Substrate.
Para uso en producción, considera almacenar en caché los resultados y actualizarlos de forma incremental. Dado que el estado solo es consistente en un bloque, puedes iterar en cada nuevo bloque finalizado y diferenciar los cambios. Esto es más eficiente que volver a iterar todo el mapa cada vez. También puedes usar state_queryStorageAt para obtener los valores de las claves modificadas. Para obtener más información sobre cambios de almacenamiento, consulta Cambios de almacenamiento de Polkadot state_queryStorageAt.
Finalmente, monitorea siempre tu uso de RPC y las tasas de error. Si estás construyendo una aplicación de alto rendimiento, considera usar un servicio de API dedicado o revisar los precios de RPC para asegurarte de tener suficiente capacidad. OnFinality proporciona endpoints confiables de red Polkadot que admiten estos métodos.
- Prueba con un mapa conocido y verifica el recuento.
- Almacena en caché los resultados y actualízalos de forma incremental en nuevos bloques.
- Monitorea el uso de RPC y las tasas de error.
- Usa endpoints dedicados para alto rendimiento.
- Consulta la Guía de RPC de Polkadot (RPC Assistant) para obtener más información.