Skip to content

Commit 5d89bb3

Browse files
Merge pull request #337 from perun-network/multi-ledger
Multi ledger
2 parents 261c9df + c051a07 commit 5d89bb3

26 files changed

+1046
-173
lines changed

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ linters:
2424
- wsl # Formatting disabled for now.
2525
- gochecknoinits # We use init functions.
2626
- gci # We have our own import order.
27+
- goerr113 # We do not strictly require static errors.
28+
- promlinter # Disabled because unstable.
2729

2830
# These could be enabled in the future:
2931
- ifshort # we often don't use `if err := …` for readability.

channel/multi/adjudicator.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2022 - See NOTICE file for copyright holders.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package multi
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
"perun.network/go-perun/channel"
22+
)
23+
24+
// Adjudicator is a multi-ledger adjudicator.
25+
type Adjudicator struct {
26+
adjudicators map[LedgerIDMapKey]channel.Adjudicator
27+
}
28+
29+
// NewAdjudicator creates a new adjudicator.
30+
func NewAdjudicator() *Adjudicator {
31+
return &Adjudicator{
32+
adjudicators: make(map[LedgerIDMapKey]channel.Adjudicator),
33+
}
34+
}
35+
36+
// RegisterAdjudicator registers an adjudicator for a given ledger.
37+
func (a *Adjudicator) RegisterAdjudicator(l LedgerID, la channel.Adjudicator) {
38+
a.adjudicators[l.MapKey()] = la
39+
}
40+
41+
// Register registers a multi-ledger channel. It dispatches Register calls to
42+
// all relevant adjudicators. If any of the calls fails, the method returns an
43+
// error.
44+
func (a *Adjudicator) Register(ctx context.Context, req channel.AdjudicatorReq, subStates []channel.SignedState) error {
45+
ledgers, err := assets(req.Tx.Assets).LedgerIDs()
46+
if err != nil {
47+
return err
48+
}
49+
50+
err = a.dispatch(ledgers, func(la channel.Adjudicator) error {
51+
return la.Register(ctx, req, subStates)
52+
})
53+
return err
54+
}
55+
56+
// Progress progresses the state of a multi-ledger channel. It dispatches
57+
// Progress calls to all relevant adjudicators. If any of the calls fails, the
58+
// method returns an error.
59+
func (a *Adjudicator) Progress(ctx context.Context, req channel.ProgressReq) error {
60+
ledgers, err := assets(req.Tx.Assets).LedgerIDs()
61+
if err != nil {
62+
return err
63+
}
64+
65+
err = a.dispatch(ledgers, func(la channel.Adjudicator) error {
66+
return la.Progress(ctx, req)
67+
})
68+
return err
69+
}
70+
71+
// Withdraw withdraws the funds from a multi-ledger channel. It dispatches
72+
// Withdraw calls to all relevant adjudicators. If any of the calls fails, the
73+
// method returns an error.
74+
func (a *Adjudicator) Withdraw(ctx context.Context, req channel.AdjudicatorReq, subStates channel.StateMap) error {
75+
ledgers, err := assets(req.Tx.Assets).LedgerIDs()
76+
if err != nil {
77+
return err
78+
}
79+
80+
err = a.dispatch(ledgers, func(la channel.Adjudicator) error {
81+
return la.Withdraw(ctx, req, subStates)
82+
})
83+
return err
84+
}
85+
86+
// dispatch dispatches an adjudicator call on all given ledgers.
87+
func (a *Adjudicator) dispatch(ledgers []LedgerID, f func(channel.Adjudicator) error) error {
88+
n := len(ledgers)
89+
errs := make(chan error, n)
90+
for _, l := range ledgers {
91+
go func(l LedgerID) {
92+
err := func() error {
93+
id := l.MapKey()
94+
la, ok := a.adjudicators[id]
95+
if !ok {
96+
return fmt.Errorf("Adjudicator not found for ledger %v", id)
97+
}
98+
99+
err := f(la)
100+
return err
101+
}()
102+
errs <- err
103+
}(l)
104+
}
105+
106+
for i := 0; i < n; i++ {
107+
err := <-errs
108+
if err != nil {
109+
return err
110+
}
111+
}
112+
113+
return nil
114+
}

channel/multi/asset.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2022 - See NOTICE file for copyright holders.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package multi
16+
17+
import (
18+
"fmt"
19+
20+
"perun.network/go-perun/channel"
21+
)
22+
23+
type (
24+
// Asset defines a multi-ledger asset.
25+
Asset interface {
26+
channel.Asset
27+
LedgerID() LedgerID
28+
}
29+
30+
// LedgerIDMapKey is the map key representation of a ledger identifier.
31+
LedgerIDMapKey string
32+
33+
// LedgerID represents a ledger identifier.
34+
LedgerID interface {
35+
MapKey() LedgerIDMapKey
36+
}
37+
38+
assets []channel.Asset
39+
)
40+
41+
// LedgerIDs returns the identifiers of the associated ledgers.
42+
func (a assets) LedgerIDs() ([]LedgerID, error) {
43+
ids := make(map[LedgerIDMapKey]LedgerID)
44+
for _, asset := range a {
45+
ma, ok := asset.(Asset)
46+
if !ok {
47+
return nil, fmt.Errorf("wrong asset type: expected multi.Asset, got %T", a)
48+
}
49+
50+
id := ma.LedgerID()
51+
ids[id.MapKey()] = id
52+
}
53+
54+
idsArray := make([]LedgerID, len(ids))
55+
i := 0
56+
for _, v := range ids {
57+
idsArray[i] = v
58+
i++
59+
}
60+
61+
return idsArray, nil
62+
}
63+
64+
// IsMultiLedgerAssets returns whether the assets are from multiple ledgers.
65+
func IsMultiLedgerAssets(assets []channel.Asset) bool {
66+
hasMulti := false
67+
var id LedgerID
68+
for _, asset := range assets {
69+
multiAsset, ok := asset.(Asset)
70+
switch {
71+
case !ok:
72+
continue
73+
case !hasMulti:
74+
hasMulti = true
75+
id = multiAsset.LedgerID()
76+
case id.MapKey() != multiAsset.LedgerID().MapKey():
77+
return true
78+
}
79+
}
80+
return false
81+
}

