Bounce Handling и Error Recovery
Проблема: ошибки в async chains
В синхронных системах ошибка = exception → try/catch → rollback. В TON каждый шаг — отдельная транзакция. Если шаг 3 из 5 упал — шаги 1 и 2 уже выполнены и необратимы.
Проблема:
Step 1: [OK] User → DEX: отправил 100 USDT (необратимо)
Step 2: [OK] DEX: рассчитал курс (необратимо)
Step 3: [NO] DEX → User: отправка TON — FAIL (контракт не существует?)
Результат: User потерял 100 USDT, TON не получил
Bounce Messages: автоматический safety net
Когда контракт получает internal message с bounce: true и не может его обработать (бросил exception), TON автоматически создаёт bounce message:
Contract A
msg (bounce: true)
Contract B FAIL
Contract A
bounce msg
A: handle bounce → refund
Что содержит bounce message
Bounce message:
- bounced: true (флаг — это bounce, не обычное сообщение)
- src: адрес Contract B (откуда bounced)
- value: остаток value после вычета gas
- body: первые 256 бит оригинального body (для идентификации)
Bounce Handler Pattern
Каждый контракт, отправляющий critical messages, должен иметь bounce handler:
// Паттерн bounce handler
recv_internal(msg) {
// Проверяем: это bounce message?
if (msg.bounced) {
// Прочитать op из bounced body
int op = msg.body.read_uint(32);
int query_id = msg.body.read_uint(64);
// Компенсация в зависимости от операции
if (op == op::internal_transfer) {
// Transfer не удался — вернуть баланс
self.balance += msg.original_amount;
// Уведомить пользователя
emit_log("transfer_failed", query_id);
}
return;
}
// Нормальная обработка сообщений...
}
WARNING
Ограничения bounce
- Bounce body содержит только первые 256 бит оригинального сообщения — может быть недостаточно для идентификации
- Bounce message тоже стоит gas — если value недостаточно, bounce не отправится
- External messages не bounceются — нет адреса возврата
- Bounce не каскадный — если bounce handler тоже бросит исключение, второго bounce не будет
Error Recovery Strategies
Strategy 1: Compensation (Saga)
При ошибке — выполнить обратное действие:
Transfer failed (bounce):
→ Вернуть баланс отправителю
→ Обновить pending state
Strategy 2: Retry with state
Сохранить pending операцию и повторить:
Retry pattern:
→ Сохранить pending_operation в state
→ При bounce — increment retry_count
→ Если retry_count < MAX_RETRIES — повторить с увеличенным gas
→ Если retry_count >= MAX_RETRIES — compensation
Strategy 3: Admin intervention
Для критических ситуаций — pause + admin recovery:
Emergency pattern:
→ Обнаружена критическая ошибка
→ Контракт переходит в PAUSED state
→ Только admin может вызвать recovery function
→ Admin анализирует ситуацию и вызывает refund/resume
Design Checklist для Error Handling
| Проверка | Описание | Критичность |
|---|---|---|
| Все internal messages с bounce:true? | Если нет — средства теряются при ошибке | Critical |
| Bounce handler реализован? | Обработка bounce для каждого типа сообщения | Critical |
| Gas достаточно для bounce? | Value > compute + forward + bounce gas | High |
| Timeout для pending операций? | Что если ответ никогда не придёт? | High |
| Admin recovery для critical paths? | Паузa + ручное восстановление при catastrophic failure | High |