Code Walkthrough for Generalised State Channels
Last time we presented a framework for generalised state channels. This is a way to construct off-chain asset transfers between known participants. One could securely transfer Ether or tokens, non-fungible energy like kitties or titties, and whatnot using the same set of blockchain transactions. We cleaned up the code, made it tolerable in terms of gas, added human-readable tests, and are ready to present the implementation of the framework.
The code is in the machinomy/mc2 repository. It is a typical Truffle-managed contracts repository, with a familiar layout:
/migrations and so on. Actually, it represents a fork of our payment channels contracts repo, so that later it could be merged with the upstream. The meat is in the
Construction of a state channel starts with the deployment of a Multisig contract. This puts a transaction on a blockchain, if all the participants agree with it. Our Multisig is restricted to just two participants. For an elaborate case, one could use a more advanced multisignature contract, Gnosis Safe.
Multisig exposes three methods:
doCall executes an ordinary call to the
doDelegate executes a
delegatecall. The latter is useful for a complex assets transfer from the Multisig to a subchannel contract, like moving funds to all the participants at once. The Multisig uses a separate contract for transferring logic. DistributeToken, for example, atomically sends ERC20 tokens to both parties.
In a happy case, the participants do just that: distribute the funds from the Multisig. After an agreement on asset distribution is achieved, a transaction goes to the Multisig to distribute the funds. A cooperative test case encodes the scenario:
- Create the Multisig;
- Move assets to the Multisig;
- Sign a Multisig transaction to move funds out of the Multisig;
- Execute it.
This kind of behaviour would be futile to assume in a cruel world. We must enforce honesty, and make cheating costly. The framework takes care of that.
Before an honest action can occur, the participants put in place a dispute resolution mechanism in place. This involves counterfactually creating a subchannel contract. In our Dispute scenario that is the Bidirectional Ether transfer subchannel. To make things simple, this is two transactions instead of one “uber-instantiation”:
- Deploy Bidirectional,
- Move funds from Multisig to Bidirectional,
- Do a Bidirectional transfer.
These two transactions are signed, but not yet deployed to the blockchain. This would happen if such a need arose. The same set of participants could support multiple channels over time. The best way to prevent a replay attack is now a sequential Multisig nonce. After a few rounds of subchannels opened and closed, a malicious participant could deploy a wrong transaction with an allowed Multisig nonce. To prevent that, we fix the set of transactions in a Lineup.
It stores a
merkleRoot of the transactions list. One could then conditionally execute a transaction. If it is contained in the
merkleRoot of the list.
So, the preparation step is really this:
- Counterfactually deploy Lineup,
- Counterfactually and conditionally deploy Bidirectional,
- Counterfactually and conditionally move funds to Bidirectional,
- Do a Bidirectional transfer.
Now a participant is able to trust her funds to the Multisig. In the case of a dispute, Lineup and Bidirectional contracts are deployed to the blockchain. Then the parties update the Bidirectional state according to their transfer state. After that, the Bidirectional subchannel distributes the funds, thus resolving the dispute.
Both the happy case and dispute resolution are covered. However, if one looks at the test code, one would see how convoluted the channel management is. It seems to follow the Interpreter pattern. Thus, at the next iteration, it would not be a good stretch to wrap every transaction into some sort of Command. Interpreter would then parse the Commands and either deploy that to the blockchain, or calculate the outcome locally. At that stage, it is feasible to provide a third party with an API to build upon the framework. That would also handle a TCR-based upgrade mechanism. In the next episode, we will see how that really works.