Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Guías de red y protocolo14 min de lectura

Polkadot state_queryStorageAt: leer cambios de almacenamiento en un bloque

Aprende a leer el almacenamiento de Substrate en un bloque específico, por qué state_queryStorageAt se limita a un solo bloque y cómo construir un flujo de trabajo de detección de cambios por fragmentos.

TL;DR

Leer el almacenamiento de Polkadot en un bloque específico requiere fijar el parámetro `at` a un hash de bloque; sin él, el nodo devuelve el estado de la cabeza actual, que no es reproducible. El método `state_queryStorageAt` acepta un array de claves de almacenamiento y un hash de bloque `at` opcional, devolviendo resultados agrupados por bloque, pero su rango de bloques documentado es un solo bloque. Para detectar qué cambió en un rango, debes recorrer bloque por bloque, comparando hashes de valores con `state_getStorageHash` en lugar de transferir cada valor. Las claves de almacenamiento se derivan de los metadatos usando hashes twox128 de los nombres de pallet e item más las partes de clave codificadas en SCALE, por lo que una clave incorrecta devuelve null silenciosamente. Un endpoint de archivo o con capacidad de estado histórico es un requisito previo para cualquier lectura histórica.

Las claves de almacenamiento como identificadores derivados

Una clave de almacenamiento de Substrate no es una cadena legible que copias de la documentación. Es una derivación determinista: un hash twox128 del nombre del pallet, concatenado con un hash twox128 del nombre del item de almacenamiento, seguido de las partes de clave codificadas en SCALE para maps y double maps. La documentación de almacenamiento en tiempo de ejecución de Substrate describe esta composición en detalle. Debido a que la clave se deriva, cualquier cambio en el nombre del pallet o del item en una actualización del runtime invalida las claves calculadas previamente.

Este modelo de derivación significa que debes tratar una clave de almacenamiento como un valor calculado, no como una constante. Si codificas una clave de forma fija y el runtime renombra un pallet, tu consulta devolverá null en lugar de un error. Ese fallo silencioso es la fuente más común de confusión al leer almacenamiento histórico. Obtén siempre las claves de los metadatos en el bloque que estás consultando, o al menos verifica la clave contra un bloque conocido como bueno antes de confiar en un resultado null.

  • Prefijo del pallet: hash twox128 del nombre del pallet (p. ej., 'System').
  • Prefijo del item: hash twox128 del nombre del item de almacenamiento (p. ej., 'Account').
  • Partes de clave: claves de map codificadas en SCALE añadidas después de los prefijos.
  • Una clave incorrecta devuelve null, no un error; desambigua siempre con un bloque conocido como bueno.

state_getStorage vs state_getStorageHash vs state_queryStorageAt

La referencia JSON-RPC de polkadot.js para métodos state documenta tres consultas distintas. state_getStorage devuelve el valor actual de una sola clave. state_getStorageHash devuelve el hash del valor en un bloque dado, que es la forma barata de detectar un cambio sin transferir el valor completo. state_queryStorageAt acepta un array de claves y un hash de bloque at opcional, devolviendo resultados agrupados por bloque.

Cada método responde a una pregunta diferente. Usa state_getStorage cuando necesites el valor real. Usa state_getStorageHash cuando solo necesites saber si un valor cambió. Usa state_queryStorageAt cuando necesites varias claves en un bloque específico en una sola solicitud. El parámetro at es crítico: sin él, el nodo lee la cabeza actual, que no es reproducible. Fijar at a un hash de bloque hace que la lectura sea reproducible y es la única forma de comparar dos observaciones de manera honesta.

  • state_getStorage: valor actual de una clave.
  • state_getStorageHash: hash del valor en un bloque; detección de cambios barata.
  • state_queryStorageAt: varias claves en un bloque, resultados agrupados por bloque.

La limitación de un solo bloque de state_queryStorageAt

El método state_queryStorageAt toma un hash de bloque at y devuelve resultados agrupados por bloque. Su rango de bloques documentado es un solo bloque. Esto no es un error; refleja el diseño del método como una consulta de múltiples claves en un punto en el tiempo. Si necesitas saber qué cambió en un rango de bloques, no puedes emitir una única consulta amplia. Debes recorrer el rango bloque por bloque.

