Token Contracts on Plasma or: a Tale about Local and Global Invariants
The plasma system defines a structure of interconnected blockchains arranged in a tree structure that promises scalable smart contracts. One of the key ideas there is that each of the blockchains regularly store their current block hash in their parent chain so that users can challenge potentially invalid child state transitions in the parent chain.
This model is secure not because of a difficult proof of work (the chains would use proof of stake or even a fixed validator set), but rather because users watch chains they have a stake in and thus will challenge invalid state transitions, potentially escalating as far up as the trusted main chain.
Here, the scalability does not come from the fact that blockchains are relieved from their load by creating a big number of smaller chains and moving the transactions there. Scalability is only achieved once a user does not have to verify every single transaction that is sent to the system.
If, for example, a user only cares about a single smart contract that resides in a single chain in a leaf of the system, it is sufficient for the user to verify this leaf and all nodes on the path to the root chain. If a transaction is committed by means of block hashes all the way up to the root chain and there is no invalid state transition in the chains on the way up to the root, the user can be reasonably sure that the transaction cannot be retroactively declared invalid.
This system still does not solve the scalability problem: As long as the smart contract only lives inside a single blockchain, it can merely process a limited amount of transactions. While this might be enough for some use-cases, a token contract can easily reach this limit. The system would scale, if the token contract exists on all of the blockchains, dividing the token transfer load and it is possible to move tokens up and down the tree. Users would have accounts in only one or perhaps some of the chains and watch the paths to the root from chose chains.
In such a simple model, an attacker could select a chain that is mostly unused, take it over, create an invalid state transition that creates tokens out of thin air and then move these tokens up to the root. If nobody is watching the attacked chain (or the attacker can turn off their computers or censor their transactions), the attacker is safe as soon as he or she is able to move the tokens far enough up.
Creating tokens out of thin air is a violation of the assumed invariants of a token contract. Unfortunately, in contrast to the other invariant “nobody but me can spend my tokens”, this is a global invariant and thus cannot be enforced by just watching a small number of chains.
Luckily, there is an invariant that is almost the same as “nobody can create tokens out of thin air”, but which can be locally enforced: We track the sum of the token balances of each direct child chain in the smart contract of the parent chain and enforce that changes to these numbers always have to come with a respective amount of tokens being moved from the child to the parent or vice-versa. This way, a child chain basically becomes a single account in the parent chain which is only subdivided into the respective token holders in the child chain.
In this situation, an attacker can still create tokens in unwatched child chains, but he or she can only move as many tokens out of these flawed chains as is the total balance of these chains. For users that are not interested in these chains, the situation would not change: For them, someone took out tokens from a pool, but it is not relevant who did it.
In effect, the attacker of course steals tokens from users who have accounts in the attacked child chains, but at least the impact of the attack is confined to chains that are not properly watched. In turn, this means that you have to constantly watch all chains you have tokens in. But there is also a “cheaper” solution to this:
If you do not want to move your tokens very often, you can transfer them to a chain close to the root chain. These chains likely have higher transaction fees (which is not relevant if you just want to park your tokens), but also provide higher security because they are watched by more people.
Example Token Contract
The following smart contract is replicated on all chains in the system. The smart contract language used follows the syntax and semantic of Solidity, but it has one additional feature: Functions can be marked “edge”.
Such functions are executed as part of a transaction sent to an edge of the tree, i.e. sent to two chains at the same time that are linked with an edge in the tree. Parts of the code of these functions are executed on the relative parent and other parts on the relative child in sequence. The actual compiled smart contract will use logs and Merkle proofs for synchronisation between these parts.
Inside an edge functions, the identifier child is an integer (0 or 1 for two children) identifying the child relative to the parent.
In the event where a child chain is declared faulty, the child parts can also be executed in the parent chain or recursively in any chain on the way up to the root (updating the root hash of the child chain stored in the parent chain and requiring relevant Merkle proofs for execution). A child that does not react to edge transactions after a certain time is declared faulty.
Because of the synchronisation points between the “child” and “parent” parts, the tokens can be safely moved up and down the tree.
Currently, you have to put a little more thought into your smart contracts if you want them to be scalable on a plasma-like system. Not every problem is suitable for map-reduce or can be processed using big data algorithms either. If you can come up with elementary promises your smart contract should fulfill and can transform them into local properties, though, you can achieve almost infinite scalablity!