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

Commit

Permalink
🔀 Merge branch '250-elaborate-funder-tests' into 'dev'
Browse files Browse the repository at this point in the history
[eth/channel] More elaborate Funder tests #250

Closes #250

See merge request perun/go-perun!184
  • Loading branch information
sebastianst committed Mar 5, 2020
2 parents d92f5af + 194410c commit 6e1d4bc
Show file tree
Hide file tree
Showing 19 changed files with 1,921 additions and 189 deletions.
325 changes: 244 additions & 81 deletions backend/ethereum/bindings/adjudicator/Adjudicator.go

Large diffs are not rendered by default.

294 changes: 291 additions & 3 deletions backend/ethereum/bindings/assets/AssetHolderETH.go

Large diffs are not rendered by default.

112 changes: 112 additions & 0 deletions backend/ethereum/channel/adjudicator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) 2020 Chair of Applied Cryptography, Technische Universität
// Darmstadt, Germany. All rights reserved. This file is part of go-perun. Use
// of this source code is governed by a MIT-style license that can be found in
// the LICENSE file.

package channel // import "perun.network/go-perun/backend/ethereum/channel"

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/pkg/errors"

"perun.network/go-perun/backend/ethereum/bindings/adjudicator"
"perun.network/go-perun/channel"
"perun.network/go-perun/log"
psync "perun.network/go-perun/pkg/sync"
)

// compile time check that we implement the perun adjudicator interface
var _ channel.Adjudicator = (*Adjudicator)(nil)

// The Adjudicator struct implements the channel.Adjudicator interface
// It provides all functionality to close a channel.
type Adjudicator struct {
ContractBackend
contract *adjudicator.Adjudicator
// The address to which we send all funds.
Receiver common.Address
// Structured logger
log log.Logger
// Transaction mutex
mu psync.Mutex
}

// NewAdjudicator creates a new ethereum adjudicator.
func NewAdjudicator(backend ContractBackend, contract common.Address, onchainAddress common.Address) *Adjudicator {
contr, err := adjudicator.NewAdjudicator(contract, backend)
if err != nil {
panic("Could not create a new instance of adjudicator")
}
return &Adjudicator{
ContractBackend: backend,
contract: contr,
Receiver: onchainAddress,
log: log.WithField("account", backend.account.Address),
}
}

func (a *Adjudicator) callRegister(ctx context.Context, req channel.AdjudicatorReq) error {
return a.call(ctx, req, a.contract.Register)
}

func (a *Adjudicator) callRefute(ctx context.Context, req channel.AdjudicatorReq) error {
return a.call(ctx, req, a.contract.Refute)
}

func (a *Adjudicator) callConclude(ctx context.Context, req channel.AdjudicatorReq) error {
// Wrapped call to Conclude, ignoring sig
conclude := func(
opts *bind.TransactOpts,
params adjudicator.ChannelParams,
state adjudicator.ChannelState,
_ [][]byte,
) (*types.Transaction, error) {
return a.contract.Conclude(opts, params, state)
}
return a.call(ctx, req, conclude)
}

func (a *Adjudicator) callConcludeFinal(ctx context.Context, req channel.AdjudicatorReq) error {
return a.call(ctx, req, a.contract.ConcludeFinal)
}

type adjFunc = func(
opts *bind.TransactOpts,
params adjudicator.ChannelParams,
state adjudicator.ChannelState,
sigs [][]byte,
) (*types.Transaction, error)

// call calls the given contract function `fn` with the data from `req`.
// `fn` should be a method of `a.contract`, like `a.contract.Register`.
func (a *Adjudicator) call(ctx context.Context, req channel.AdjudicatorReq, fn adjFunc) error {
ethParams := channelParamsToEthParams(req.Params)
ethState := channelStateToEthState(req.Tx.State)
tx, err := func() (*types.Transaction, error) {
if !a.mu.TryLockCtx(ctx) {
return nil, errors.Wrap(ctx.Err(), "context canceled while acquiring tx lock")
}
defer a.mu.Unlock()

trans, err := a.newTransactor(ctx, big.NewInt(0), GasLimit)
if err != nil {
return nil, errors.WithMessage(err, "creating transactor")
}
tx, err := fn(trans, ethParams, ethState, req.Tx.Sigs)
if err != nil {
return nil, errors.Wrap(err, "calling adjudicator function")
}
log.Debugf("Sent transaction %v", tx.Hash().Hex())
return tx, nil
}()
if err != nil {
return err
}

return errors.WithMessage(confirmTransaction(ctx, a.ContractBackend, tx), "mining transaction")
}
149 changes: 149 additions & 0 deletions backend/ethereum/channel/adjudicator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) 2020 Chair of Applied Cryptography, Technische Universität
// Darmstadt, Germany. All rights reserved. This file is part of go-perun. Use
// of this source code is governed by a MIT-style license that can be found in
// the LICENSE file.

package channel

