MoonBlokz series part IV. — snake_chain

This article is part of the MoonBlokz series, which focuses on creating a hyper-local blockchain using microcontrollers and radio communication. You can find the previous articles here:

Today, we talk about a unique aspect of MoonBlokz called snake_chain. We are continuously adding blocks to our chain. However, we have a limited storage capacity on the nodes. It means we have to delete some parts of our chain from time to time. I chose a mechanism that automatically deletes the oldest block when a new block arrives.

Why is it called snake_chain?

Snake game on Nokia 3310

If you had a mobile phone in the 1990s, you might remember a game on the Nokia phones called “Snake”. In this game, you controlled a snake using the phone’s directional keys to navigate it around the screen. The snake grew longer by eating items, and your objective was to avoid collisions with the snake’s own body and the screen boundaries. The game ends when the snake either runs into itself or the walls. In this game, the snake has a certain length, meaning that when the head moves, the tail moves too. It looks similar to how our blockchain behaves.

Storing transactions

Let’s take a closer look at how different blockchains store transactions. Different blockchains utilize different representations for transactions. For instance, Bitcoin uses a system called Unspent Transaction Outputs (UTXO), which refers to amounts of money that can be used as inputs for new transactions. On the other hand, Ethereum adopts a more traditional account-based model, where transactions involve account numbers and the amounts being transferred between them.

For MoonBlokz, I have chosen a hybrid approach. The system utilizes a balance-based model for transaction outputs when the receiver is a node. In contrast, UTXO model is used for transaction outputs where the receiver is an address.

This structure is complex, but it is essential for supporting an efficient consensus model — where all nodes must be known — as well as ensuring privacy since we don’t have any information about the addresses.

First, let’s focus on balances before we address UTXOs. How can we calculate balances if we delete old blocks with previous transactions? If we only keep track of transactions, we can only perform calculations by gathering all transactions from the start of the chain. Therefore, it’s essential to periodically store balances to avoid losing data. This leads us to a fundamental question:

What should we store to ensure we do not lose any data even if the beginning of the chain is deleted?

MoonBlokz’s block types

We will use four types of blocks:

  • Transaction blocks contain many transactions (limited by the pre-configured maximal block size). There can be multiple transaction types in the system. The basic type is transfer. For a transfer transaction, the block includes the initializer’s node address, the recipient’s node address, the transaction amount, a comment field, and the initializer’s digital signature to authenticate the transaction.
  • Balance blocks contain many balances (limited by the pre-configured maximal block size). A balance consists of the node’s identifier, public key, balance of owned currency, and vote scores.
  • Chain-config blocks: We have referred to the configurable parameters multiple times before. We handle configuration as a special type of block. When a blockchain is created, a configuration block containing all the necessary parameters is added to the chain. All configurations are defined during the initialization of the chain, and there is no way to alter them later.
  • Approval evidence blocks: As previously discussed, when a chain deviates from the standard vote-based algorithm, the majority of nodes must approve the change. These blocks contain proof of the majority’s support.

When we zoom into these blocks, we see the following structure:

Transaction & balance blocks

The transaction and balance blocks must be consistent, and no balances can drop below zero.

Last one, take the lead!

To preserve all critical information, we should append the essential data from discarded blocks to the end of the blockchain. This process is straightforward for chain-config blocks, as they are immutable. If we remove a chain configuration, we must ensure that the same content is added to the end of the chain. However, managing balance blocks is more complex. While we can discard old transactions, it is essential to keep an up-to-date balance for every node in the active chain. When we remove the balances of certain nodes, we must add the current balances of those nodes to the end of the chain. At this step, the network (all the nodes) does not accept any other blocks, just the dropped balance or chain configuration block.

Managing UTXOs can be even more complex. If a transaction block is dropped, we must analyze all transactions. All balance outputs could be discarded, and any previously used UTXOs can also be removed. However, we must retain the unused UTXOs. If any remaining UTXOs are in a block, a new transaction is created. It is a special type of transaction without any inputs and as many outputs as there are unused UTXOs in the original block. To compress data, UTXOs can also be collected from the following ’n’ blocks (n can be configured) until the block size limit is reached. If there are not enough unspent transaction outputs in the next n blocks, new transactions from the mempool can also be included in the block. When a UTXO is re-added its value is reduced by a custodian fee. If the amount of the UTXO is smaller than the custodian fee, it is discarded. This custodian fee incentivizes users to merge their UTXOs, freeing up space in the blockchain.

Adding the dropped block to the end of the chain

Add new nodes to the network

