Відтворення production-помилки в локальному середовищі — це не про запуск тестів, а про ізоляцію конкретного стану та послідовності дій, які призвели до збою. Нижче — перевірена послідовність кроків для Solana local validator, яка дозволяє завантажити реальні дані з mainnet і програти сценарій до моменту виникнення помилки.

Експорт стану акаунтів з mainnet

Перший крок — перенести стан акаунтів, залучених у помилку, з mainnet у локальне середовище. Без цього local validator працюватиме з порожніми або тестовими даними, і відтворити умови збоя неможливо.

Передумови: встановлений solana-cli (перевірте актуальну версію через solana --version), доступ до надійного mainnet-beta RPC-ендпоінта з історією транзакцій.

Два способи експорту:

1. Клонування через --clone

Local validator підтримує пряме клонування акаунтів з кластера. Це найпростіший шлях, якщо RPC-ендпоінт дає повні дані акаунта:

solana-test-validator --url mainnet-beta --clone <address_1> --clone <address_2></address_2></address_1>

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

2. Ручний експорт у файл

Коли потрібно зафіксувати стан для повторного використання або коли --clone не працює (наприклад, RPC обмежує розмір відповіді для великих акаунтів):

solana account <address> --output json-compact > account_state.json</address>

Потім завантажте цей файл при старті:

solana-test-validator --account <address> account_state.json</address>

Що обов'язково експортувати:

  • Акаунт програми, де сталася помилка.
  • Всі акаунти, які фігурують у транзакції, що завершилася помилкою (включно з PDA).
  • Акаунти програм, до яких відбувається CPI-виклик у рамках цієї транзакції.
  • Системні акаунти, якщо помилка пов'язана з Sysvar (наприклад, Clock або Rent).

Очікуваний результат: local validator стартує з акаунтами, стан яких ідентичний mainnet на момент експорту. Перевірте через solana account <address></address> у новому вікні терміналу, що розмір даних (data length) і lamports збігаються з mainnet.

Ризик: якщо експортовано неповний набір акаунтів, помилка на локальному середовищі буде іншою (наприклад, AccountNotFound замість оригінальної помилки). Це не означає, що сценарій неробочий — потрібно доповнити набір.

Відтворення послідовності транзакцій

Сам по собі стан акаунтів рідко достатній. Більшість production-помилок виникає в результаті конкретної послідовності транзакцій, яка змінює стан крок за кроком. Відтворення цієї послідовності — ключовий етап.

Крок 1. Отримайте сигнатури транзакцій з production.

Джерело залежить від того, як ви зафіксували помилку. Якщо є сигнатура проблемної транзакції, витягніть попередні транзакції для того ж акаунта через RPC-метод getSignaturesForAddress з обмеженням глибини. Збережіть список сигнатур у хронологічному порядку.

Крок 2. Витягніть повні дані кожної транзакції.

Для кожної сигнатури викличте getTransaction з параметром maxSupportedTransactionVersion та рівнем деталізації json. Збережіть серіалізовану транзакцію (поле transaction у форматі base58 або base64).

Крок 3. Програйте транзакції послідовно.

Local validator не має вбудованої опції масового відтворення транзакцій з файлу. Реалізація зводиться до написання скрипта, який відправляє кожну транзакцію через RPC локального валідатора в правильному порядку. Базова логіка:

  • Встановіть з'єднання з локальним RPC (за замовчуванням http://127.0.0.1:8899).
  • Для кожної транзакції з колекції: десеріалізуйте, підпишіть (або використовуйте оригінальні підписи, якщо local validator налаштований на їх прийняття), відправте через sendTransaction.
  • Чекайте підтвердження перед відправкою наступної.
  • Фіксуйте результат кожної транзакції (успіх/помилка, логи).

Критичний нюанс: підписи. Local validator за замовчуванням не приймає транзакції з mainnet-підписами, оскільки вони сформовані для іншого блокчейну (інший recent_blockhash, інша мережа). Вам потрібно або перепідписати транзакції локальним ключем (що змінює сценарій, якщо логіка залежить від signer), або налаштувати валідатор відповідним чином.

Очікуваний результат: на певному кроці послідовності ви отримуєте ту саму помилку, що і в production. Логи local validator на цьому етапі містять повний трасування викликів.

Типова помилка: ігнорування порядку транзакцій. Якщо відправити лише проблемну транзакцію без попередніх, стан акаунтів буде іншим, і помилка може не відтворитися або буде іншою.

Використання дампів для аналізу

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

Дамп акаунта до транзакції:

solana account <address> --output json > before.json</address>

Виконайте проблемну транзакцію (або серію транзакцій до проблемної).

Дамп акаунта після транзакції:

solana account <address> --output json > after.json</address>

Що аналізувати:

  • Різниця в даних акаунта. Декодуйте поля згідно зі структурою вашої програми. Зміна конкретного байта або поля часто вказує на логічну помилку в обробці.
  • Зміна lamports. Перевірте, чи відповідає рух коштів очікуваному. Несподіваний перерозподіл lamports між акаунтами — часта причина помилок, які важко знайти через логіку програми.
  • Логи виконання. Local validator виводить повні логи програми (instruction logs) у термінал. Збережіть їх і порівняйте з production-логами, якщо вони доступні.

Порівняння з production: якщо ви маєте дамп того ж акаунта з mainnet на аналогічному етапі, порівняння before.json з mainnet-дампом дозволяє перевірити, чи ідентичний початковий стан. Розбіжність означає, що ви пропустили якийсь акаунт або транзакцію при експорті.

Обмеження підходу: дампи показують стан, але не показують причину зміни. Якщо помилка пов'язана з таймінгом (наприклад, перевірка Clock у середині транзакції), дамп до і після не дасть повної картини — потрібен аналіз логів виконання на рівні BPF-інструкцій.

Обмеження відтворення складних станів

Local validator — це інструмент з фундаментальними обмеженнями. Чим складніший production-сценарій, тим менш ймовірне точне відтворення. Розуміння цих меж запобігає марній роботі.

1. Часові залежності та Sysvar Clock.

Local validator має власний Clock, який не відповідає mainnet. Якщо логіка програми залежить від slot, unix_timestamp або epoch (наприклад, перевірка термінів, блокування на час, розрахунок винагород за період), відтворити точний часовий контекст неможливо. Працює лише ручне встановлення слота через --warp-slot, але це дає фіксоване значення, а не динамічну послідовність.

2. Залежність від стану інших програм.

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

3. Конкурентні транзакції та MEV.

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

4. Епохальні переходи та оновлення програм.

Якщо помилка пов'язана з переходом між епохами (зміна комісій, активація фіч, оновлення авторитетів), local validator не відтворить цей контекст. Стан епохи в локальному середовищі статичний.

5. Розмір і складність стану.

Деякі акаунти мають розмір, що перевищує можливості експорту через стандартний RPC (обмеження розміру відповіді). Великі AMM-пули, агрегатори або акаунти з накопиченою історією можуть не експортуватися повністю.

Коли local validator недостатній:

  • Помилка відтворюється на devnet з тими ж даними — залишайтеся на devnet для діагностики.
  • Помилка має часову природу — розгляньте фірмове тестування з моками Clock або модифікацію програми для ін'єкції часу в тестовому режимі.
  • Помилка пов'язана з конкурентністю — потрібен аналіз логів production і статичний аналіз коду, а не відтворення.

План відкату: якщо відтворення не дає результату, поверніться до аналізу production-логів. Збережіть експортовані дані та скрипт відтворення — вони стануть основою для наступної спроби, коли з'явиться додаткова інформація про стан.

Джерела