Plasma Cash Chain as a solution to the blockchain scalability trilemma

  • Tutorial
Good afternoon, dear readers!

This article is about the Plasma Cash Chain and sheds light on the following topics:

  • the scalability trilemma and methods for its solution;
  • child chain data structures and their display in the root chain;
  • implementation of input to the root chain;
  • implementation of the withdrawal from the root chain.

Opporty used the Javascript programming language to implement the child chain, as well as Solidity for the root chain. Code examples are provided in these languages.



Blockchain and decentralization make it possible to optimize and improve the work of almost any sphere of life where the Internet and information technologies are used. They increase reliability, financial efficiency, and also facilitate the digitalization of real things and goods.

Smart contracts bring business logic to decentralized networks. This allows you to build new DAPP applications.

The execution of smart contracts and the fast operation of applications with a distributed database can only be possible if the scalability condition is met.

Modern decentralized blockchains have several drawbacks. The main one is scalability. Ethereum processes around 20 tx / s. This is not enough in modern financial realities. At the same time, Ethereum has the highest possible degree of protection against hacking and network breakdowns. Other cryptocurrencies and systems built on the blockchain do not have such a high degree of decentralization, which reduces the trust in the network.

The scalability trilemma


There is a blockchain scalability trilemma that includes three components:

  • decentralization;
  • security;
  • scalability.

Decentralization in the Trilemma


Decentralization, as the term implies, reflects the degree of diversification of ownership of activity in the blockchain, as well as the degree of diversification of creating blocks and generating new ledger entries.

For clarity, it is necessary to talk about the most centralized organizations. Usually a simple database is used instead of the blockchain. Such an organization is run by special administrators. All transactions can be canceled by manual intervention.

In fully decentralized networks, each user can participate in building a network.

The most important consequence of decentralization is that most of the value goes to the community that is involved in creating the blockchain. There is no intermediate team of managers who receive all the benefits instead of those who generate the network structure itself. In fact, most crypto projects are wholly owned by their contributors or users, not the founders. This is obviously a more attractive model for those who are not a founder.

Safety in the Trilemma


It's about the ability of the blockchain to withstand attacks from external sources and keep the system in an unchanged state. Most blockchains are subject to many potential security threats. It is imperative to know about the most common attack vectors and defense options.

In this case, decentralization and security go hand in hand. The more nodes, the less the network depends on the centralized side and, therefore, the risk of having a central point of failure. However, there are many other attack vectors that pose a danger to decentralized networks, including:
> 50% attack - an object that owns more than 50% of the total number of unpaid tokens actually owns the network;
> Sybil Attack- the user can generate many identifiers in the system in order to effectively control a significant share in the ownership and / or decision making in the network;
> DDoS - a distributed denial of service (DDoS) attack occurs when there is an intention to disrupt traffic on the network, filling the network with malicious transactions;
> Collusion attack - one or several objects (or nodes) decide to unite to perform any malicious operation on the network.

Scalability in the Trilemma


The degree of scalability is important because it determines the ultimate throughput, in other words, the upper limit of network size. The most important question to ask when evaluating a network is: “How many users can this system withstand?” Bitcoin currently has between 2.9 and 5.8 million wallet holders. EOS has several thousand members.

Scalability and decentralization can coexist, but security is reduced. Developers choose the platforms that best suit their needs. Users do the same. Opinions of the two sides sometimes differ. Some users are willing to sacrifice security for scalability, others are willing to sacrifice scalability for security, but balancing is much more difficult.

“Holy Grail” in blockchain technology


By definition, a blockchain has only two of the following three properties:

  • Decentralization (each participant has access only to O © resources, that is, to a regular laptop or a small VPS);
  • Scalability (ability to process transactions O (n)> O ©) ;
  • Security (protection against intruders using O (n) resources).


Green: A balanced state of three conditions.
Red: strong security but limited decentralization and scalability.
Blue: efficiency is high, but security and decentralization are limited.
Black: decentralization is high, but there are no aspects of scalability and security.
Gray: full decentralization, with minimal or missing qualities of security and scalability.
Violet: an equal balance between security and scalability, the rejection of decentralization.

The “holy grail” in blockchain technology means combining all three aspects.
In most current projects working with cryptocurrencies, two basic properties are achieved: decentralization and security. Scalability suffers.

