A state channel can be thought of as an emergent property of data (which we call
state) exchanged between a fixed set of actors (which we call
participants). The participants have the ability to digitially sign the states that they exchange, and they also keep track of the underlying
channelNonce to uniquely identify the interaction they are about to perform.
Construct a State with the correct format
@statechannels/nitro-protocol exposes a
State type as a container for all the fields that are required:
Notice that the outcome field must conform to the
Outcome type, which we also imported from
@statechannels/nitro-protocol. The outcome is some data that specifies a redistribution of funds when the channel finalizes. Don't worry about this field just yet, we will revisit it later (we got away with an empty array, for now).
Fixed and Variable Parts
It is convenient to define some solidity structs, each containing a subset of the above data:
which contains information which cannot be changed for the lifetime of the channel (unless by unanimous consensus), and
Conform to an on chain
In ForceMove, every state has an associated 'mover' - the participant who had the unique ability to progress the channel at the point the state was created. The mover can be calculated from the
turnNum and the
participants as follows:
The implication of this formula is that participants take turns to update the state of the channel. Furthermore, there are strict rules about whether a state update is valid, based on the previous state that has been announced. Beyond conforming to the state format, there are certain relationships that must hold between the state in question, and the previously announced state.
The full rule set is (pseudo-code):
Note the use of
app.ValidTransition. This function should be written by third party DApp developers. We provide a
TrivialApp contract which always returns
true, to aid in testing:
n is the number of participants, then states with turn numbers
n-1 (inclusive) are known as "pre fund setup" states. They signal each participant's intention to join the channel with a particular outcome specified.
Once a pre fund setup state with turn number
n-1 is supported, it is safe for the channel to be funded.
States with turn numbers
2n-1 (inclusive) are known as "post fund setup" states. They signal each participant's agreement that the channel is fully funded. Once a post fund setup state with turn number
2n-1 is supported, the state channel can begin execution in earnest (updating the
Support a state in several different ways
Although you can check the validity of a state transition by providing the data above to an on-chain method, to cause any meaningful change in on-chain state (such as releasing funds), digitial signatures on those states are also required.
Nitro protocol uses the idea of supporting a state: in order for the chain to accept a channel state,
s, that channel state must be accompanied by a support proof: i.e. exactly
n signatures (where
n = participants.length). The simplest way to accomplish this is to provide a sequence of
n states terminating is state
s, where each state is signed by its mover and each consecutive pair of states form a valid transition.
There is also an optimization where a state can be supported by
n signatures on a sequence of
m < n states, provided again that each consecutive pair of those
m states form a valid transition and further provided each participant has provided a signature on a state later or equal in the sequence than the state for which they were the mover.
In the extreme, this allows a single state signed by all
n parties to be accepted by the chain.
In most cases where a support proof is required for some change of state of the chain, the entire proof is submitted with the blockchain transaction: no on-chain channel states are involved. The
respond method is an exception to this rule, and allows for the submission of only a single state in certain circumstances, with the support proof being implied by a combination of on-chain storage and submitted data.
Nitro wallets need only store the "last"
n states, because they never need to submit more than
n states to the chain.
In the following diagram, A is participant 0, B is participant 1 and C is participant 2. The states are shown by circles and numbered 0, 1, and 2. We are considering whether state with
turnNum = 2 is supported by various sets of signatures on the states in the sequence.
The yellow boxes show who signed what: in the first example, everyone signed their own state. This is acceptable:
Alternatively, A could sign a later state in the sequence:
or A, B and C could all sign the final state in the sequence:
The following signatures would not be acceptable:
This is because C signed a state earlier in the sequence than the one she is the mover for.
Note that there is no need to submit a state if it is both not signed by anybody and is not preceeded by any signed states.
We provide a helper function to sign a
it returns an object of the
which we can make use of in the rest of the tutorial.
Alternatively you may use
signStates(states, wallets, whoSignedWhat),
which accepts an array of
States, an array of ethers.js
Wallets and a
whoSignedWhat array of integers. The implicit definition of this last argument is as follows: For each participant, we are asserting that
sigs is an array of signatures for each participant, in participant order (e.g.
[sig of participant, sig of participant, ...]). The above call to
signStates returned an appropriate array because we passed in the
wallets in the correct, participant order.
Notice that a state may be supported simply by having a full set of signatures (one from each participant). In such a case, there will be no
validTransiiton check made by the chain.
State updates can always be made via unanimous consensus.
This mechanism can be used to dynamically update the
FixedPart.challengeDuration and/or the