Конвейер обработки транзакций

Кратко

Транзакции проходят 8 этапов: получение, проверка подписи, санитизация, проверка бюджета/возраста, валидация плательщика комиссии, загрузка аккаунтов, выполнение инструкций и фиксация.

Конвейер обработки транзакций

Когда транзакция поступает на validator, она проходит серию этапов валидации и исполнения. Ниже описан полный конвейер от получения до фиксации с указанием исходных файлов в клиенте validator agave.

1. Получение и десериализация

Validator получает байты транзакции по UDP/QUIC. Сырые байты должны помещаться в один пакет (PACKET_DATA_SIZE = 1232 байта). Байты десериализуются в VersionedTransaction, который содержит массив подписей и VersionedMessage (legacy или v0).

2. Проверка подписи (sigverify)

Подписи проверяются на этапе sigverify до того, как транзакция попадёт в банковский этап. Для каждой подписи по индексу i проверяется Ed25519(signatures[i], account_keys[i], message_bytes). Если хотя бы одна подпись недействительна, пакет отбрасывается.

Проверка выполняется параллельно: validator разбивает пакеты на чанки по VERIFY_PACKET_CHUNK_SIZE (128) и обрабатывает их параллельно.

3. Санитизация

Десериализованная транзакция санитизируется для получения SanitizedTransaction (или RuntimeTransaction). Санитизация проверяет структурные инварианты:

  • Количество подписей соответствует num_required_signatures в заголовке
  • Все индексы инструкций program_id_index и account_indices находятся в допустимых пределах
  • Плательщик комиссии (индекс аккаунта 0) — подписант с правом записи

Обёртка RuntimeTransaction кэширует предвычисленные метаданные из TransactionMeta: хэш сообщения, флаг голосовой транзакции, количество подписей precompile (Ed25519/secp256k1/secp256r1), детали инструкции compute budget и общую длину instruction data.

4. Проверьте вычислительный бюджет, возраст и кэш статусов

Метод check_transactions выполняет несколько проверок для каждой транзакции:

Вычислительный бюджет: Сначала разбираются и валидируются инструкции вычислительного бюджета транзакции. Детали комиссии рассчитываются на основе лимитов бюджета и приоритетной комиссии. Если вычислительный бюджет некорректен или конфликтует, транзакция отклоняется с ошибками разбора compute-budget, такими как DuplicateInstruction, InstructionError(..., InvalidInstructionData) или InvalidLoadedAccountsDataSizeLimit.

Возраст blockhash: recent_blockhash транзакции ищется в BlockhashQueue. Если хеш найден и его возраст не превышает MAX_PROCESSING_AGE (150 слотов), транзакция продолжается. Если не найден, validator проверяет наличие действительного устойчивого nonce.

Кэш статусов: Хеш сообщения транзакции проверяется в кэше статусов. Если найден, транзакция отклоняется с ошибкой AlreadyProcessed.

5. Валидация nonce и плательщика комиссии

Метод validate_transaction_nonce_and_fee_payer в SVM выполняет две проверки:

Валидация nonce (если применимо): Для транзакций с nonce validator загружает nonce-аккаунт и проверяет:

  • Аккаунт принадлежит System Program
  • Он разбирается как State::Initialized
  • Сохранённый устойчивый nonce совпадает с recent_blockhash транзакции
  • Nonce может быть обновлён (текущий устойчивый nonce отличается от следующего, то есть nonce ещё не использовался в текущем блоке)
  • Владелец nonce подписал транзакцию

Если всё верно, nonce обновляется до следующего значения устойчивого nonce. Подробнее см. validate_transaction_nonce.

Валидация плательщика комиссии: Аккаунт плательщика комиссии (всегда индекс 0) загружается и проверяется методом validate_fee_payer:

  • Аккаунт должен существовать (lamports > 0), иначе AccountNotFound
  • Аккаунт должен быть системным или nonce-аккаунтом, иначе InvalidAccountForFee
  • Lamports должны покрывать min_balance + total_fee, где min_balance — это 0 для системных аккаунтов или rent.minimum_balance(NonceState::size()) для nonce-аккаунтов; иначе InsufficientFundsForFee
  • После списания комиссии аккаунт должен оставаться освобождённым от аренды (не может перейти из rent-exempt в rent-paying)

