Транзакции с версией

Кратко

Solana поддерживает три формата транзакций: legacy, v0 и v1. v0 добавляет таблицы поиска адресов (ALT) для обращения к аккаунтам через однобайтовые индексы. v1 увеличивает ограничение размера до 4 096 байт, переносит ограничения ресурсов непосредственно в сообщение и убирает ALT.

Solana поддерживает три формата транзакций: legacy, v0 и v1. Каждый формат описан ниже по одному и тому же плану из трёх частей: как он располагает байты в сети, как обращается к аккаунтам и откуда берёт ограничения ресурсов.

Статус активации v1

Формат v1 пока не активен ни на одном кластере. Активация запланирована в Agave v4.2. solana-test-validator версии 4.2+ позволяет тестировать транзакции v1 локально. Существующим приложениям рекомендуется ознакомиться с разделом подготовка к v1.

Сравнение форматов

Ограничениеlegacyv0v1
Максимальный размер транзакции1 232 байта1 232 байта4 096 байт
Адреса аккаунтов~32, ограничено размером64, через таблицы поиска64, встроенные
Таблицы поиска адресовне поддерживаютсяподдерживаютсяне поддерживаются
Ограничения ресурсовИнструкции ComputeBudgetИнструкции ComputeBudgetконфигурация сообщения

Перейти к: Legacy · v0 · v1

Формат legacy

Исходный формат, по-прежнему используемый по умолчанию в большинстве инструментов. Он не имеет префикса версии вообще: первый байт транзакции — это счётчик массива подписей в формате compact-u16, а первый байт сообщения — num_required_signatures, у которого старший бит всегда сброшен.

Структура legacy на уровне байтов

ПолеРазмерОписание
num_signaturescompact-u16Количество подписей
signaturesnum_signatures x 64 байтаПодписи Ed25519
header3 байтаMessageHeader — старший бит первого байта сброшен
num_account_keyscompact-u16Количество ключей аккаунтов
account_keysnum_account_keys x 32 байтаПубличные ключи, все встроенные
recent_blockhash32 байтаИдентификатор времени жизни
num_instructionscompact-u16Количество инструкций
instructionsпеременнаяКаждая инструкция сериализована последовательно

Каждый массив переменной длины предваряется длиной в формате compact-u16: 1 байт для значений 0–127, 2–3 байта для больших значений. Описание структуры отдельной инструкции и пример расчёта размера см. в разделе двоичный формат транзакций.

Аккаунты в legacy

Каждый аккаунт записывается в account_keys как полный 32-байтовый публичный ключ, а инструкции ссылаются на него по однобайтовому индексу в этом массиве. Нет возможности обратиться к аккаунту, который не указан явно в транзакции, — именно это ограничивает транзакцию legacy примерно 32 аккаунтами до исчерпания лимита в 1 232 байта.

Ограничения ресурсов в legacy

Лимит вычислительных единиц, лимит размера данных загруженных аккаунтов, размер кучи и приоритетная комиссия задаются путём включения инструкций программы ComputeBudget в транзакцию. Каждая из них занимает один слот инструкции и 150 вычислительных единиц. Опускать их безопасно: среда выполнения использует значения по умолчанию — 200 000 CU на инструкцию (не более 1,4 млн), лимит размера данных 64 МиБ, куча 32 КиБ и приоритетная комиссия равна нулю.

Формат v0

v0 — это legacy-сообщение плюс два дополнения: байт-префикс версии 0x80 и массив address_table_lookups, добавляемый после инструкций. Всё, что предшествует этим добавлениям, побайтово идентично legacy.

Структура v0 на уровне байтов

ПолеРазмерОписание
num_signaturescompact-u16Количество подписей
signaturesnum_signatures x 64 байтаПодписи Ed25519
0x801 байтБайт-префикс версии — первый байт сообщения
header3 байтаMessageHeader (как в legacy)
num_account_keyscompact-u16Количество статических ключей аккаунтов
static_account_keysnum_account_keys x 32 байтаКлючи, которые явно присутствуют в транзакции
recent_blockhash32 байтаИдентификатор времени жизни
num_instructionscompact-u16Количество инструкций
instructionsпеременнаяТакой же формат, как в legacy
address_table_lookupscompact-u16 + переменныйСсылки на ALT (см. ниже)

