Версійні транзакції

Підсумок

Solana має три формати транзакцій: legacy, v0 та v1. v0 додає таблиці пошуку адрес (ALT) для посилань на акаунти через 1-байтові індекси. 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

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

Ліміти ресурсів у форматі legacy

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

Формат v0

v0 — це повідомлення legacy плюс дві речі: байт-префікс версії 0x80 та масив address_table_lookups, доданий після інструкцій. Усе, що передує їм, побайтово ідентично legacy.

Розміщення байт v0 при передачі

ПолеРозмірОпис
num_signaturescompact-u16Кількість підписів
signaturesnum_signatures x 64 байтиПідписи Ed25519
0x801 байтБайт-префікс версії — перший байт повідомлення
header3 байтиMessageHeader (такий самий, як у застарілому)
num_account_keyscompact-u16Кількість статичних ключів акаунтів
static_account_keysnum_account_keys x 32 байтиКлючі, що з'являються буквально в транзакції
recent_blockhash32 байтиСпецифікатор терміну дії
num_instructionscompact-u16Кількість інструкцій
instructionsзміннаТой самий формат, що й у застарілому
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-байтових публічних ключів, що значно зменшує витрати на кожен обліковий запис.

Під час виконання, перед початком обробки, валідатор розв'язує всі посилання на 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 — без множення, без округлення. Не переносьте арифметику per-CU. Формула загальної комісії залишається незмінною: (signatures × lamports_per_signature) + priority_fee.

Інструкції ComputeBudget є холостими операціями у v1

Транзакція v1 не відхиляє інструкції ComputeBudget — вона ігнорує їх для налаштування. Вони все одно виконуються як успішні холості операції, витрачаючи 150 обчислювальних одиниць та один із 64 slot інструкцій, не vпливаючи на бюджет. Видаляйте їх при побудові транзакцій 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 завершується помилкою на транзакції v1 з кодом -32015, а одна транзакція 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, але не може будувати чи надсилати транзакції v1.

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

Is this page helpful?