Кратко
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.
Сравнение форматов
| Ограничение | legacy | v0 | v1 |
|---|---|---|---|
| Максимальный размер транзакции | 1 232 байта | 1 232 байта | 4 096 байт |
| Адреса аккаунтов | ~32, ограничено размером | 64, через таблицы поиска | 64, встроенные |
| Таблицы поиска адресов | не поддерживаются | поддерживаются | не поддерживаются |
| Ограничения ресурсов | Инструкции ComputeBudget | Инструкции ComputeBudget | конфигурация сообщения |
Формат legacy
Исходный формат, по-прежнему используемый по умолчанию в большинстве инструментов. Он не
имеет префикса версии вообще: первый байт транзакции — это счётчик массива подписей в
формате compact-u16, а первый байт сообщения — num_required_signatures, у которого
старший бит всегда сброшен.
Структура legacy на уровне байтов
| Поле | Размер | Описание |
|---|---|---|
num_signatures | compact-u16 | Количество подписей |
signatures | num_signatures x 64 байта | Подписи Ed25519 |
header | 3 байта | MessageHeader — старший бит первого байта сброшен |
num_account_keys | compact-u16 | Количество ключей аккаунтов |
account_keys | num_account_keys x 32 байта | Публичные ключи, все встроенные |
recent_blockhash | 32 байта | Идентификатор времени жизни |
num_instructions | compact-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_signatures | compact-u16 | Количество подписей |
signatures | num_signatures x 64 байта | Подписи Ed25519 |
0x80 | 1 байт | Байт-префикс версии — первый байт сообщения |
header | 3 байта | MessageHeader (как в legacy) |
num_account_keys | compact-u16 | Количество статических ключей аккаунтов |
static_account_keys | num_account_keys x 32 байта | Ключи, которые явно присутствуют в транзакции |
recent_blockhash | 32 байта | Идентификатор времени жизни |
num_instructions | compact-u16 | Количество инструкций |
instructions | переменная | Такой же формат, как в legacy |
address_table_lookups | compact-u16 + переменный | Ссылки на ALT (см. ниже) |
Каждая запись в таблице поиска адресов содержит:
| Поле | Размер | Описание |
|---|---|---|
account_key | 32 байта | Публичный ключ ALT аккаунта |
writable_indexes | compact-u16 + N x 1 байт | Индексы в ALT для записей с правом записи |
readonly_indexes | compact-u16 + N x 1 байт | Индексы в ALT для записей только для чтения |
Таблицы поиска адресов
ALT — это ончейн-аккаунт, который хранит до 256 публичных ключей. Ссылаясь на ALT, транзакция может включать дополнительные аккаунты, используя 1-байтовые индексы вместо 32-байтовых публичных ключей, что значительно снижает накладные расходы на каждый аккаунт.
Во время выполнения, до начала исполнения, validator разрешает все ссылки на ALT в полные публичные ключи. Разрешённые адреса добавляются к статическим ключам аккаунтов для формирования полного списка ключей аккаунтов. Аккаунты, разрешённые через ALT, следуют тому же порядку, что и статические аккаунты: сначала идут записи с правом записи, затем только для чтения.
Таблицы поиска адресов влияют только на то, как аккаунты указываются в транзакции на проводе. Во время исполнения рантайм разрешает все индексы в полные адреса аккаунтов. Аккаунты, разрешённые через ALT, могут быть только с правом записи или только для чтения (не подписанты); они не могут быть подписантами.
Ограничения ресурсов в v0
Без изменений по сравнению с legacy: инструкции ComputeBudget с теми же значениями по умолчанию при их отсутствии.
Формат v1
v1 увеличивает ограничение размера до 4 096 байт и реструктурирует сообщение вокруг конфигурации транзакции: ограничения ресурсов выносятся из инструкций ComputeBudget в фиксированные поля самого сообщения. Это позволяет сети определять приоритет транзакции по комиссии с помощью одного чтения по фиксированному смещению, без необходимости сканировать и десериализовывать список инструкций.
Структура v1 на уровне байтов
| Поле | Размер | Описание |
|---|---|---|
0x81 | 1 байт | Байт-префикс версии — первый байт транзакции |
header | 3 байта | MessageHeader (такой же, как в legacy) |
config_mask | 4 байта | Битовая маска u32 LE, отмечающая присутствующие поля конфигурации |
recent_blockhash | 32 байта | Идентификатор времени жизни |
num_instructions | 1 байт | Счётчик фиксированной ширины, максимум 64 |
num_addresses | 1 байт | Счётчик фиксированной ширины, максимум 64 |
addresses | N x 32 байта | Адреса аккаунтов, все встроенные — без ссылок на таблицы поиска |
config_values | 0–20 байт | По одному значению на каждый установленный бит маски, в порядке битов (см. ниже) |
instruction_headers | N x 4 байта | Для каждой инструкции: program_id_index (u8), num_accounts (u8), data_len (u16 LE) |
instruction_payloads | переменная | Для каждой инструкции: индексы аккаунтов, затем instruction data |
signatures | N 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 / v0 | v1 | Симптом при отсутствии |
|---|---|---|---|
| Лимит вычислительных единиц | 200k на инструкцию, максимум 1,4M | 0 CU | Немедленный сбой, бюджет исчерпан |
| Размер данных загруженных аккаунтов | 64 МиБ | 0 байт | MaxLoadedAccountsDataSizeExceeded при загрузке первого аккаунта |
| Приоритетная комиссия | 0 | 0 | — |
| Размер кучи | 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/kit8.0+, Rust-крейты поколения Agave 4.2.x или web3.js v3. web3.js v1 читает v1 начиная с версии 1.99.0, но не может формировать и отправлять такие транзакции.
Полное руководство по миграции — включая поддержку клиентских библиотек, определение версии при потоковой передаче (Geyser/gRPC) и поведение при симуляции — см. на странице обновления до формата транзакций v1.
Is this page helpful?