Toda transacción de Solana debe hacer referencia a un blockhash reciente, que caduca después de aproximadamente 150 bloques (~75 segundos). Si firmas fuera de línea o tu pipeline es lento, la transacción puede ser rechazada con 'BlockhashNotFound'. Los nonces duraderos reemplazan el blockhash reciente con un valor almacenado que solo avanza cuando lo permites explícitamente, lo que hace que las transacciones sean seguras para firmar con mucha antelación. Este artículo explica el mecanismo, muestra cómo implementar ambos caminos y proporciona una lista de verificación para la solución de problemas.
Respuesta directa: por qué caduca tu transacción de Solana y cómo solucionarlo
Si alguna vez has construido una transacción de Solana, esperado unos minutos y luego la has enviado solo para ver BlockhashNotFound, has encontrado el mecanismo de caducidad incorporado del protocolo. Cada transacción de Solana debe incluir un blockhash reciente – un hash de un bloque reciente – y ese hash solo es válido durante un período limitado. El método RPC getLatestBlockhash devuelve tanto un blockhash como un lastValidBlockHeight. Una vez que la red alcanza esa altura, la transacción se considera caducada y será rechazada. Para la mayoría de los casos de uso, esta ventana es de aproximadamente 150 bloques, lo que a 400 ms por slot en Solana se traduce en unos 60–75 segundos, pero el número exacto no es una constante fija del protocolo – es la ventana de blockhash reciente del nodo.
El patrón robusto para la firma en línea es: obtener un blockhash fresco, firmar inmediatamente y enviar. Si el blockhash caduca antes del envío, debes volver a obtener y volver a firmar – no puedes simplemente reintentar la misma transacción firmada. Para pipelines fuera de línea o lentos, sin embargo, volver a firmar no es posible. Ahí es donde entran los nonces duraderos. Un nonce duradero es una cuenta especial que almacena un valor que solo cambia cuando lo avanzas explícitamente. Al usar un nonce duradero en lugar del blockhash reciente, tu transacción ya no depende de la actualidad del reloj de pared. Sigue siendo válida hasta que alguien más avance el nonce, lo cual solo tú (o tu autoridad) puedes hacer. Esto hace que los nonces duraderos sean la solución estándar para la firma segura fuera de línea en Solana.
- Las transacciones de Solana requieren un blockhash reciente para prevenir la repetición y garantizar la frescura.
- El blockhash es válido solo hasta
lastValidBlockHeight; después de eso, la transacción es rechazada. - Los nonces duraderos desacoplan la validez de la transacción del tiempo, permitiendo la firma fuera de línea.
- Usa
getLatestBlockhashpara flujos en línea; usa nonces duraderos para envíos fuera de línea o retrasados.
El mecanismo: blockhash reciente y lastValidBlockHeight
El formato de transacción de Solana incluye un campo recent_blockhash. Este hash se utiliza para deduplicar transacciones y para asegurar que una transacción no sea válida indefinidamente. Cuando llamas a getLatestBlockhash, el RPC devuelve un blockhash y el lastValidBlockHeight – la altura de bloque en la que ese blockhash ya no será aceptado. El nodo mantiene una ventana de blockhashes recientes (aproximadamente los últimos 150 bloques). Si envías una transacción con un blockhash más antiguo que esa ventana, el nodo devuelve un error: BlockhashNotFound.
La documentación oficial de Solana sobre confirmación de transacciones explica que una transacción solo es válida mientras su blockhash esté dentro de la ventana de blockhash reciente del nodo. El método RPC sendTransaction rechazará una transacción con un blockhash caducado. El método getLatestBlockhash es la forma recomendada de obtener un blockhash fresco y su altura de validez. El método más antiguo getRecentBlockhash está obsoleto pero aún funciona; devuelve solo el blockhash, no el lastValidBlockHeight, por lo que no puedes saber exactamente cuándo caduca.
La conclusión clave: el blockhash no es una marca de tiempo. Es un hash de un bloque reciente, y su validez está ligada al progreso de la cadena. Si tu nodo está rezagado respecto al último bloque, el blockhash que devuelve puede ser más antiguo que el último bloque real de la red, haciendo que tu transacción caduque antes de lo esperado. Por eso es crítico usar un endpoint RPC saludable y actualizado – consulta Detección de un nodo RPC rezagado respecto al último bloque para saber cómo comprobarlo.
getLatestBlockhashdevuelveblockhashylastValidBlockHeight.- La ventana de blockhash reciente es aproximadamente los últimos 150 bloques, pero no es una constante fija del protocolo.
- Si envías después de
lastValidBlockHeight, obtienesBlockhashNotFound. - Un RPC rezagado puede devolver un blockhash obsoleto, aumentando el riesgo de caducidad.
El patrón en línea robusto: obtener, firmar, enviar y manejar la caducidad
Para la mayoría de las aplicaciones, el flujo correcto es sencillo: obtener un blockhash fresco, construir y firmar la transacción, y enviarla inmediatamente. Si la transacción no se envía dentro de la ventana de validez, debes volver a obtener un nuevo blockhash y volver a firmar. No puedes simplemente reenviar la misma transacción firmada porque la firma es sobre el blockhash antiguo.
Cuando envías una transacción y falla con BlockhashNotFound, no reintentes la misma transacción. En su lugar, reconsérvala con un nuevo blockhash. Esto es especialmente importante para pagos o transferencias de tokens donde un duplicado podría causar doble gasto si accidentalmente reenvías una transacción que en realidad fue confirmada pero no viste la confirmación.
El siguiente ejemplo en JavaScript usa @solana/web3.js para demostrar el patrón. Obtiene un blockhash, firma una transferencia simple y la envía. Si la transacción caduca, captura el error y reconstruye con un blockhash fresco.
// Firma en línea con manejo de caducidad de blockhash
import { Connection, SystemProgram, Transaction, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
const connection = new Connection('https://api.mainnet-beta.solana.com');
const from = Keypair.generate(); // reemplaza con tu keypair
const to = new PublicKey('...');
async function sendWithRetry() {
let blockhashInfo = await connection.getLatestBlockhash();
const transaction = new Transaction();
transaction.add(SystemProgram.transfer({
fromPubkey: from.publicKey,
toPubkey: to,
lamports: 0.01 * LAMPORTS_PER_SOL,
}));
transaction.recentBlockhash = blockhashInfo.blockhash;
transaction.feePayer = from.publicKey;
transaction.sign(from);
try {
const signature = await connection.sendTransaction(transaction);
console.log('Transacción enviada:', signature);
} catch (error) {
if (error.message.includes('BlockhashNotFound')) {
console.log('Blockhash caducado, reintentando con un blockhash fresco');
return sendWithRetry();
}
throw error;
}
}
sendWithRetry();Nonces duraderos: la solución para la firma fuera de línea
Los nonces duraderos son una característica de Solana que permite firmar una transacción sin un blockhash reciente. En su lugar, la transacción usa un nonce duradero – un valor almacenado en una cuenta especial (una NonceAccount). Esta cuenta es creada y propiedad de un programa, y tiene una autoridad que puede avanzar el nonce. Cuando incluyes un nonce duradero en una transacción, también debes incluir una instrucción SystemProgram.advanceNonceAccount, que actualiza el nonce almacenado a un nuevo valor. Esta instrucción debe ser firmada por la autoridad del nonce, y también paga una tarifa.
La propiedad clave es que el nonce solo avanza cuando la autoridad firma explícitamente una instrucción advanceNonceAccount. Por lo tanto, una transacción que usa un nonce duradero no caduca según el tiempo; solo se vuelve inválida si otra transacción ya ha avanzado el nonce. Esto hace que sea seguro firmar una transacción fuera de línea, almacenarla y enviarla más tarde – incluso días o semanas después – siempre que la cuenta de nonce siga financiada y no se haya usado ya.
El Libro de cocina de Solana sobre nonces duraderos proporciona una receta para crear y usar una cuenta de nonce. El proceso implica: crear una cuenta de nonce, inicializarla con una autoridad, y luego usar el nonce en lugar del blockhash reciente. La biblioteca @solana/web3.js proporciona funciones auxiliares como createNonceAccount y NonceAccount para simplificar esto.
- Un nonce duradero se almacena en una
NonceAccounty solo puede ser avanzado por su autoridad. - Las transacciones que usan un nonce duradero incluyen una instrucción
advanceNonceAccount. - El nonce no caduca según el tiempo, solo cuando es consumido por otra transacción.
- Esto permite la firma segura fuera de línea para transacciones aisladas o programadas.
Paso a paso: crear y usar un nonce duradero
Aquí hay un flujo completo para crear una cuenta de nonce y usarla para una transacción fuera de línea. Este ejemplo usa @solana/web3.js y asume que tienes una cuenta pagadora financiada. Los pasos son: crear la cuenta de nonce, inicializarla, luego construir una transacción que use el nonce e incluya la instrucción de avance.
Primero, crea una cuenta de nonce. Esto requiere financiar la cuenta con suficientes lamports para estar exenta de alquiler. El auxiliar createNonceAccount hace esto por ti. Luego, puedes recuperar el valor del nonce usando getNonce.
Cuando estés listo para firmar fuera de línea, obtienes el valor actual del nonce de la cuenta (o de un valor previamente almacenado), construyes tu transacción con ese nonce como recentBlockhash, y agregas la instrucción advanceNonceAccount. Fírmala con el pagador de tarifas y la autoridad del nonce. La transacción puede ser almacenada y enviada más tarde.
// Crear y usar un nonce duradero
import { Connection, SystemProgram, Transaction, Keypair, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
const connection = new Connection('https://api.mainnet-beta.solana.com');
const payer = Keypair.generate(); // financia esta cuenta
const nonceAuthority = Keypair.generate(); // autoridad para el nonce
// Crear una cuenta de nonce
const nonceAccount = Keypair.generate();
const tx = new Transaction().add(
SystemProgram.createNonceAccount({
fromPubkey: payer.publicKey,
noncePubkey: nonceAccount.publicKey,
authority: nonceAuthority.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(80), // Tamaño de NonceAccount
})
);
tx.feePayer = payer.publicKey;
await connection.sendTransaction(tx, [payer, nonceAccount]);
// Obtener el valor del nonce
const nonceInfo = await connection.getNonce(nonceAccount.publicKey);
const nonce = nonceInfo.nonce;
// Construir una transacción fuera de línea (ej., una transferencia)
const transfer = SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: someDestination,
lamports: 0.1 * LAMPORTS_PER_SOL,
});
const offlineTx = new Transaction().add(
SystemProgram.advanceNonceAccount({
noncePubkey: nonceAccount.publicKey,
authorizedPubkey: nonceAuthority.publicKey,
}),
transfer
);
offlineTx.recentBlockhash = nonce;
offlineTx.feePayer = payer.publicKey;
// Firmar fuera de línea (ej., en un entorno aislado)
offlineTx.sign(payer, nonceAuthority);
// Más tarde, enviar la transacción firmada
const signature = await connection.sendTransaction(offlineTx);
console.log('Transacción fuera de línea enviada:', signature);Salidas esperadas y una tabla de resultados para completar
Cuando ejecutes el ejemplo en línea, deberías ver una firma impresa y la transacción debería confirmarse en unos segundos. Si retrasas deliberadamente el envío más allá del lastValidBlockHeight, verás un error que contiene BlockhashNotFound. Para el ejemplo de nonce duradero, deberías ver una firma impresa incluso si esperas minutos antes de enviar, siempre que el nonce no se haya usado.
Para verificar el comportamiento tú mismo, puedes ejecutar la siguiente prueba y registrar los resultados. Esta tabla te ayudará a documentar los tiempos de caducidad y los mensajes de error que observes.
- Ejecuta el ejemplo en línea y anota el tiempo entre obtener el blockhash y enviar la transacción.
- Espera deliberadamente 2 minutos antes de enviar para provocar
BlockhashNotFound. - Ejecuta el ejemplo de nonce duradero y espera 5 minutos antes de enviar – debería tener éxito.
- Intenta enviar la misma transacción de nonce duradero dos veces – la segunda debería fallar con
DurableNonceMismatch.
// Completa tus observaciones
| Escenario | Tiempo de espera | Resultado (firma/error) |
|-----------|------------------|-------------------------|
| En línea, envío inmediato | 0s | |
| En línea, retraso de 2 min | 120s | |
| Nonce duradero, retraso de 5 min | 300s | |
| Nonce duradero, doble envío | - | |Lista de verificación de fallos y soluciones
Aquí hay errores comunes que podrías encontrar y cómo solucionarlos. Esta lista se basa en el comportamiento documentado y en informes de la comunidad.
- BlockhashNotFound: El blockhash caducó. Vuelve a obtener un blockhash fresco y vuelve a firmar. No reintentes la misma transacción.
- DurableNonceMismatch: El valor del nonce en tu transacción no coincide con el nonce almacenado actual. Esto generalmente significa que el nonce ya fue avanzado por otra transacción. Obtén el nonce actual y vuelve a firmar.
- Intento de avanzar nonce duradero: Este error ocurre cuando la instrucción
advanceNonceAccountno está firmada correctamente o la autoridad es incorrecta. Asegúrate de que la autoridad del nonce firme la transacción. - Cuenta de nonce no inicializada: Debes inicializar la cuenta de nonce con
initializeNonceAccountantes de usarla. - Lamports insuficientes para el alquiler: La cuenta de nonce debe estar exenta de alquiler. Finánciala con suficientes lamports (usa
getMinimumBalanceForRentExemption). - Retraso del RPC: Si tu endpoint RPC está rezagado, el blockhash que devuelve puede estar obsoleto. Usa un endpoint saludable y verifica el retraso – consulta Detección de un nodo RPC rezagado respecto al último bloque.
Limitaciones y compensaciones de los nonces duraderos
Si bien los nonces duraderos resuelven el problema de la firma fuera de línea, conllevan compensaciones. Primero, la cuenta de nonce debe estar financiada y exenta de alquiler, lo que bloquea una pequeña cantidad de SOL. Segundo, el nonce solo se puede usar una vez – después de que se avanza, necesitas obtener un nuevo nonce para la siguiente transacción. Esto significa que debes tener un proceso para gestionar las cuentas de nonce, especialmente si firmas muchas transacciones fuera de línea.
En cuanto a la seguridad, la autoridad del nonce es una clave crítica. Si se ve comprometida, un atacante puede avanzar el nonce e invalidar tus transacciones pendientes. Por lo tanto, la autoridad debe mantenerse tan segura como tus claves de firma principales. Además, ten en cuenta que una transacción con nonce duradero aún requiere un pagador de tarifas, y la instrucción advanceNonceAccount en sí misma incurre en una tarifa.
Finalmente, los nonces duraderos no protegen contra todos los tipos de repetición. Solo aseguran que una transacción no pueda repetirse después de que el nonce haya sido avanzado. Si necesitas prevenir la repetición en diferentes contextos, aún debes diseñar tu transacción cuidadosamente.
Próximos pasos y lecturas adicionales
Ahora que comprendes la caducidad del blockhash y los nonces duraderos, puedes construir aplicaciones de Solana más confiables. Para una inmersión más profunda en la mecánica de transacciones de Solana, consulta la documentación de Solana y el Libro de cocina de Solana.
Si estás usando un proveedor de RPC, asegúrate de usar un endpoint confiable. OnFinality ofrece endpoints RPC de Solana con alta disponibilidad. Para más sobre cómo manejar problemas de RPC, lee sobre tiempos de espera y reintentos de RPC de Solana y Decodificación de errores de simulación de transacciones de Solana.
Para comprender las transacciones versionadas y cómo interactúan con los blockhashes, consulta Transacciones versionadas de Solana y su parseo. Y si estás construyendo en Solana, explora el centro de aprendizaje de OnFinality para más guías, o consulta nuestro servicio de API y precios de RPC para infraestructura de nivel de producción.