Типы Сообщений: Internal и External
Различие между internal и external сообщениями — это ключ к безопасности ваших контрактов. External сообщения приходят из «внешнего мира» (кошельки, боты), а internal — от других контрактов внутри блокчейна. От типа сообщения зависит, кто платит за газ, какие проверки нужно выполнять и какие атаки возможны. Неправильная обработка external сообщений — одна из самых критичных уязвимостей в TON.
В TON существует три типа сообщений, каждый из которых играет свою роль в экосистеме.
External-in сообщения
External-in — сообщения из внешнего мира в блокчейн. Это точка входа для пользователей.
- Отправитель: кошелёк пользователя (off-chain)
- Получатель: смарт-контракт (on-chain)
- Подпись: обязательна (проверяется контрактом-получателем)
- Value: 0 TON (нельзя прикрепить TON извне)
Пользователь (off-chain) ──── external-in ────→ Wallet Contract
Типичный пример: пользователь подписывает транзакцию в Tonkeeper и отправляет external message в свой wallet contract.
В TON wallet contract оплачивает gas за обработку external message из своего баланса. В отличие от Ethereum, где gas оплачивается из EOA напрямую.
Internal сообщения
Internal — сообщения между контрактами внутри блокчейна. Основной механизм взаимодействия.
- Отправитель: смарт-контракт (on-chain)
- Получатель: смарт-контракт (on-chain)
- Value: любое количество TON (прикрепляется к сообщению)
- Bounce: можно включить/выключить
Contract A ──── internal ────→ Contract B
Internal сообщения создаются контрактами в процессе обработки входящих сообщений.
External-out сообщения
External-out — сообщения из блокчейна во внешний мир. Аналог events в Ethereum.
- Отправитель: смарт-контракт (on-chain)
- Получатель: off-chain сервис
- Назначение: логирование, уведомления, events
Contract ──── external-out ────→ Off-chain (indexer, UI)
External-out сообщения не доставляются другим контрактам. Они записываются в блок и могут быть прочитаны off-chain сервисами.
Структура сообщения
Каждое сообщение в TON содержит:
| Поле | Описание |
|---|---|
| src | Адрес отправителя (для internal) |
| dest | Адрес получателя |
| value | Количество прикреплённых TON (нано-TON) |
| body | Payload — данные для обработки (Cell) |
| bounce | Флаг: возвращать ли TON при ошибке |
| bounced | Флаг: это bounced сообщение? |
| ihr_disabled | Флаг: использовать ли Instant Hypercube Routing |
| fwd_fee | Комиссия за маршрутизацию |
Механизм Bounce
Bounce — ключевой механизм безопасности в TON.
Если контракт-получатель не может обработать internal message (ошибка, нехватка gas, контракт не существует), и флаг bounce: true, то:
- Сообщение “отскакивает” обратно отправителю
- Прикреплённые TON возвращаются (минус gas за bounce)
- Bounced message имеет флаг
bounced: true
Contract A ──── bounce: true ────→ Contract B (ОШИБКА!)
Contract B ──── bounced: true ────→ Contract A (возврат TON)
Распространённая ошибка: если контракт отправляет сообщение с bounce: true, он обязан обрабатывать bounced сообщения. Иначе возвращённые TON будут потеряны или вызовут неожиданное поведение.
Когда использовать bounce?
| Сценарий | bounce | Почему |
|---|---|---|
| Перевод TON | true | Вернуть TON при ошибке получателя |
| Уведомление | false | Не нужен возврат, уведомление опционально |
| Деплой контракта | false | Контракт ещё не существует, bounce невозможен |
| Запрос данных | true | Вернуть gas при ошибке |
Обработка сообщений контрактом
Контракт на Tact обрабатывает сообщения через receive():
contract MyContract {
// Обработка internal message
receive(msg: MyMessage) {
// ... логика
}
// Обработка bounced message
bounced(msg: bounced<MyMessage>) {
// Откат локального состояния
}
// Обработка external message
external(msg: MyExternalMessage) {
acceptMessage(); // Подтвердить оплату gas
// ... логика
}
}
Частые ошибки
- Вызывают accept_message() до проверки подписи во внешнем сообщении, что позволяет злоумышленнику опустошить баланс контракта, заставив его оплачивать газ за мусорные сообщения.
- Забывают, что external-сообщения не имеют отправителя (sender = 0), и проверка sender для авторизации не работает для внешних сообщений.
- Не учитывают replay protection: одно и то же external-сообщение может быть отправлено повторно, если контракт не проверяет seqno или другой механизм защиты.
- Путают формат тела (body) internal и external сообщений, которые имеют разную структуру, и парсинг одного формата как другого приведёт к ошибке.
Проверка знанийЧто произойдёт, если контракт A отправит internal message с bounce: true контракту B, а B не существует?
Проверьте понимание
Закончили урок?
Отметьте его как пройденный, чтобы отслеживать свой прогресс
Войдите чтобы оценить урок