Esta limitación convierte tu diseño de detección de cambios en un recorrido explícito por bloque. El patrón eficiente es leer el conjunto de claves una vez con state_queryStorageAt en el bloque inicial del rango, y luego avanzar bloque por bloque leyendo solo los hashes de valores con state_getStorageHash. Reporta el primer bloque en el que un hash difiere. Esto es más barato que transferir cada valor y es preciso sobre cuándo ocurrió el cambio.

  • state_queryStorageAt devuelve resultados para un solo bloque, no para un rango.
  • Para detectar cambios en un rango, recorre bloque por bloque.
  • Compara hashes primero; obtén valores solo cuando un hash difiera.

Fijar el parámetro at para lecturas reproducibles

Toda lectura histórica debe fijar el parámetro at a un hash de bloque. Leer sin un bloque at significa leer la cabeza actual, que cambia con cada nuevo bloque. Una lectura en la cabeza actual no es reproducible: la misma consulta ejecutada dos veces puede devolver resultados diferentes. Fijar at a un hash de bloque específico hace que la lectura sea reproducible y es la única forma de comparar dos observaciones de manera honesta.

Cuando compares dos bloques, usa siempre el mismo conjunto de claves y la misma semántica de at. Si lees el bloque A sin at y el bloque B con at, estás comparando la cabeza actual con un bloque histórico, lo cual no tiene sentido. La guía de estado histórico de nodo de archivo de Polkadot explica por qué se requieren endpoints de archivo para este patrón.

  • Fija siempre at a un hash de bloque para lecturas históricas.
  • Sin at, lees la cabeza actual, que no es reproducible.
  • Compara bloques usando conjuntos de claves idénticos y la misma semántica de at.

Flujo de trabajo de comparación por fragmentos para la detección de cambios

El flujo de trabajo de comparación por fragmentos convierte 'qué cambió' en evidencia. Comienza leyendo el conjunto de claves una vez con state_queryStorageAt en el bloque inicial del rango. Luego avanza bloque por bloque, leyendo solo los hashes de valores con state_getStorageHash. Reporta el primer bloque en el que un hash difiere. Este enfoque es a la vez más barato que transferir cada valor y preciso sobre cuándo ocurrió el cambio.

El recorrido es O(bloques) solicitudes, lo que convierte el tamaño del fragmento en una decisión real de presupuesto. Si estás escaneando un rango grande, es posible que necesites agrupar solicitudes o usar un proveedor con límites de tasa generosos. La página de precios de RPC y el servicio de API describen cómo OnFinality estructura los presupuestos de solicitudes. Para un ejemplo práctico de lectura de extrinsics y eventos en un solo bloque, consulta el tutorial de extrinsics y eventos de Polkadot en un bloque.

  • Lee el conjunto de claves una vez en el bloque inicial con state_queryStorageAt.
  • Avanza bloque por bloque con state_getStorageHash.
  • Reporta el primer bloque donde el hash difiere; ese es tu punto de cambio.
  • El tamaño del fragmento es una decisión de presupuesto: O(bloques) solicitudes.

Script ejecutable de Node.js para la detección de cambios de almacenamiento

El siguiente script toma una clave de almacenamiento y un rango de bloques, lee el valor en el bloque inicial y luego recorre el rango comparando hashes. Imprime el hash del bloque donde el valor cambió por primera vez junto con los valores antes y después. Reemplaza la URL del endpoint con tu propio endpoint de nodo de archivo. El script usa la librería @polkadot/api, que maneja la decodificación SCALE automáticamente.

Ten en cuenta que el script asume que la clave ya está derivada. En la práctica, deberías derivar la clave de los metadatos en el bloque inicial. La siguiente sección cubre cómo obtener la clave de los metadatos en lugar de codificarla de forma fija.

const { ApiPromise, WsProvider } = require('@polkadot/api');