Каждая запись в таблице поиска адресов содержит:

ПолеРазмерОписание
account_key32 байтаПубличный ключ ALT аккаунта
writable_indexescompact-u16 + N x 1 байтИндексы в ALT для записей с правом записи
readonly_indexescompact-u16 + N x 1 байтИндексы в ALT для записей только для чтения

Таблицы поиска адресов

ALT — это ончейн-аккаунт, который хранит до 256 публичных ключей. Ссылаясь на ALT, транзакция может включать дополнительные аккаунты, используя 1-байтовые индексы вместо 32-байтовых публичных ключей, что значительно снижает накладные расходы на каждый аккаунт.

Во время выполнения, до начала исполнения, validator разрешает все ссылки на ALT в полные публичные ключи. Разрешённые адреса добавляются к статическим ключам аккаунтов для формирования полного списка ключей аккаунтов. Аккаунты, разрешённые через ALT, следуют тому же порядку, что и статические аккаунты: сначала идут записи с правом записи, затем только для чтения.

Таблицы поиска адресов влияют только на то, как аккаунты указываются в транзакции на проводе. Во время исполнения рантайм разрешает все индексы в полные адреса аккаунтов. Аккаунты, разрешённые через ALT, могут быть только с правом записи или только для чтения (не подписанты); они не могут быть подписантами.

Ограничения ресурсов в v0

Без изменений по сравнению с legacy: инструкции ComputeBudget с теми же значениями по умолчанию при их отсутствии.

Формат v1

v1 увеличивает ограничение размера до 4 096 байт и реструктурирует сообщение вокруг конфигурации транзакции: ограничения ресурсов выносятся из инструкций ComputeBudget в фиксированные поля самого сообщения. Это позволяет сети определять приоритет транзакции по комиссии с помощью одного чтения по фиксированному смещению, без необходимости сканировать и десериализовывать список инструкций.

Структура v1 на уровне байтов

ПолеРазмерОписание
0x811 байтБайт-префикс версии — первый байт транзакции
header3 байтаMessageHeader (такой же, как в legacy)
config_mask4 байтаБитовая маска u32 LE, отмечающая присутствующие поля конфигурации
recent_blockhash32 байтаИдентификатор времени жизни
num_instructions1 байтСчётчик фиксированной ширины, максимум 64
num_addresses1 байтСчётчик фиксированной ширины, максимум 64
addressesN x 32 байтаАдреса аккаунтов, все встроенные — без ссылок на таблицы поиска
config_values0–20 байтПо одному значению на каждый установленный бит маски, в порядке битов (см. ниже)
instruction_headersN x 4 байтаДля каждой инструкции: program_id_index (u8), num_accounts (u8), data_len (u16 LE)
instruction_payloadsпеременнаяДля каждой инструкции: индексы аккаунтов, затем instruction data
signaturesN x 64 байтаВ конце, без префикса длины — количество берётся из заголовка

При написании декодера стоит обратить внимание на два структурных отличия от legacy и v0. Счётчики представлены полями фиксированной ширины u8, а не compact-u16, а инструкции разделены на два блока: сначала все заголовки фиксированного размера, затем все полезные нагрузки переменной длины — вместо того чтобы каждая инструкция шла последовательно.

Аккаунты в v1: без таблиц поиска адресов

v1 намеренно убирает поддержку ALT. 64 сырых адреса занимают 2 048 байт, что комфортно вписывается в лимит 4 096 байт, поэтому каждый адрес встроен напрямую, как и в legacy. Если ваше приложение использует таблицы поиска, переход на v1 означает встраивание этих адресов непосредственно в транзакцию.

Ограничения ресурсов в v1: конфигурация транзакции

Конфигурация представляет собой битовую маску u32, за которой следуют значения фиксированной ширины для каждого поля с установленным битом:

Бит(ы)ПолеШиринаПримечания
0–1Приоритетная комиссияu64Итоговое количество lamport — оба бита устанавливаются вместе
2Лимит вычислительных единицu32
3Лимит размера данных загруженных аккаунтовu32
4Запрошенный размер кучиu32

