Pipeline de transacciones

Resumen

Las transacciones pasan por 8 etapas: recepción, verificación de firmas, sanitización, verificaciones de presupuesto/antigüedad, validación del pagador de comisiones, carga de cuentas, ejecución de instrucciones y confirmación.

Pipeline de procesamiento de transacciones

Cuando una transacción llega a un validator, pasa por una serie de etapas de validación y ejecución. Lo siguiente describe el pipeline completo desde la recepción hasta la confirmación, con referencias a archivos fuente del cliente validator agave.

1. Recepción y deserialización

El validator recibe bytes de transacción a través de UDP/QUIC. Los bytes sin procesar deben caber dentro de un solo paquete (PACKET_DATA_SIZE = 1.232 bytes). Los bytes se deserializan en una VersionedTransaction, que contiene el array de firmas y un VersionedMessage (ya sea legacy o v0).

2. Verificación de firmas (sigverify)

Las firmas se verifican en la etapa sigverify antes de que la transacción entre en la etapa bancaria. Para cada firma en el índice i, el verificador comprueba Ed25519(signatures[i], account_keys[i], message_bytes). Si alguna firma es inválida, el paquete se descarta.

La verificación se paraleliza: el validator divide los lotes de paquetes en fragmentos de VERIFY_PACKET_CHUNK_SIZE (128) y los procesa en paralelo.

3. Sanitización

La transacción deserializada se sanitiza para producir una SanitizedTransaction (o RuntimeTransaction). La sanitización valida invariantes estructurales:

  • El número de firmas coincide con num_required_signatures en el encabezado
  • Todos los program_id_index y account_indices de las instrucciones están dentro de los límites
  • El pagador de comisiones (índice de cuenta 0) es un firmante con permisos de escritura

El wrapper RuntimeTransaction almacena en caché metadatos precalculados de TransactionMeta: el hash del mensaje, la bandera de transacción de voto, recuentos de firmas de precompilación (Ed25519/secp256k1/secp256r1), detalles de instrucciones de presupuesto de cómputo y longitud total de datos de instrucciones.

4. Verificar presupuesto de cómputo, antigüedad y caché de estado

El método check_transactions realiza varias verificaciones por transacción:

Presupuesto de cómputo: Las instrucciones de presupuesto de cómputo de la transacción se analizan y validan primero. Los detalles de la tarifa se calculan a partir de los límites del presupuesto y la tarifa de priorización. Si el presupuesto de cómputo es inválido o conflictivo, la transacción falla con errores de análisis del presupuesto de cómputo como DuplicateInstruction, InstructionError(..., InvalidInstructionData), o InvalidLoadedAccountsDataSizeLimit.

Antigüedad del blockhash: El recent_blockhash de la transacción se busca en la BlockhashQueue. Si se encuentra el hash y su antigüedad está dentro de MAX_PROCESSING_AGE (150 slots), la transacción continúa. Si no se encuentra, el validator verifica si existe un nonce duradero válido.

Caché de estado: El hash del mensaje de la transacción se verifica contra una caché de estado. Si se encuentra, la transacción se rechaza con AlreadyProcessed.

5. Validar nonce y pagador de tarifas

El método validate_transaction_nonce_and_fee_payer en el SVM maneja dos validaciones:

Validación de nonce (si aplica): Para transacciones con nonce, el validator carga la cuenta de nonce y verifica:

  • La cuenta es propiedad del System Program
  • Se analiza como State::Initialized
  • El nonce duradero almacenado coincide con el recent_blockhash de la transacción
  • El nonce puede avanzarse (su nonce duradero actual difiere del siguiente nonce duradero, es decir, el nonce no se ha usado ya en el bloque actual)
  • La autoridad del nonce ha firmado la transacción

Si es válido, el nonce avanza al siguiente valor de nonce duradero. Ver validate_transaction_nonce.

Validación del pagador de tarifas: La cuenta del pagador de tarifas (siempre índice 0) se carga y verifica mediante validate_fee_payer:

  • La cuenta debe existir (lamports > 0), de lo contrario AccountNotFound
  • La cuenta debe ser una cuenta del sistema o cuenta de nonce, de lo contrario InvalidAccountForFee
  • Los lamports deben cubrir min_balance + total_fee, donde min_balance es 0 para cuentas del sistema o rent.minimum_balance(NonceState::size()) para cuentas de nonce; de lo contrario InsufficientFundsForFee
  • Después de la deducción de la tarifa, la cuenta debe permanecer exenta de renta (no puede transicionar de exenta de renta a pagadora de renta)

