Simulation дозволяє виконати транзакцію на валідаторі без фактичного запису в леджер і без витрати SOL. Для production-застосунків це основний інструмент перевірки перед відправкою: він дає змогу виявити помилки, оцінити споживання compute units та перевірити повернуті дані, зберігаючи кошти користувача.

У цій інструкції розібрано метод simulateTransaction з боку RPC, формат його відповіді, практичні схеми аналізу ресурсів та відомі обмеження, які впливають на достовірність результату.

simulateTransaction: що повертає

Метод simulateTransaction приймає серіалізовану транзакцію та набір параметрів конфігурації. Два найважливіші прапорці:

  • sigVerify — якщо true, валідатор перевіряє криптографічні підписи. У більшості випадків для швидкої перевірки логіки достатньо false, що суттєво зменшує навантаження.
  • replaceRecentBlockhash — якщо true, валідатор замінює recent_blockhash у транзакції на актуальний перед симуляцією. Це корисно, коли транзакція була сформована давно, але її логіка досі актуальна.

У відповідь RPC повертає об'єкт value, який містить кілька ключових полів:

Поле Тип Призначення
err null | об'єкт помилки Якщо null — транзакція виконалась успішно в рамках симуляції. Інакше містить структурований опис помилки (наприклад, InstructionError з індексом інструкції та кодом).
logs масив рядків Лог-вивід програм, аналогічний до того, що генерується при реальному виконанні. Корисний для діагностики, але не замінює повноцінний аналіз логів, який розглядається окремо.
unitsConsumed число Кількість compute units, витрачених під час симуляції. Це орієнтовне значення для встановлення ліміту в computeBudget.
accounts масив об'єктів (за запитом) Стан акаунтів після симуляції. Повертається лише якщо у конфігурації вказано encoding та масив accounts.addresses. Дозволяє перевірити зміну даних без виконання транзакції.
returnData об'єкт (за наявності) Дані, повернуті інструкцією через sol_log_data. Містить data (Base58) та programId.

Типова відповідь успішної симуляції виглядає так:

{ "err": null, "logs": [...], "unitsConsumed": 12450, "returnData": null }

Важливо розуміти: поле err у симуляції відповідає саме за логіку виконання. Відсутність помилки тут не гарантує, що транзакція пройде при реальній відправці — про це детально у розділі обмежень.

Аналіз compute unit consumption до відправки

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

Базова схема оцінки

  1. Сформуйте транзакцію без інструкції ComputeBudget (або з мінімальним лімітом).
  2. Викличте simulateTransaction із sigVerify: false.
  3. Значення unitsConsumed з відповіді використайте як базу для розрахунку фінального ліміту.

Чому не можна брати значення один в один

Реальне споживання compute units може відрізнятися від симульованого з кількох причин:

  • Зміна стану між симуляцією та відправкою. Якщо інший агент змінив дані акаунтів, які читає ваша транзакція, гілка виконання може змінитися, і витрати зростуть.
  • Час виконання CPI. Зовнішні виклики (CPI) до програм, які ви не контролюєте, можуть мати різну тривалість залежно від внутрішнього стану.
  • Попередні інструкції в тій самій транзакції. Якщо ваша транзакція містить кілька інструкцій, і перша змінює стан, який впливає на другу, симуляція враховує це, але лише в межах поточного стану леджера на момент виклику.

Практична формула

Для production-сценаріїв рекомендується додавати запас поверх значення unitsConsumed. Типовий підхід — додати 10–20% залежно від стабільності виконання програми:

  • Для транзакцій із детермінованою логікою (без умовних гілок, що залежать від зовнішнього стану) — запас 5–10%.
  • Для транзакцій із CPI до сторонніх програм або умовними гілками — запас 15–25%.
  • Абсолютний максимум не повинен перевищувати 1 400 000 CU (з урахуванням резерву для самого runtime).

Після встановлення ліміту проведіть повторну симуляцію вже з фінальною транзакцією (включно з ComputeBudget), щоб переконатися, що ліміт не обрізає виконання.

Виявлення помилок без витрати SOL

