Generalized State Channels with Move

Tom Close
Magmo
Published in
5 min readJul 9, 2019

The thing that excited me the most about the recent Libra announcement was the introduction of the Move programming language. In a brilliantly clear paper, the Libra team introduce a language designed to make it easier to write safe smart contracts. In particular, it puts resources and their transfer center stage, and uses clever techniques for ruling out subtle smart contract issues, such as re-entrancy.

However, as is usually the case, these innovations come with tradeoffs. In particular, the approach for avoiding re-entrancy also makes some of the patterns used to build state channels on Ethereum impossible to build in Move. The purpose of this post is to explore this issue and potential work-arounds.

What’s a State Channel?

A state channel is a way for a fixed set of participants to execute a joint protocol to reach an outcome. The outcome will typically determine how a set of assets held on the chain will be split. By enabling protocols to be executed trustlessly off-chain, state channels reduce load on the blockchain, helping it to scale.

State channels limit co-party risk by providing an on-chain challenge mechanism, through which any participant can force the channel to come to an outcome within a finite time, regardless of the actions of the other participants. In this way, state channels interactions do not require the participants involved to trust one another.

A useful way to think about a state channel protocol is as a state machine. To advance the state participants broadcast signed state transitions to one another. The allowed transitions are defined by a set of rules that the participants agree to when they enter the channel. In this model, the on-chain challenge mechanism amounts to a demand that the next participant provide their next state transition on-chain. If the participant fails to respond within a timeout, the state channel ends and the assets are distributed — typically in a manner that is unfavorable to the participant who blocked.

For this to work, it’s important that the chain can evaluate whether the state provided represents a valid state transition according to the rules of the channel. The rules of the channel must therefore be stored on-chain and be accessible to the “adjudicator” — the contract that manages the challenge process.

A generalized state channel framework is one that supports an extendable set of protocols: developers can define their own protocols and run them without requiring any modification to the framework itself.

How does this work on Ethereum?

Nitro Protocol is a generalized state channel framework on Ethereum. In Nitro, a state has the following format:

The AppDefinition and AppData fields are the parts that allow for generalized state channel behaviour: in order to define a protocol, the user creates a library, which is deployed on chain and whose address is used for the AppDefinition. This library defines a single public function, validTransition, which takes two states and returns a boolean. The validTransition function typically parses the AppData and also inspects the DefaultOutcome to determine if the state transition is allowed.

The validTransition method is called by the adjudicator both in the creation of a challenge and in the response to a challenge. In the rest of the example, we’ll focus on the response part of this interaction. To do this, we start by assuming that a challenge on state s1 is already underway. In order to respond, the responder calls the following method on the adjudicator:

The respond method first does some framework-level validity checks, ensuring that the framework invariants hold for all state transitions. It then uses the AppDefinition property of the state to look up the contract storing the app-specific transition rules, and then calls out to that contract to see if the provided AppData corresponds to a valid transition. If so, it cancels the challenge.

Note that this is where the extensibility comes from: in order to run a custom protocol, a user just had to (a) deploy the app definition library to the blockchain and (b) use the address of this library in the state. The adjudicator can then dynamically call this contract as part of its logic.

Can you do this on Move?

The short answer to this is “no”. In order to rule out re-entrancy attacks, the designers of Move have chosen to put certain restrictions on function calls. From the Move paper:

The Call bytecode instruction requires a unique procedure ID as input. This ensures that all procedure calls in Move are statically determined — there are no function pointers or virtual calls. In addition, the dependency relationship among modules is acyclic by construction. A module can only depend on modules that were published earlier in the linear transaction history.

Unfortunately, this rules out the dynamic call we used earlier when checking that the app’s rules for a state transition were satisfied. There is no analogue of app = NitroApp(s2.appDefinition) in Move. What’s more, dynamic calls can’t be added without breaking the strategy for ruling out re-entrancy.

The best you can do in this system is to use a property of the state to select one of a predetermined set of app rules:

While this leads to a state channel system capable of multiple applications, it doesn’t allow the user to extend the system with their own applications.

Is there another approach?

We know we can’t build a generalized system on Move where the adjudicator calls the game library: as we’ve already seen, the module dependency rules would mean that the game library would have to be on-chain before the adjudicator for this to be possible, which rules out extensibility. What if we were to turn this on its head? If the adjudicator can’t call the game library, what if the game library were to call the adjudicator?

In the code above, the user calls the respond method on the application library, which first checks the application-specific transition rules, then calls the adjudicator itself. Note that in order to get this to work, we need the call from the application library to the adjudicator to be privileged — and in order to achieve this we need to be able to check within the adjudicator what the calling module is.

There isn’t currently a get_calling_module in Move, so the approach above won’t work at the moment. If this functionality were added in the future, it would make it much easier to build state channels and other extensible systems on Move!

Summary

Move’s approach to ruling out re-entrancy attacks means that the approach to building generalized state channels on Ethereum isn’t directly applicable to blockchains running Move. There are other approaches though, which could be enabled with a small change to the Move language.

Magmo is the state channel research and development team behind Nitro Protocol and ForceMove. For more information, come chat in our Discord group!

--

--