Last active
July 20, 2020 22:14
-
-
Save andrewgordstewart/c7daea8bfc46e8b7bbf655cafed6f45d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { State, StateVariables } from '@statechannels/wallet-core'; | |
import { Channel } from '../models/channel'; | |
type PresentStage = 'PrefundSetup' | 'PostfundSetup' | 'Running' | 'Final'; | |
type Stage = PresentStage | 'Missing'; | |
export type ChannelState = Channel & { peer: string }; | |
// stage is a property with a finite number of values, so it is easy to partition | |
const stage = (state?: State): Stage => | |
!state | |
? 'Missing' | |
: state.isFinal | |
? 'Final' | |
: state.turnNum === 0 | |
? 'PrefundSetup' | |
: state.turnNum === 3 | |
? 'PostfundSetup' | |
: 'Running'; | |
type Partitioner<T> = (ps: ProtocolState) => T; | |
type Left<E> = { readonly _tag: 'Left'; readonly left: E }; | |
const left = <T>(left: T): Left<T> => ({ _tag: 'Left', left }); | |
type Right<A> = { readonly _tag: 'Right'; readonly right: A }; | |
const right = <T>(right: T): Right<T> => ({ _tag: 'Right', right }); | |
type Either<E, A> = Left<E> | Right<A>; | |
type StateUpdate = { | |
type: 'StateUpdate'; | |
channelId: Bytes32; | |
} & Partial<StateVariables>; | |
type SupportState = { | |
type: 'SupportState'; | |
channelId: Bytes32; | |
hash: Bytes32; | |
}; | |
type SendMessage = { type: 'SendMessage'; message: any; to: string }; | |
type Notify = { type: 'Notify'; message: any }; | |
type ProtocolAction = StateUpdate | SendMessage | Notify | SupportState; | |
type ExecutionResult = ProtocolAction[]; | |
type Executor = (ps: ProtocolState) => Either<Error, ExecutionResult>; | |
type ProtocolState = { app: ChannelState }; | |
const partialMatch = <T extends string>( | |
partitioner: Partitioner<T>, | |
executors: Partial<Record<T, Executor>> | |
) => (ps: ProtocolState) => executors[partitioner(ps)](ps); | |
const match = <T extends string>( | |
partitioner: Partitioner<T>, | |
executors: Record<T, Executor> | |
) => (ps: ProtocolState) => executors[partitioner(ps)](ps); | |
const error = (reason: string) => (_ps: ProtocolState) => left(Error(reason)); | |
const signLatestPrefund: Executor = (ps: ProtocolState) => { | |
if (stage(ps.app.latest) !== 'PrefundSetup') { | |
return left(Error('latest state is wrong')); | |
} | |
return right([ | |
{ | |
type: 'SupportState', | |
channelId: ps.app.channelId, | |
hash: 'ps.app.latest.stateHash', | |
}, | |
]); | |
}; | |
const prefundAlreadySigned = partialMatch((ps) => stage(ps.app.latest), { | |
Missing: (ps: ProtocolState) => { | |
return right([ | |
{ | |
type: 'SendMessage', | |
message: 'Please sign state 0!', | |
to: ps.app.peer, | |
}, | |
]); | |
}, | |
}); | |
const signPostfundState: Executor = (ps: ProtocolState) => | |
right([{ type: 'StateUpdate', channelId: ps.app.channelId, turnNum: 3 }]); | |
const prefundSupported = partialMatch((ps) => stage(ps.app.latestSignedByMe), { | |
PrefundSetup: signPostfundState, | |
NotAMatch: error('This is a type error'), | |
}); | |
const noSupportedState = match((ps) => stage(ps.app.latestSignedByMe), { | |
Missing: signLatestPrefund, | |
PrefundSetup: prefundAlreadySigned, | |
PostfundSetup: error('You were too eager'), | |
Running: error('You were too eager'), | |
// Type error: the following line is needed to not get a type error, since | |
// the `match` helper requires that you specify executors for each case that the "matcher" can spit out | |
// Final: error('You were too eager'), | |
}); | |
export const protocol = partialMatch((ps) => stage(ps.app.supported), { | |
Missing: noSupportedState, | |
PrefundSetup: prefundSupported, | |
PostfundSetup: (ps: ProtocolState) => | |
right([{ type: 'Notify', message: `Channel ${ps.app.channelId}is open!` }]), | |
}); | |
// Note that the `protocol` pattern matching syntax is logically equivalent to the following. | |
// Something about the `match` syntax above seems well suited to our problem. | |
// The `match` syntax also makes it possible to generate flow diagrams from the protocols: | |
// the second argument is defining arrows from where you "are" in your decision making process to where you will "go", | |
// if the world is a certain way. | |
export const protocol2: Executor = function (ps: ProtocolState) { | |
switch (stage(ps.app.supported)) { | |
case 'Missing': | |
return noSupportedState(ps); | |
case 'PrefundSetup': | |
return prefundSupported(ps); | |
case 'PostfundSetup': | |
return right([ | |
{ type: 'Notify', message: `Channel ${ps.app.channelId}is open!` }, | |
]); | |
case 'Final': | |
case 'Running': | |
return left(Error('Whoops')); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment