Кратко
Транзакции проходят 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
оборачивает внешний стор аккаунтов и поддерживает локальный кэш для батча, чтобы
аккаунты, изменённые предыдущими транзакциями в том же батче, были видны
последующим.
Для каждого аккаунта, не являющегося плательщиком комиссии, загрузчик:
- Получает аккаунт из кэша или базы данных аккаунтов
- Обновляет статус освобождения от аренды, если это необходимо
- Суммирует размер данных аккаунта в общий лимит
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
проходит по каждой инструкции и вызывает целевую программу:
- Для каждой инструкции рантайм вызывает
prepare_next_top_level_instruction, который формируетInstructionContext. Этот контекст содержит ссылки на аккаунты инструкции (разрешённые по скомпилированным индексам), instruction data и индекс program account. - Рантайм проверяет, является ли программа precompile (Ed25519, Secp256k1, Secp256r1). Precompile-программы проверяются напрямую, без вызова BPF VM.
- Для всех остальных программ рантайм вызывает
process_instruction, который загружает программу из кэша и выполняет её в BPF виртуальной машине. - После завершения инструкции рантайм
проверяет,
что общий баланс в lamport по всем аккаунтам инструкции не изменился
(проверка
UnbalancedInstruction). - Если какая-либо инструкция завершилась с ошибкой, вся транзакция откатывается. Промежуточные изменения состояния не сохраняются.
Каждая инструкция увеличивает 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 budget | SetLoadedAccountsDataSizeLimit установлен в 0 |
ResanitizationNeeded | Проверка | Транзакция отличается до/после активации функции и требует повторной проверки |
ProgramExecutionTemporarilyRestricted | Загрузка аккаунта | Исполнение программы временно ограничено на данном аккаунте |
UnbalancedTransaction | Постпроверка | Общий баланс lamport до транзакции не равен балансу после |
ProgramCacheHitMaxLimit | Загрузка аккаунта | Кэш программ достиг максимального лимита |
CommitCancelled | Коммит | Коммит отменён внутренне |
Is this page helpful?