La tarifa se deduce del pagador de tarifas en esta etapa. Se guarda una instantánea del pagador de tarifas con la tarifa deducida (y el nonce avanzado, si aplica) como RollbackAccounts, que son las cuentas que se confirman incluso si la ejecución falla.

6. Cargar cuentas

load_transaction carga todas las cuentas referenciadas por la transacción. El AccountLoader envuelve el almacén de cuentas externo y mantiene una caché local del lote para que las cuentas modificadas por transacciones anteriores en el mismo lote sean visibles para las posteriores.

Para cada cuenta que no sea pagadora de tarifas, el cargador:

  1. Obtiene la cuenta desde la caché o accounts-db
  2. Actualiza el estado de exención de renta si es necesario
  3. Acumula el tamaño de datos de la cuenta hacia el loaded_accounts_data_size_limit (predeterminado 64 MiB). Cada cuenta incurre en una sobrecarga base de TRANSACTION_ACCOUNT_BASE_SIZE (64 bytes) más su longitud de datos

Para cada programa invocado por las instrucciones de la transacción, el cargador verifica que la cuenta del programa exista y sea propiedad de un cargador válido (NativeLoader o uno de los PROGRAM_OWNERS). Los programas inválidos fallan con ProgramAccountNotFound o InvalidProgramForExecution.

Los programas LoaderV3 (actualizables) cargan implícitamente su cuenta programdata asociada, que también cuenta hacia el límite de tamaño de datos cargados.

Si la carga de cuentas falla pero el pagador de tarifas fue validado exitosamente, la transacción se convierte en un resultado FeesOnly: la tarifa aún se cobra pero no se ejecutan instrucciones.

7. Ejecutar instrucciones

execute_loaded_transaction crea un TransactionContext con todas las cuentas cargadas e invoca process_message. Las instrucciones se ejecutan secuencialmente en el orden en que aparecen en el mensaje. Cada invocación de instrucción crea un InvokeContext y llama al programa objetivo.

Detalles del procesamiento de instrucciones

La función process_message del runtime itera a través de cada instrucción y llama al programa objetivo:

  1. Para cada instrucción, el runtime llama a prepare_next_top_level_instruction, que construye el InstructionContext. Este contexto contiene referencias a las cuentas de la instrucción (resueltas desde los índices compilados), los datos de la instrucción y el índice de la cuenta del programa.
  2. El runtime verifica si el programa es un precompile (Ed25519, Secp256k1, Secp256r1). Los precompiles se verifican directamente sin invocar la VM BPF.
  3. Para todos los demás programas, el runtime invoca process_instruction, que carga el programa desde la caché y lo ejecuta en la máquina virtual BPF.
  4. Después de que la instrucción se completa, el runtime verifica que el saldo total de lamports en todas las cuentas de la instrucción no ha cambiado (verificación UnbalancedInstruction).
  5. Si alguna instrucción falla, toda la transacción se revierte. No se confirman cambios de estado intermedios.

Cada instrucción incrementa el trace de instrucciones. El trace incluye tanto instrucciones de nivel superior como cualquier CPI que invoquen. La longitud total del trace (instrucciones de nivel superior más todos los CPIs anidados) no puede exceder 64 (MAX_INSTRUCTION_TRACE_LENGTH). Exceder este límite devuelve InstructionError::MaxInstructionTraceLengthExceeded.

Después de la ejecución, el runtime verifica que:

  • La suma de lamports en todas las cuentas no ha cambiado
  • Ninguna cuenta pasó de estar exenta de renta a pagar renta

8. Confirmar o revertir

Si la ejecución tiene éxito, los estados de cuenta modificados del TransactionContext se escriben de vuelta en la caché local del lote del AccountLoader. Si la ejecución falla, solo se escriben de vuelta las RollbackAccounts (pagador de comisión con comisión deducida y nonce avanzado). La comisión aún se cobra, pero todos los demás cambios de cuenta se descartan.

Resumen del pipeline

Receive packet (UDP/QUIC)
--> Deserialize into VersionedTransaction
--> Sigverify (parallel Ed25519 verification)
--> Sanitize (structural validation, metadata extraction)
--> Parse compute budget, calculate fees
--> Check blockhash age (or verify nonce account)
--> Check status cache (dedup)
--> Validate nonce authority and advanceability (if nonce transaction)
--> Validate fee payer (load, check balance, deduct fee)
--> Load all accounts (with data size limits)
--> Load programs (verify loaders)
--> Execute instructions sequentially
--> Verify post-conditions (lamport balance, rent state)
--> Commit account changes (or rollback on failure)

Referencia de errores de transacción

La siguiente tabla lista todas las variantes de TransactionError y en qué etapa del pipeline ocurren:

ErrorEtapaCausa
AccountInUseProgramaciónLa cuenta ya está bloqueada por otra transacción en el mismo lote
AccountLoadedTwiceProgramaciónUn pubkey aparece dos veces en el account_keys de la transacción
AccountNotFoundValidación del pagador de comisiónLa cuenta del pagador de comisión no existe
ProgramAccountNotFoundCarga de cuentaUn programa invocado no existe
InsufficientFundsForFeeValidación del pagador de comisiónEl pagador de comisión no puede cubrir la comisión + el mínimo exento de rent
InvalidAccountForFeeValidación del pagador de comisiónEl pagador de comisión no es una cuenta del sistema o nonce
AlreadyProcessedCaché de estadoLa transacción ya fue procesada
BlockhashNotFoundVerificación de antigüedadBlockhash no está en la cola y no es un nonce válido
InstructionErrorEjecuciónOcurrió un error al procesar una instrucción (incluye el índice de instrucción y el InstructionError específico)
CallChainTooDeepCarga de cuentaLa cadena de llamadas del cargador es demasiado profunda
MissingSignatureForFeeSanitizaciónLa transacción requiere una comisión pero no tiene firma presente
InvalidAccountIndexSanitizaciónLa transacción contiene una referencia de cuenta inválida
SignatureFailureVerificación de firmaLa firma Ed25519 no se verifica (el paquete se descarta)
InvalidProgramForExecutionCarga de cuentaEl programa no es propiedad de un cargador válido
SanitizeFailureSanitizaciónLa transacción no pudo sanitizar los desplazamientos de cuentas correctamente
ClusterMaintenanceProgramaciónLas transacciones están actualmente deshabilitadas debido a mantenimiento del clúster
AccountBorrowOutstandingEjecuciónEl procesamiento de la transacción dejó una cuenta con una referencia prestada pendiente
WouldExceedMaxBlockCostLimitProgramaciónLa transacción excedería el límite máximo de costo del bloque
UnsupportedVersionSanitizaciónLa versión de la transacción no es compatible
InvalidWritableAccountCarga de cuentaLa transacción carga una cuenta escribible que no puede ser escrita
WouldExceedMaxAccountCostLimitProgramaciónLa transacción excedería el límite máximo de costo de cuenta dentro del bloque
WouldExceedAccountDataBlockLimitProgramaciónLa transacción excedería el límite de datos de cuenta dentro del bloque
TooManyAccountLocksProgramaciónLa transacción bloqueó demasiadas cuentas
AddressLookupTableNotFoundCarga de cuentaLa cuenta de la tabla de búsqueda de direcciones no existe
InvalidAddressLookupTableOwnerCarga de cuentaLa tabla de búsqueda de direcciones es propiedad del programa incorrecto
InvalidAddressLookupTableDataCarga de cuentaLa tabla de búsqueda de direcciones contiene datos inválidos
InvalidAddressLookupTableIndexCarga de cuentaLa búsqueda en la tabla de direcciones usa un índice inválido
InvalidRentPayingAccountVerificación post-ejecuciónLa cuenta pasó de exenta de rent a pagadora de rent
WouldExceedMaxVoteCostLimitProgramaciónLa transacción excedería el límite máximo de costo de voto
WouldExceedAccountDataTotalLimitProgramaciónLa transacción excedería el límite total de datos de cuenta
DuplicateInstructionAnálisis del presupuesto de cómputoVariante de instrucción de presupuesto de cómputo duplicada en la misma transacción
InsufficientFundsForRentVerificación post-ejecuciónLa cuenta no tiene suficientes lamports para cubrir el rent de su tamaño de datos
MaxLoadedAccountsDataSizeExceededCarga de cuentaLos datos cargados totales exceden el límite de 64 MiB
InvalidLoadedAccountsDataSizeLimitAnálisis del presupuesto de cómputoSetLoadedAccountsDataSizeLimit establecido en 0
ResanitizationNeededSanitizaciónLa transacción difirió antes/después de la activación de la característica y necesita resanitización
ProgramExecutionTemporarilyRestrictedCarga de cuentaLa ejecución del programa está temporalmente restringida en la cuenta referenciada
UnbalancedTransactionVerificación post-ejecuciónEl saldo total de lamports antes de la transacción no es igual al saldo después
ProgramCacheHitMaxLimitCarga de cuentaLa caché del programa alcanzó el límite máximo
CommitCancelledConfirmaciónConfirmación cancelada internamente

Is this page helpful?

Gestionado por

© 2026 Fundación Solana.
Todos los derechos reservados.
Conéctate