import (
"context"
"math/big"
"math/rand"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"perun.network/go-perun/backend/ethereum/channel/test"
"perun.network/go-perun/backend/ethereum/wallet"
ethwallettest "perun.network/go-perun/backend/ethereum/wallet/test"
"perun.network/go-perun/channel"
channeltest "perun.network/go-perun/channel/test"
perunwallet "perun.network/go-perun/wallet"
wallettest "perun.network/go-perun/wallet/test"
)

func newAdjudicator(t *testing.T, rng *rand.Rand, n int) ([]perunwallet.Account, []perunwallet.Address, []*Funder, []*Adjudicator, common.Address) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
simBackend := test.NewSimulatedBackend()
ks := ethwallettest.GetKeystore()
deployAccount := wallettest.NewRandomAccount(rng).(*wallet.Account).Account
simBackend.FundAddress(ctx, deployAccount.Address)
contractBackend := NewContractBackend(simBackend, ks, deployAccount)
// Deploy Adjudicator
adjudicator, err := DeployAdjudicator(context.Background(), contractBackend)
require.NoError(t, err, "Deploying the adjudicator should not error")
// Deploy Assetholder
assetETH, err := DeployETHAssetholder(ctx, contractBackend, adjudicator)
require.NoError(t, err, "Deploying asset holder failed")
t.Logf("asset holder address is %v", assetETH)
t.Logf("adjudicator address is %v", adjudicator)
accs := make([]perunwallet.Account, n)
parts := make([]perunwallet.Address, n)
funders := make([]*Funder, n)
adjudicators := make([]*Adjudicator, n)
for i := 0; i < n; i++ {
acc := wallettest.NewRandomAccount(rng).(*wallet.Account)
simBackend.FundAddress(ctx, acc.Account.Address)
accs[i] = acc
parts[i] = acc.Address()
cb := NewContractBackend(simBackend, ks, acc.Account)
funders[i] = NewETHFunder(cb, assetETH)
adjudicators[i] = NewAdjudicator(cb, adjudicator, deployAccount.Address)
}
return accs, parts, funders, adjudicators, assetETH
}

func signState(t *testing.T, accounts []perunwallet.Account, params *channel.Params, state *channel.State) channel.Transaction {
// Sign valid state.
sigs := make([][]byte, len(accounts))
for i := 0; i < len(accounts); i++ {
sig, err := Sign(accounts[i], params, state)
assert.NoError(t, err, "Sign should not return error")
sigs[i] = sig
}
return channel.Transaction{
State: state,
Sigs: sigs,
}
}

func TestSubscribeRegistered(t *testing.T) {
seed := time.Now().UnixNano()
t.Logf("seed is %v", seed)
rng := rand.New(rand.NewSource(int64(seed)))
// create new Adjudicator
accs, parts, funders, adjs, asset := newAdjudicator(t, rng, 1)
// create valid state and params
app := channeltest.NewRandomApp(rng)
params := channel.NewParamsUnsafe(uint64(100*time.Second), parts, app.Def(), big.NewInt(rng.Int63()))
state := newValidState(rng, params, asset)
// Set up subscription
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
registered, err := adjs[0].SubscribeRegistered(ctx, params)
assert.NoError(t, err, "Subscribing to valid params should not error")
// we need to properly fund the channel
fundingCtx, funCancel := context.WithTimeout(context.Background(), timeout)
defer funCancel()
// fund the contract
reqFund := channel.FundingReq{
Params: params,
Allocation: &state.Allocation,
Idx: channel.Index(0),
}
require.NoError(t, funders[0].Fund(fundingCtx, reqFund), "funding should succeed")
// Now test the register function
ctxReg, regCancel := context.WithTimeout(context.Background(), timeout)
defer regCancel()
tx := signState(t, accs, params, state)
req := channel.AdjudicatorReq{
Params: params,
Acc: accs[0],
Idx: channel.Index(0),
Tx: tx,
}
event, err := adjs[0].Register(ctxReg, req)
assert.NoError(t, err, "Registering state should succeed")
assert.Equal(t, event, registered.Next(), "Events should be equal")
assert.NoError(t, registered.Close(), "Closing event channel should not error")
assert.Nil(t, registered.Next(), "Next on closed channel should produce nil")
assert.NoError(t, registered.Err(), "Closing should produce no error")
// Setup a new subscription
registered2, err := adjs[0].SubscribeRegistered(ctx, params)
assert.NoError(t, err, "registering two subscriptions should not fail")
assert.Equal(t, event, registered2.Next(), "Events should be equal")
assert.NoError(t, registered2.Close(), "Closing event channel should not error")
assert.Nil(t, registered2.Next(), "Next on closed channel should produce nil")
assert.NoError(t, registered2.Err(), "Closing should produce no error")
}

