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

contract, swap: refactor backend to commit on sendTransaction #2092

Merged
merged 13 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions contracts/swap/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/chain"
)

var (
Expand All @@ -26,7 +27,7 @@ var (
type simpleSwapFactory struct {
instance *chequebookFactory.SimpleSwapFactory
address common.Address
backend Backend
backend chain.Backend
}

// SimpleSwapFactory interface defines the methods available for a factory contract for SimpleSwap
Expand All @@ -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 chain.Backend) (SimpleSwapFactory, error) {
simple, err := chequebookFactory.NewSimpleSwapFactory(address, backend)
if err != nil {
return nil, err
Expand Down Expand Up @@ -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 := chain.WaitMined(auth.Context, sf.backend, tx.Hash())
if err != nil {
return nil, err
}
Expand Down
64 changes: 5 additions & 59 deletions contracts/swap/swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,17 @@
package swap

import (
"context"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/chain"
"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
Expand Down Expand Up @@ -89,13 +76,13 @@ type Params struct {
type simpleContract struct {
instance *contract.ERC20SimpleSwap
address common.Address
backend Backend
backend chain.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 chain.Backend) (Contract, error) {
instance, err := contract.NewERC20SimpleSwap(address, backend)
if err != nil {
return nil, err
Expand All @@ -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 chain.WaitMined(auth.Context, s.backend, tx.Hash())
}

// Deposit sends an amount in ERC20 token to the chequebook and blocks until the transaction is mined
Expand Down Expand Up @@ -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 chain.WaitMined(auth.Context, s.backend, tx.Hash())
}

// CashChequeBeneficiaryStart sends the transaction to cash a cheque as the beneficiary
Expand Down Expand Up @@ -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
}
7 changes: 4 additions & 3 deletions swap/cashout.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/chain"
"github.com/ethersphere/swarm/uint256"
)

Expand All @@ -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 chain.Backend // ethereum backend to use
privateKey *ecdsa.PrivateKey // private key to use
}

Expand All @@ -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 chain.Backend, privateKey *ecdsa.PrivateKey) *CashoutProcessor {
return &CashoutProcessor{
backend: backend,
privateKey: privateKey,
Expand Down Expand Up @@ -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 := chain.WaitMined(ctx, c.backend, activeCashout.TransactionHash)
if err != nil {
return err
}
Expand Down
7 changes: 3 additions & 4 deletions swap/cashout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/chain"
"github.com/ethersphere/swarm/uint256"
)

Expand All @@ -37,7 +37,6 @@ func TestContractIntegration(t *testing.T) {
defer reset()

payout := uint256.FromUint64(42)

chequebook, err := testDeployWithPrivateKey(context.Background(), backend, ownerKey, ownerAddress, payout)
if err != nil {
t.Fatal(err)
Expand All @@ -55,7 +54,7 @@ func TestContractIntegration(t *testing.T) {
t.Fatal(err)
}

receipt, err := contract.WaitForTransactionByHash(context.Background(), backend, tx.Hash())
receipt, err := chain.WaitMined(nil, backend, tx.Hash())
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -99,7 +98,7 @@ func TestContractIntegration(t *testing.T) {
t.Fatal(err)
}

receipt, err = contract.WaitForTransactionByHash(context.Background(), backend, tx.Hash())
receipt, err = chain.WaitMined(nil, backend, tx.Hash())
if err != nil {
t.Fatal(err)
}
Expand Down
84 changes: 84 additions & 0 deletions swap/chain/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package chain

import (
"context"
"errors"
"time"

"github.com/ethereum/go-ethereum/log"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"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 is the minimum amount of functionality required by the underlying ethereum backend
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)
}

// TestBackend is the backend to use for tests with a simulated backend
type TestBackend struct {
acud marked this conversation as resolved.
Show resolved Hide resolved
*backends.SimulatedBackend
}

// WaitMined waits until either the transaction with the given hash has been mined or the context is cancelled
// this is an adapted version of go-ethereums bind.WaitMined
func WaitMined(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 err != nil {
log.Trace("Receipt retrieval failed", "err", err)
acud marked this conversation as resolved.
Show resolved Hide resolved
}
if receipt != nil {
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, ErrTransactionReverted
}
return receipt, nil
}

log.Trace("Transaction not yet mined")
ralph-pichler marked this conversation as resolved.
Show resolved Hide resolved
// Wait for the next round.
select {
case <-ctx.Done():
ralph-pichler marked this conversation as resolved.
Show resolved Hide resolved
return nil, ctx.Err()
case <-queryTicker.C:
}
}
}

// SendTransaction adds a commit after a successful send
acud marked this conversation as resolved.
Show resolved Hide resolved
func (b *TestBackend) SendTransaction(ctx context.Context, tx *types.Transaction) (err error) {
err = b.SimulatedBackend.SendTransaction(ctx, tx)
if err == nil {
b.SimulatedBackend.Commit()
}
return err
}

// Close overrides the Close function of the underlying SimulatedBackend so that it does nothing
// This allows the same SimulatedBackend backend to be reused across tests
// This is necessary due to some memory leakage issues with the used version of the SimulatedBackend
func (b *TestBackend) Close() {

}

// NewTestBackend returns a new TestBackend for the given SimulatedBackend
// It also causes an initial commit to make sure that genesis accounts are set up
func NewTestBackend(backend *backends.SimulatedBackend) *TestBackend {
backend.Commit()
return &TestBackend{
SimulatedBackend: backend,
}
}
Loading