English
EnglishRussian

Blockchain

Venom 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 BC 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 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 address of a sender or a recipient address. 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.


External messages work like this - you send a message with data to a contract from nowhere (the external world). The validator allocates 10,000 credit gas to this message and attempts to execute the transaction by invoking the contract and passing your message to it. The contract must agree to pay for the transaction with 10,000 gas from its account by calling the method 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 VENOMs:



pragma ever-solidity >= 0.64.0; // 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 contract 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() is the public key that signed the message, // which can be 0 if the pragma AbiHeader pubkey is not set. // We check that the constructor is called with a message signed // by a private key associated with the pubkey specified during // contract deployment 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 message signed by a private key // associated with the pubkey specified during // contract deployment require(msg.pubkey() == tvm.pubkey(), 100); // we agree 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 venoms dest.transfer(value); } }

External messages can also be used as log messages. This is when a smart contract creates a message without a destination address during execution. This message is either from the external world to the contract or from the contract to the external world.



Internal Message

Basically, an internal message is just a message from one contract to another. A message should always carry some VENOM coins 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 nano VENOM value which will call the function receive() of the recipient contract without any data.


Every time an account receives a message, the execution of a smart contract is triggered, even if the message did not contain any data. Therefore, it should be understood that even in the case of a regular transfer, the recipient will always receive slightly less money because a transaction to receive the funds will be triggered and a small amount of code will be executed regardless.


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

Communication between contracts is strictly asynchronous. The blockchain guarantees that the sent internal message will be delivered strictly in one instance. It is also guaranteed that if contract A sends multiple messages to contract B, they will be delivered strictly in the order they were sent. However, there are no guarantees on the delivery time. In a stable blockchain, this is 3-5 seconds, but it may increase under high load.


Account Lifecycle #

At the very beginning, the account does not exist on the blockchain. To create a record of it, we need to calculate the address of the future contract (I remind you that the contract address is hash(hash(code) + hash(initial data))) and send the necessary amount of coins VENOM to this address with a special flag bounce=false. This flag means that in case the receiving account does not exist or an error occurs during message processing, the coins should be left at this address instead of being sent back with a special error message.


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 + data(public key in this case), and send VENOMs to this address. When there are coins there, 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 VENOMs. 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 VENOMs + 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 Venom. 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 security guarantees. Since they can exchange messages, these messages carry network tokens. Only two workchains are live now: the masterchain and first processing workchain(basechain). 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(100 seconds), 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 before expired.


Delivery guarantees for external messages will be improved in the "EMQ 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.