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_signaturesen el encabezado - Todos los
program_id_indexyaccount_indicesde 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_blockhashde 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, dondemin_balancees 0 para cuentas del sistema orent.minimum_balance(NonceState::size())para cuentas de nonce; de lo contrarioInsufficientFundsForFee - 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:
- Obtiene la cuenta desde la caché o accounts-db
- Actualiza el estado de exención de renta si es necesario
- 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 deTRANSACTION_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:
- Para cada instrucción, el runtime llama a
prepare_next_top_level_instruction, que construye elInstructionContext. 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. - El runtime verifica si el programa es un precompile (Ed25519, Secp256k1, Secp256r1). Los precompiles se verifican directamente sin invocar la VM BPF.
- 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. - 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). - 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:
| Error | Etapa | Causa |
|---|---|---|
AccountInUse | Programación | La cuenta ya está bloqueada por otra transacción en el mismo lote |
AccountLoadedTwice | Programación | Un pubkey aparece dos veces en el account_keys de la transacción |
AccountNotFound | Validación del pagador de comisión | La cuenta del pagador de comisión no existe |
ProgramAccountNotFound | Carga de cuenta | Un programa invocado no existe |
InsufficientFundsForFee | Validación del pagador de comisión | El pagador de comisión no puede cubrir la comisión + el mínimo exento de rent |
InvalidAccountForFee | Validación del pagador de comisión | El pagador de comisión no es una cuenta del sistema o nonce |
AlreadyProcessed | Caché de estado | La transacción ya fue procesada |
BlockhashNotFound | Verificación de antigüedad | Blockhash no está en la cola y no es un nonce válido |
InstructionError | Ejecución | Ocurrió un error al procesar una instrucción (incluye el índice de instrucción y el InstructionError específico) |
CallChainTooDeep | Carga de cuenta | La cadena de llamadas del cargador es demasiado profunda |
MissingSignatureForFee | Sanitización | La transacción requiere una comisión pero no tiene firma presente |
InvalidAccountIndex | Sanitización | La transacción contiene una referencia de cuenta inválida |
SignatureFailure | Verificación de firma | La firma Ed25519 no se verifica (el paquete se descarta) |
InvalidProgramForExecution | Carga de cuenta | El programa no es propiedad de un cargador válido |
SanitizeFailure | Sanitización | La transacción no pudo sanitizar los desplazamientos de cuentas correctamente |
ClusterMaintenance | Programación | Las transacciones están actualmente deshabilitadas debido a mantenimiento del clúster |
AccountBorrowOutstanding | Ejecución | El procesamiento de la transacción dejó una cuenta con una referencia prestada pendiente |
WouldExceedMaxBlockCostLimit | Programación | La transacción excedería el límite máximo de costo del bloque |
UnsupportedVersion | Sanitización | La versión de la transacción no es compatible |
InvalidWritableAccount | Carga de cuenta | La transacción carga una cuenta escribible que no puede ser escrita |
WouldExceedMaxAccountCostLimit | Programación | La transacción excedería el límite máximo de costo de cuenta dentro del bloque |
WouldExceedAccountDataBlockLimit | Programación | La transacción excedería el límite de datos de cuenta dentro del bloque |
TooManyAccountLocks | Programación | La transacción bloqueó demasiadas cuentas |
AddressLookupTableNotFound | Carga de cuenta | La cuenta de la tabla de búsqueda de direcciones no existe |
InvalidAddressLookupTableOwner | Carga de cuenta | La tabla de búsqueda de direcciones es propiedad del programa incorrecto |
InvalidAddressLookupTableData | Carga de cuenta | La tabla de búsqueda de direcciones contiene datos inválidos |
InvalidAddressLookupTableIndex | Carga de cuenta | La búsqueda en la tabla de direcciones usa un índice inválido |
InvalidRentPayingAccount | Verificación post-ejecución | La cuenta pasó de exenta de rent a pagadora de rent |
WouldExceedMaxVoteCostLimit | Programación | La transacción excedería el límite máximo de costo de voto |
WouldExceedAccountDataTotalLimit | Programación | La transacción excedería el límite total de datos de cuenta |
DuplicateInstruction | Análisis del presupuesto de cómputo | Variante de instrucción de presupuesto de cómputo duplicada en la misma transacción |
InsufficientFundsForRent | Verificación post-ejecución | La cuenta no tiene suficientes lamports para cubrir el rent de su tamaño de datos |
MaxLoadedAccountsDataSizeExceeded | Carga de cuenta | Los datos cargados totales exceden el límite de 64 MiB |
InvalidLoadedAccountsDataSizeLimit | Análisis del presupuesto de cómputo | SetLoadedAccountsDataSizeLimit establecido en 0 |
ResanitizationNeeded | Sanitización | La transacción difirió antes/después de la activación de la característica y necesita resanitización |
ProgramExecutionTemporarilyRestricted | Carga de cuenta | La ejecución del programa está temporalmente restringida en la cuenta referenciada |
UnbalancedTransaction | Verificación post-ejecución | El saldo total de lamports antes de la transacción no es igual al saldo después |
ProgramCacheHitMaxLimit | Carga de cuenta | La caché del programa alcanzó el límite máximo |
CommitCancelled | Confirmación | Confirmación cancelada internamente |
Is this page helpful?