Solana пропонує три рівні підтвердження транзакцій через параметр commitment у WebSocket-підписках: processed, confirmed та finalized. Правильний вибір рівня безпосередньо визначає, чи ваш застосунок отримає дані вчасно, чи отримає їх надійно — і чи взагалі отримає. Цей матеріал розбирає механіку кожного рівня, компроміси між затримкою та надійністю та перевірені патерни роботи з підписками в production-середовищах.
Різниця між рівнями підтвердження
Solana використовує механізм Proof of History у поєднанні з Tower BFT-консенсусом. Кожен слот (slot) містить блок транзакцій, а валідатори голосують за ланцюжок слотів. Рівні підтвердження відображають різні етапи цього процесу:
- processed — транзакцію включено до поточного слота лідером. Жодних голосів інших валідаторів ще не надійшло. Це найшвидший сигнал, але найменш надійний: при форку транзакція може бути відкинута.
- confirmed — супер-більшість стейку (понад дві третини) проголосувала за блок, що містить транзакцію. На практиці це означає, що форк, який би скасував цей блок, вимагав би одночасного відключення значної частини мережі. Транзакція вважається підтвердженою, але теоретична можливість відкату існує.
- finalized — блок став кореневим (rooted) у дереві форків. Це означає, що існує неперервний ланцюжок підтверджених блоків від генезису до цього блоку. Відкат фіналізованої транзакції неможливий без руйнування консенсусу всієї мережі.
Ключове інженерне розуміння: ці рівні не є паралельними каналами. Це послідовні стани однієї й тієї ж транзакції. Транзакція завжди проходить шлях processed → confirmed → finalized, хоча на будь-якому етапі може бути відкинута через форк.
Коли використовувати кожен рівень
Processed: лише для внутрішньої діагностики
Підписка з commitment: "processed" підходить виключно для локального дебагу та моніторингу затримок. У production цей рівень не слід використовувати для прийняття бізнес-рішень, оскільки транзакція може бути відкинута мережею на етапі голосування. Типовий випадок — вимірювання часу між відправкою транзакції та її потраплянням у слот лідера для діагностики проблем із мережею.
Confirmed: стандарт для більшості застосунків
Для користувацьких інтерфейсів, торгових платформ, NFT-маркетплейсів та інших dApps confirmed є робочим стандартом. Затримка становить приблизно один раунд голосування валідаторів після обробки. Ризик відкату на цьому етапі існує, але на практиці в стабільній мережі він мінімальний. Більшість сервісів показують користувачеві «успішно» саме на цьому рівні.
Finalized: settlement, мости та аудит
Фіналізація потрібна там, де відкат транзакції створює неприпустимі фінансові або юридичні наслідки:
- Cross-chain мости — фіналізована транзакція на Solana є підставою для випуску активів на іншому ланцюжку. Будь-який відкат означає подвійне витрачання.
- Settlement-системи та інституційні рішення — коли підтвердження має юридичну вагу або регуляторні вимоги.
- Аудит-трейли та індексація — при побудові надійних історичних даних, де цілісність логу критична.
- Внутрішня бухгалтерія протоколу — фіксування змін стану, які впливають на розподіл коштів між учасниками.
Вплив на затримку та надійність
Компроміс між рівнями підтвердження є фундаментальним: швидше означає менш надійно. Конкретні значення затримки залежать від поточного стану мережі, кількості активних валідаторів та мережевих умов, тому їх слід вимірювати у вашому конкретному середовищі, а не брати з документації як константи.
| Рівень | Типова затримка | Можливість відкату | Використання в production |
|---|---|---|---|
| processed | Найнижча (один слот) | Висока | Ні |
| confirmed | Середня (один раунд голосування) | Низька, але не нульова | Так, для UX |
| finalized | Найвища (декілька раундів) | Неможлива без колапсу мережі | Так, для settlement |
Важливий нюанс: під час тимчасової дестабілізації мережі інтервал між confirmed та finalized може значно зростати. Якщо ваш застосунок покладається на фіналізацію для критичних шляхів, необхідно мати механізм обробки таких затримок без зависання користувацького інтерфейсу або блокування черг обробки.
Щодо надійності WebSocket-підписок: навіть з правильним commitment з'єднання може розірватися, а RPC-вузол може відставати від мережі. Тому підписка — це інструмент сповіщення, а не заміна поллінгу через getSignatureStatuses для критичних транзакцій.
Практичні патерни для фінальності
Патерн подвійної підписки
Найпоширеніший production-патерн: підписатися на confirmed для швидкого відгуку користувачеві та окремо на finalized для фактичного settlement. Це дозволяє показати «транзакція підтверджена» через секунду, а фоново зачекати фіналізації для оновлення внутрішнього стану.
Логіка реалізації:
- Відправляєте транзакцію та отримуєте signature.
- Створюєте дві WebSocket-підписки через
signatureSubscribe: одну з commitment: "confirmed", іншу з commitment: "finalized". - По confirmed оновлюєте UI та повідомляєте користувача.
- По finalized виконуєте settlement-логіку: списання з резерву, оновлення балансів у базі, відправку подій в інші системи.
- Обидві підписки скасовуєте після отримання відповідного сигналу.
Цей підхід вимагає обережної роботи з станом: переконайтеся, що finalized обробник не спрацює після скасування підписки через таймаут, і що повторна обробка однієї транзакції є ідемпотентною.
Обробка таймаутів та відкатів
Транзакція може не досягти жодного рівня підтвердження з кількох причин: недостатній пріоритет (fee не вистачає для конкуренції в блоку), невдала симуляція на етапі лідера, або мережева проблема. Патерн обробки:
- Встановіть таймаут для confirmed підписки (значення залежить від вашого SLA, зазвичай 10–30 секунд).
- Після таймауту перевірте статус через
getSignatureStatusesз commitment: "confirmed" — можливо, підписка втратила з'єднання, але транзакція підтверджена. - Якщо статус відсутній — перевірте з commitment: "processed", щоб зрозуміти, чи потрапила транзакція в слот взагалі.
- Якщо транзакція не оброблена, перевірте префікс логу помилок у відповіді
getSignatureStatusesдля визначення фактичної причини (наприклад, InstructionError, InsufficientFundsForFee, TransactionTooLarge).
Не робіть припущень про причину невдалої транзакції без перевірки фактичного статусу. Спроба повторної відправки без діагностики може призвести до дублювання транзакцій або повторних сплат комісій.
Перевірка стану після відновлення з'єднання
WebSocket-з'єднання з RPC-вузлом може розірватися в будь-який момент. Якщо ви використовуєте підписки для оновлення стану (наприклад, відстеження зміни балансу акаунта), після перепідключення необхідно:
- Відновити підписки через нове з'єднання.
- Виконати точковий запит поточного стану через
getAccountInfoабоgetBalanceз відповідним commitment. - Зіставити отриманий стан із локальним кешем та обробити розбіжності.
Цей крок критичний для індексерів та data-пайплайнів, де пропуск події під час розриву з'єднання означає неконсистентність даних.
Відстеження форків через підписки
У рідкісних випадках мережевого форку транзакція може отримати confirmed статус на одному форку, а потім опинитися на іншому форку, який стає основним. У такій ситуації підписка з commitment: "confirmed" може спрацювати, але подальша перевірка через getSignatureStatuses покаже інший статус. Якщо ваш протокол чутливий до таких сценаріїв (наприклад, DEX з ордерами), додайте періодичну верифікацію статусу нещодавно підтверджених транзакцій протягом інтервалу, достатнього для фіналізації.
Примітка щодо безпеки: Наведені патерни описують архітектурні підходи. Конкретна реалізація повинна проходити код-рев'ю та тестування у середовищі, що відповідає вашому production. Для систем, що оперують значними коштами, обов'язковий незалежний аудит безпеки.