English
EnglishRussian

Blockchain

Everscale features a unique combination of data sharding and computational resources sharding to allow huge throughput capacity. This article contains a basic description of the BC’s operation. There are other articles that offer a more detailed description of how it works.



Accounts

The basic ES entity is the account. We can view accounts and smart contracts as the same. An account has an address which is hash(hash(smart_contract_code), hash(initial_data)). In other words, its address is an unambiguously calculated value based on its code and initial data. There are no special types of accounts for user wallets that initiate ES transactions. Such wallets are ordinary smart contracts of many different types. However, a transaction can be initiated by any smart contract that allows for the acceptance of external messages.



Two Types of Messages

External Message

This is a message from nowhere or to nowhere :-) Messages of this type lack the addresses of both a sender and a recipient. Basically, this message type is used to interact with contracts from the outer world. This is quite a unique concept that allows any contract to start a sequence of messages and transactions. The validator provides the message with 10k of credit gas and attempts to execute a transaction by calling the contract and passing your message to it. By using 10k of credit gas, the contract should validate the message and agree to pay for the transaction by calling the function tvm.accept(). If this function is called, the transaction continues, and the contract may create other outcoming messages. But, if an exception was raised, or the contract did not call tvm.accept(), or the credit gas has been spent, this message will not go to the blockchain and will get rejected by the validator. (Roughly speaking, a message does not go to mempool unless it can be added to the blockchain successfully.)


It is interesting to note that an external message may have any data and may not contain a signature. (For example, you can make a contract receive a random message from anybody every minute and perform some action, like a timer.)


Here is an example of a basic wallet contract that can only send EVERs:



pragma ever-solidity >= 0.61.2; // This header informs sdk which will create the external message has to be signed by a key. // Also directing the compiler that it should only accepted signed external // messages pragma AbiHeader pubkey; contract Wallet { constructor() public { // We check that the conract has a pubkey set. // tvm.pubkey() - is essentially a static variable, // which is set at the moment of the creation of the contract, // We can set any pubkey here or just leave it empty. require(tvm.pubkey() != 0, 101); // msg.pubkey() - public key with which the message was signed, // it can be 0 if the pragma AbiHeader pubkey has not been defined; // We check that the constructor was called by a message signed by a private key // from that pubkey that was set when the contract was deployed. require(msg.pubkey() == tvm.pubkey(), 102); // we agree to pay for the transaction calling the constructor // from the balance of the smart contract tvm.accept(); } function send(address dest, uint128 value) external { // we check that the signature of the external message is right require(msg.pubkey() == tvm.pubkey(), 100); // we aggree to pay for the external message from the contract balance tvm.accept(); // everything is simple here, we create an outgoing // internal message which carries the value nano evers dest.transfer(value); } }

Also, external messages may be used as message logs when a smart contract creates a message without a destination address during execution.



Internal Message

Basically, an internal message is just a message from one contract to another. A message should always carry some EVER because there is no credit gas when calling to another contract, unlike an external message. Before calling tvm.accept(); (if it even happens), the payment for gas is charged from the value carried by the message. If the value is less than the charge of gas, the transaction is halted.


dest.transfer(value); – creates an internal message with EVER value which will call the function receive() of the recipient contract without any data.


Every time an account receives messages, the execution of a smart contract is triggered, even if a message has zero content. So we should understand that even in the case of a simple money transfer, the recipient will always receive a bit less money because a tiny portion of the code necessary for the transaction will be executed.

pragma ever-solidity >= 0.61.2; pragma AbiHeader pubkey; contract Receiver { uint public counter = 0; receive() external { ++counter; } }

Communication between contracts is always asynchronous. The BC guarantees the delivery of the sent internal message strictly in one instance (exactly once). It also guarantees that in the case Contract A sends multiple messages to Contract B, they will be delivered strictly in the order of the time of sending. But there is no guarantee as to the timing of the delivery. This is around 3-5 seconds in a stable blockchain but may increase under higher loads.


Account Lifecycle #

At the very beginning, there are no accounts in the blockchain. For a contract to emerge, we need to calculate the address of the future contract (remember that the contract address is hash(hash(code) + hash(initial data))) and send the necessary amount of EVER coins to this address with a special flag bounce = false. This flag means that if the recipient account does not exist or there is an error during the handling of the message, EVER will be left to remain at this address rather than sent back with a special message notifying a handling error.


After that, an account with a status of Uninitialized will appear in the BC. In other words, we now have an account in the blockchain, but it is lacking any data and code. To change the account’s status to Active, we need to send a special message that contains the contract's data and code. Anyone can send such a message, and validators will check whether the contract's address equals hash(hash(code) + hash(data)). If everything is correct, the account gets initialized. The same message may also have a function that will be called right after the initialization along with its arguments. A constructor will be called by default.


After the account becomes active, it may receive incoming internal and external messages. Every time an account receives a message, a transaction starts that enables the account to create up to 255 outgoing internal messages.


