Транзакция
В блокчейне TON любое изменение состояния аккаунта записывается с помощью транзакции. В отличие от сообщений, транзакции ничего не перемещают, отправляют или получают. Эти термины часто путают, но важно понимать, что транзакция — это просто запись всех изменений, произошедших с конкретным аккаунтом.
В этом разделе вы узнаете, как устроена транзакция, как она проходит через каждую фазу, как получать данные о транзакциях с помощью API и как определить, завершилось ли событие в блокчейне успехом.
Структура транзакции
TL-B
Перед тем, как погружаться в принципы работы транзакций, нам нужно понять их структуру с помощью TL-B (Type Language – Binary). Стоит отметить, что в TON существует несколько типов транзакций; однако в этом руководстве мы сосредоточимся исключительно на обычной транзакции. Такие транзакции актуальны для обработки платежей и разработки большинства приложений на TON.
trans_ord$0000 credit_first:Bool
storage_ph:(Maybe TrStoragePhase)
credit_ph:(Maybe TrCreditPhase)
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool bounce:(Maybe TrBouncePhase)
destroyed:Bool
= TransactionDescr;
Согласно схеме TL-B, транзакция состоит из следующих полей:
Поле | Тип | Описание |
---|---|---|
credit_first | Bool | Указывает, должна ли фаза кредита исполняться первой. Это зависит от того, выставлен ли флаг возврата. Это будет объяснено подробнее дальше. |
storage_ph | Maybe TrStoragePhase | Фаза хранения, отвечает за обработку комиссий, связанных с постоянным хранилищем аккаунта. |
credit_ph | Maybe TrCreditPhase | Фаза кредита, отвечает за передачу средств, доставленных с входящим сообщением, если оно внутреннее (тип #1 или #2). |
compute_ph | TrComputePhase | Фаза вычислений, отвечает за исполнение кода смарт-контракта, хранящегося у аккаунта в ячейке code . |
action | Maybe ^TrActionPhase | Фаза действий, отвечает за обработку любых действий, инициированных в ходе фазе вычислений. |
aborted | Bool | Указывает, была ли транзакция прервана на одном из этапов. Если true , транзакция не была выполнена, и изменения из фаз compute_ph и action не были применены. |
bounce | Maybe TrBouncePhase | Фаза отскока, отвечающая за обработку ошибок, произошедших на этапах compute_ph или action . |
destroyed | Bool | Указывает, был ли аккаунт уничтожен во время выполнения транзакции. |
Другие типы транзакций, например, trans_storage, trans_tick_tock и trans_split_prepare, используются для внутренних событий, невидимых для конечных пользователей. К ним относятся разделение и слияние шардов, tick-tock-транзакции и т. д.
Поскольку они не имеют отношения к разработке dApp, мы не будем рассматривать их в этом руководстве.
Фаза кредита (credit phase)
Эта фаза (credit phase) относительно небольшая и простая. Если вы посмотрите исходный код блокчейна, увидите, что её основная логика заключается в пополнении баланса контракта оставшимися средствами из входящего сообщения.
credit_phase->credit = msg_balance_remaining;
if (!msg_balance_remaining.is_valid()) {
LOG(ERROR) << "cannot compute the amount to be credited in the credit phase of transaction";
return false;
}
// NB: msg_balance_remaining may be deducted from balance later during bounce phase
balance += msg_balance_remaining;
if (!balance.is_valid()) {
LOG(ERROR) << "cannot credit currency collection to account";
return false;
}
Фаза кредита сериализуется в TL-B следующим образом:
tr_phase_credit$_ due_fees_collected:(Maybe Grams)
credit:CurrencyCollection = TrCreditPhase;
Эта фаза состоит из двух полей:
Поле | Тип | Описание |
---|---|---|
due_fees_collected | Maybe Grams | Сумма взимаемых сборов за хранение. Это поле присутствует, если у аккаунта нет средств на балансе, и накопился долг за оплату хранилища. |
credit | CurrencyCollection | Сумма, зачисленная аккаунту в результате получения сообщения. |
Фаза хранения (storage phase)
В этой фазе блокчейн обрабатывает комиссии, связанные с постоянным хранилищем аккаунта. Для начала посмотрим на схему TL-B:
tr_phase_storage$_ storage_fees_collected:Grams
storage_fees_due:(Maybe Grams)
status_change:AccStatusChange
= TrStoragePhase;
Эта фаза включает следующие поля:
Поле | Тип | Описание |
---|---|---|
storage_fees_collected | Grams | Сумма комиссии за хранение данных, взимаемая с аккаунта. |
storage_fees_due | Maybe Grams | Сумма комиссии за хранение данных, которая была начислена, однако не могла быть получена из-за недостаточного баланса. Эта сумма представляет собой накопленный долг. |
status_change | AccStatusChange | Изменение статуса аккаунта после исполнения транзакции. |
Поле storage_fees_due
имеет тип Maybe
, потому что оно присутствует только тогда, когда у аккаунта недостаточный баланс для покрытия комиссии за хранение. Когда у аккаунта достаточно средств, это поле опускается.
Поле AccStatusChange
указывает, изменился ли статус аккаунта на этом этапе. Например:
- Если долг превышает 0,1 TON, аккаунт переходит в статус frozen.
- Если долг превышает 1 TON, аккаунт удаляется.
Фаза вычислений (compute phase)
Эта фаза — одна из самых сложных в транзакции. Здесь выполняется код смарт-контракта, сохранённый в состоянии аккаунта.
В отличие от предыдущих фаз, определение TL-B для этой включает различные варианты.
tr_phase_compute_skipped$0 reason:ComputeSkipReason
= TrComputePhase;
tr_phase_compute_vm$1 success:Bool msg_state_used:Bool
account_activated:Bool gas_fees:Grams
^[ gas_used:(VarUInteger 7)
gas_limit:(VarUInteger 7) gas_credit:(Maybe (VarUInteger 3))
mode:int8 exit_code:int32 exit_arg:(Maybe int32)
vm_steps:uint32
vm_init_state_hash:bits256 vm_final_state_hash:bits256 ]
= TrComputePhase;
cskip_no_state$00 = ComputeSkipReason;
cskip_bad_state$01 = ComputeSkipReason;
cskip_no_gas$10 = ComputeSkipReason;
cskip_suspended$110 = ComputeSkipReason;
Для начала обратите внимание, что фаза вычислений может быть полностью пропущена. В этом случае причина для пропуска указывается явным образом, и может быть одной из следующих:
Причина пропуска | Описание |
---|---|
cskip_no_state | У смарт-контракта нет состояния, а следовательно, и кода, поэтому его исполнение невозможно. |
cskip_bad_state | Возникает в двух случаях: когда поле fixed_prefix_length имеет недопустимое значение или когда StateInit , предоставленный во входящем сообщении, не соответствует адресу аккаунта. |
cskip_no_gas | Входящее сообщение не предоставило достаточно TON для покрытия газа, необходимого для исполнения смарт-контракта. |
cskip_suspended | Аккаунт заморожен, так что исполнение его кода недоступно. Этот вариант был использован для заморозки аккаунтов ранних майнеров в ходе стабилизации токеномики TON. |
Поле fixed_prefix_length
можно использовать для указания фиксированного префикса для адреса аккаунта, чтобы быть уверенным, что аккаунт находится в определённом шарде. Эта тема выходит за рамки данного руководства, но дополнительную информацию можно найти [здесь](https://github.com/ton-blockchain/ton/blob/master/doc/GlobalVersions.md#anycast- addresses-and-address-rewrite).
Теперь, когда мы разобрали причины, по которым фаза вычислений может быть пропущена, давайте разберёмся с ситуацией, когда код смарт-контракта всё же выполняется. Для описания результата используются следующие поля:
Поле | Тип | Описание |
---|---|---|
success | Bool | Показывает, завершилась ли фаза вычислений успешно. Если значение false , то любые изменения состояния, сделанные в ходе этой фазы, признаются недействительными. |
msg_state_used , account_activated , mode , vm_init_state_hash , vm_final_state_hash | - | Эти поля сейчас не используются в блокчейне. Их значения всегда записываются как нули. |
gas_fees | Grams | Сумма комиссий, уплаченных за исполнение кода смарт-контракта. |
gas_used , gas_limit | VarUInteger | Фактическое количество использованного газа и лимит, установленный на его расход в ходе выполнения контракта. |
gas_credit | Maybe (VarUInteger 3) | Используется только во внешних сообщениях. Поскольку они не могут содержать TON, выдаётся маленький кредит газа, чтобы смарт-контракт мог начать исполнение и определить, хочет ли он продолжать использовать свой баланс. |
exit_code | int32 | Код возврата виртуальной машины. Значение 0 или 1 (альтернативный успех) означает успешное выполнение. Любое другое означает, что код контракта завершил выполнение с ошибкой — за исключением случаев, где использовалась инструкция commit . Примечание: для удобства разработчики часто называют это кодом возврата смарт-контракта, хотя это технически неточно. |
exit_arg | Maybe int32 | Виртуальная машина может выбрасывать необязательный аргумент в случае сбоя. Полезно для отладки ошибок смарт-контрактов. |
vm_steps | uint32 | Количество шагов, выполненных виртуальной машиной во время выполнения кода. |
Инструкция commit
используется для сохранения любых изменений, сделанных до её вызова, даже если позже в ходе той же фазы произойдёт ошибка. Эти изменения будут отменены только в случае сбоя фазы действий.
Фаза действий (action phase)
Когда исполнение кода смарт-контракта завершается, начинается фаза действий. Если в ходе фазы вычислений были созданы любые действия, то они обрабатываются на этой стадии.
В TON есть ровно 4 типа возможных действий:
action_send_msg#0ec3c86d mode:(## 8)
out_msg:^(MessageRelaxed Any) = OutAction;
action_set_code#ad4de08e new_code:^Cell = OutAction;
action_reserve_currency#36e6b809 mode:(## 8)
currency:CurrencyCollection = OutAction;
libref_hash$0 lib_hash:bits256 = LibRef;
libref_ref$1 library:^Cell = LibRef;
action_change_library#26fa1dd4 mode:(## 7)
libref:LibRef = OutAction;
Тип | Описание |
---|---|
action_send_msg | Отправляет сообщение. |
action_set_code | Обновляет код смарт-контракта. |
action_reserve_currency | Резервирует часть баланса аккаунта. Это особенно полезно для управления газом. |
action_change_library | Меняет библиотеку, используемую смарт-контрактом. |
Эти действия выполняются в порядке их создания во время исполнения кода. Всего может быть создано до 255 действий.
Теперь давайте изучим схему TL-B, определяющую фазу действий.
tr_phase_action$_ success:Bool valid:Bool no_funds:Bool
status_change:AccStatusChange
total_fwd_fees:(Maybe Grams) total_action_fees:(Maybe Grams)
result_code:int32 result_arg:(Maybe int32) tot_actions:uint16
spec_actions:uint16 skipped_actions:uint16 msgs_created:uint16
action_list_hash:bits256 tot_msg_size:StorageUsed
= TrActionPhase;
Она включает следующие поля:
Поле | Тип | Описание |
---|---|---|
success | Bool | Указывает, была ли успешно завершена фаза действий. При значении false все изменения, сделанные на этом этапе, отбрасываются. Изменения, сделанные на эта пе вычислений, также отменяются. |
valid | Bool | Указывает, была ли фаза действий валидной. Значение false означает, что во время выполнения смарт-контракта были созданы невалидные действия. Каждый тип действия имеет свои критерии валидности. |
no_funds | Bool | Указывает, было ли на счету достаточно средств для выполнения действий. Если значение false , фаза действий была прервана из-за нехватки средств. |
status_change | AccStatusChange | Изменение статуса аккаунта после фазы действий. Поскольку удаление аккаунта происходит через действия (через режим 32), это поле может указывать, был ли аккаунт удалён. |
total_fwd_fees | Maybe Grams | Общая сумма комиссий за пересылку, уплаченных за сообщения, созданные на этапе действий. |
total_action_fees | Maybe Grams | Общая сумма комиссий, уплаченных за выполнение действий. |
result_code | int32 | Код результата выполнения действий. Значение 0 означает, что все действия были успешно завершены. |
result_arg | Maybe int32 | Сообщени е об ошибке, возвращаемое в случае ошибки. Полезно для отладки кода смарт-контракта. |
tot_actions | uint16 | Общее количество действий, созданных во время выполнения смарт-контракта. |
spec_actions | uint16 | Количество специальных действий (все, кроме action_send_msg ). |
skipped_actions | uint16 | Количество действий, пропущенных во время выполнения смарт-контракта. Относится к случаям, когда отправка сообщений завершилась неудачей, но был установлен флаг ignore_errors (значение 2). |
msgs_created | uint16 | Количество сообщений, созданных во время выполнения действия. |
action_list_hash | bits256 | Хеш списка действий. |
tot_msg_size | StorageUsed | Суммарный размер всех сообщений. |
Фаза возврата (bounce phase)
Если фаза вычислений или фаза действий завершаются ошибкой, и у входящего сообщения у становлен флаг bounce
, система вызывает фазу возврата (bounce phase, также можно перевести как «фаза отскока»).
Чтобы фаза возврата сработала из-за ошибки в фазе действий, у неудачного действия должен быть установлен флаг 16, который позволяет возврат при ошибке.
tr_phase_bounce_negfunds$00 = TrBouncePhase;
tr_phase_bounce_nofunds$01 msg_size:StorageUsed
req_fwd_fees:Grams = TrBouncePhase;
tr_phase_bounce_ok$1 msg_size:StorageUsed
msg_fees:Grams fwd_fees:Grams = TrBouncePhase;
Тип tr_phase_bounce_negfunds
не используется в текущей версии блокчейна. Два других типа функционируют следующим образом:
Тип | Описание |
---|---|
tr_phase_bounce_nofunds | Указывает, что у аккаунта недостаточно средств для обработки сообщения, которое должно быть возвращено отправителю. |
tr_phase_bounce_ok | Указывает, что система успешно обрабатывает возврат и отправляет сообщение обратно отправителю. |
На этом этапе msg_fees
и fwd_fees
рассчитываются на основе общей комиссии за пересылку fwd_fees
для сообщения:
- Одна треть комиссии идет в
msg_fees
и взимается немедленно. - Оставшиеся две трети идут в
fwd_fees
.