Good afternoon, dear readers!
This article focuses on the Plasma Cash Chain and sheds light on the following topics:
- trilemma scalability and ways to solve it;
- Child Cheyne data structures and their display in rutchein;
- implementation of input in rutcheyn;
- implementation of output from rutchain.
Opporty used the Javascript programming language to implement childchain, as well as Solidity for rutchain. Code examples are provided in these languages.
')
Blockchain and decentralization provide an opportunity to optimize and improve the work of almost any sphere of life, which uses the Internet and information technology. 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 rapid operation of applications with a distributed database can only be possible if the condition of high scalability is fulfilled.
Modern decentralized blockchains have several drawbacks. The main one is scalability. Ethereum processes about 20 tx / s. This is not enough in today's financial realities. At the same time, Ethereum has the highest 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 trust in the network.
Trilemma scalability
There is a blockchain scalability trilemma that includes three components:
- decentralization;
- security;
- scalability.
Deleralization 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 the creation of blocks and the generation of new Ledger records.
For clarity, you need to talk about the most centralized organizations. Usually a simple database is used instead of the blockchain. This organization is managed by special administrators. All transactions can be canceled by manual intervention.
In a fully decentralized network, each user can participate in building a network.
The most important consequence of decentralization is that the majority of the value goes to the community that participates in the creation of the blockchain. There is no intermediate team of managers who receive all the benefits instead of those who generate the very structure of the network. In fact, most crypto projects belong entirely to their distributors or users, and not to the founders. This is obviously a more attractive model for those who are not the founders.
Safety in the trilemma
We are talking about the ability of the blockchain to resist attacks from external sources and to keep the system intact. Most blockchains are subject to many potential security threats. It is extremely important to know about the most common attack vectors and protection options.
In this case, decentralization and security go hand in hand. The more nodes, the less network depends on the centralized side and, consequently, less 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 multiple 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 by filling the network with malicious transactions;
>
Collusion attack - one or more 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 the size of the network. The most important question to ask when evaluating the network is: “How many users can this system withstand?” Bitcoin currently has between 2.9 and 5.8 million purse holders. The 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 diverge. Some users are willing to sacrifice security for the sake of scalability, others for scalability for the sake of security, but achieving balance is 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 the resources of O ©, that is, to a regular laptop or a small VPS);
- Scalability (ability to handle transactions O (n)> O ©);
- Security (protection against intruders using O (n) resources).
Green: a balanced state of the 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 some aspects of scalability and security.
Gray: full decentralization, with minimal or no security features and scalability.
Purple: Equal Balance between Security and Scalability, Non-decentralization.
“Holy Grail” in the blockchain technology means the unification of all three aspects.
In most current projects working with cryptocurrencies, two basic properties are achieved: decentralization and security. However, scalability suffers.
Promising options for solving the trilemma
Proof of Stake (PoS)
Proof of Stake (PoS) provides potential scalability enhancement. POS replaces cryptocurrency mining based on the Proof of Work (PoW) system. The selection of the validator is very fast - in a deterministic way. At the same time there is no electricity consumption and it is environmentally beneficial.
Sidechains
In the Ethereum virtual network, it is possible to create 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 EVM, but causes greater trust in the management of the sidechain. Thus, trust to a third party reduces decentralization.
Sharding
Sharding breaks transactions into smaller “pieces” of data. Instead of each individual node in the network processing whole transactions, the 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 in the blockchain.
Increase block size
Litecoin and Bitcoin Cash (BCH) are forks for 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 storing more transactions per block, thereby increasing transaction processing speed.
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 it is not necessary to register each transfer. Simply open a kind of payment channel and record the data on its opening in the blockchain. This channel will remain open as needed. When it is necessary to close it, the result of all transactions made in this channel will simply be recorded in the blockchain. Following this idea, you can create a whole network of channels for payments. Then transactions in the blockchain will be used much less frequently.
A payment channel is just a merger of several transactions. The channel can close any of its members. This action will be similar to opening a safe, allowing you to collect funds belonging to participants and write down the data on their transfer to the blockchain.
This technology becomes really powerful when several similar channels join together in a network called The Lightning Network. This network is specifically built for Bitcoin.
Raiden network
For Ethereum, the best known equivalent of Lightning is the Raiden Network.
This solution is 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 interacting 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, increasing the confidentiality of transactions and micropayments. Most of the payment channels exist outside the network and only occasionally form transactions within the ruthchan, which significantly reduces the throughput of child labor.
Optimal solution
Lightning ideologists have created a new childchain concept that solves the problems of the speed of the blockchain.
Opporty practically implements the concept of Plasma and Plasma Cash.
Plasma is a set of smart contracts that run over Ethereum rutchain and consist of a network of child chains connected to a root chain in a hierarchical tree structure.
That is, Ethereum rutchain security is used to optimize scalability.
Plasma Cash: an implementation option from Opporty
Opporty uses the Plasma Cash implementation 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 used and tokens on the network receive unique serial numbers.
Plasma Cash features:
- Sharded client side validation — clients just need to follow their Plasma chain to get their tokens. This means that transaction throughput can increase without increasing the burden on individual users.
- Simplify Mass Exit — Mass exits become less of a threat to the network, since the thief must submit an exit transaction for each token he wants to steal.
- No two-way confirmations — transactions no longer require two-step submission and confirmation. Instead, a transaction can be spent as soon as it is included in the main chain.
Disadvantage:
Large token values ​​- since each token must be assigned a serial number, it is not possible to produce arbitrarily small tokens. This is due to the fact that at some point the cost of buying a token will be more than the cost 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 the link to the previous prevBlock block (it is needed to move along the block chain), the tokenId token identifier (it must be unique), and also the newOwner last owner of the token.
Further, in order to assemble a block and get a rutchain hash, a special type of tree Patricia Merkle Trie is used. The same tree is used in Ethereum. It has a compressed appearance. At the same time, it is still possible to receive proofs of the inclusion or non-inclusion of a transaction in a block.
Signature is a signature on elliptic curves.
A transaction that spends 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” that spends this token where transactions are allowed to take place. This format allows users to check the complete history of the Plasma chain, as well as to prove and disprove possession for specific tokens.
In order to spend a token, you need to validate the chain, check it for missing blocks and only then re-sign the transaction along with the entire history.
The block looks like this:
const blockFields = [ {name: 'prevHash'}, {name: 'blockNum', isDecimal: true}, {name: 'transactions'}, {name: 'merkleRoot'}, {name: 'time'} ]
At a basic level, a blockchain is just a chain of blocks with reference to the previous block. Such a structure makes it possible to obtain the property of immutability, that is, not rewriting history. merkleRoot allows you to write checkpoints in rutchain.
In rutchain, at the level of a smart contract, 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 functions — RLP serialization / deserialization.
Input Methods in Plasma Cash
Anyone can enter funds into Plasma Cash simply by transferring the broadcast to a smart contract. As a result, an OPP token will be received at a specific tokenId position.
Here is the implementation in the Solidity language:
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.
Withdrawal methods in Plasma Cash
Each person can withdraw their token by providing the last two transactions in the history of ownership of the token.
Implementing an output from rutchain:
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, the presence of two transactions is checked. If the current user is the owner of the transaction, then we simply add his output to the structure and leave two weeks to be able to challenge the output.
The conclusion can be challenged in three ways:
- Providing confirmation of spend on the transaction:
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 token to be displayed, then such a withdrawal will be canceled!
- Providing evidence of the costs of 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 test as if the token was spent before the withdrawal. First, the presence of the transaction in the root hash is checked. Next, we delete the output if it has already been spent.
- providing a transaction in the transaction history of the token to it.
This may be the 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 constructing part of the Opporty Plasma Cash protocol, the following conclusion was made:
This protocol provides security by rutchain in Ethereum.
By complicating the procedure of input and output from the Routchein and state compression (transaction blocks), all methods of output and input into the Routchain were examined, and basic data structures were investigated: transactions and blocks.
Using the sidebar on the basis of the Ethereum network, you can significantly speed up the execution of transactions. Opporty received up to
300,000 transactions per second on a single statement. This is much more than what current payment systems can provide.
Despite some data availability problems, the operator provides a high level of blockchain stability and makes it possible to perform effective international business transactions.
Plasma Cash brings a huge increase in scalability. Therefore, Opporty uses Plasma as part of its PoE protocol.
useful links
- White paper plasma
- Git hub
- Use cases and architecture description
- Lightning network paper