channel/multi/doc.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2022 - See NOTICE file for copyright holders.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package multi provides a multi-ledger funder and adjudicator.
16+
package multi // import "perun.network/go-perun/channel/multi"

channel/multi/funder.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2022 - See NOTICE file for copyright holders.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package multi
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"time"
21+
22+
"perun.network/go-perun/channel"
23+
)
24+
25+
// Funder is a multi-ledger funder.
26+
type Funder struct {
27+
funders map[LedgerIDMapKey]channel.Funder
28+
}
29+
30+
// NewFunder creates a new funder.
31+
func NewFunder() *Funder {
32+
return &Funder{
33+
funders: make(map[LedgerIDMapKey]channel.Funder),
34+
}
35+
}
36+
37+
// RegisterFunder registers a funder for a given ledger.
38+
func (f *Funder) RegisterFunder(l LedgerID, lf channel.Funder) {
39+
f.funders[l.MapKey()] = lf
40+
}
41+
42+
// Fund funds a multi-ledger channel. It dispatches funding calls to all
43+
// relevant registered funders. It waits until all participants have funded the
44+
// channel. If any of the funder calls fails, the method returns an error.
45+
func (f *Funder) Fund(ctx context.Context, request channel.FundingReq) error {
46+
// Define funding timeout.
47+
d := time.Duration(request.Params.ChallengeDuration) * time.Second
48+
ctx, cancel := context.WithTimeout(ctx, d)
49+
defer cancel()
50+
51+
ledgers, err := assets(request.State.Assets).LedgerIDs()
52+
if err != nil {
53+
return err
54+
}
55+
56+
n := len(ledgers)
57+
errs := make(chan error, n)
58+
for _, l := range ledgers {
59+
go func(l LedgerID) {
60+
errs <- func() error {
61+
id := l.MapKey()
62+
lf, ok := f.funders[id]
63+
if !ok {
64+
return fmt.Errorf("Funder not found for ledger %v", id)
65+
}
66+
67+
err := lf.Fund(ctx, request)
68+
return err
69+
}()
70+
}(l)
71+
}
72+
73+
for i := 0; i < n; i++ {
74+
err := <-errs
75+
if err != nil {
76+
return err
77+
}
78+
}
79+
80+
return nil
81+
}

channel/multi/subscription.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2022 - See NOTICE file for copyright holders.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package multi
16+
17+
import (
18+
"context"
19+
20+
"perun.network/go-perun/channel"
21+
)
22+
23+
// Subscribe creates a new multi-ledger AdjudicatorSubscription.
24+
func (a *Adjudicator) Subscribe(ctx context.Context, chID channel.ID) (channel.AdjudicatorSubscription, error) {
25+
asub := &AdjudicatorSubscription{
26+
events: make(chan channel.AdjudicatorEvent),
27+
errors: make(chan error),
28+
subs: []channel.AdjudicatorSubscription{},
29+
}
30+
31+
for _, la := range a.adjudicators {
32+
sub, err := la.Subscribe(ctx, chID)
33+
if err != nil {
34+
asub.Close()
35+
return nil, err
36+
}
37+
asub.subs = append(asub.subs, sub)
38+
39+
go func() {
40+
asub.events <- sub.Next()
41+
}()
42+
43+
go func() {
44+
asub.errors <- sub.Err()
45+
}()
46+
}
47+
48+
return asub, nil
49+
}
50+
51+
// AdjudicatorSubscription is a multi-ledger adjudicator subscription.
52+
type AdjudicatorSubscription struct {
53+
subs []channel.AdjudicatorSubscription
54+
events chan channel.AdjudicatorEvent
55+
errors chan error
56+
}
57+
58+
// Next returns the next event.
59+
func (s *AdjudicatorSubscription) Next() channel.AdjudicatorEvent {
60+
return <-s.events
61+
}
62+
63+
// Err blocks until an error occurred and returns it.
64+
func (s *AdjudicatorSubscription) Err() error {
65+
for i := 0; i < len(s.subs); i++ {
66+
err := <-s.errors
67+
if err != nil {
68+
return err
69+
}
70+
}
71+
return nil
72+
}
73+
74+
// Close closes the subscription.
75+
func (s *AdjudicatorSubscription) Close() error {
76+
for _, sub := range s.subs {
77+
sub.Close()
78+
}
79+
return nil
80+
}

channel/persistence/test/channel.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ func NewRandomChannel(
5454
user channel.Index,
5555
peers []wire.Address,
5656
parent *Channel,
57-
rng *rand.Rand) (c *Channel) {
57+
rng *rand.Rand,
58+
) (c *Channel) {
5859
accs, parts := wtest.NewRandomAccounts(rng, len(peers))
5960
params := ctest.NewRandomParams(rng, ctest.WithParts(parts...))
6061
csm, err := channel.NewStateMachine(accs[0], *params)

0 commit comments

Comments
 (0)