It is the right time to discuss how new nodes can be added to the network. Different blockchains use different approaches. There are two typical solutions:

  • Permissioned blockchains use specific decision-making logic, typically controlled by a central authority, to determine whether a node can join the network.
  • Anyone can join public networks, and the network’s operating model is designed to prevent fraud by newly created nodes.

In MoonBlokz, adding new nodes is not cost-free for the network. Increasing nodes necessitates more frequent balance blocks, which degrades the network’s efficiency. We implement a hybrid model. New nodes cannot join the network themselves, but every node can register new nodes. However, registration is not free. The chain’s configuration controls the registration price, preventing attackers from flooding the network with new nodes.

Registration is a special transaction where the initiator pays the registration amount and the transaction fee to register a new public key on the network. The registration fee is absorbed by the network; no one receives it.

However, it is the basic setup, the following models can also be parametrized by chain configuration:

  • The network is free to join if the registration amount is set to 0 and registration transactions signed by new keys are enabled.
  • Only node #0 can initiate new registrations if the permissioned registration option is enabled. In this model, node #0 acts as a central authority.

When a new node joins the network, it doesn’t have any knowledge of the past of the chain but knows two things:

  • The existing nodes on the network have accepted the current state of the blockchain, which validates the chain for the new node.
  • The living part of the blockchain includes balance blocks for all network nodes, allowing the new node to construct the complete balance model.

Once the registration transaction is complete, the new node is fully operational. It can send and receive transactions as well as create blocks. There is no immediate need to generate a balance block. A balance block is created when there are enough new registrations or before the snake’s tail consumes the block containing the registration transaction. This approach typically ensures that the balance blocks are filled to capacity, containing as many balances as possible within the pre-configured maximum block size. Consequently, this technique reduces the total number of balance blocks required.

Adding a new balance block only when it is full

The chain operates most efficiently, exhibiting the lowest transaction delays when the balance blocks are evenly distributed along the active chain. We can move towards achieving this distribution when we re-add the dropped balance blocks to the chain. We can calculate the optimal spacing between balance blocks based on the total number of balance blocks and the length of the active chain.

If the distance between the previous balance blocks is too small, we cannot take any action because we cannot afford to delay re-adding a balance block. Conversely, if the distance is too large, we can move the re-adding of a balance block forward by several sequences. Repeating this process over multiple cycles can achieve a more even distribution of all balance blocks.

Starting a new chain

To move one step further, we also discuss how a new chain is created. Every blockchain starts with a genesis block. It is the big bang of the new chain. In MoonBlokz, the genesis is made from two blocks:

  • Block #0: A transaction block created by node #0 contains two transactions: A register transaction that registers node #0-s public key and a transfer transaction from node #0 to itself with the initial amount of node #0 (and it is the total money on whole network, at this point).
  • Block #1: A chain configuration block is also created by node #0.

The network processes the first block differently than the subsequent ones. It can be signed with the same key used for registration and operates without undergoing balance checks or fee calculations. This special handling is essential for bringing the chain to a normal operational state. When the snake’s tail reaches block #0, this block will also be discarded; however, after initialization, it no longer has any unique role. The chain configuration will be repeated periodically, maintaining the same content each time. Currently, in MoonBlokz, there is no option to modify the chain’s configuration. This feature may be added in future versions. Still, it will require an approval process (which is relatively straightforward) and strict checks to ensure that the new chain is compatible with the hardware setup of the existing nodes (and handle if it is not).

After the genesis block, node #0 can add new nodes, and these new nodes also can create additional nodes. The nodes can also facilitate transfers. Mining generates more currency in the network. However, adding new nodes reduces the total amount of currency. The entire process (mining rewards and fees) can be customized in the chain configuration using many parameters (like the actual number of nodes, total currency in the network, and the average transaction size).

Efficiency calculation

Snake_chain introduces an inefficiency factor to the network. If we don’t need to delete older blocks, we can eliminate the balance blocks and periodic updates to the chain configuration. To better understand the extent of this inefficiency, let’s do some math. First calculate only with balances (not UTXOs)

Definition of parameters
chain_length: active chain length (count of blocks)
node_count: the total number of nodes in the network
balance_per_block: The number of balance items that can fit into a single block.

Number of balance blocks in the active chain = node_count/balance_per_block
Number of non-transaction blocks = node_count/balance_per_block+1

efficiency=(chain_length-number_of_non_transction_blocks)/chain_length
efficiency=(chain_length-node_count/balance_per_block-1)/chain_length
efficiency=1-node_count/(balance_per_block*chain_length)-1/chain_length