Неизвестные биты отклоняются. Поскольку сообщение подписано, нераспознанные поля конфигурации не могут быть молча проигнорированы.

Приоритетная комиссия — это итоговое количество lamport, а не цена

В legacy и v0 приоритетная комиссия задаётся через SetComputeUnitPrice как микро-lamport за вычислительную единицу, умноженные на лимит вычислительных единиц. В v1 это абсолютная итоговая сумма в lamport — без умножения и округления. Не переносите арифметику на основе CU. Итоговая формула комиссии в остальном не изменилась: (signatures × lamports_per_signature) + priority_fee.

Инструкции ComputeBudget в v1 являются no-op

Транзакция v1 не отклоняет инструкции ComputeBudget — она игнорирует их для целей конфигурации. Они по-прежнему выполняются как успешные no-op, потребляя 150 вычислительных единиц и один из 64 слотов инструкций, но не влияя на бюджет. Удаляйте их при формировании транзакций v1 и не сканируйте их при чтении транзакций v1: значения хранятся в конфигурации сообщения.

Поля конфигурации должны задаваться явно

Наиболее важное поведенческое изменение для отправителей: в отличие от legacy и v0, транзакции v1 должны явно задавать лимит вычислительных единиц и лимит размера данных загруженных аккаунтов, иначе транзакция завершится с ошибкой.

Незаданное полеlegacy / v0v1Симптом при отсутствии
Лимит вычислительных единиц200k на инструкцию, максимум 1,4M0 CUНемедленный сбой, бюджет исчерпан
Размер данных загруженных аккаунтов64 МиБ0 байтMaxLoadedAccountsDataSizeExceeded при загрузке первого аккаунта
Приоритетная комиссия00
Размер кучи32 КиБ32 КиБ

Рекомендуемый подход — выполнить одну симуляцию с максимальными значениями обоих лимитов, затем записать возвращённые unitsConsumed и loadedAccountsDataSize обратно в конфигурацию, округлив размер данных вверх до следующей страницы по 32 КиБ для запаса (модель стоимости блока тарифицирует страницами по 32 КиБ, поэтому запас ниже границы следующей страницы бесплатен).

Подготовка к v1

v1 затрагивает чтение транзакций, а не только их отправку. Когда v1 активируется, любой клиент, вызывающий getTransaction или getBlock без явного указания поддержки, начнёт получать ошибки на транзакциях v1:

  • Передавайте maxSupportedTransactionVersion: 1 — JSON целое число 1, а не строку "1" — в getTransaction и getBlock. Передача 0 даёт сбой на транзакциях v1 точно так же, как и отсутствие параметра, поэтому кодовая база, обновлённая во время перехода на v0, всё равно требует изменения этого значения.
  • getTransaction завершается ошибкой -32015 на транзакции v1, а одна транзакция v1 приводит к сбою всего ответа getBlock с той же ошибкой — частичный результат не возвращается.
  • blockSubscribe возвращает block: null и прекращает продвижение, поэтому потребитель, интерпретирующий это как пустой блок, молча начинает отставать начиная с первого slot v1.
  • getSignaturesForAddress никогда не анализирует тела транзакций, поэтому подписи v1 отображаются в обычном режиме.
  • Ответы с явным указанием поддержки включают объект transactionConfig в сообщении для транзакций v1 (полностью отсутствует для legacy и v0). Конвейеры, определяющие приоритетные комиссии или лимиты вычислений путём сканирования инструкций ComputeBudget, будут молча возвращать ноль для каждой транзакции v1.
  • Используйте encoding: 'base64' при декодировании транзакций на стороне клиента, а также для sendTransaction/simulateTransaction с транзакциями размером более 1 232 байт — кодировка base58 по-прежнему ограничена старым размером.
  • Поддержка в клиентских библиотеках требует актуальных версий: @solana/kit 8.0+, Rust-крейты поколения Agave 4.2.x или web3.js v3. web3.js v1 читает v1 начиная с версии 1.99.0, но не может формировать и отправлять такие транзакции.

Полное руководство по миграции — включая поддержку клиентских библиотек, определение версии при потоковой передаче (Geyser/gRPC) и поведение при симуляции — см. на странице обновления до формата транзакций v1.

Is this page helpful?