Підсумок
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.
Порівняння форматів
| Ліміт | 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
Кожен акаунт записується як повний 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_signatures | compact-u16 | Кількість підписів |
signatures | num_signatures x 64 байти | Підписи Ed25519 |
0x80 | 1 байт | Байт-префікс версії — перший байт повідомлення |
header | 3 байти | MessageHeader (такий самий, як у застарілому) |
num_account_keys | compact-u16 | Кількість статичних ключів акаунтів |
static_account_keys | num_account_keys x 32 байти | Ключі, що з'являються буквально в транзакції |
recent_blockhash | 32 байти | Специфікатор терміну дії |
num_instructions | compact-u16 | Кількість інструкцій |
instructions | змінна | Той самий формат, що й у застарілому |
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-байтових публічних ключів, що значно зменшує витрати на кожен обліковий запис.
Під час виконання, перед початком обробки, валідатор розв'язує всі посилання на 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 — без множення, без
округлення. Не переносьте арифметику per-CU. Формула загальної комісії
залишається незмінною: (signatures × lamports_per_signature) + priority_fee.
Інструкції ComputeBudget є холостими операціями у v1
Транзакція v1 не відхиляє інструкції ComputeBudget — вона ігнорує їх для налаштування. Вони все одно виконуються як успішні холості операції, витрачаючи 150 обчислювальних одиниць та один із 64 slot інструкцій, не vпливаючи на бюджет. Видаляйте їх при побудові транзакцій 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— ціле число JSON1, а не рядок"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/kit8.0+, Rust-крейти покоління Agave 4.2.x або web3.js v3. web3.js v1 читає v1 починаючи з версії 1.99.0, але не може будувати чи надсилати транзакції v1.
Повний посібник з міграції — включно з підтримкою клієнтських бібліотек, визначенням версії у потоках (Geyser/gRPC) та поведінкою симуляції — дивіться на сторінці оновлення формату транзакцій до v1.
Is this page helpful?