Let's see some examples:
if the block size is around 2000 bytes (10 LoRa packets/block),
40 balances can fit in a block.

chain_length=5000 (about 20Mbytes of storage), node_count=10000, balance_per_block=40
efficiency=1–10000/(40*5000)-1/5000=0,95

chain_length=3000(about 12Mbytes of storage), node_count=1000, balance_per_block=40
efficiency=1–1000/(40*3000)-1/3000=0,991

chain_length=1000(about 4Mbytes of storage), node_count=1000, balance_per_block=40
efficiency=1–1000/(40*500)-1/1000=0,949

In typical setups, the efficiency loss due to the snake chain is less than 5%. Additionally, the chain length, determined by hardware parameters, limits the maximum practical number of nodes. Therefore, it would be beneficial to calculate the price of new key registrations based on the chain length and the current number of nodes.

Now, let’s analyze the impact of UTXOs. There are two key areas to consider:

  • First, typical UTXO transactions (2 inputs — 2 outputs) are 2.5 times larger than node transfers. This increased size negatively affects the network’s -performance; however, it is not directly related to the snake_chain structure.
  • Second, when we re-add UTXO transactions, we experience a loss of efficiency. This aspect is related to the snake_chain. Considering the size of these transactions, we find that a re-added UTXO transaction is not larger than a balance transaction, but there is no limit to the number of UTXOs. We will evaluate this by calculating multiple scenarios.
If the block size is around 2000 bytes (10 LoRa packets/block),
around 40 re-added UTXOs can fit into a block.

chain_length=5000, utxo_count=10000, utxo_per_block=40
efficiency=1-10000/(40*5000)=0,95

chain_length=5000, utxo_count=50000, utxo_per_block=40
efficiency=1-50000/(40*5000)=0,75

chain_length=5000, utxo_count=100000, utxo_per_block=40
efficiency=1-100000/(40*5000)=0,5

chain_length=3000, utxo_count=50000, utxo_per_block=40
efficiency=1-10000/(40*5000)=0,58

The network is capable of managing a large number of UTXOs, and the custodian fee helps to reduce their number. When the blockchain becomes full — meaning no additional transactions can be added because UTXOs occupy all available space — the network may experience a stall. However, after several cycles, the number of UTXOs will gradually be reduced due to the custodian fee, allowing the network to return to a functional state.

snake_chain and the approval process

We have two (sometimes) conflicting rules:

  • If chain config or balance block is dropped out from the chain, it is necessary to add the same type of block to the end of the chain.
  • If a block is not created by the most-voted node, an approval process starts, and when it ends, an evidence block must added to the chain.

What should we do if both situations happen together? Which rule is the stronger?

The stronger rule is the snake_chain repeat block rule. Elsewhere, an essential block could be dropped out from the active chain. It leads to an even more complex approval process. Multiple blocks can be added between the block that requires approval and the evidence block. To complicate the implementation further, these blocks can initiate additional acceptance procedures. In such cases, a new approval process is immediately started for the subsequent blocks once the original block is approved.

Limitations of snake_chain

The main limitation of the snake_chain system is its inability to resynchronize when one or more nodes become disconnected for a very long time. Any conflict resolution mechanism is effective only as long as all nodes share a common block in their chains. If the overall active chain changes during the disconnection, the network will suffer a permanent fork.

Let’s see an example:

active chain length: 5000 blocks
transactions/block: 15
average transaction/hour: 60
snake_chain_inefficiency: 5%
disconnect_tolerance_time=5000*15/60/1,05=1 190 hours=49 days
If a node disconnects for more than 49 days, it can't reconnect to the network.

It is a significant limitation, but as we observe, the typical length of the resynchronization window is wide in real-world setups.

That’s all about the snake chain for now. Some more details will come in the implementation parts.

                              (_)(_)
                             /     \
                            /       |
                           /   \  * |
             ________     /    /\__/
     _      /        \   /    /                              @
    / \    /  ____    \_/    /                               |
   //\ \  /  /    \         /                               \|/
   V  \ \/  /      \       /                                 |
-------\___/--------\_____/----------------------------------+--------------

Before we jump into the implementation details, next time, we will investigate the data structures to use.

Author: Péter Sallai
Source: https://medium.com/@peter.sallai/moonblokz-series-part-iv-snake-chain-762075a1cbbc

Have a question?

If you are interested in our solutions, contact us via the form below.

Dorsum is proud to benefit from the European Commission’s Brexit Adjusment Reserve.
Learn more about our project aimed at aligning our Wealth Management Suite to the UK market. (Hungarian content)