async function findStorageChange(wsEndpoint, storageKey, startBlock, endBlock) {
  const provider = new WsProvider(wsEndpoint);
  const api = await ApiPromise.create({ provider });

  // Get block hashes for the range
  const startHash = (await api.rpc.chain.getBlockHash(startBlock)).toString();
  const endHash = (await api.rpc.chain.getBlockHash(endBlock)).toString();

  // Read initial value at start block
  const initialValue = await api.rpc.state.getStorage(storageKey, startHash);
  const initialHash = await api.rpc.state.getStorageHash(storageKey, startHash);
  console.log(`Start block ${startBlock} hash: ${startHash}`);
  console.log(`Initial value: ${initialValue.toHex()}`);
  console.log(`Initial hash: ${initialHash.toHex()}`);

  let previousHash = initialHash.toHex();
  let previousValue = initialValue.toHex();

  // Walk forward block by block
  for (let block = startBlock + 1; block <= endBlock; block++) {
    const blockHash = (await api.rpc.chain.getBlockHash(block)).toString();
    const currentHash = await api.rpc.state.getStorageHash(storageKey, blockHash);

    if (currentHash.toHex() !== previousHash) {
      const currentValue = await api.rpc.state.getStorage(storageKey, blockHash);
      console.log(`\nChange detected at block ${block}`);
      console.log(`Block hash: ${blockHash}`);
      console.log(`Before: ${previousValue}`);
      console.log(`After:  ${currentValue.toHex()}`);
      return { block, blockHash, before: previousValue, after: currentValue.toHex() };
    }

    previousHash = currentHash.toHex();
    previousValue = (await api.rpc.state.getStorage(storageKey, blockHash)).toHex();
  }

  console.log('No change detected in range.');
  return null;
}

// Example usage
findStorageChange(
  'wss://your-archive-endpoint.example.com',
  '0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9', // System.Account key example
  1000000,
  1000100
).catch(console.error);

Obtener claves de almacenamiento de los metadatos

Codificar una clave de almacenamiento de forma fija es frágil. Los nombres de pallet e item son las entradas de los prefijos twox128, por lo que una actualización del runtime que renombre un pallet invalida la clave. Debes volver a derivar la clave de los metadatos en el bloque que estás consultando. El tutorial de state_getMetadata y versiones de runtime de Substrate explica cómo obtener y decodificar metadatos.

Para derivar una clave, necesitas el nombre del pallet, el nombre del item de almacenamiento y las partes de clave codificadas en SCALE para los maps. La librería @polkadot/api proporciona ayudantes como api.query.system.account.key(accountId) que calculan la clave completa por ti. Si trabajas a nivel de RPC sin procesar, debes calcular los hashes twox128 tú mismo y concatenar las partes de clave codificadas en SCALE. Verifica siempre la clave derivada contra un bloque conocido como bueno antes de confiar en un resultado null.

  • Deriva las claves de los metadatos en el bloque objetivo, no de cadenas codificadas de forma fija.
  • Los nombres de pallet e item son entradas de los prefijos twox128; los renombrados invalidan las claves.
  • Usa los ayudantes api.query.*.key() o calcula los hashes twox128 manualmente.
  • Verifica las claves derivadas contra un bloque conocido como bueno antes de confiar en null.

Requisitos previos del nodo de archivo para lecturas históricas

Un endpoint de archivo o con capacidad de estado histórico es un requisito previo para cualquier lectura de almacenamiento histórico. Un nodo podado no puede servir el estado en un bloque pasado arbitrario y responderá con un error o una respuesta no disponible en lugar de un valor incorrecto. Esta es una restricción estricta: si tu endpoint no conserva el estado histórico, ninguna cantidad de lógica de reintento ayudará.

OnFinality proporciona endpoints de archivo para Polkadot y otras cadenas de Substrate. La página de la red Polkadot enumera los endpoints disponibles, y la guía de endpoints RPC de Polkadot explica cómo seleccionar el endpoint adecuado para tu caso de uso. Para una discusión más profunda sobre los requisitos de nodo de archivo, consulta el artículo sobre estado histórico de nodo de archivo de Polkadot.

  • Los nodos podados no pueden servir el estado en bloques pasados arbitrarios.
  • Los endpoints de archivo conservan el estado histórico para lecturas reproducibles.
  • Comprueba las capacidades del endpoint antes de construir un flujo de trabajo de lectura histórica.

Modos de fallo y solución de problemas

Un valor null de state_getStorage o state_queryStorageAt significa 'ausente en este bloque' o 'clave incorrecta'. Lo desambiguas confirmando primero que la misma clave devuelve un valor en un bloque conocido como bueno. Si devuelve un valor en el bloque X pero null en el bloque Y, la clave es válida y el valor está genuinamente ausente en el bloque Y. Si devuelve null en ambos, es probable que la clave sea incorrecta.