In other words, to create a new wallet, we just create a couple of keys (public/private), take our wallet's code, calculate the address based on the code + public key, and send EVERs to this address. If there are any coins in it, we can initialize the wallet and start using it.


When the account gets the message, the Transaction Executor starts. We will cover it in detail in the section on writing smart contracts. Now it is important to know that prior to the execution of the contract code, a storage fee is deducted from its account covering all the time passed since the previous transaction. This storage fee is in direct proportion to the size of data and contract code. If, after charging this fee, the contract balance drops below zero, the transaction will not be carried out, and the contract will be moved to a Frozen state. In this state, the contract's data and code are deleted, and only the state hash remains. The contract will stay in the Frozen state until its debt for storage reaches the deletion threshold. This network parameter is currently -0.1 Ever. After reaching this threshold, the contract is permanently and irreversibly deleted. To unfreeze the contract, you have to top up the account balance and return the account’s state + code at the time of freezing.


Also, during a transaction, a contract can create an outgoing internal message with a special flag to signify that all the remaining money should be sent out along with this message, after which the account will be deleted.



Above, you can find a description of the algorithm used to deploy contracts using an external message. A contract can also be deployed from another one via a single internal message. We just send EVERs + stateInit (code + initial data) + specify which arguments are passed to the constructor.



Masterchain

This tutorial is based on the assumption that you have some basic understanding of blockchain technology. Speaking very superficially, a blockchain is a series of blocks that register changes in its state. With a POS blockchain, we can say that validators first agree on how they want to change the blockchain state by compiling a block with a list of changes. Then they vote for this block, and if enough votes are gathered, they apply this block to the blockchain state and move to the next one.


The throughput capacity of one block thread is very limited because validators have to check all transactions in a block before agreeing to accept it. So there are many threads in ES, which you can think of, roughly speaking, as mini-blockchains. They exist in parallel and each has its own set of validators.



The masterchain is the main block thread in Everscale. It serves to synchronize all the rest of blocks and also recalculates the set of validators. After all the threads agree on a new block, they sign it and register it in the masterchain. However, masterchain validators do not verify the validity of this block, they only check if it is signed by proper validators. So a lot of threads may co-exist in parallel. Contracts from various threads talk to each other by sending messages.



Workchains

Workchains are separate address spaces that can live according to their rules. For example, they may have different virtual machines or extended time for issuing blocks with high gas limits. Most importantly, workchains have to have the same message queue format so they can exchange messages. This also means that all workchains must have approximately the same safety guarantees. Since they can exchange messages, these messages carry network tokens. Only two workchains are live now: the masterchain and first processing workchain. The workchain is determined by its prefix to the address:


-1:ax...1s2 - account address in the masterchain. -1 is the masterchain prefix.

0:zx...123 - account address in the first workchain. 0 - is the prefix of the first processing workchain.


All workchains use the masterchain as a means for synchronization and Truth source.



Processing Threads

Processing threads (or Shards in Nikolay Durov's terminology) are separate block threads in processing workchains. By default, workchain 0 has only one thread with one chain. Validators of this thread accept external messages and process internal messages which they have sent themselves or from other workchains. If a situation arises when a thread has been overloaded during the latest N blocks, it is split: one thread is divided into two, and transactions in them go in parallel.

The accounts with addresses starting with 0:00.. - 0:88.. are now in thread 1 and accounts 0:88.. - 0:FF.. are in thread 2. Since all smart contracts talk to each other asynchronously, nothing breaks down, while the throughput increases in two. When the load is down, threads are merged back after some time. If the load keeps increasing, the two threads can be split again, and again, and so on.


The masterchain only ever has one thread.



Message Delivery

We have 100% guaranteed internal message delivery. If a contract sends an internal message, it will be delivered sooner or later and strictly in a single instance. When a contract sends a message to another contract, the message is placed in the outgoing queue of the thread where the contract currently resides. If the destination contract is in the same thread and there is enough gas limit to execute a transaction, which will be triggered by this message (simply put, there is enough room in the block), then the message will be considered delivered and a new transaction will be triggered. If, however, the destination contract is in another thread or workchain, the delivery will take place no earlier than a new master block is issued, which is currently about 3 seconds. This is because other threads will see this message only after they can see the master block.



There are no guarantees as to the delivery of external messages. Such messages are simply broadcast across the network to all validators and placed on the queue for inclusion. Usually, such a message goes into the next block, but we know nothing about the load of the destination thread. As threads cannot add up instantly, it may take more time in some cases, or the external message may be rejected by a validator altogether if there is a very large queue.


Therefore, contracts usually use the pragma AbiHeader expire;. This instructs SDK to add the flag "This external message is valid only for 2 minutes" to all external messages to this contract. Afterwards, such a message cannot be included in the block. After sending the external message, SDK simply looks up all new transactions in the destination thread and returns an error if the sent message fails to appear.


Delivery guarantees for external messages will be improved in the "Remp protocol" update.



Conclusion

Generally, these are the basics one needs to know about the blockchain. From here, we can go on to the next chapters or get familiarized with other articles of this chapter to better understand the guarantees and operations of the blockchain.