Комиссия списывается с аккаунта плательщика комиссии на этом этапе. Снимок состояния аккаунта плательщика комиссии после вычета комиссии (и продвинутого nonce, если применимо) сохраняется как RollbackAccounts, то есть это аккаунты, которые фиксируются даже в случае неудачного выполнения.

6. Загрузка аккаунтов

load_transaction загружает все аккаунты, на которые ссылается транзакция. AccountLoader оборачивает внешний стор аккаунтов и поддерживает локальный кэш для батча, чтобы аккаунты, изменённые предыдущими транзакциями в том же батче, были видны последующим.

Для каждого аккаунта, не являющегося плательщиком комиссии, загрузчик:

  1. Получает аккаунт из кэша или базы данных аккаунтов
  2. Обновляет статус освобождения от аренды, если это необходимо
  3. Суммирует размер данных аккаунта в общий лимит loaded_accounts_data_size_limit (по умолчанию 64 МБ). Каждый аккаунт добавляет базовую нагрузку TRANSACTION_ACCOUNT_BASE_SIZE (64 байта) плюс длина его данных.

Для каждой программы, вызываемой инструкциями транзакции, загрузчик проверяет, что program account существует и принадлежит допустимому загрузчику (NativeLoader или одному из PROGRAM_OWNERS). Недопустимые программы завершаются с ошибкой ProgramAccountNotFound или InvalidProgramForExecution.

Программы LoaderV3 (с возможностью обновления) неявно загружают связанный с ними programdata аккаунт, который также учитывается в лимите загружаемых данных.

Если загрузка аккаунтов не удалась, но плательщик комиссии был успешно подтверждён, транзакция получает статус FeesOnly: комиссия всё равно взимается, но инструкции не выполняются.

7. Выполнение инструкций

execute_loaded_transaction создаёт TransactionContext со всеми загруженными аккаунтами и вызывает process_message. Инструкции выполняются последовательно в том порядке, в котором они указаны в сообщении. Каждый вызов инструкции создаёт InvokeContext и вызывает целевую программу.

Подробности обработки инструкций

Функция рантайма process_message проходит по каждой инструкции и вызывает целевую программу:

  1. Для каждой инструкции рантайм вызывает prepare_next_top_level_instruction, который формирует InstructionContext. Этот контекст содержит ссылки на аккаунты инструкции (разрешённые по скомпилированным индексам), instruction data и индекс program account.
  2. Рантайм проверяет, является ли программа precompile (Ed25519, Secp256k1, Secp256r1). Precompile-программы проверяются напрямую, без вызова BPF VM.
  3. Для всех остальных программ рантайм вызывает process_instruction, который загружает программу из кэша и выполняет её в BPF виртуальной машине.
  4. После завершения инструкции рантайм проверяет, что общий баланс в lamport по всем аккаунтам инструкции не изменился (проверка UnbalancedInstruction).
  5. Если какая-либо инструкция завершилась с ошибкой, вся транзакция откатывается. Промежуточные изменения состояния не сохраняются.

Каждая инструкция увеличивает trace инструкций. Trace включает как инструкции верхнего уровня, так и все CPI, которые они вызывают. Общая длина trace (инструкции верхнего уровня плюс все вложенные CPI) не может превышать 64 (MAX_INSTRUCTION_TRACE_LENGTH). Превышение этого лимита возвращает InstructionError::MaxInstructionTraceLengthExceeded.

После выполнения рантайм проверяет, что:

  • Сумма lamport по всем аккаунтам не изменилась
  • Ни один аккаунт не перешёл из rent-exempt в rent-paying

8. Коммит или откат

Если выполнение прошло успешно, изменённые состояния аккаунтов из TransactionContext записываются обратно в batch-local cache AccountLoader. Если выполнение завершилось с ошибкой, только RollbackAccounts (fee payer с удержанной комиссией и продвинутым nonce) записывается обратно. Комиссия всё равно взимается, но все остальные изменения аккаунтов отбрасываются.

Краткое описание пайплайна

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)

Справочник ошибок транзакций

В следующей таблице перечислены все варианты TransactionError и этапы пайплайна, на которых они возникают:

ОшибкаЭтапПричина
AccountInUseПланированиеАккаунт уже заблокирован другой транзакцией в том же пакете
AccountLoadedTwiceПланированиеОдин и тот же pubkey встречается дважды в account_keys транзакции
AccountNotFoundПроверка плательщика комиссииАккаунт плательщика комиссии не существует
ProgramAccountNotFoundЗагрузка аккаунтаВызываемая программа не существует
InsufficientFundsForFeeПроверка плательщика комиссииПлательщик комиссии не может покрыть комиссию + минимальный остаток для освобождения от rent
InvalidAccountForFeeПроверка плательщика комиссииПлательщик комиссии не является системным или nonce аккаунтом
AlreadyProcessedКэш статусаТранзакция уже была обработана
BlockhashNotFoundПроверка возрастаBlockhash отсутствует в очереди и не является допустимым nonce
InstructionErrorИсполнениеПроизошла ошибка при обработке инструкции (включает индекс инструкции и конкретный InstructionError)
CallChainTooDeepЗагрузка аккаунтаЦепочка вызовов загрузчика слишком длинная
MissingSignatureForFeeПроверкаДля транзакции требуется комиссия, но подпись отсутствует
InvalidAccountIndexПроверкаТранзакция содержит недопустимую ссылку на аккаунт
SignatureFailureПроверка подписиПодпись Ed25519 не подтверждена (пакет отклонён)
InvalidProgramForExecutionЗагрузка аккаунтаПрограмма не принадлежит допустимому загрузчику
SanitizeFailureПроверкаНе удалось корректно проверить смещения аккаунтов в транзакции
ClusterMaintenanceПланированиеТранзакции временно отключены из-за обслуживания кластера
AccountBorrowOutstandingИсполнениеВ результате обработки транзакции аккаунт остался с незавершённой заимствованной ссылкой
WouldExceedMaxBlockCostLimitПланированиеТранзакция превысит максимальный лимит стоимости блока
UnsupportedVersionПроверкаВерсия транзакции не поддерживается
InvalidWritableAccountЗагрузка аккаунтаТранзакция загружает доступный для записи аккаунт, который нельзя изменять
WouldExceedMaxAccountCostLimitПланированиеТранзакция превысит максимальный лимит стоимости аккаунта в блоке
WouldExceedAccountDataBlockLimitПланированиеТранзакция превысит лимит данных аккаунта в блоке
TooManyAccountLocksПланированиеТранзакция заблокировала слишком много аккаунтов
AddressLookupTableNotFoundЗагрузка аккаунтаАккаунт таблицы поиска адресов не существует
InvalidAddressLookupTableOwnerЗагрузка аккаунтаТаблица поиска адресов принадлежит неверной программе
InvalidAddressLookupTableDataЗагрузка аккаунтаТаблица поиска адресов содержит некорректные данные
InvalidAddressLookupTableIndexЗагрузка аккаунтаВ таблице поиска адресов используется некорректный индекс
InvalidRentPayingAccountПостпроверкаАккаунт перешёл из состояния освобождённого от rent в состояние с оплатой rent
WouldExceedMaxVoteCostLimitПланированиеТранзакция превысит максимальный лимит стоимости голосования
WouldExceedAccountDataTotalLimitПланированиеТранзакция превысит общий лимит данных аккаунтов
DuplicateInstructionРазбор compute budgetДублирующий вариант инструкции compute budget в одной транзакции
InsufficientFundsForRentПостпроверкаНа аккаунте недостаточно lamport для оплаты rent за размер данных
MaxLoadedAccountsDataSizeExceededЗагрузка аккаунтаОбщий объём загруженных данных превышает лимит 64 МБ
InvalidLoadedAccountsDataSizeLimitРазбор compute budgetSetLoadedAccountsDataSizeLimit установлен в 0
ResanitizationNeededПроверкаТранзакция отличается до/после активации функции и требует повторной проверки
ProgramExecutionTemporarilyRestrictedЗагрузка аккаунтаИсполнение программы временно ограничено на данном аккаунте
UnbalancedTransactionПостпроверкаОбщий баланс lamport до транзакции не равен балансу после
ProgramCacheHitMaxLimitЗагрузка аккаунтаКэш программ достиг максимального лимита
CommitCancelledКоммитКоммит отменён внутренне

Is this page helpful?

Управляется

© 2026 Solana Foundation.
Все права защищены.
Связаться с нами