forked from hyperledger-labs/go-perun
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathapp.go
293 lines (257 loc) · 8.85 KB
/
app.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// 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 (
"encoding"
"io"
"github.com/pkg/errors"
"perun.network/go-perun/wire/perunio"
)
type (
// AppID represents an app identifier.
AppID interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
Equal(AppID) bool
// Key returns the object key which can be used as a map key.
Key() AppIDKey
}
// An App is an abstract interface for an app definition. Either a StateApp or
// ActionApp should be implemented.
App interface {
// Def is an identifier of the channel application. It is usually the
// (counterfactual) on-chain address of the stateless contract that defines
// what valid actions or transitions are.
// Calling this function on a NoApp panics, so ensure that IsNoApp
// returns false.
Def() AppID
// NewData returns a new instance of data specific to NoApp, intialized
// to its zero value.
//
// This should be used for unmarshalling the data from its binary
// representation.
//
// This has to be defined on an application-level because every app can
// have completely different data.
NewData() Data
}
// A StateApp is advanced by full state updates. The validity of state
// transitions is checked with method ValidTransition.
StateApp interface {
App
// ValidTransition should check that the app-specific rules of the given
// transition from `from` to `to` are fulfilled.
// `actor` is the index of the acting party whose action resulted in the new state.
// The implementation should return a StateTransitionError describing the
// invalidity of the transition, if it is not valid. It should return a normal
// error (with attached stacktrace from pkg/errors) if there was any other
// runtime error, not related to the invalidity of the transition itself.
ValidTransition(parameters *Params, from, to *State, actor Index) error
// ValidInit should perform app-specific checks for a valid initial state.
// The framework guarantees to only pass initial states with version == 0,
// correct channel ID and valid initial allocation.
ValidInit(*Params, *State) error
}
// An ActionApp is advanced by first collecting actions from the participants
// and then applying those actions to the state. In a sense it is a more
// fine-grained version of a StateApp and allows for more optimized
// state channel applications.
ActionApp interface {
App
// ValidAction checks if the provided Action by the participant at the given
// index is valid, applied to the provided Params and State.
// The implementation should return an ActionError describing the invalidity
// of the action. It should return a normal error (with attached stacktrace
// from pkg/errors) if there was any other runtime error, not related to the
// invalidity of the action itself.
ValidAction(*Params, *State, Index, Action) error
// ApplyAction applies the given actions to the provided channel state and
// returns the resulting new state.
// The version counter should be increased by one.
// The implementation should return an ActionError describing the invalidity
// of the action. It should return a normal error (with attached stacktrace
// from pkg/errors) if there was any other runtime error, not related to the
// invalidity of the action itself.
ApplyActions(*Params, *State, []Action) (*State, error)
// InitState creates the initial state from the given actions. The actual
// State will be created by the machine and only the initial allocation of
// funds and app data can be set, as the channel id is specified by the
// parameters and version must be 0.
InitState(*Params, []Action) (Allocation, Data, error)
// NewAction returns an instance of action specific to this
// application. This has to be defined on an application-level because
// every app can have completely different action. This instance should
// be used for unmarshalling the binary representation of the action.
//
// It is zero-value is safe to use.
NewAction() Action
}
// An Action is applied to a channel state to result in new state.
//
// It is sent as binary data over the wire. For unmarshalling an action from
// its binary representation, use ActionApp.NewAction() to create an Action
// instance specific to the application and then unmarshal using it.
Action interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
}
// AppResolver provides functionality to create an App from an Address.
// The AppResolver needs to be implemented for every state channel application.
AppResolver interface {
// Resolve creates an app from its defining identifier. It is possible that
// multiple apps are in use, which is why creation happens over a central
// Resolve function. This function is intended to resolve app definitions
// coming in on the wire.
Resolve(AppID) (App, error)
}
)
// IsStateApp returns true if the app is a StateApp.
func IsStateApp(app App) bool {
_, ok := app.(StateApp)
return ok
}
// IsActionApp returns true if the app is an ActionApp.
func IsActionApp(app App) bool {
_, ok := app.(ActionApp)
return ok
}
// OptAppEnc makes an optional App value encodable.
type OptAppEnc struct {
App
}
// OptAppEncMap makes an optional App value encodable.
type OptAppEncMap struct {
App map[int]App
}
// Encode encodes an optional App value.
func (e OptAppEnc) Encode(w io.Writer) error {
if IsNoApp(e.App) {
return perunio.Encode(w, false)
}
return perunio.Encode(w, true, e.App.Def())
}
// Encode encodes an optional App value.
func (e OptAppEncMap) Encode(w io.Writer) error {
if e.App == nil {
return perunio.Encode(w, false)
}
// Encode the map length first
if err := perunio.Encode(w, len(e.App)); err != nil {
return err
}
// Encode each map entry
for key, app := range e.App {
if IsNoApp(app) {
return errors.New("app in map is nil")
}
if err := perunio.Encode(w, key, app.Def()); err != nil {
return err
}
}
return nil
}
// OptAppDec makes an optional App value decodable.
type OptAppDec struct {
App *App
}
// OptAppDecMap makes an optional App value decodable.
type OptAppDecMap struct {
App *map[int]App
}
// Decode decodes an optional App value.
func (d OptAppDec) Decode(r io.Reader) (err error) {
var hasApp bool
if err = perunio.Decode(r, &hasApp); err != nil {
return err
}
if !hasApp {
*d.App = NoApp()
return nil
}
appDef, _ := NewAppID()
err = perunio.Decode(r, appDef)
if err != nil {
return errors.WithMessage(err, "decode app address")
}
*d.App, err = Resolve(appDef)
return errors.WithMessage(err, "resolve app")
}
// Decode decodes an optional App value.
func (d OptAppDecMap) Decode(r io.Reader) (err error) {
var mapLen int
if err := perunio.Decode(r, &mapLen); err != nil {
return err
}
*d.App = make(map[int]App, mapLen)
for i := 0; i < mapLen; i++ {
var key int
if err := perunio.Decode(r, &key); err != nil {
return err
}
var hasApp bool
if err := perunio.Decode(r, &hasApp); err != nil {
return err
}
if !hasApp {
(*d.App)[key] = nil
continue
}
var appID AppID
if err := perunio.Decode(r, &appID); err != nil {
return err
}
app, err := Resolve(appID)
if err != nil {
return errors.WithMessage(err, "resolve app")
}
(*d.App)[key] = app
}
return nil
}
// OptAppAndDataEnc makes an optional pair of App definition and Data encodable.
type OptAppAndDataEnc struct {
App App
Data Data
}
// Encode encodes an optional pair of App definition and Data.
func (o OptAppAndDataEnc) Encode(w io.Writer) error {
return perunio.Encode(w, OptAppEnc{App: o.App}, o.Data)
}
// OptAppAndDataDec makes an optional pair of App definition and Data decodable.
type OptAppAndDataDec struct {
App *App
Data *Data
}
// Decode decodes an optional pair of App definition and Data.
func (o OptAppAndDataDec) Decode(r io.Reader) (err error) {
if err = perunio.Decode(r, OptAppDec{App: o.App}); err != nil {
return err
}
*o.Data = (*o.App).NewData()
return perunio.Decode(r, *o.Data)
}
// AppShouldEqual compares two Apps for equality.
func AppShouldEqual(expected, actual App) error {
if IsNoApp(expected) && IsNoApp(actual) {
return nil
}
if !IsNoApp(expected) && IsNoApp(actual) ||
IsNoApp(expected) && !IsNoApp(actual) {
return errors.New("(non-)nil App definitions")
}
if !expected.Def().Equal(actual.Def()) {
return errors.New("different App definitions")
}
return nil
}