Skip to content
This repository has been archived by the owner on Feb 24, 2021. It is now read-only.

Commit

Permalink
🔀 Merge branch '310-channel-dispute-states' into 'dev'
Browse files Browse the repository at this point in the history
[channel] Dispute states in channel state machine

Closes #310

See merge request perun/go-perun!214
  • Loading branch information
sebastianst committed Mar 5, 2020
2 parents 6e1d4bc + a5e1002 commit 5cf716e
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 46 deletions.
14 changes: 7 additions & 7 deletions backend/ethereum/channel/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (

// Register registers a state on-chain.
// If the state is a final state, register becomes a no-op.
func (a *Adjudicator) Register(ctx context.Context, req channel.AdjudicatorReq) (*channel.Registered, error) {
func (a *Adjudicator) Register(ctx context.Context, req channel.AdjudicatorReq) (*channel.RegisteredEvent, error) {
if req.Tx.State.IsFinal {
return a.registerFinal(ctx, req)
}
Expand All @@ -28,21 +28,21 @@ func (a *Adjudicator) Register(ctx context.Context, req channel.AdjudicatorReq)

// registerFinal registers a final state. It ensures that the final state is
// concluded on the adjudicator conctract.
func (a *Adjudicator) registerFinal(ctx context.Context, req channel.AdjudicatorReq) (*channel.Registered, error) {
func (a *Adjudicator) registerFinal(ctx context.Context, req channel.AdjudicatorReq) (*channel.RegisteredEvent, error) {
// In the case of final states, we already call concludeFinal on the
// adjudicator. Method ensureConcluded calls concludeFinal for final states.
if err := a.ensureConcluded(ctx, req); err != nil {
return nil, errors.WithMessage(err, "ensuring Concluded")
}

return &channel.Registered{
return &channel.RegisteredEvent{
ID: req.Params.ID(),
Timeout: time.Time{}, // concludeFinal skips registration
Version: req.Tx.Version,
}, nil
}

func (a *Adjudicator) registerNonFinal(ctx context.Context, req channel.AdjudicatorReq) (*channel.Registered, error) {
func (a *Adjudicator) registerNonFinal(ctx context.Context, req channel.AdjudicatorReq) (*channel.RegisteredEvent, error) {
_sub, err := a.SubscribeRegistered(ctx, req.Params)
if err != nil {
return nil, err
Expand Down Expand Up @@ -165,7 +165,7 @@ func (r *RegisteredSub) hasPast() bool {
// is closed. If the subscription is closed, Next immediately returns nil.
// If there was a past event when the subscription was set up, the first call to
// Next will return it.
func (r *RegisteredSub) Next() *channel.Registered {
func (r *RegisteredSub) Next() *channel.RegisteredEvent {
if r.pastStored != nil {
s := r.pastStored
r.pastStored = nil
Expand Down Expand Up @@ -205,11 +205,11 @@ func (r *RegisteredSub) updateErr(err error) error {
return r.err
}

func storedToRegisteredEvent(event *adjudicator.AdjudicatorStored) *channel.Registered {
func storedToRegisteredEvent(event *adjudicator.AdjudicatorStored) *channel.RegisteredEvent {
if event == nil {
return nil
}
return &channel.Registered{
return &channel.RegisteredEvent{
ID: event.ChannelID,
Version: event.Version,
Timeout: time.Unix(int64(event.Timeout), 0),
Expand Down
4 changes: 2 additions & 2 deletions backend/ethereum/channel/register_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func registerMultipleConcurrent(t *testing.T, numParts int, parallel bool) {
}
event, err := adjs[i].Register(ctx, req)
assert.NoError(t, err, "Registering should succeed")
assert.NotEqual(t, event, &channel.Registered{}, "registering should return valid event")
assert.NotEqual(t, event, &channel.RegisteredEvent{}, "registering should return valid event")
t.Logf("Peer[%d] registered successful", i)
}
if parallel {
Expand Down Expand Up @@ -138,7 +138,7 @@ func TestRegister_FinalState(t *testing.T) {
}
event, err := adjs[0].Register(ctx, req)
assert.NoError(t, err, "Registering final state should succeed")
assert.NotEqual(t, event, &channel.Registered{}, "registering should return valid event")
assert.NotEqual(t, event, &channel.RegisteredEvent{}, "registering should return valid event")
assert.Equal(t, time.Time{}, event.Timeout, "Registering final state should produce zero timeout")
t.Logf("Peer[%d] registered successful", 0)
}
Expand Down
4 changes: 2 additions & 2 deletions backend/ethereum/client/dummy_adjudicator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ type DummyAdjudicator struct {
t *testing.T
}

func (d *DummyAdjudicator) Register(ctx context.Context, req perunchannel.AdjudicatorReq) (*perunchannel.Registered, error) {
return &perunchannel.Registered{
func (d *DummyAdjudicator) Register(ctx context.Context, req perunchannel.AdjudicatorReq) (*perunchannel.RegisteredEvent, error) {
return &perunchannel.RegisteredEvent{
ID: req.Params.ID(),
Idx: req.Idx,
Version: req.Tx.Version,
Expand Down
16 changes: 8 additions & 8 deletions channel/adjudicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type (
// A channel state needs to be registered before the concluded state can be
// withdrawn after a possible timeout.
//
// Furthermore, it has a method for subscribing to Registered events. Those
// Furthermore, it has a method for subscribing to RegisteredEvents. Those
// events might be triggered by a Register call on the adjudicator from any
// channel participant.
Adjudicator interface {
Expand All @@ -27,15 +27,15 @@ type (
// even an old state for the same channel. If registration was successful,
// it should return the timeout when withdrawal can be initiated with
// Withdraw.
Register(context.Context, AdjudicatorReq) (*Registered, error)
Register(context.Context, AdjudicatorReq) (*RegisteredEvent, error)

// Withdraw should conclude and withdraw the registered state, so that the
// final outcome is set on the asset holders and funds are withdrawn
// (dependent on the architecture of the contracts). It must be taken into
// account that a peer might already have concluded the same channel.
Withdraw(context.Context, AdjudicatorReq) error

// SubscribeRegistered returns a Registered event subscription. The
// SubscribeRegistered returns a RegisteredEvent subscription. The
// subscription should be a subscription of the newest past as well as
// future events. The subscription should only be valid within the given
// context: If the context is canceled, its Next method should return nil
Expand All @@ -52,18 +52,18 @@ type (
Idx Index
}

// Registered is the abstract event that signals a successful state
// RegisteredEvent is the abstract event that signals a successful state
// registration on the blockchain.
Registered struct {
RegisteredEvent struct {
ID ID // Channel ID
Idx Index // Index of the participant who registered the event.
Version uint64 // Registered version.
Timeout time.Time // Timeout when the event can be concluded or progressed
}

// A RegisteredSubscription is a subscription to Registered events for a
// A RegisteredSubscription is a subscription to RegisteredEvents for a
// specific channel. The subscription should also return the newest past
// Registered event, if there is any.
// RegisteredEvent, if there is any.
//
// The usage of the subscription should be similar to that of an iterator.
// Next calls should block until a new event is generated (or the first past
Expand All @@ -72,7 +72,7 @@ type (
RegisteredSubscription interface {
// Next returns the newest past or next future event. If the subscription is
// closed or any other error occurs, it should return nil.
Next() *Registered
Next() *RegisteredEvent

// Err returns the error status of the subscription. After Next returns nil,
// Err should be checked for an error.
Expand Down
96 changes: 77 additions & 19 deletions channel/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,25 @@ const (
Acting
Signing
Final
Settled
Registering
Registered
Withdrawing
Withdrawn
)

func (p Phase) String() string {
return [...]string{"InitActing", "InitSigning", "Funding", "Acting", "Signing", "Final", "Settled"}[p]
return [...]string{
"InitActing",
"InitSigning",
"Funding",
"Acting",
"Signing",
"Final",
"Registering",
"Registered",
"Withdrawing",
"Withdrawn",
}[p]
}

func (t PhaseTransition) String() string {
Expand All @@ -48,6 +62,8 @@ func (t PhaseTransition) String() string {

var signingPhases = []Phase{InitSigning, Signing}

var postInitPhases = []Phase{Funding, Acting, Signing, Final}

// A machine is the channel pushdown automaton that handles phase transitions.
// It checks for correct signatures and valid state transitions.
// machine only contains implementations for the state transitions common to
Expand Down Expand Up @@ -274,40 +290,82 @@ func (m *machine) enableStaged(expected PhaseTransition) error {
// SetFunded tells the state machine that the channel got funded and progresses
// to the Acting phase.
func (m *machine) SetFunded() error {
if err := m.expect(PhaseTransition{Funding, Acting}); err != nil {
return err
return m.simplePhaseTransition(Funding, Acting)
}

// SetRegistering tells the state machine that the current channel state is
// being registered on the adjudicator. This phase can be reached after the
// initial phases are done, i.e., when there's at least one state with
// signatures.
func (m *machine) SetRegistering() error {
if !inPhase(m.phase, postInitPhases) {
return m.phaseErrorf(m.selfTransition(), "can only register after init phases")
}

m.setPhase(Acting)
m.setPhase(Registering)
return nil
}

// SetRegistered tells the state machine that a channel state got registered on
// the adjudicator. This phase can be reached after the initial phases are done,
// i.e., when there's at least one state with signatures.
func (m *machine) SetRegistered() error {
if !inPhase(m.phase, append(postInitPhases, Registering)) {
return m.phaseErrorf(m.selfTransition(),
"state can only be registered after init phases or registering")
}

m.setPhase(Registered)
return nil
}

// SetSettled tells the state machine that the final state was settled on the
// blockchain or funding channel and progresses to the Settled state.
func (m *machine) SetSettled() error {
if err := m.expect(PhaseTransition{Final, Settled}); err != nil {
// SetWithdrawing sets the state machine to the Withdrawing phase. The current
// state was registered on-chain and funds withdrawal is in progress.
// This phase can only be reached from the Registered phase.
func (m *machine) SetWithdrawing() error {
return m.simplePhaseTransition(Registered, Withdrawing)
}

// SetWithdrawn sets the state machine to the final phase Withdrawn. The current
// state was registered on-chain and funds withdrawal was successful.
func (m *machine) SetWithdrawn() error {
return m.simplePhaseTransition(Withdrawing, Withdrawn)
}

func (m *machine) simplePhaseTransition(from, to Phase) error {
if err := m.expect(PhaseTransition{from, to}); err != nil {
return err
}

m.setPhase(Settled)
m.setPhase(to)
return nil
}

var validPhaseTransitions = map[PhaseTransition]bool{
{InitActing, InitSigning}: true,
{InitSigning, Funding}: true,
{Funding, Acting}: true,
{Acting, Signing}: true,
{Signing, Acting}: true,
{Signing, Final}: true,
{Final, Settled}: true,
var validPhaseTransitions = map[PhaseTransition]struct{}{
{InitActing, InitSigning}: {},
{InitSigning, Funding}: {},
{Funding, Acting}: {},
{Acting, Signing}: {},
{Signing, Acting}: {},
{Signing, Final}: {},
{Funding, Registering}: {},
{Acting, Registering}: {},
{Signing, Registering}: {},
{Final, Registering}: {},
{Funding, Registered}: {},
{Acting, Registered}: {},
{Signing, Registered}: {},
{Final, Registered}: {},
{Registering, Registered}: {},
{Registered, Withdrawing}: {},
{Withdrawing, Withdrawn}: {},
}

func (m *machine) expect(tr PhaseTransition) error {
if m.phase != tr.From {
return m.phaseErrorf(tr, "not in correct phase")
}
if ok := validPhaseTransitions[PhaseTransition{m.phase, tr.To}]; !ok {
if _, ok := validPhaseTransitions[PhaseTransition{m.phase, tr.To}]; !ok {
return m.phaseErrorf(tr, "forbidden phase transition")
}
return nil
Expand Down
24 changes: 19 additions & 5 deletions client/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,26 @@ func (c *Channel) Settle(ctx context.Context) error {
return errors.New("currently, only channels in a final state can be settled")
}

if err := c.machine.SetRegistering(); err != nil {
return err // this should never happen
}

req := c.machine.AdjudicatorReq()

if reg, err := c.adjudicator.Register(ctx, req); err != nil {
reg, err := c.adjudicator.Register(ctx, req)
if err != nil {
return errors.WithMessage(err, "calling Register")
} else if reg.Version != req.Tx.Version {
}
if err := c.machine.SetRegistered(); err != nil {
return err // this should never happen
}

if reg.Version != req.Tx.Version {
return errors.Errorf(
"unexpected version %d registered, expected %d", reg.Version, req.Tx.Version)
} else if reg.Timeout.After(time.Now()) {
}

if reg.Timeout.After(time.Now()) {
c.log.Warnf("Unexpected withdrawal timeout during Settle(). Waiting until %v.", reg.Timeout)
timeout := time.After(time.Until(reg.Timeout))
select {
Expand All @@ -189,9 +201,11 @@ func (c *Channel) Settle(ctx context.Context) error {
}
}

if err := c.machine.SetWithdrawing(); err != nil {
return err // this should never happen
}
if err := c.adjudicator.Withdraw(ctx, req); err != nil {
return errors.WithMessage(err, "calling Withdraw")
}

return c.machine.SetSettled()
return c.machine.SetWithdrawn()
}
2 changes: 1 addition & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ type DummyAdjudicator struct {
t *testing.T
}

func (d *DummyAdjudicator) Register(context.Context, channel.AdjudicatorReq) (*channel.Registered, error) {
func (d *DummyAdjudicator) Register(context.Context, channel.AdjudicatorReq) (*channel.RegisteredEvent, error) {
d.t.Error("DummyAdjudicator.Register called")
return nil, errors.New("DummyAdjudicator.Register called")
}
Expand Down
4 changes: 2 additions & 2 deletions client/happy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ func (f *logFunder) Fund(_ context.Context, req channel.FundingReq) error {
return nil
}

func (a *logAdjudicator) Register(ctx context.Context, req channel.AdjudicatorReq) (*channel.Registered, error) {
func (a *logAdjudicator) Register(ctx context.Context, req channel.AdjudicatorReq) (*channel.RegisteredEvent, error) {
a.log.Infof("Register: %v", req)
return &channel.Registered{
return &channel.RegisteredEvent{
ID: req.Params.ID(),
Idx: req.Idx,
Version: req.Tx.Version,
Expand Down

0 comments on commit 5cf716e

Please sign in to comment.