-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathstatemachine.go
154 lines (132 loc) · 4.45 KB
/
statemachine.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Copyright 2025 - See NOTICE file for copyright holders.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package channel
import (
"github.com/pkg/errors"
"perun.network/go-perun/wallet"
)
// A StateMachine is the channel pushdown automaton around a StateApp.
// It implements the state transitions specific for StateApps: Init and Update.
type StateMachine struct {
*machine
app StateApp `cloneable:"shallow"`
}
// NewStateMachine creates a new StateMachine.
func NewStateMachine(acc map[wallet.BackendID]wallet.Account, params Params) (*StateMachine, error) {
app, ok := params.App.(StateApp)
if !ok {
return nil, errors.New("app must be StateApp")
}
m, err := newMachine(acc, params)
if err != nil {
return nil, err
}
return &StateMachine{
machine: m,
app: app,
}, nil
}
// RestoreStateMachine restores a state machine to the data given by Source.
func RestoreStateMachine(acc map[wallet.BackendID]wallet.Account, source Source) (*StateMachine, error) {
app, ok := source.Params().App.(StateApp)
if !ok {
return nil, errors.New("app must be StateApp")
}
m, err := restoreMachine(acc, source)
if err != nil {
return nil, err
}
return &StateMachine{
machine: m,
app: app,
}, nil
}
// Init sets the initial staging state to the given balance and data.
// It returns the initial state and own signature on it.
func (m *StateMachine) Init(initBals Allocation, initData Data) error {
if err := m.expect(PhaseTransition{InitActing, InitSigning}); err != nil {
return err
}
// we start with the initial state being the staging state
initState, err := newState(&m.params, initBals, initData)
if err != nil {
return err
}
if err := m.app.ValidInit(&m.params, initState); err != nil {
return err
}
m.setStaging(InitSigning, initState)
return nil
}
// Update makes the provided state the staging state.
// It is checked whether this is a valid state transition.
func (m *StateMachine) Update(stagingState *State, actor Index) error {
if err := m.expect(PhaseTransition{Acting, Signing}); err != nil {
return err
}
if err := m.validTransition(stagingState, actor); err != nil {
return err
}
m.setStaging(Signing, stagingState)
return nil
}
// ForceUpdate makes the provided state the staging state.
func (m *StateMachine) ForceUpdate(stagingState *State, actor Index) error {
m.setStaging(Signing, stagingState)
return nil
}
// CheckUpdate checks if the given state is a valid transition from the current
// state and if the given signature is valid. It is a read-only operation that
// does not advance the state machine.
func (m *StateMachine) CheckUpdate(
state *State, actor Index,
sig wallet.Sig, sigIdx Index,
) error {
if err := m.validTransition(state, actor); err != nil {
return err
}
for _, add := range m.params.Parts[sigIdx] {
if ok, err := Verify(add, state, sig); err != nil {
return errors.WithMessagef(err, "verifying signature[%d]", sigIdx)
} else if !ok {
return errors.Errorf("invalid signature[%d]", sigIdx)
}
}
return nil
}
// validTransition makes all the default transition checks and additionally
// checks for a valid application specific transition.
// This is where a StateMachine and ActionMachine differ. In an ActionMachine,
// every action is checked as being a valid action by the application definition
// and the resulting state by applying all actions to the old state is by
// definition a valid new state.
func (m *StateMachine) validTransition(to *State, actor Index) (err error) {
if actor >= m.N() {
return errors.New("actor index is out of range")
}
if err := m.machine.ValidTransition(to); err != nil {
return err
}
if err = m.app.ValidTransition(&m.params, m.currentTX.State, to, actor); IsStateTransitionError(err) {
return err
}
return errors.WithMessagef(err, "runtime error in application's ValidTransition()")
}
// Clone returns a deep copy of StateMachine.
func (m *StateMachine) Clone() *StateMachine {
return &StateMachine{
machine: m.machine.Clone(),
app: m.app,
}
}