Головна цінність симуляції — можливість діагностувати помилки до відправки. Кожна невдала відправка на mainnet-beta коштує базову комісію транзакції (навіть якщо транзакція відхилена). При масових операціях або складних інструкціях ці витрати стають відчутними.

Типові помилки, які виявляє симуляція

  • InstructionError з кодами помилок програми. Наприклад, InstructionError [0, Custom(6001)] вказує на кастомну помилку з кодом 6001 у першій інструкції. Це дозволяє точно локалізувати проблему без аналізу логів валідатора.
  • AccountInUse. Акаунт вже заблокований іншою транзакцією в поточному слоті. Симуляція може показати це, хоча реальна ситуація залежить від моменту відправки.
  • InsufficientFunds. На акаунті, що сплачує комісію, недостатньо SOL. Симуляція перевіряє баланс на момент виклику.
  • PreinstructionFailure для ComputeBudget. Неправильно встановлені параметри бюджету (наприклад, ліміт менший за мінімально допустимий).

Алгоритм перевірки перед відправкою

  1. Симулюйте з sigVerify: false. Швидка перевірка логіки. Якщо err не null — розбирайте помилку, не відправляйте.
  2. Симулюйте з sigVerify: true. Перевірка підписів. Це повільніше, але виявляє проблеми з серіалізацією підписів та несупадіння підписувачів.
  3. Перевірте unitsConsumed. Якщо значення близьке до встановленого ліміту або перевищує його — збільште ліміт і поверніться до кроку 1.
  4. Якщо всі перевірки пройдені — відправляйте транзакцію через sendTransaction.

Цей підхід не гарантує 100% успіху відправки, але усуває переважну більшість помилок, які можна виявити на стороні клієнта.

Обмеження simulation для складних сценаріїв

Symulation виконується в ізольованому контексті на одному валідаторі. Це створює фундаментальні обмеження, які треба враховувати при проектуванні перевірок.

Стан не фіксується

Зміни, які симуляція вносить у стан акаунтів, не записуються в леджер. Це означає, що неможливо симулювати ланцюжок транзакцій, де друга залежить від результату першої, викликавши simulateTransaction послідовно. Кожен виклик починається з одного й того ж стану леджера.

Для тестування ланцюжків використовуйте local validator, де транзакції виконуються реально. Це окрема тема, розглянута в матеріалі про налаштування local validator для тестування.

Блокування акаунтів (account locking)

Під час реальної обробки в банку транзакцій (transaction processing pipeline) валідатор блокує акаунти, що записуються, на рівні всього кластера. Симуляція не бере участь у цьому механізмі. Тому помилка AccountInUse, яку показує симуляція, базується на поточному стані банку на конкретному валідаторі, а не на реальній ситуації в момент потрапляння транзакції в банк.

Недетерміновані програми

Якщо програма залежить від даних, які змінюються між симуляцією та відправкою (курси оракулів, стан пулів ліквідності, nonce-значення), результат симуляції може бути невалідним до моменту реальної відправки. У таких випадках симуляція підтверджує лише коректність структури транзакції, а не її семантичний результат.

Обмеження на запитувані акаунти

Параметр accounts.addresses у конфігурації симуляції дозволяє отримати стан акаунтів після виконання. Проте є обмеження на розмір відповіді та кількість акаунтів. Для транзакцій, що зачіпають десятки акаунтів (типово для DeFi-операцій із кількома пулами), запит усіх акаунтів може призвести до перевищення ліміту розміру відповіді RPC-вузла.

MEV та порядок транзакцій

Симуляція не моделює порядок виконання транзакцій у банку. Для сценаріїв, де результат залежить від порядку (арбітраж, ліквідації, sniping), симуляція дає лише верхню оцінку ресурсів, але не передбачає реальний результат виконання.

Коли симуляції недостатньо

  • Транзакції, що залежать від попередніх транзакцій у тому ж блоці.
  • Сценарії з критичною залежністю від точного стану пулу чи оракула в момент виконання.
  • Тестування edge-кейсів, що вимагають специфічного стану леджера, який неможливо відтворити на mainnet-beta.

У цих випадках комбінуйте симуляцію з тестуванням на local validator та аналізом логів програми для повної діагностики.

Джерела