Кожна транзакція в Solana виконується в ізольованому середовищі з обмеженим обсягом обчислень. Цей обмежений ресурс вимірюється в compute units (CU). Якщо програма перевищить виділений ліміт — транзакція відхилиться з помилкою ComputeBudgetExceeded, а сплачені комісії не повернуться. Нижче наведено повну практичну модель того, як compute units працюють, як їх вимірювати, налаштовувати та оптимізувати.

Середовище перевірки прикладів: Solana CLI 1.18.x, кластер Devnet, @solana/web3.js 1.95.x. Перед запуском прикладів переконайтеся, що ваша версія CLI та SDK відповідають вказаним або перевірте сумісність у офіційній документації.

Що таке compute units

Одиниця виміру обчислювальної роботи

Compute unit — це абстрактна одиниця, що вимірює обсяг обчислювальних ресурсів, які споживає транзакція. Solana не рахує час виконання в секундах — вона рахує кількість "одиниць роботи", де кожна базова операція має фіксовану вартість у CU. Це дозволяє мережі справедливо розподіляти ресурси між усіма транзакціями в межах одного слота (400 мс).

Концептуально compute units можна уявити як "бюджет палива" для транзакції. Кожна інструкція, системний виклик, операція з пам'яттю — усе це спалює частину цього палива. Коли паливо закінчується, виконання зупиняється.

Ліміт на транзакцію (200 000 CU за замовчуванням)

Якщо ви не вказали інше, кожна транзакція отримує ліміт 200 000 CU. Це значення застосовується автоматично і не вимагає жодних додаткових інструкцій у транзакції.

Максимально допустимий ліміт змінюється з оновленнями мережі. На момент написання матеріалу він становить 1 400 000 CU, але це значення може бути оновлене наступним апгрейдом. Перевірте актуальний максимум командою solana cluster-version та в офіційній документації перед розгортанням.

Важливий нюанс: compute units не накопичуються між транзакціями. Кожна транзакція починає виконання з нульовим лічильником спожитих CU і власним лімітом.

Як програма витрачає compute units

Приклади: BPF-інструкції, системні виклики, CPI

Розглянемо три основні категорії споживання compute units:

  • BPF-інструкції. Кожна інструкція в скомпільованій програмі (Berkeley Packet Filter VM) коштує приблизно 1–4 CU. Проста арифметика (додавання, множення) — близько 1 CU. Операції з пам'яттю (завантаження, збереження) — 2–4 CU залежно від типу доступу.
  • Системні виклики (syscalls). Звернення до функцій середовища виконання, наприклад sol_log_ для логування або sol_memcpy для копіювання пам'яті. Кожен syscall має власну фіксовану вартість. Наприклад, sol_log_64 коштує приблизно 100 CU за виклик.
  • CPI (Cross-Program Invocation). Коли ваша програма викликає іншу програму через CPI, витрати CU складаються з: базової вартості самого виклику CPI (приблизно 250 CU) плюс усі compute units, які споживає викликана програма під час цього виклику. Ці "вкладені" витрати повністю зараховуються до бюджету вашої транзакції.

Приклад: якщо ваша програма виконує 50 000 BPF-інструкцій (≈50 000–200 000 CU), робить 10 викликів sol_log_64 (≈1 000 CU) і викликає іншу програму через CPI, яка споживає 80 000 CU — загальне споживання може сягнути 130 000–280 000 CU залежно від типу інструкцій.

Як виміряти споживання CU у своїй програмі

Solana надає syscall sol_log_compute_units(), який виводить у лог поточне значення лічильника спожитих compute units. У Rust-програмі це виглядає так:

solana_program::log::sol_log_compute_units();

Розмістіть цей виклик у різних точках програми, щоб побачити, який саме блок коду споживає найбільше ресурсів.

Для перевірки на Devnet виконайте транзакцію з детальним виведенням логів:

solana confirm -v <TRANSACTION_SIGNATURE>

У виводі знайдіть рядки з префіксом Program log: CU consumed: — це значення, які вивів ваш виклик sol_log_compute_units(). Останнє значення перед завершенням програми покаже загальне споживання.

Compute budget

Як встановити compute limit через CLI та SDK

Compute budget — це механізм, через який ви явно заявляєте мережі, скільки compute units потребує ваша транзакція. Це робиться спеціальними інструкціями ComputeBudgetProgram, які обов'язково мають бути першими інструкціями в транзакції — до будь-яких інших інструкцій.

Через CLI:

solana transfer --compute-unit-limit 300000 <RECIPIENT_ADDRESS> 0.001

Ця команда додасть інструкцію SetComputeUnitLimit з значенням 300 000 CU на початок транзакції.