func newValidState(rng *rand.Rand, params *channel.Params, assetholder common.Address) *channel.State {
// Create valid state.
assets := []channel.Asset{
&Asset{Address: assetholder},
}
ofparts := make([][]channel.Bal, len(params.Parts))
for i := 0; i < len(ofparts); i++ {
ofparts[i] = make([]channel.Bal, len(assets))
for k := 0; k < len(assets); k++ {
ofparts[i][k] = big.NewInt(rng.Int63n(999) + 1)
}
}
allocation := channel.Allocation{
Assets: assets,
OfParts: ofparts,
Locked: []channel.SubAlloc{},
}

return &channel.State{
ID: params.ID(),
Version: 4,
App: params.App,
Allocation: allocation,
Data: channeltest.NewRandomData(rng),
IsFinal: false,
}
}
18 changes: 9 additions & 9 deletions backend/ethereum/channel/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ var (
// compile time check that we implement the channel backend interface.
_ channel.Backend = new(Backend)
// Definition of ABI datatypes.
abiUint256, _ = abi.NewType("uint256", nil)
abiUint256Arr, _ = abi.NewType("uint256[]", nil)
abiUint256ArrArr, _ = abi.NewType("uint256[][]", nil)
abiAddress, _ = abi.NewType("address", nil)
abiAddressArr, _ = abi.NewType("address[]", nil)
abiBytes, _ = abi.NewType("bytes", nil)
abiBytes32, _ = abi.NewType("bytes32", nil)
abiUint64, _ = abi.NewType("uint64", nil)
abiBool, _ = abi.NewType("bool", nil)
abiUint256, _ = abi.NewType("uint256", "", nil)
abiUint256Arr, _ = abi.NewType("uint256[]", "", nil)
abiUint256ArrArr, _ = abi.NewType("uint256[][]", "", nil)
abiAddress, _ = abi.NewType("address", "", nil)
abiAddressArr, _ = abi.NewType("address[]", "", nil)
abiBytes, _ = abi.NewType("bytes", "", nil)
abiBytes32, _ = abi.NewType("bytes32", "", nil)
abiUint64, _ = abi.NewType("uint64", "", nil)
abiBool, _ = abi.NewType("bool", "", nil)
)

// Backend implements the interface defined in channel/Backend.go.
Expand Down
81 changes: 81 additions & 0 deletions backend/ethereum/channel/conclude.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) 2020 Chair of Applied Cryptography, Technische Universität
// Darmstadt, Germany. All rights reserved. This file is part of go-perun. Use
// of this source code is governed by a MIT-style license that can be found in
// the LICENSE file.

package channel // import "perun.network/go-perun/backend/ethereum/channel"

import (
"context"

"github.com/pkg/errors"
"perun.network/go-perun/backend/ethereum/bindings/adjudicator"

"perun.network/go-perun/channel"
)

// ensureConcluded ensures that conclude or concludeFinal (for non-final and
// final states, resp.) is called on the adjudicator.
// - a subscription on Concluded events is established
// - it searches for a past concluded event
// - if found, channel is already concluded and success is returned
// - if none found, conclude/concludeFinal is called on the adjudicator
// - it waits for a Concluded event from the blockchain
func (a *Adjudicator) ensureConcluded(ctx context.Context, req channel.AdjudicatorReq) error {
// Listen for Concluded event.
watchOpts, err := a.newWatchOpts(ctx)
if err != nil {
return errors.WithMessage(err, "creating watchOpts")
}
concluded := make(chan *adjudicator.AdjudicatorConcluded)
sub, err := a.contract.WatchConcluded(watchOpts, concluded, [][32]byte{req.Params.ID()})
if err != nil {
return errors.Wrap(err, "WatchConcluded failed")
}
defer sub.Unsubscribe()

if found, err := a.filterConcluded(ctx, req.Params.ID()); err != nil {
return errors.WithMessage(err, "filtering old Concluded events")
} else if found {
return nil
}

// No conclude event found in the past, send transaction.
if req.Tx.IsFinal {
err = errors.WithMessage(a.callConcludeFinal(ctx, req), "calling concludeFinal")
} else {
err = errors.WithMessage(a.callConclude(ctx, req), "calling conclude")
}
if IsTxFailedError(err) {
a.log.Warn("Calling conclude(Final) failed, waiting for event anyways...")
} else if err != nil {
return err
}

select {
case <-concluded:
return nil
case <-ctx.Done():
return errors.Wrap(ctx.Err(), "context cancelled")
case err = <-sub.Err():
return errors.Wrap(err, "subscription error")
}
}

// filterConcluded returns whether there has been a Concluded event in the past.
func (a *Adjudicator) filterConcluded(ctx context.Context, channelID channel.ID) (bool, error) {
filterOpts, err := a.newFilterOpts(ctx)
if err != nil {
return false, err
}
iter, err := a.contract.FilterConcluded(filterOpts, [][32]byte{channelID})
if err != nil {
return false, errors.Wrap(err, "creating iterator")
}

if !iter.Next() {
return false, errors.Wrap(iter.Error(), "iterating")
}
// Event found
return true, nil
}
Loading

0 comments on commit 6e1d4bc

Please sign in to comment.