Транзакція у Solana може завершитися невдало з десятків різних причин — від банальної нестачі SOL на комісію до внутрішньої логічної помилки в смарт-контракті. Усі вони зводяться до одного рядка в відповіді RPC: transaction failed. Щоб відновити роботу, потрібно витягнути фактичну причину з ланцюжка логів, класифікувати її та застосувати точкове виправлення. Нижче — інженерний алгоритм діагностики від сигнатури до кореневої причини.
Кроки отримання деталей помилки
Використання getTransaction з maxSupportedTransactionVersion
Перший крок — отримати повні метадані транзакції через RPC-метод getTransaction. Ключовий параметр: maxSupportedTransactionVersion. Якщо ваша транзакція використовує формат v0 (з адресною таблицею lookup table), без цього параметра RPC-вузол поверне помилку або взагалі не знайде транзакцію.
Передумови: підключення до RPC-вузла з рівнем консенсусу finalized (щоб уникнути хибних негативних результатів для ще не підтверджених транзакцій) та наявність сигнатури транзакції (base58-рядок, 88 символів).
Очікуваний результат: об'єкт із заповненим полем meta.err та масивом meta.logMessages.
Приклад виклику через solana-cli:
solana confirm -v --url RPC_ENDPOINT SIGNATURE
Ця команда внутрішно викликає getTransaction із maxSupportedTransactionVersion=0. Для транзакцій v0 потрібен прямий RPC-виклик:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransaction",
"params": [
"SIGNATURE",
{
"encoding": "json",
"maxSupportedTransactionVersion": 0,
"commitment": "finalized"
}
]
}
Спосіб перевірки: якщо meta.err дорівнює null, а meta.err відсутнє — перевірте, чи не потрапила транзакція в статус dropped (у такому разі getTransaction її не знайде, потрібен getSignatureStatuses).
Ризик: деякі публічні RPC-вузли обмежують глибину історії. Якщо транзакція старіша за кілька блоків, запит може повернути null. У такому разі зверніться до архівного вузла або альтернативного провайдера.
Розшифрування логів програми
Поле meta.logMessages містить послідовність рядків, згенерованих усіма програмами під час виконання транзакції. Кожен рядок має префікс із програмного ID, що дозволяє відстежити, яка саме програма і в якому інструкційному контексті згенерувала повідомлення.
Типова структура логів:
- Program PROGRAM_ID invoke [1] — початок виконання інструкції (число в дужках — рівень вкладеності CPI).
- Program log: TEXT — користувацький лог з msg! у Rust-коді програми.
- Program PROGRAM_ID consumed N of M compute units — спожиті одиниці обчислень.
- Program PROGRAM_ID failed: custom program error: 0xHEX — програма повернула власний код помилки.
- Program PROGRAM_ID return: DATA — успішне повернення даних.
Для програм, написаних на Anchor, логи містять додаткову інформацію: ім'я помилки та її числовий код. Наприклад: AnchorError caused by account: system_program. Error Code: InvalidSeeds. Error Number: 4006.
Критичний нюанс: якщо логи обриваються без фінального рядка про помилку, найімовірніше транзакція вичерпала обчислювальний бюджет (compute budget exceeded). У такому разі meta.err міститиме InstructionError::ComputationalBudgetExceeded.
Безпечний крок для відтворення: перед тим як модифікувати код, використовуйте simulateTransaction з тим самим набором інструкцій. Симуляція поверне логи без реального відправлення транзакції та списання коштів. Це дозволяє перевірити гіпотезу без ризику.
Класифікація помилок за типом
Усі помилки виконання транзакцій у Solana поділяються на чотири фактичні категорії. Класифікація базується на тому, на якому етапі обробки сталася відмова та що саме містить meta.err.
| Категорія | Ознака в meta.err | Фактична причина |
|---|---|---|
| Помилки передумов (pre-flight) | TransactionError з кодами 1–9 (наприклад, InsufficientFundsForFee, AccountNotFound, BlockhashNotFound) | Транзакція не пройшла валідацію до потрапляння в слот. Перевірте баланс, наявність акаунтів, валідність blockhash. |
| Помилки виконання інструкцій | InstructionError з підкодами (0–10: InvalidAccountData, AccountBorrowFailed, DuplicateAccountIndex тощо) | Рантайм-помилка під час виконання конкретної інструкції. Індекс інструкції вказано в структурі InstructionError. |
| Програмні помилки (custom) | InstructionError::Custom(ErrorCode) або текстове представлення 0xHEX | Програма навмисно повернула помилку через код, написаний розробником. Потрібно звернутися до вихідного коду програми. |
| Ресурсні обмеження | InstructionError::ComputationalBudgetExceeded або TransactionError::TooManyAccountLocks | Вичерпано compute units, перевищено ліміт блокувань акаунтів або розмір транзакції. |
Окремий підтип — помилки CPI (Cross-Program Invocation). Вони фіксуються в логах як Program CALLEE invoke [N] з подальшим Program CALLEE failed, після чого викликана програма повертає управління, і батьківська програма теж фіксує відмову. Індекс інструкції в meta.err вказує на батьківську інструкцію, тому для точного визначення місця відмови потрібно аналізувати логи за рівнем вкладеності.
Типова помилка діагностики: припущення, що custom program error: 0x0 означає «успіх». Насправді код 0 у контексті помилки — це просто перший визначений розробником код помилки. Успіх не генерує рядка про помилку взагалі.
Інструменти для автоматичної діагностики
Ручний розбір логів працює для поодиноких випадків. У production-середовищі, де обсяг транзакцій вимірюється сотнями або тисячами на хвилину, потрібна автоматизація.
1. Власний парсер логів на базі RPC. Скрипт, який періодично опитує getSignatureStatuses для масиву сигнатур, фільтрує ті, де confirmationStatus не дорівнює finalized або err не є null, і для кожної такої транзакції викликає getTransaction для витягування логів. Парсер застосовує регулярні вирази для витягування програмних ID, рівнів вкладеності та кодів помилок.
Обмеження: цей підхід генерує значне навантаження на RPC-вузол і може потрапити під rate limits. Обов'язково кешуйте результати та використовуйте пакетну обробку.
2. geyser-плагіни для потокової діагностики. Підключення до потоку транзакцій через Geyser Plugin інтерфейс дозволяє отримувати логи в реальному часі без повторних RPC-запитів. Плагін отримує повні метадані кожної транзакції, включно з logMessages, одразу після обробки в валідаторі.
Обмеження: для цього потрібен власний валідатор або виділений інфраструктурний вузол із увімкненим Geyser. Це виходить за межі типової backend-інфраструктури й належить до компетенції інфраструктурної команди.
3. Моніторинг через WebSocket-підписки. Метод signatureSubscribe дозволяє отримувати статус конкретної транзакції в реальному часі. Це корисно для синхронних сценаріїв, коли ваш сервіс очікує підтвердження відправленої транзакції.
Обмеження: signatureSubscribe повертає лише статус, а не повні логи. Для отримання деталей все одно потрібен додатковий виклик getTransaction.
Усі три підходи комбінуються: WebSocket для миттєвого виявлення відмов, RPC-парсер для деталізації, Geyser — для масштабування на рівні інфраструктури.
План відкату при масових відмовах
Масова відмова транзакцій — ситуація, коли відсоток невдач різко зростає порівняно з базовим рівнем. План дій має бути задокументований заздалегідь, а не розробляється в момент кризи.
Крок 1. Підтвердження масштабу. Зафіксуйте метрику: відсоток невдач за останні 10 хвилин порівняно з попередньою годиною. Якщо зростання менше ніж у 3 рази — це, ймовірно, не масова відмова, а локальна проблема з конкретним типом транзакцій. Перейдіть до звичайної діагностики.
Крок 2. Ізоляція типу помилки. Згрупуйте невдалі транзакції за значенням meta.err. Якщо всі помилки однотипні (наприклад, BlockhashNotFound) — причина в інфраструктурі, а не в логіці програм. Якщо помилки різні, але всі стосуються однієї програми — проблема, ймовірно, у стані цієї програми (наприклад, зміна даних акаунта, який порушив інваріанти).
Крок 3. Зупинка відправки. Припиніть відправку нових транзакцій, що використовують проблемний шлях. Це критичний крок: продовження відправки під час масової відмови лише посилює навантаження на RPC і ускладнює діагностику через шум у логах.
Крок 4. Перевірка гіпотези на simulateTransaction. Візьміть кілька невдалих транзакцій і прогоніть через simulateTransaction з поточним станом ланцюжка. Якщо симуляція повертає ту саму помилку — проблема відтворювана і пов'язана зі станом ланцюжка. Якщо симуляція успішна — проблема могла бути тимчасовою (конкуренція за слот, мережева затримка).
Крок 5. Відновлення. Після усунення кореневої причини відновлюйте відправку поступово: спочатку 10% від звичайного обсягу, моніторинг відсотка невдач протягом 2 хвилин, потім 50%, потім 100%. Не відправляйте масово всі транзакції, що накопичилися в черзі, одночасно — це може спровокувати нову хвилю відмов через перевантаження.
Крок 6. Постмортем. Зафіксуйте: часові мітки, відсоток невдач, тип помилки, кореневу причину, час відновлення, зміни в коді чи конфігурації. Без цього документа наступна масова відмова буде діагностуватися з нуля.
Межа застосування цього плану: він не покриває випадки, коли масова відмова пов'язана з проблемами самого кластера Solana (зупинка консенсусу, деградація валідаторів). У таких ситуаціях діагностика транзакцій не має сенсу — потрібно моніторити статус кластера через офіційні канали.
Наступний логічний крок після діагностики невдалої транзакції — перевірка, чи не пов'язана помилка зі строком дії blockhash. Якщо meta.err містить BlockhashNotFound, перейдіть до матеріалу про те, що означає blockhash expired і як це виправити.