Promising solutions to the trilemma


Proof of Stake (PoS)


Proof of Stake (PoS) provides potential scalability enhancements. POS replaces cryptocurrency mining based on the Proof of Work (PoW) system. The choice of validator is very fast - in a deterministic way. At the same time, there is no energy cost and it is environmentally friendly.

Sidechains


In the Ethereum virtual network, there is the possibility of creating a side network in which the project can process its individual transactions, and then record only the initial and final results in the Ethereum network. This reduces the load on the EVM, but gives more confidence in the management of the sidechain. Thus, trust in a third party reduces decentralization.

Sharding


Sharding breaks transactions into smaller pieces of data. Instead of each individual node in the network processing entire transactions, nodes are divided into groups, and these groups of nodes process certain pieces of data. Later, during processing, these pieces of data are re-assimilated for permanent storage on the blockchain.

Increase block size


Litecoin and Bitcoin Cash (BCH) are “forks” for the Bitcoin blockchain. Forking basically copies one blockchain. After branching, you can make changes. Both LTC and BCH increased the size of each block, which allowed to store more transactions per block, thereby increasing the processing speed of transactions.

Lightning network


The very first sidechain solution was lightning. The main idea of ​​the Lightning Network is that not all transactions should be recorded in the blockchain, as this overloads the network. If users transfer funds to each other several times, then registering each transfer is optional. It’s enough just to open a kind of payment channel and write down the data about its opening on the blockchain. This channel will remain open as needed. When it will be necessary to close it, the result of all transactions made in this channel is simply written to the blockchain. Following this idea, you can create a whole network of channels for payments. Then transactions on the blockchain will be used much less frequently.

A payment channel is just a combination of several transactions. A channel can be closed by any of its members. This action will be like opening a safe, which allows you to take the funds belonging to the participants and write down the data on their transfer to the blockchain.
This technology becomes really powerful when several such channels are combined into a network called The Lightning Network. This network is specially built for Bitcoin.

Raiden network


For Ethereum, the best-known counterpart of Lightning is the Raiden Network.
This is a solution for scaling outside the main blockchain. It is compatible with the transfer of ERC-20 tokens in bidirectional payment channels.

Its basic architecture is complex, but interaction with Raiden requires developers to only interact with the API to create scalable applications on Raiden.

Raiden is designed to provide instant payments and low commissions, increase transaction confidentiality and micropayments. Most payment channels exist outside the network and only occasionally form transactions within the root chain, which significantly reduces the throughput of the child chain.

Optimal solution


Lightning ideologists have created a new childchain concept that solves blockchain speed problems.

Opporty practically implements the concept of Plasma and Plasma Cash.

Plasma is a set of smart contracts that run on top of the Ethereum root chain and consist of a network of child chains connected to the root chain in a hierarchical tree structure.

That is, the security of the Ethereum rootchain is used to optimize scalability.

Plasma Cash: Opporty Option


Opporty uses the implementation of Plasma Cash in the first version.

This model is the most effective plasma implementation in terms of scalability.
Plasma Cash is a system based on the use of unique identifiers for each token in the Plasma chain. That is, NFT is applied and tokens in the network receive unique serial numbers.

Features of Plasma Cash:

  • Sharded validation on the client side - customers just need to monitor their Plasma chain to get their tokens. This means that transaction throughput can increase without increasing the load on individual users.
  • Simplification of mass exit - mass exits become less of a threat to the network, as the thief must submit an exit transaction for each token that he wants to steal.
  • No two-way confirmations - transactions no longer require two-step sending and confirmation. Instead, a transaction can be spent as soon as it is included in the main chain.

Disadvantage:

Large token denominations - since each token must be assigned a serial number, you cannot produce arbitrarily small tokens. This is due to the fact that at some point the value of the token purchase will be more than the value of the token itself.

Transaction Structure in Opporty Plasma Cash


Opporty used Javascript to implement childchain. Each transaction in Opporty Plasma Cash is a similar structure:

const transactionFields = [
{name: 'prevHash'},
{name: 'prevBlock', int: true, default: 0},
{name: 'tokenId', isDecimal: true},
{name: 'newOwner'},
{name: 'type'},
{name: 'signature'},
]