Через @solana/web3.js (TypeScript):

import { ComputeBudgetProgram, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
const setComputeUnitLimit = ComputeBudgetProgram.setComputeUnitLimit({
  units: 300_000,
});

Додайте setComputeUnitLimit як перший елемент масиву інструкцій при побудові TransactionMessage.

Як встановити priority fee

Priority fee — це додаткова комісія за обчислювальні одиниці, що впливає на порядок включення транзакції в блок. Вона встановлюється окремою інструкцією SetComputeUnitPrice, яка також має бути серед перших інструкцій транзакції (після SetComputeUnitLimit, якщо він є).

Через CLI:

solana transfer --compute-unit-price 50000 <RECIPIENT_ADDRESS> 0.001

Значення 50000 — це мікролампорти за один compute unit. Загальна додаткова комісія розраховується як compute_unit_limit × compute_unit_price.

Через @solana/web3.js:

const setComputeUnitPrice = ComputeBudgetProgram.setComputeUnitPrice({
  microLamports: 50_000,
});

Детальний розбір того, як формується загальна комісія транзакції та як вибрати значення priority fee, розглядається в окремому матеріалі про комісії транзакцій у Solana.

Що робити при ComputeBudgetExceeded

Діагностика: яка instruction перевищила ліміт

Помилка ComputeBudgetExceeded вказує на те, що сукупне споживання CU усіма інструкціями транзакції перевищило встановлений ліміт. Щоб знайти винуватця, виконайте такі кроки:

  1. Отримайте детальні логи транзакції:
    solana confirm -v <SIGNATURE>
  2. Знайдіть в логах останній успішний виклик sol_log_compute_units() перед помилкою. Значення поруч із ним покаже, скільки CU було спожито до моменту відмови.
  3. Визначте, яка саме інструкція транзакції виконувалася в момент перевищення ліміту. У логах це буде остання програма, з якою взаємодіяла транзакція перед помилкою.
  4. Якщо в програмі немає викликів sol_log_compute_units(), додайте їх у ключові точки (перед циклами, після CPI-викликів, перед великими обчисленнями) і повторіть транзакцію на Devnet.

Оптимізація: зменшення кількості операцій

Після того, як ви ідентифікували "важку" ділянку, застосуйте один із підходів:

  • Зменште кількість ітерацій у циклах. Якщо програма обробляє масив даних у циклі, перевірте, чи можна обробляти елементи частинами (chunking) через кілька транзакцій замість однієї.
  • Оптимізуйте CPI-виклики. Кожен CPI додає базову вартість плюс витрати викликаної програми. Перевірте, чи всі CPI-виклики необхідні, чи можна об'єднати кілька викликів в один.
  • Зменшіть використання syscalls для логування. Виклики sol_log_* споживають CU. У production-версії програми видаліть або мінімізуйте логування.
  • Збільште compute limit. Якщо програма вже оптимізована і просто потребує більше ресурсів — встановіть вищий ліміт через SetComputeUnitLimit. Це коректне рішення, якщо обчислення неможливо розбити на частини.

Типові помилки

ComputeBudgetExceeded

Найпоширеніша помилка початківців. Причини:

  • Програма обробляє більше даних, ніж очікувалося (наприклад, масив змінної довжини виявився більшим).
  • CPI-виклик передав управління програмі, яка сама спожила значну частину бюджету.
  • Не було встановлено явний compute limit, і дефолтних 200 000 CU виявилося недостатньо.

Рішення: додайте sol_log_compute_units() у програму, виміряйте реальне споживання на Devnet і встановіть адекватний ліміт з запасом 10–20%.

Невірний compute limit

Ця помилка має два виміри:

Занижений ліміт. Транзакція постійно відхиляється з ComputeBudgetExceeded, незважаючи на коректну логіку програми. Рішення — виміряти реальне споживання і встановити ліміт вище.

Завищений ліміт. Ви встановили 1 400 000 CU для транзакції, яка споживає 30 000 CU. Сама по собі це не викликає помилки, але якщо ви також встановили priority fee, ви переплачуєте: комісія розраховується як compute_unit_limit × compute_unit_price, і завищений ліміт множиться на ціну за CU.

Правильний підхід: виміряйте реальне споживання на Devnet, додайте запас 10–20% і встановіть цей значення як compute limit. Не використовуйте максимальний ліміт "на всякий випадок" — це марно витрачає кошти, особливо з високим priority fee.

Наступний крок: тепер, коли ви розумієте, як compute units керують обчислювальними ресурсами транзакції, ви готові до практичної роботи — створення та відправки першої транзакції на Devnet, де ці знання знадобляться для налаштування коректного compute budget.

Джерела