Messages and Transactions
TON is an asynchronous blockchain with a complex structure very different from other blockchains. Because of this, new developers often have questions about low-level things in TON. In this article, we will have a look at one of these related to message delivery.
What is a message?
A message is a packet of data sent between actors (users, applications, smart contracts). It typically contains information instructing the receiver on what action to perform, such as updating storage or sending a new message.
Working with this type of communication is reminiscent of launching a satellite into space. We know the message we've formed, but after its launch, it is necessary to conduct separate observation to find out what results we will obtain.
What is a Transaction?
A transaction in TON consists of the following:
- the incoming message that initially triggers the contract (special ways to trigger exist)
- contract actions caused by the incoming message, such as an update to the contract's storage (optional)
- outgoing generated messages that are sent to other actors (optional)
Technically, a contract can be triggered through special functions such as Tick-Tock, but this function more used for internal TON Blockchain core contracts.
Not every transaction results in outgoing messages or updates to the contract's storage — this depends on the actions defined by the contract's code.
If we look at Ethereum or almost any other synchronous blockchain, each transaction can contain several smart contract calls in it. For example, DEXs perform multiple exchanges in one transaction if there is no liquidity for the selected trading pair.
In an asynchronous system you can't get a response from the destination smart contract in the same transaction. A contract call may take a few blocks to be processed, depending on the length of the route between source and destination.
To achieve the infinite sharding paradigm, it is necessary to ensure full parallelization, which means that the execution of each transactions is independent of every other. Therefore, instead of transactions which affect and change the state of many contracts at one time, each transaction in TON is only executed on a single smart contract and smart contracts communicate through messages. That way, smart contracts can only interact with each other by calling their functions with special messages and getting a response to them via other messages later.
More detailed and accurate description on the Transaction Layout page.
Transaction outcome
There is a TVM exit code for transaction which had compute phase, if it is not 0 or 1 then there was an error. Also TVM compute phase may be skipped for some reasons like absence of funds or state.
To determine successful transaction one should use tx.description.action.success && tx.description.compute_ph.success:
"transactions": [
{
"description": {
. . . . . . . .
"action": {
"valid": true,
"success": true,
. . . . . . . .
},
. . . . . . . .
"destroyed": false,
"compute_ph": {
"mode": 0,
"type": "vm",
"success": true,
Transaction may have one of three results:
- Success, exit code 0 or 1
- Fail,
aborted: true
without execution - Fail, exit code,
aborted: true
aborted: true
is a toncenter field, transaction has no such field
What is a Logical time?
In such a system with asynchronous and parallel smart contract calls, it can be hard to define the order of actions to process. That's why each message in TON has its Logical time or Lamport time (later just lt). It is used to understand which event caused another and what a validator needs to process first.
It is strictly guaranteed that the transaction resulting from a message will have a lt greater than the lt of the message. Likewise, the lt of a message sent in some transaction is strictly greater than the lt of the transaction that caused it. As well as this, messages that were sent from one account and transactions which happened on one account are strictly ordered as well.
For the case in the image, it turns out: in_msg_lt < tx0_lt < out_msg_lt
Thanks to this, for every account we always know the order of transactions, received messages and sent messages.
Moreover, if account A sent two messages to account B, it is guaranteed that the message with a lower lt will be processed earlier:
If msg1_lt < msg2_lt
=> tx1_lt < tx2_lt
.
Otherwise, an attempt to synchronize delivery would require the state of all the others to be known before processing one shard, thereby breaking parallelization and destroying efficient sharding.
For each block, we can define the lt span as starting from the first transaction and ending with the lt of the last event in the block (message or transaction). Blocks are ordered the same way as other events in TON and so if one block depends on the other, it has a higher lt. The child block in a shard has a higher lt than its parent. A masterchain block's lt is higher that the lts of shard blocks that it lists, since a master block depends on listed shard blocks. Each shard block contains an ordered reference to the latest (at the moment of shard block creation) master block and thus the shard block lt is higher than the referenced master block lt.