Skip to main content

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?

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.

If a transaction in Ethereum is just a set of function calls in different contracts, a transaction in TON is made up of the inbound message which initially triggered it and a set of outbound messages which are sent to other contracts.

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. 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. 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.

Message delivery

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.

Delivery order

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.

concept image

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.

concept image

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.

concept image

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.

concept imageconcept imageconcept image

Conclusion

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.