The main elements here are a link to the previous prevBlock block (it is needed to move around the block chain), the tokenId token identifier (it must be unique), as well as newOwner the last owner of the token.

Further, in order to assemble the block and get the root chain hash, a special type of the Patricia Merkle Trie tree is used. The same tree is used in Ethereum. It has a compressed look. At the same time, you can still receive proofs of inclusion or non-inclusion of a transaction in a block.
Signature is a signature on elliptical curves.

A transaction spending a token with a given tokenId is valid only if it is included in the Merkle tree in the tokenId position, that is, for each token in the Merkle tree there is only one “place” spending this token where transactions are allowed. This format allows users to check the full history of the Plasma chain, as well as prove and disprove ownership for specific tokens.

In order to spend a token, you need to validate the chain, check for missing blocks, and only then re-sign the transaction along with the entire history.

The block is as follows:

const blockFields = [
{name: 'prevHash'},
{name: 'blockNum', isDecimal: true},
{name: 'transactions'},
{name: 'merkleRoot'},
{name: 'time'}
]

At a basic level, a blockchain is simply a chain of blocks with a link to the previous block. Such a structure makes it possible to obtain the property of immutability, that is, not rewriting history. merkleRoot makes it possible to write checkpoints to the root chain.

In the root chain, at the smart contract level, it looks like this (Solidity language):

/*
* Block structure (represents one block in a chain)
*/
struct Block {
uint block_num;
bytes32 merkle_root;
uint time;
/*
* Transaction structure (decoded from RLP form)
*/
struct Transaction {
bytes32 prevhash;
uint prev_block;
uint token_id;
address new_owner;
}
 

Encoding is performed using the encoding / decoding - RLP serialization / deserialization functions.

Ways to enter Plasma Cash


Anyone can deposit funds into Plasma Cash simply by transferring ether to a smart contract. As a result, an OPP token will be received at a specific tokenId position.

Here is the implementation in Solidity:

function deposit() public payable {
uint token_id = uint(keccak256(msg.sender, msg.value, deposit_blk));
// token.index = deposit_blk;
tokens[token_id] = msg.value;
deposit_blk += 1;
emit DepositAdded(msg.sender, msg.value, token_id, current_blk);
} 

That is, tokenId is generated as a random number (hash). Next, an event is generated that is scanned in the child chain.

Ways to withdraw to Plasma Cash


Each person can withdraw his token by providing the last two transactions in the token's ownership history.

Implementation of exit from the root chain:

function startExit(uint block_num, bytes tx1, bytes tx0, bytes proof1, bytes proof0) public returns (uint exit_id) {
require(checkPatriciaProof(keccak256(tx1), childChain[block_num].merkle_root, proof1));
bytes32 prev_hash;
uint prev_blk;
uint token_id;
address new_owner;
(prev_hash, prev_blk, token_id, new_owner,) = getTransactionFromRLP(tx1);
require(msg.sender == new_owner);
require(tokens[token_id] > 0);
bytes32 hashPrevTx = keccak256(tx0);
require(checkPatriciaProof(hashPrevTx, childChain[prev_blk].merkle_root, proof0));
require(prev_hash == hashPrevTx);
Exit storage record = exitRecords[token_id];
require(record.block_num == 0);
record.block_num = block_num;
record.new_owner = msg.sender;
record.prev_block = prev_blk;
if (childChain[block_num].time > block.timestamp - week)
record.priority = childChain[block_num].time;
else
record.priority = block.timestamp - week;
exits.add(record.priority);
exit_ids[record.priority].push(token_id);
emit ExitAdded(msg.sender, record.priority, token_id);
return token_id;
}
 

First, two transactions are checked. If the current user is the owner of the transaction, then we simply add its output to the structure and leave two weeks for the opportunity to challenge the output.

The conclusion can be challenged in three ways:

  • Providing confirmation of spend on transactions:

function challengeSpent(uint exit_id, uint blk_num, bytes tx1, bytes proof) public { 
require(checkPatriciaProof(keccak256(tx1), childChain[blk_num].merkle_root, proof));
Exit memory record = exitRecords[exit_id];
require(record.block_num > 0);
uint prev_block;
uint token_id;
(, prev_block , token_id, ) = getTransactionFromRLP(tx1);
require(tokens[token_id] > 0);
require(prev_block == record.block_num && record.block_num < blk_num);
require(token_id == exit_id);
exit_ids[record.priority].remove(exit_id);
delete exitRecords[exit_id];
emit ExitChallengedEvent(exit_id);
}    

If there is a transaction that is already spending the displayed token, then such a withdrawal will be canceled!

  • Proof of expenses for the previous transaction:

/*
* Challenge exit by providing
* a proof of a transaction spending P(C) that appears before C
*/
function challengeDoubleSpend(uint exit_id, uint blk_num, bytes tx1, bytes proof) public { 
require(checkPatriciaProof(keccak256(tx1), childChain[blk_num].merkle_root, proof));
Exit memory record = exitRecords[exit_id];
require(record.block_num > 0);
// bytes32 prev_hash; 
uint prev_block;
uint token_id; 
(, prev_block , token_id, ) = getTransactionFromRLP(tx1);
require(tokens[token_id] > 0);
// check if token double spent
require(prev_block == record.prev_block && blk_num < record.block_num);
// require(token_id == exit_id);
exit_ids[record.priority].remove(exit_id);
delete exitRecords[exit_id];
emit ExitChallengedEvent(exit_id);
}   

This is the same check as if the token had been spent before the withdrawal. First, check for a transaction in the root hash. Next, we delete the output if it has already been spent.

  • providing a transaction in the transaction history of the token before it.

This may be a wrong story, so you need to confirm it with a child transaction:

//  */
function challengeInvalidHistory(uint exit_id, uint blk_num, bytes tx0, bytes proof) public { 
// check if proof is valid
require(checkPatriciaProof(keccak256(tx0), childChain[blk_num].merkle_root, proof));
Exit memory record = exitRecords[exit_id];
require(record.block_num > 0);
bytes32 prev_hash; 
uint token_id; 
(prev_hash, , token_id, ) = getTransactionFromRLP(tx0);
//require(exit_id == token_id);
require(tokens[token_id] > 0);
// transaction should be before exit tx in history
require(blk_num < record.block_num - 1);
challenged[exit_id] = blk_num;
emit ChallengedInvalidHistory(exit_id, token_id);
}
 

Calling the first and second script blocks the output immediately.

The call to the third scenario can be answered by providing a direct descendant. It must be equal to or ahead of the parent transaction.

/*
* Respond to invalid history challenge by providing
* the direct child of C*, which must be either equal to or before P( C )
*/
function respondChallenge(uint exit_id, uint blk_num, bytes childtx, bytes proof) public {
require(challenged[exit_id] > 0);
Exit memory record = exitRecords[exit_id];
require(record.block_num > 0);
require(checkPatriciaProof(keccak256(childtx), childChain[blk_num].merkle_root, proof));
// get transaction from rlpencoded form
bytes32 prev_hash; 
uint prev_block;
uint token_id; 
(prev_hash, prev_block, token_id, ) = getTransactionFromRLP(childtx);
// if direct child
if (prev_block == challenged[exit_id] ) {
if (blk_num <= record.prev_block && token_id == exit_id ) {
delete challenged[exit_id];
emit ExitRespondedEvent(exit_id);
} else {
exit_ids[record.priority].remove(exit_id);
delete exitRecords[exit_id];
emit ExitChallengedEvent(exit_id);
}
}
} 

That is, if the correct child transaction is received, the output is disputed and remains in the queue!
After building part of the Opporty Plasma Cash protocol, the following conclusion was made:
This protocol provides security through the Ethereum root chain.

By complicating the input and output procedures from the root chain and state compression (transaction blocks), we examined all methods of output and input to the root chain, and also investigated the basic data structures: transactions and blocks.

Using the sidechain based on the Ethereum network, you can significantly speed up transactions. Opporty received up to 300,000 transactions per second on a single operator. This is much more than what current payment systems can provide.

Despite some problems of data availability, the operator provides a high level of stability of the blockchain and makes it possible to carry out effective international business transactions.

Plasma Cash brings a huge increase in scalability. Therefore, Opporty uses Plasma as part of its PoE protocol.

useful links


  1. White paper plasma
  2. Git hub
  3. Use cases and architecture description
  4. Lightning network paper

Also popular now: