diff --git a/.golangci.yml b/.golangci.yml index 1d00606..0bc649b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -43,7 +43,7 @@ linters-settings: goheader: values: regexp: - ANY_YEAR: "20(19|20|21|22)" # 2019-2022 + ANY_YEAR: "20(19|20|21|22|23|24)" # 2019-2024 template-path: .copyright-header forbidigo: forbid: diff --git a/channel/app.go b/channel/app.go new file mode 100644 index 0000000..adc9fbb --- /dev/null +++ b/channel/app.go @@ -0,0 +1,78 @@ +// Copyright 2024 - 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 ( + "math/rand" + + ethwallet "github.com/perun-network/perun-eth-backend/wallet" + ethtestwallet "github.com/perun-network/perun-eth-backend/wallet/test" + "perun.network/go-perun/channel" +) + +var _ channel.AppID = new(AppID) + +// AppID described an app identifier. +type AppID struct { + *ethwallet.Address +} + +// AppIDKey is the key representation of an app identifier. +type AppIDKey string + +// Equal compares two AppID objects for equality. +func (a AppID) Equal(b channel.AppID) bool { + bTyped, ok := b.(*AppID) + if !ok { + return false + } + return a.Address.Equal(bTyped.Address) +} + +// Key returns the key representation of this app identifier. +func (a AppID) Key() channel.AppIDKey { + b, err := a.MarshalBinary() + if err != nil { + panic(err) + } + return channel.AppIDKey(b) +} + +// MarshalBinary marshals the contents of AppID into a byte string. +func (a AppID) MarshalBinary() ([]byte, error) { + data, err := a.Address.MarshalBinary() + if err != nil { + return nil, err + } + return data, nil +} + +// UnmarshalBinary converts a bytestring, representing AppID into the AppID struct. +func (a *AppID) UnmarshalBinary(data []byte) error { + addr := ðwallet.Address{} + err := addr.UnmarshalBinary(data) + if err != nil { + return err + } + appaddr := &AppID{addr} + *a = *appaddr + return nil +} + +// NewRandomAppID calls NewRandomAddress to generate a random Ethereum test address. +func NewRandomAppID(rng *rand.Rand) *AppID { + addr := ethtestwallet.NewRandomAddress(rng) + return &AppID{&addr} +} diff --git a/channel/backend.go b/channel/backend.go index d317e84..77a800a 100644 --- a/channel/backend.go +++ b/channel/backend.go @@ -91,6 +91,12 @@ func (*Backend) CalcID(p *channel.Params) (id channel.ID) { return CalcID(p) } +// NewAppID creates a new app identifier, using an empty ethereum struct. +func (b *Backend) NewAppID() channel.AppID { + addr := ðwallet.Address{} + return &AppID{addr} +} + // Sign signs the channel state as needed by the ethereum smart contracts. func (*Backend) Sign(acc wallet.Account, s *channel.State) (wallet.Sig, error) { return Sign(acc, s) @@ -152,7 +158,11 @@ func Verify(addr wallet.Address, s *channel.State, sig wallet.Sig) (bool, error) func ToEthParams(p *channel.Params) adjudicator.ChannelParams { var app common.Address if p.App != nil && !channel.IsNoApp(p.App) { - app = ethwallet.AsEthAddr(p.App.Def()) + appDef, ok := p.App.Def().(*AppID) + if !ok { + panic("appDef is not of type *AppID") + } + app = ethwallet.AsEthAddr(appDef.Address) } return adjudicator.ChannelParams{ diff --git a/channel/conclude.go b/channel/conclude.go index 4651270..56d8219 100644 --- a/channel/conclude.go +++ b/channel/conclude.go @@ -18,22 +18,16 @@ import ( "context" "fmt" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/core/types" - "github.com/pkg/errors" - "github.com/perun-network/perun-eth-backend/bindings" "github.com/perun-network/perun-eth-backend/bindings/adjudicator" - cherrors "github.com/perun-network/perun-eth-backend/channel/errors" "github.com/perun-network/perun-eth-backend/subscription" + "github.com/pkg/errors" "perun.network/go-perun/channel" "perun.network/go-perun/log" ) const ( - secondaryWaitBlocks = 2 - adjEventBuffSize = 10 - adjHeaderBuffSize = 10 + adjEventBuffSize = 10 ) // ensureConcluded ensures that conclude or concludeFinal (for non-final and @@ -51,16 +45,8 @@ func (a *Adjudicator) ensureConcluded(ctx context.Context, req channel.Adjudicat return nil } - // If the secondary flag is set, we wait for someone else to conclude. - concluded, err := a.waitConcludedSecondary(ctx, req) - if err != nil { - return errors.WithMessage(err, "waiting for secondary conclude") - } else if concluded { - return nil - } - // Wait until we can conclude. - err = a.waitConcludable(ctx, req) + err := a.waitConcludable(ctx, req) if err != nil { return fmt.Errorf("waiting for concludability: %w", err) } @@ -160,25 +146,6 @@ func (a *Adjudicator) checkConcludedState( } } -func (a *Adjudicator) waitConcludedSecondary(ctx context.Context, req channel.AdjudicatorReq) (concluded bool, err error) { - // In final Register calls, as the non-initiator, we optimistically wait for - // the other party to send the transaction first for - // `secondaryWaitBlocks + TxFinalityDepth` many blocks. - if req.Tx.IsFinal && req.Secondary { - // Create subscription. - sub, events, subErr, err := a.createEventSub(ctx, req.Tx.ID, false) - if err != nil { - return false, errors.WithMessage(err, "subscribing") - } - defer sub.Close() - - // Wait for concluded event. - waitBlocks := secondaryWaitBlocks + int(a.txFinalityDepth) - return waitConcludedForNBlocks(ctx, a, events, subErr, waitBlocks) - } - return false, nil -} - func (a *Adjudicator) conclude(ctx context.Context, req channel.AdjudicatorReq, subStates channel.StateMap) error { // If the on-chain state resulted from forced execution, we do not have a fully-signed state and cannot call concludeFinal. forceExecuted, err := a.isForceExecuted(ctx, req.Params.ID()) @@ -335,45 +302,3 @@ func updateEventType(channelID [32]byte) subscription.EventFactory { } } } - -// waitConcludedForNBlocks waits for up to numBlocks blocks for a Concluded -// event on the concluded channel. If an event is emitted, true is returned. -// Otherwise, if numBlocks blocks have passed, false is returned. -// -// cr is the ChainReader used for setting up a block header subscription. sub is -// the Concluded event subscription instance. -func waitConcludedForNBlocks(ctx context.Context, - cr ethereum.ChainReader, - concluded <-chan *subscription.Event, - subErr <-chan error, - numBlocks int, -) (bool, error) { - h := make(chan *types.Header, adjHeaderBuffSize) - hsub, err := cr.SubscribeNewHead(ctx, h) - if err != nil { - err = cherrors.CheckIsChainNotReachableError(err) - return false, errors.WithMessage(err, "subscribing to new blocks") - } - defer hsub.Unsubscribe() - for i := 0; i < numBlocks; i++ { - select { - case <-h: // do nothing, wait another block - case _e := <-concluded: // other participant performed transaction - e, ok := _e.Data.(*adjudicator.AdjudicatorChannelUpdate) - if !ok { - log.Panic("wrong event type") - } - if e.Phase == phaseConcluded { - return true, nil - } - case <-ctx.Done(): - return false, errors.Wrap(ctx.Err(), "context cancelled") - case err = <-hsub.Err(): - err = cherrors.CheckIsChainNotReachableError(err) - return false, errors.WithMessage(err, "header subscription error") - case err = <-subErr: - return false, errors.WithMessage(err, "event subscription error") - } - } - return false, nil -} diff --git a/channel/conclude_test.go b/channel/conclude_test.go index 37e5561..ae57596 100644 --- a/channel/conclude_test.go +++ b/channel/conclude_test.go @@ -71,28 +71,18 @@ func testConcludeFinal(t *testing.T, numParts int) { ctx, cancel := context.WithTimeout(context.Background(), defaultTxTimeout) defer cancel() ct = pkgtest.NewConcurrent(t) - initiator := int(rng.Int31n(int32(numParts))) // pick a random initiator for i := 0; i < numParts; i++ { i := i go ct.StageN("register", numParts, func(t pkgtest.ConcT) { req := channel.AdjudicatorReq{ - Params: params, - Acc: s.Accs[i], - Idx: channel.Index(i), - Tx: tx, - Secondary: (i != initiator), + Params: params, + Acc: s.Accs[i], + Idx: channel.Index(i), + Tx: tx, } - diff, err := test.NonceDiff(s.Accs[i].Address(), s.Adjs[i], func() error { - return s.Adjs[i].Register(ctx, req, nil) - }) + err := s.Adjs[i].Register(ctx, req, nil) + require.NoError(t, err, "Withdrawing should succeed") - if !req.Secondary { - // The Initiator must send a TX. - require.Equal(t, diff, 1) - } else { - // Everyone else must NOT send a TX. - require.Equal(t, diff, 0) - } }) } ct.Wait("register") @@ -254,11 +244,10 @@ func register(ctx context.Context, adj *test.SimAdjudicator, accounts []*keystor } req := channel.AdjudicatorReq{ - Params: ch.params, - Acc: accounts[0], - Idx: 0, - Tx: tx, - Secondary: false, + Params: ch.params, + Acc: accounts[0], + Idx: 0, + Tx: tx, } return adj.Register(ctx, req, sub) } @@ -277,11 +266,10 @@ func withdraw(ctx context.Context, adj *test.SimAdjudicator, accounts []*keystor for i, a := range accounts { req := channel.AdjudicatorReq{ - Params: c.params, - Acc: a, - Idx: channel.Index(i), - Tx: tx, - Secondary: i != 0, + Params: c.params, + Acc: a, + Idx: channel.Index(i), + Tx: tx, } if err := adj.Withdraw(ctx, req, subStates); err != nil { diff --git a/channel/subscription.go b/channel/subscription.go index 5e20d00..c250d64 100644 --- a/channel/subscription.go +++ b/channel/subscription.go @@ -172,18 +172,20 @@ func (a *Adjudicator) convertEvent(ctx context.Context, e *adjudicator.Adjudicat if err != nil { return nil, errors.WithMessage(err, "fetching call data") } - ch, ok := args.signedState(e.ChannelID) if !ok { return nil, errors.Errorf("channel not found in calldata: %v", e.ChannelID) } - var app channel.App var zeroAddress common.Address if ch.Params.App == zeroAddress { app = channel.NoApp() } else { - app, err = channel.Resolve(wallet.AsWalletAddr(ch.Params.App)) + appAddr := wallet.AsWalletAddr(ch.Params.App) + appID := &AppID{ + Address: appAddr, + } + app, err = channel.Resolve(appID) if err != nil { return nil, err } @@ -201,7 +203,11 @@ func (a *Adjudicator) convertEvent(ctx context.Context, e *adjudicator.Adjudicat if err != nil { return nil, errors.WithMessage(err, "fetching call data") } - app, err := channel.Resolve(wallet.AsWalletAddr(args.Params.App)) + appAddr := wallet.AsWalletAddr(args.Params.App) + appID := &AppID{ + Address: appAddr, + } + app, err := channel.Resolve(appID) if err != nil { return nil, errors.WithMessage(err, "resolving app") } diff --git a/channel/test/adjudicator.go b/channel/test/adjudicator.go index d75b421..9562bfb 100644 --- a/channel/test/adjudicator.go +++ b/channel/test/adjudicator.go @@ -21,9 +21,8 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - ethchannel "github.com/perun-network/perun-eth-backend/channel" + "github.com/pkg/errors" "perun.network/go-perun/channel" "perun.network/go-perun/log" diff --git a/channel/test/init.go b/channel/test/init.go index 56bbbb7..e816bcd 100644 --- a/channel/test/init.go +++ b/channel/test/init.go @@ -15,9 +15,16 @@ package test import ( + "math/rand" + + "github.com/perun-network/perun-eth-backend/channel" + pchannel "perun.network/go-perun/channel" "perun.network/go-perun/channel/test" ) func init() { test.SetRandomizer(new(randomizer)) + test.SetNewRandomAppID(func(r *rand.Rand) pchannel.AppID { + return channel.NewRandomAppID(r) + }) } diff --git a/channel/withdraw_test.go b/channel/withdraw_test.go index 2e86236..dcb114c 100644 --- a/channel/withdraw_test.go +++ b/channel/withdraw_test.go @@ -125,6 +125,7 @@ func TestWithdrawZeroBalance(t *testing.T) { } // shouldFunders decides who should fund. 1 indicates funding, 0 indicates skipping. + //nolint:thelper // Not a helper. func testWithdrawZeroBalance(t *testing.T, n int) { rng := pkgtest.Prng(t) diff --git a/client/app_test.go b/client/app_test.go index 6561fe3..8b46b3b 100644 --- a/client/app_test.go +++ b/client/app_test.go @@ -45,7 +45,9 @@ func TestProgression(t *testing.T) { } appAddress := deployMockApp(t, backendSetup) - app := channel.NewMockApp(appAddress) + appAddrBackend := appAddress.(*ethwallet.Address) + appID := ðchannel.AppID{Address: appAddrBackend} + app := channel.NewMockApp(appID) channel.RegisterApp(app) execConfig := &clienttest.ProgressionExecConfig{ diff --git a/go.mod b/go.mod index 404f8bf..84fdf6c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - perun.network/go-perun v0.10.5 + perun.network/go-perun v0.10.7-0.20240115114750-da863c83cb9f polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37 ) diff --git a/go.sum b/go.sum index 87856e9..eb348fb 100644 --- a/go.sum +++ b/go.sum @@ -653,6 +653,10 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= perun.network/go-perun v0.10.5 h1:bIaAoLLh8R+RXdPh+MkCJcbtumsFkNvoVAJwb60JKjg= perun.network/go-perun v0.10.5/go.mod h1:BGBZC3npkX457u87pjDd0NEIXr1a4dsH4H/YpLdGGe8= +perun.network/go-perun v0.10.7-0.20230808153546-74844191e56e h1:4SOKO0WZtcsQUwP5nKVUrLUohgUPIhMa8wto5iNCA/k= +perun.network/go-perun v0.10.7-0.20230808153546-74844191e56e/go.mod h1:BGBZC3npkX457u87pjDd0NEIXr1a4dsH4H/YpLdGGe8= +perun.network/go-perun v0.10.7-0.20240115114750-da863c83cb9f h1:suN6qyoBSBuNBirStAtX+5gIc3tplHzbdB1H+RWlRTc= +perun.network/go-perun v0.10.7-0.20240115114750-da863c83cb9f/go.mod h1:BGBZC3npkX457u87pjDd0NEIXr1a4dsH4H/YpLdGGe8= polycry.pt/poly-go v0.0.0-20220222131629-aa4bdbaab60b/go.mod h1:XUBrNtqgEhN3EEOP/5gh7IBd3xVHKidCjXDZfl9+kMU= polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37 h1:iA5GzEa/hHfVlQpimEjPV09NATwHXxSjWNB0VVodtew= polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37/go.mod h1:XUBrNtqgEhN3EEOP/5gh7IBd3xVHKidCjXDZfl9+kMU=