Se requiere la decodificación SCALE antes de que el valor sea significativo. Un valor hexadecimal es la representación intermedia, no la respuesta. Si usas llamadas RPC sin procesar, debes decodificar el hexadecimal según el tipo del item de almacenamiento. La librería @polkadot/api maneja esto automáticamente cuando usas consultas tipadas. Otro fallo común es un recorrido de rango de bloques que supera los límites de tasa: O(bloques) solicitudes puede ser costoso, así que agrupa o limita la tasa según sea necesario.

  • Null significa ausente o clave incorrecta; desambigua con un bloque conocido como bueno.
  • Los valores hexadecimales requieren decodificación SCALE antes de ser significativos.
  • Los recorridos de rango de bloques son O(bloques) solicitudes; vigila los límites de tasa.
  • Las actualizaciones del runtime pueden invalidar claves; vuelve a derivarlas de los metadatos.

Tabla de resultados para la verificación de endpoints

Debido a que la latencia y el rendimiento específicos del proveedor varían, debes medir contra tu propio endpoint. La siguiente tabla es una plantilla que puedes completar con tus propias mediciones. Ejecuta el script de la sección anterior contra tu endpoint y registra los resultados. Esto te da una línea base reproducible para tu configuración específica.

No confíes en números de referencia publicados por ningún proveedor, incluido OnFinality. Mide los tuyos. La siguiente tabla es un punto de partida para tu propia verificación.

  • URL del endpoint: tu endpoint WebSocket de nodo de archivo.
  • Rango de bloques: bloques inicial y final que estás escaneando.
  • Número de solicitudes: total de llamadas RPC realizadas durante el recorrido.
  • Tiempo transcurrido: tiempo de reloj de pared para el escaneo completo.
  • Cambio detectado en el bloque: el número de bloque donde el hash difirió por primera vez.
  • Valor antes: valor hexadecimal antes del cambio.
  • Valor después: valor hexadecimal después del cambio.

Limitaciones y compensaciones

El flujo de trabajo de comparación por fragmentos es preciso pero no gratuito. Recorrer un rango grande de bloques es O(bloques) solicitudes, lo que puede ser lento y costoso. Si necesitas escanear millones de bloques, es posible que necesites un enfoque diferente, como suscribirte a eventos de cambio de almacenamiento o usar un indexador. La limitación de un solo bloque del método state_queryStorageAt significa que no puedes evitar el recorrido si trabajas a nivel de RPC sin procesar.

Otra compensación es que la comparación de hashes solo te dice que un valor cambió, no qué cambió. Si necesitas el diff real, debes obtener y decodificar ambos valores. Para valores grandes, esto puede ser costoso. Finalmente, los endpoints de archivo no son gratuitos: requieren más almacenamiento y a menudo tienen precios diferentes. La página de precios de RPC describe cómo OnFinality estructura los costos para el acceso a archivos.

  • O(bloques) solicitudes puede ser lento y costoso para rangos grandes.
  • La comparación de hashes detecta el cambio pero no la naturaleza del cambio.
  • Obtener y decodificar valores añade costo para items de almacenamiento grandes.
  • Los endpoints de archivo son un requisito previo y pueden tener precios diferentes.

Próximos pasos y lecturas adicionales

Ahora que entiendes la ruta de lectura de cambios de almacenamiento, puedes extenderla. Combínala con el tutorial de extrinsics y eventos de Polkadot en un bloque para correlacionar los cambios de almacenamiento con los extrinsics que los causaron. Usa el tutorial de state_getMetadata y versiones de runtime de Substrate para derivar claves dinámicamente. Para lecturas relacionadas con la finalidad, consulta el artículo sobre finalidad de Polkadot y justificaciones GRANDPA.

Para comenzar con un endpoint de archivo confiable, visita la página de la red Polkadot o explora el centro de aprendizaje de OnFinality para más tutoriales. La guía de endpoints RPC de Polkadot te ayuda a elegir el endpoint adecuado para tu carga de trabajo.

  • Correlaciona los cambios de almacenamiento con extrinsics y eventos.
  • Deriva claves dinámicamente a partir de los metadatos.
  • Elige un endpoint de archivo que admita estado histórico.
  • Explora más tutoriales en el centro de aprendizaje de OnFinality.

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