Message Delivery Guarantees
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.
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 it's 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:
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.
Fortunately, TON works in such a way that any internal message will definitely be received by the destination account. A message cannot be lost anywhere between the source and its destination. External messages are a little bit different since their acceptance to the block is at the validator's discretion however, once the message is accepted into the incoming message queue, it will be delivered.
It therefore seems like lt solves the issue about message delivery order, because we know that a transaction with a lower lt will be processed first. But this doesn't work in every scenario.
Suppose that there are two contracts - A and B. A receives an external message which triggers it to send two internal messages to B, let's call these messages 1 and 2. In this simple case, we can be 100% sure that 1 will be processed by B before 2 because it has a lower lt.
But this is just a simple case when we only have two contracts. How does our system works in more complex cases?
Several Smart Contracts
Suppose that we have three contracts - A, B and C. In a transaction, A sends two internal messages 1 and 2: one to B and another to C. Even though they were created in an exact order (1, then 2), we can't be sure that 1 will be processed before 2. This is the case because routes from A to B and from A to C can differ in length and validator sets. If these contracts are in different shard chains, one of the messages may require several blocks to reach the destination contract.
For better clearness suppose our contracts send back messages
msg2 executed by
C contracts. As a result it will be apply
tx1' on the contract
We have two possible trace for these transaction,
- The first possible order is
tx1'_lt < tx2'_lt:
- The second possible order is
tx2'_lt < tx1'_lt:
The same thing happens in the reverse case, when two contracts B and C send a message to one contract A. Even if message
B -> A was sent before
C -> A, we can't know which one of them will be delivered first. The
B -> A route may require more shard chain hops.
There can be many possible scenarios of smart contract interactions, and in any scenario with more than 2 contracts, the order of messages delivery may be arbitrary. The only guarantee is that messages from any contract A to any contract B will be processed in order of their logical time. Some examples are below.
The TON blockchain's asynchronous structure creates challenges for message delivery guarantees. Logical time helps to establish event and transaction order but doesn't guarantee message delivery order between multiple smart contracts due to varying routes in shard chains. Despite these complexities, TON ensures internal message delivery, maintaining network reliability. Developers must adapt to these nuances to harness TON's full potential in building innovative decentralized applications.