diff --git a/contracts/swap/factory.go b/contracts/swap/factory.go index 5c3c7a0be1..d8cb5443b5 100644 --- a/contracts/swap/factory.go +++ b/contracts/swap/factory.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" chequebookFactory "github.com/ethersphere/go-sw3/contracts-v0-2-0/simpleswapfactory" + "github.com/ethersphere/swarm/swap/txqueue" ) var ( @@ -26,7 +27,7 @@ var ( type simpleSwapFactory struct { instance *chequebookFactory.SimpleSwapFactory address common.Address - backend Backend + backend txqueue.Backend } // SimpleSwapFactory interface defines the methods available for a factory contract for SimpleSwap @@ -40,7 +41,7 @@ type SimpleSwapFactory interface { } // FactoryAt creates a SimpleSwapFactory instance for the given address and backend -func FactoryAt(address common.Address, backend Backend) (SimpleSwapFactory, error) { +func FactoryAt(address common.Address, backend txqueue.Backend) (SimpleSwapFactory, error) { simple, err := chequebookFactory.NewSimpleSwapFactory(address, backend) if err != nil { return nil, err @@ -83,7 +84,7 @@ func (sf simpleSwapFactory) DeploySimpleSwap(auth *bind.TransactOpts, issuer com return nil, err } - receipt, err := WaitFunc(auth.Context, sf.backend, tx) + receipt, err := txqueue.WaitFunc(auth.Context, sf.backend, tx.Hash()) if err != nil { return nil, err } diff --git a/contracts/swap/swap.go b/contracts/swap/swap.go index defbf55d91..435732f329 100644 --- a/contracts/swap/swap.go +++ b/contracts/swap/swap.go @@ -20,8 +20,6 @@ package swap import ( - "context" - "errors" "fmt" "math/big" @@ -29,21 +27,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" contract "github.com/ethersphere/go-sw3/contracts-v0-2-0/erc20simpleswap" + "github.com/ethersphere/swarm/swap/txqueue" "github.com/ethersphere/swarm/uint256" ) -var ( - // ErrTransactionReverted is given when the transaction that cashes a cheque is reverted - ErrTransactionReverted = errors.New("Transaction reverted") -) - -// Backend wraps all methods required for contract deployment. -type Backend interface { - bind.ContractBackend - TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) - TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) -} - // Contract interface defines the methods exported from the underlying go-bindings for the smart contract type Contract interface { // Withdraw attempts to withdraw ERC20-token from the chequebook @@ -89,13 +76,13 @@ type Params struct { type simpleContract struct { instance *contract.ERC20SimpleSwap address common.Address - backend Backend + backend txqueue.Backend } // InstanceAt creates a new instance of a contract at a specific address. // It assumes that there is an existing contract instance at the given address, or an error is returned // This function is needed to communicate with remote Swap contracts (e.g. sending a cheque) -func InstanceAt(address common.Address, backend Backend) (Contract, error) { +func InstanceAt(address common.Address, backend txqueue.Backend) (Contract, error) { instance, err := contract.NewERC20SimpleSwap(address, backend) if err != nil { return nil, err @@ -110,7 +97,7 @@ func (s simpleContract) Withdraw(auth *bind.TransactOpts, amount *big.Int) (*typ if err != nil { return nil, err } - return WaitFunc(auth.Context, s.backend, tx) + return txqueue.WaitFunc(auth.Context, s.backend, tx.Hash()) } // Deposit sends an amount in ERC20 token to the chequebook and blocks until the transaction is mined @@ -140,7 +127,7 @@ func (s simpleContract) Deposit(auth *bind.TransactOpts, amount *big.Int) (*type if err != nil { return nil, err } - return WaitFunc(auth.Context, s.backend, tx) + return txqueue.WaitFunc(auth.Context, s.backend, tx.Hash()) } // CashChequeBeneficiaryStart sends the transaction to cash a cheque as the beneficiary @@ -224,44 +211,3 @@ func (s simpleContract) Issuer(opts *bind.CallOpts) (common.Address, error) { func (s simpleContract) PaidOut(opts *bind.CallOpts, addr common.Address) (*big.Int, error) { return s.instance.PaidOut(opts, addr) } - -// WaitFunc is the default function to wait for transactions -// We can overwrite this in tests so that we don't need to wait for mining -var WaitFunc = waitForTx - -// waitForTx waits for transaction to be mined and returns the receipt -func waitForTx(ctx context.Context, backend Backend, tx *types.Transaction) (*types.Receipt, error) { - // it blocks here until tx is mined - receipt, err := bind.WaitMined(ctx, backend, tx) - if err != nil { - return nil, err - } - // indicate whether the transaction did not revert - if receipt.Status != types.ReceiptStatusSuccessful { - return nil, ErrTransactionReverted - } - return receipt, nil -} - -// WaitForTransactionByHash waits for a transaction to by mined by hash -func WaitForTransactionByHash(ctx context.Context, backend Backend, txHash common.Hash) (*types.Receipt, error) { - tx, pending, err := backend.TransactionByHash(ctx, txHash) - if err != nil { - return nil, err - } - - var receipt *types.Receipt - if pending { - receipt, err = WaitFunc(ctx, backend, tx) - if err != nil { - return nil, err - } - } else { - receipt, err = backend.TransactionReceipt(ctx, txHash) - if err != nil { - return nil, err - } - } - - return receipt, nil -} diff --git a/swap/cashout.go b/swap/cashout.go index 1b15383e12..4af5d4d980 100644 --- a/swap/cashout.go +++ b/swap/cashout.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/metrics" contract "github.com/ethersphere/swarm/contracts/swap" + "github.com/ethersphere/swarm/swap/txqueue" "github.com/ethersphere/swarm/uint256" ) @@ -31,7 +32,7 @@ const CashChequeBeneficiaryTransactionCost = 50000 // CashoutProcessor holds all relevant fields needed for processing cashouts type CashoutProcessor struct { - backend contract.Backend // ethereum backend to use + backend txqueue.Backend // ethereum backend to use privateKey *ecdsa.PrivateKey // private key to use } @@ -48,7 +49,7 @@ type ActiveCashout struct { } // newCashoutProcessor creates a new instance of CashoutProcessor -func newCashoutProcessor(backend contract.Backend, privateKey *ecdsa.PrivateKey) *CashoutProcessor { +func newCashoutProcessor(backend txqueue.Backend, privateKey *ecdsa.PrivateKey) *CashoutProcessor { return &CashoutProcessor{ backend: backend, privateKey: privateKey, @@ -128,7 +129,7 @@ func (c *CashoutProcessor) waitForAndProcessActiveCashout(activeCashout *ActiveC ctx, cancel := context.WithTimeout(context.Background(), DefaultTransactionTimeout) defer cancel() - receipt, err := contract.WaitForTransactionByHash(ctx, c.backend, activeCashout.TransactionHash) + receipt, err := txqueue.WaitFunc(ctx, c.backend, activeCashout.TransactionHash) if err != nil { return err } diff --git a/swap/cashout_test.go b/swap/cashout_test.go index 27bcc46b6b..c947425366 100644 --- a/swap/cashout_test.go +++ b/swap/cashout_test.go @@ -22,7 +22,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/log" - contract "github.com/ethersphere/swarm/contracts/swap" + "github.com/ethersphere/swarm/swap/txqueue" "github.com/ethersphere/swarm/uint256" ) @@ -55,7 +55,7 @@ func TestContractIntegration(t *testing.T) { t.Fatal(err) } - receipt, err := contract.WaitForTransactionByHash(context.Background(), backend, tx.Hash()) + receipt, err := txqueue.WaitFunc(context.Background(), backend, tx.Hash()) if err != nil { t.Fatal(err) } @@ -99,7 +99,7 @@ func TestContractIntegration(t *testing.T) { t.Fatal(err) } - receipt, err = contract.WaitForTransactionByHash(context.Background(), backend, tx.Hash()) + receipt, err = txqueue.WaitFunc(context.Background(), backend, tx.Hash()) if err != nil { t.Fatal(err) } diff --git a/swap/common_test.go b/swap/common_test.go index 124bbbb7c7..856265e5d9 100644 --- a/swap/common_test.go +++ b/swap/common_test.go @@ -24,6 +24,7 @@ import ( "github.com/ethersphere/swarm/network" "github.com/ethersphere/swarm/p2p/protocols" "github.com/ethersphere/swarm/state" + "github.com/ethersphere/swarm/swap/txqueue" "github.com/ethersphere/swarm/uint256" ) @@ -226,7 +227,7 @@ func testCashCheque(s *Swap, cheque *Cheque) { } // when testing, we don't need to wait for a transaction to be mined -func testWaitForTx(ctx context.Context, backend cswap.Backend, tx *types.Transaction) (*types.Receipt, error) { +func testWaitForTx(ctx context.Context, backend txqueue.Backend, txHash common.Hash) (*types.Receipt, error) { var stb *swapTestBackend var ok bool @@ -235,12 +236,12 @@ func testWaitForTx(ctx context.Context, backend cswap.Backend, tx *types.Transac } stb.Commit() - receipt, err := backend.TransactionReceipt(ctx, tx.Hash()) + receipt, err := backend.TransactionReceipt(ctx, txHash) if err != nil { return nil, err } if receipt.Status != types.ReceiptStatusSuccessful { - return nil, cswap.ErrTransactionReverted + return nil, txqueue.ErrTransactionReverted } return receipt, nil } @@ -250,21 +251,21 @@ func testWaitForTx(ctx context.Context, backend cswap.Backend, tx *types.Transac func setupContractTest() func() { // we overwrite the waitForTx function with one which the simulated backend // immediately commits - currentWaitFunc := cswap.WaitFunc + currentWaitFunc := txqueue.WaitFunc // we also need to store the previous cashCheque function in case this is called multiple times currentCashCheque := defaultCashCheque defaultCashCheque = testCashCheque // overwrite only for the duration of the test, so... - cswap.WaitFunc = testWaitForTx + txqueue.WaitFunc = testWaitForTx return func() { // ...we need to set it back to original when done - cswap.WaitFunc = currentWaitFunc + txqueue.WaitFunc = currentWaitFunc defaultCashCheque = currentCashCheque } } // deploy for testing (needs simulated backend commit) -func testDeployWithPrivateKey(ctx context.Context, backend cswap.Backend, privateKey *ecdsa.PrivateKey, ownerAddress common.Address, depositAmount *uint256.Uint256) (cswap.Contract, error) { +func testDeployWithPrivateKey(ctx context.Context, backend txqueue.Backend, privateKey *ecdsa.PrivateKey, ownerAddress common.Address, depositAmount *uint256.Uint256) (cswap.Contract, error) { opts := bind.NewKeyedTransactor(privateKey) opts.Context = ctx diff --git a/swap/swap.go b/swap/swap.go index bd2d5c8dd9..d02a488f66 100644 --- a/swap/swap.go +++ b/swap/swap.go @@ -39,6 +39,7 @@ import ( "github.com/ethersphere/swarm/network" "github.com/ethersphere/swarm/p2p/protocols" "github.com/ethersphere/swarm/state" + "github.com/ethersphere/swarm/swap/txqueue" "github.com/ethersphere/swarm/uint256" ) @@ -60,7 +61,7 @@ type Swap struct { peers map[enode.ID]*Peer // map of all swap Peers peersLock sync.RWMutex // lock for peers map owner *Owner // contract access - backend contract.Backend // the backend (blockchain) used + backend txqueue.Backend // the backend (blockchain) used chainID uint64 // id of the chain the backend is connected to params *Params // economic and operational parameters contract contract.Contract // reference to the smart contract @@ -135,7 +136,7 @@ func swapRotatingFileHandler(logdir string) (log.Handler, error) { } // newSwapInstance is a swap constructor function without integrity checks -func newSwapInstance(stateStore state.Store, owner *Owner, backend contract.Backend, chainID uint64, params *Params, chequebookFactory contract.SimpleSwapFactory) *Swap { +func newSwapInstance(stateStore state.Store, owner *Owner, backend txqueue.Backend, chainID uint64, params *Params, chequebookFactory contract.SimpleSwapFactory) *Swap { return &Swap{ store: stateStore, peers: make(map[enode.ID]*Peer), @@ -246,7 +247,7 @@ const ( ) // createFactory determines the factory address and returns and error if no factory address has been specified or is unknown for the network -func createFactory(factoryAddress common.Address, chainID *big.Int, backend contract.Backend) (factory swap.SimpleSwapFactory, err error) { +func createFactory(factoryAddress common.Address, chainID *big.Int, backend txqueue.Backend) (factory swap.SimpleSwapFactory, err error) { if (factoryAddress == common.Address{}) { if factoryAddress, err = contract.FactoryAddressForNetwork(chainID.Uint64()); err != nil { return nil, err @@ -497,7 +498,7 @@ func cashCheque(s *Swap, cheque *Cheque) { if err != nil { metrics.GetOrRegisterCounter("swap.cheques.cashed.errors", nil).Inc(1) - swapLog.Error("cashing cheque:", err) + swapLog.Error("cashing cheque:", "error", err) } } diff --git a/swap/txqueue/txqueue.go b/swap/txqueue/txqueue.go new file mode 100644 index 0000000000..921829f396 --- /dev/null +++ b/swap/txqueue/txqueue.go @@ -0,0 +1,54 @@ +package txqueue + +import ( + "context" + "errors" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + // ErrTransactionReverted is given when the transaction that cashes a cheque is reverted + ErrTransactionReverted = errors.New("Transaction reverted") +) + +// Backend wraps all methods required for contract deployment. +type Backend interface { + bind.ContractBackend + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) +} + +// WaitFunc is the default function used for waiting for a receipt of a transaction +// this is overridden during in tests to automatically create a block on wait +var WaitFunc = func(ctx context.Context, b Backend, hash common.Hash) (*types.Receipt, error) { + queryTicker := time.NewTicker(time.Second) + defer queryTicker.Stop() + + for { + receipt, err := b.TransactionReceipt(ctx, hash) + if receipt != nil { + // indicate whether the transaction did not revert + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, ErrTransactionReverted + } + return receipt, nil + } + if err != nil { + log.Trace("Receipt retrieval failed", "err", err) + } else { + log.Trace("Transaction not yet mined") + } + // Wait for the next round. + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-queryTicker.C: + } + } +}