Commit 9a2634d4 authored by Ralph Pichler's avatar Ralph Pichler Committed by GitHub

transaction: add simple nonce tracking (#1266)

parent 3c9f6cfe
...@@ -14,11 +14,15 @@ import ( ...@@ -14,11 +14,15 @@ import (
) )
type signerMock struct { type signerMock struct {
signTx func(transaction *types.Transaction) (*types.Transaction, error) signTx func(transaction *types.Transaction) (*types.Transaction, error)
signTypedData func(*eip712.TypedData) ([]byte, error) signTypedData func(*eip712.TypedData) ([]byte, error)
ethereumAddress func() (common.Address, error)
} }
func (*signerMock) EthereumAddress() (common.Address, error) { func (m *signerMock) EthereumAddress() (common.Address, error) {
if m.ethereumAddress != nil {
return m.ethereumAddress()
}
return common.Address{}, nil return common.Address{}, nil
} }
...@@ -66,3 +70,9 @@ func WithSignTypedDataFunc(f func(*eip712.TypedData) ([]byte, error)) Option { ...@@ -66,3 +70,9 @@ func WithSignTypedDataFunc(f func(*eip712.TypedData) ([]byte, error)) Option {
s.signTypedData = f s.signTypedData = f
}) })
} }
func WithEthereumAddressFunc(f func() (common.Address, error)) Option {
return optionFunc(func(s *signerMock) {
s.ethereumAddress = f
})
}
...@@ -153,7 +153,7 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey, ...@@ -153,7 +153,7 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
if err != nil { if err != nil {
return nil, err return nil, err
} }
transactionService, err := transaction.NewService(logger, swapBackend, signer) transactionService, err := transaction.NewService(logger, swapBackend, signer, stateStore)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -24,6 +24,10 @@ type Backend interface { ...@@ -24,6 +24,10 @@ type Backend interface {
BalanceAt(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) BalanceAt(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error)
} }
// IsSynced will check if we are synced with the given blockchain backend. This
// is true if the current wall clock is after the block time of last block
// with the given maxDelay as the maximum duration we can be behind the block
// time.
func IsSynced(ctx context.Context, backend Backend, maxDelay time.Duration) (bool, error) { func IsSynced(ctx context.Context, backend Backend, maxDelay time.Duration) (bool, error) {
number, err := backend.BlockNumber(ctx) number, err := backend.BlockNumber(ctx)
if err != nil { if err != nil {
...@@ -40,6 +44,9 @@ func IsSynced(ctx context.Context, backend Backend, maxDelay time.Duration) (boo ...@@ -40,6 +44,9 @@ func IsSynced(ctx context.Context, backend Backend, maxDelay time.Duration) (boo
return blockTime.After(time.Now().UTC().Add(-maxDelay)), nil return blockTime.After(time.Now().UTC().Add(-maxDelay)), nil
} }
// WaitSynced will wait until we are synced with the given blockchain backend,
// with the given maxDelay duration as the maximum time we can be behind the
// last block.
func WaitSynced(ctx context.Context, backend Backend, maxDelay time.Duration) error { func WaitSynced(ctx context.Context, backend Backend, maxDelay time.Duration) error {
for { for {
synced, err := IsSynced(ctx, backend, maxDelay) synced, err := IsSynced(ctx, backend, maxDelay)
......
...@@ -6,7 +6,9 @@ package transaction ...@@ -6,7 +6,9 @@ package transaction
import ( import (
"errors" "errors"
"fmt"
"math/big" "math/big"
"sync"
"time" "time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
...@@ -14,10 +16,17 @@ import ( ...@@ -14,10 +16,17 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/crypto" "github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/logging" "github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/storage"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
const (
noncePrefix = "transaction_nonce_"
)
var ( var (
// ErrTransactionReverted denotes that the sent transaction has been
// reverted.
ErrTransactionReverted = errors.New("transaction reverted") ErrTransactionReverted = errors.New("transaction reverted")
) )
...@@ -30,7 +39,8 @@ type TxRequest struct { ...@@ -30,7 +39,8 @@ type TxRequest struct {
Value *big.Int // amount of wei to send Value *big.Int // amount of wei to send
} }
// Service is the service to send transactions. It takes care of gas price, gas limit and nonce management. // Service is the service to send transactions. It takes care of gas price, gas
// limit and nonce management.
type Service interface { type Service interface {
// Send creates a transaction based on the request and sends it. // Send creates a transaction based on the request and sends it.
Send(ctx context.Context, request *TxRequest) (txHash common.Hash, err error) Send(ctx context.Context, request *TxRequest) (txHash common.Hash, err error)
...@@ -39,14 +49,17 @@ type Service interface { ...@@ -39,14 +49,17 @@ type Service interface {
} }
type transactionService struct { type transactionService struct {
lock sync.Mutex
logger logging.Logger logger logging.Logger
backend Backend backend Backend
signer crypto.Signer signer crypto.Signer
sender common.Address sender common.Address
store storage.StateStorer
} }
// NewService creates a new transaction service. // NewService creates a new transaction service.
func NewService(logger logging.Logger, backend Backend, signer crypto.Signer) (Service, error) { func NewService(logger logging.Logger, backend Backend, signer crypto.Signer, store storage.StateStorer) (Service, error) {
senderAddress, err := signer.EthereumAddress() senderAddress, err := signer.EthereumAddress()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -56,12 +69,21 @@ func NewService(logger logging.Logger, backend Backend, signer crypto.Signer) (S ...@@ -56,12 +69,21 @@ func NewService(logger logging.Logger, backend Backend, signer crypto.Signer) (S
backend: backend, backend: backend,
signer: signer, signer: signer,
sender: senderAddress, sender: senderAddress,
store: store,
}, nil }, nil
} }
// Send creates and signs a transaction based on the request and sends it. // Send creates and signs a transaction based on the request and sends it.
func (t *transactionService) Send(ctx context.Context, request *TxRequest) (txHash common.Hash, err error) { func (t *transactionService) Send(ctx context.Context, request *TxRequest) (txHash common.Hash, err error) {
tx, err := prepareTransaction(ctx, request, t.sender, t.backend) t.lock.Lock()
defer t.lock.Unlock()
nonce, err := t.nextNonce(ctx)
if err != nil {
return common.Hash{}, err
}
tx, err := prepareTransaction(ctx, request, t.sender, t.backend, nonce)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
...@@ -76,10 +98,16 @@ func (t *transactionService) Send(ctx context.Context, request *TxRequest) (txHa ...@@ -76,10 +98,16 @@ func (t *transactionService) Send(ctx context.Context, request *TxRequest) (txHa
return common.Hash{}, err return common.Hash{}, err
} }
err = t.putNonce(nonce + 1)
if err != nil {
return common.Hash{}, err
}
return signedTx.Hash(), nil return signedTx.Hash(), nil
} }
// WaitForReceipt waits until either the transaction with the given hash has been mined or the context is cancelled. // WaitForReceipt waits until either the transaction with the given hash has
// been mined or the context is cancelled.
func (t *transactionService) WaitForReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) { func (t *transactionService) WaitForReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) {
for { for {
receipt, err := t.backend.TransactionReceipt(ctx, txHash) receipt, err := t.backend.TransactionReceipt(ctx, txHash)
...@@ -102,7 +130,7 @@ func (t *transactionService) WaitForReceipt(ctx context.Context, txHash common.H ...@@ -102,7 +130,7 @@ func (t *transactionService) WaitForReceipt(ctx context.Context, txHash common.H
} }
// prepareTransaction creates a signable transaction based on a request. // prepareTransaction creates a signable transaction based on a request.
func prepareTransaction(ctx context.Context, request *TxRequest, from common.Address, backend Backend) (tx *types.Transaction, err error) { func prepareTransaction(ctx context.Context, request *TxRequest, from common.Address, backend Backend, nonce uint64) (tx *types.Transaction, err error) {
var gasLimit uint64 var gasLimit uint64
if request.GasLimit == 0 { if request.GasLimit == 0 {
gasLimit, err = backend.EstimateGas(ctx, ethereum.CallMsg{ gasLimit, err = backend.EstimateGas(ctx, ethereum.CallMsg{
...@@ -127,11 +155,6 @@ func prepareTransaction(ctx context.Context, request *TxRequest, from common.Add ...@@ -127,11 +155,6 @@ func prepareTransaction(ctx context.Context, request *TxRequest, from common.Add
gasPrice = request.GasPrice gasPrice = request.GasPrice
} }
nonce, err := backend.PendingNonceAt(ctx, from)
if err != nil {
return nil, err
}
if request.To != nil { if request.To != nil {
return types.NewTransaction( return types.NewTransaction(
nonce, nonce,
...@@ -141,13 +164,45 @@ func prepareTransaction(ctx context.Context, request *TxRequest, from common.Add ...@@ -141,13 +164,45 @@ func prepareTransaction(ctx context.Context, request *TxRequest, from common.Add
gasPrice, gasPrice,
request.Data, request.Data,
), nil ), nil
} else {
return types.NewContractCreation(
nonce,
request.Value,
gasLimit,
gasPrice,
request.Data,
), nil
} }
return types.NewContractCreation(
nonce,
request.Value,
gasLimit,
gasPrice,
request.Data,
), nil
}
func (t *transactionService) nonceKey() string {
return fmt.Sprintf("%s%x", noncePrefix, t.sender)
}
func (t *transactionService) nextNonce(ctx context.Context) (uint64, error) {
onchainNonce, err := t.backend.PendingNonceAt(ctx, t.sender)
if err != nil {
return 0, err
}
var nonce uint64
err = t.store.Get(t.nonceKey(), &nonce)
if err != nil {
// If no nonce was found locally used whatever we get from the backend.
if errors.Is(err, storage.ErrNotFound) {
return onchainNonce, nil
}
return 0, err
}
// If the nonce onchain is larger than what we have there were external
// transactions and we need to update our nonce.
if onchainNonce > nonce {
return onchainNonce, nil
}
return nonce, nil
}
func (t *transactionService) putNonce(nonce uint64) error {
return t.store.Put(t.nonceKey(), nonce)
} }
...@@ -7,6 +7,7 @@ package transaction_test ...@@ -7,6 +7,7 @@ package transaction_test
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"testing" "testing"
...@@ -14,168 +15,299 @@ import ( ...@@ -14,168 +15,299 @@ import (
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/crypto"
signermock "github.com/ethersphere/bee/pkg/crypto/mock" signermock "github.com/ethersphere/bee/pkg/crypto/mock"
"github.com/ethersphere/bee/pkg/logging" "github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction" "github.com/ethersphere/bee/pkg/settlement/swap/transaction"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction/backendmock" "github.com/ethersphere/bee/pkg/settlement/swap/transaction/backendmock"
storemock "github.com/ethersphere/bee/pkg/statestore/mock"
) )
func nonceKey(sender common.Address) string {
return fmt.Sprintf("transaction_nonce_%x", sender)
}
func signerMockForTransaction(signedTx *types.Transaction, sender common.Address, t *testing.T) crypto.Signer {
return signermock.New(
signermock.WithSignTxFunc(func(transaction *types.Transaction) (*types.Transaction, error) {
if signedTx.To() == nil {
if transaction.To() != nil {
t.Fatalf("signing transaction with recipient. wanted nil, got %x", transaction.To())
}
} else {
if transaction.To() == nil || *transaction.To() != *signedTx.To() {
t.Fatalf("signing transactiono with wrong recipient. wanted %x, got %x", signedTx.To(), transaction.To())
}
}
if !bytes.Equal(transaction.Data(), signedTx.Data()) {
t.Fatalf("signing transaction with wrong data. wanted %x, got %x", signedTx.Data(), transaction.Data())
}
if transaction.Value().Cmp(signedTx.Value()) != 0 {
t.Fatalf("signing transaction with wrong value. wanted %d, got %d", signedTx.Value(), transaction.Value())
}
if transaction.Gas() != signedTx.Gas() {
t.Fatalf("signing transaction with wrong gas. wanted %d, got %d", signedTx.Gas(), transaction.Gas())
}
if transaction.GasPrice().Cmp(signedTx.GasPrice()) != 0 {
t.Fatalf("signing transaction with wrong gasprice. wanted %d, got %d", signedTx.GasPrice(), transaction.GasPrice())
}
if transaction.Nonce() != signedTx.Nonce() {
t.Fatalf("signing transaction with wrong nonce. wanted %d, got %d", signedTx.Nonce(), transaction.Nonce())
}
return signedTx, nil
}),
signermock.WithEthereumAddressFunc(func() (common.Address, error) {
return sender, nil
}),
)
}
func TestTransactionSend(t *testing.T) { func TestTransactionSend(t *testing.T) {
logger := logging.New(ioutil.Discard, 0) logger := logging.New(ioutil.Discard, 0)
sender := common.HexToAddress("0xddff")
recipient := common.HexToAddress("0xabcd") recipient := common.HexToAddress("0xabcd")
signedTx := types.NewTransaction(0, recipient, big.NewInt(0), 0, nil, nil)
txData := common.Hex2Bytes("0xabcdee") txData := common.Hex2Bytes("0xabcdee")
value := big.NewInt(1) value := big.NewInt(1)
suggestedGasPrice := big.NewInt(2) suggestedGasPrice := big.NewInt(2)
estimatedGasLimit := uint64(3) estimatedGasLimit := uint64(3)
nonce := uint64(2) nonce := uint64(2)
request := &transaction.TxRequest{ t.Run("send", func(t *testing.T) {
To: &recipient, signedTx := types.NewTransaction(nonce, recipient, value, estimatedGasLimit, suggestedGasPrice, txData)
Data: txData, request := &transaction.TxRequest{
Value: value, To: &recipient,
} Data: txData,
Value: value,
}
store := storemock.NewStateStore()
err := store.Put(nonceKey(sender), nonce)
if err != nil {
t.Fatal(err)
}
transactionService, err := transaction.NewService(logger, transactionService, err := transaction.NewService(logger,
backendmock.New( backendmock.New(
backendmock.WithSendTransactionFunc(func(ctx context.Context, tx *types.Transaction) error { backendmock.WithSendTransactionFunc(func(ctx context.Context, tx *types.Transaction) error {
if tx != signedTx { if tx != signedTx {
t.Fatal("not sending signed transaction") t.Fatal("not sending signed transaction")
} }
return nil return nil
}), }),
backendmock.WithEstimateGasFunc(func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { backendmock.WithEstimateGasFunc(func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
if !bytes.Equal(call.To.Bytes(), recipient.Bytes()) { if !bytes.Equal(call.To.Bytes(), recipient.Bytes()) {
t.Fatalf("estimating with wrong recipient. wanted %x, got %x", recipient, call.To) t.Fatalf("estimating with wrong recipient. wanted %x, got %x", recipient, call.To)
} }
if !bytes.Equal(call.Data, txData) { if !bytes.Equal(call.Data, txData) {
t.Fatal("estimating with wrong data") t.Fatal("estimating with wrong data")
} }
return estimatedGasLimit, nil return estimatedGasLimit, nil
}), }),
backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) { backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) {
return suggestedGasPrice, nil return suggestedGasPrice, nil
}), }),
backendmock.WithPendingNonceAtFunc(func(ctx context.Context, account common.Address) (uint64, error) { backendmock.WithPendingNonceAtFunc(func(ctx context.Context, account common.Address) (uint64, error) {
return nonce, nil return nonce - 1, nil
}), }),
), ),
signermock.New( signerMockForTransaction(signedTx, sender, t),
signermock.WithSignTxFunc(func(transaction *types.Transaction) (*types.Transaction, error) { store,
if !bytes.Equal(transaction.To().Bytes(), recipient.Bytes()) { )
t.Fatalf("signing transaction with wrong recipient. wanted %x, got %x", recipient, transaction.To()) if err != nil {
} t.Fatal(err)
if !bytes.Equal(transaction.Data(), txData) { }
t.Fatalf("signing transaction with wrong data. wanted %x, got %x", txData, transaction.Data())
}
if transaction.Value().Cmp(value) != 0 {
t.Fatalf("signing transaction with wrong value. wanted %d, got %d", value, transaction.Value())
}
if transaction.Gas() != estimatedGasLimit {
t.Fatalf("signing transaction with wrong gas. wanted %d, got %d", estimatedGasLimit, transaction.Gas())
}
if transaction.GasPrice().Cmp(suggestedGasPrice) != 0 {
t.Fatalf("signing transaction with wrong gasprice. wanted %d, got %d", suggestedGasPrice, transaction.GasPrice())
}
if transaction.Nonce() != nonce { txHash, err := transactionService.Send(context.Background(), request)
t.Fatalf("signing transaction with wrong nonce. wanted %d, got %d", nonce, transaction.Nonce()) if err != nil {
} t.Fatal(err)
}
return signedTx, nil if !bytes.Equal(txHash.Bytes(), signedTx.Hash().Bytes()) {
}), t.Fatal("returning wrong transaction hash")
), }
)
if err != nil {
t.Fatal(err)
}
txHash, err := transactionService.Send(context.Background(), request) var storedNonce uint64
if err != nil { err = store.Get(nonceKey(sender), &storedNonce)
t.Fatal(err) if err != nil {
} t.Fatal(err)
}
if storedNonce != nonce+1 {
t.Fatalf("nonce not stored correctly: want %d, got %d", nonce+1, storedNonce)
}
})
if !bytes.Equal(txHash.Bytes(), signedTx.Hash().Bytes()) { t.Run("send_no_nonce", func(t *testing.T) {
t.Fatal("returning wrong transaction hash") signedTx := types.NewTransaction(nonce, recipient, value, estimatedGasLimit, suggestedGasPrice, txData)
} request := &transaction.TxRequest{
} To: &recipient,
Data: txData,
Value: value,
}
store := storemock.NewStateStore()
func TestTransactionDeploy(t *testing.T) { transactionService, err := transaction.NewService(logger,
logger := logging.New(ioutil.Discard, 0) backendmock.New(
signedTx := types.NewContractCreation(0, big.NewInt(0), 0, nil, nil) backendmock.WithSendTransactionFunc(func(ctx context.Context, tx *types.Transaction) error {
txData := common.Hex2Bytes("0xabcdee") if tx != signedTx {
value := big.NewInt(1) t.Fatal("not sending signed transaction")
suggestedGasPrice := big.NewInt(2) }
estimatedGasLimit := uint64(3) return nil
nonce := uint64(2) }),
backendmock.WithEstimateGasFunc(func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
if !bytes.Equal(call.To.Bytes(), recipient.Bytes()) {
t.Fatalf("estimating with wrong recipient. wanted %x, got %x", recipient, call.To)
}
if !bytes.Equal(call.Data, txData) {
t.Fatal("estimating with wrong data")
}
return estimatedGasLimit, nil
}),
backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) {
return suggestedGasPrice, nil
}),
backendmock.WithPendingNonceAtFunc(func(ctx context.Context, account common.Address) (uint64, error) {
return nonce, nil
}),
),
signerMockForTransaction(signedTx, sender, t),
store,
)
if err != nil {
t.Fatal(err)
}
request := &transaction.TxRequest{ txHash, err := transactionService.Send(context.Background(), request)
To: nil, if err != nil {
Data: txData, t.Fatal(err)
Value: value, }
}
transactionService, err := transaction.NewService(logger, if !bytes.Equal(txHash.Bytes(), signedTx.Hash().Bytes()) {
backendmock.New( t.Fatal("returning wrong transaction hash")
backendmock.WithSendTransactionFunc(func(ctx context.Context, tx *types.Transaction) error { }
if tx != signedTx {
t.Fatal("not sending signed transaction")
}
return nil
}),
backendmock.WithEstimateGasFunc(func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
if call.To != nil {
t.Fatalf("estimating with recipient. wanted nil, got %x", call.To)
}
if !bytes.Equal(call.Data, txData) {
t.Fatal("estimating with wrong data")
}
return estimatedGasLimit, nil
}),
backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) {
return suggestedGasPrice, nil
}),
backendmock.WithPendingNonceAtFunc(func(ctx context.Context, account common.Address) (uint64, error) {
return nonce, nil
}),
),
signermock.New(
signermock.WithSignTxFunc(func(transaction *types.Transaction) (*types.Transaction, error) {
if transaction.To() != nil {
t.Fatalf("signing transaction with recipient. wanted nil, got %x", transaction.To())
}
if !bytes.Equal(transaction.Data(), txData) {
t.Fatalf("signing transaction with wrong data. wanted %x, got %x", txData, transaction.Data())
}
if transaction.Value().Cmp(value) != 0 {
t.Fatalf("signing transaction with wrong value. wanted %d, got %d", value, transaction.Value())
}
if transaction.Gas() != estimatedGasLimit {
t.Fatalf("signing transaction with wrong gas. wanted %d, got %d", estimatedGasLimit, transaction.Gas())
}
if transaction.GasPrice().Cmp(suggestedGasPrice) != 0 {
t.Fatalf("signing transaction with wrong gasprice. wanted %d, got %d", suggestedGasPrice, transaction.GasPrice())
}
if transaction.Nonce() != nonce {
t.Fatalf("signing transaction with wrong nonce. wanted %d, got %d", nonce, transaction.Nonce())
}
return signedTx, nil var storedNonce uint64
}), err = store.Get(nonceKey(sender), &storedNonce)
), if err != nil {
) t.Fatal(err)
if err != nil { }
t.Fatal(err) if storedNonce != nonce+1 {
} t.Fatalf("did not store nonce correctly. wanted %d, got %d", nonce+1, storedNonce)
}
})
txHash, err := transactionService.Send(context.Background(), request) t.Run("send_skipped_nonce", func(t *testing.T) {
if err != nil { nextNonce := nonce + 5
t.Fatal(err) signedTx := types.NewTransaction(nextNonce, recipient, value, estimatedGasLimit, suggestedGasPrice, txData)
} request := &transaction.TxRequest{
To: &recipient,
Data: txData,
Value: value,
}
store := storemock.NewStateStore()
err := store.Put(nonceKey(sender), nonce)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(txHash.Bytes(), signedTx.Hash().Bytes()) { transactionService, err := transaction.NewService(logger,
t.Fatal("returning wrong transaction hash") backendmock.New(
} backendmock.WithSendTransactionFunc(func(ctx context.Context, tx *types.Transaction) error {
if tx != signedTx {
t.Fatal("not sending signed transaction")
}
return nil
}),
backendmock.WithEstimateGasFunc(func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
if !bytes.Equal(call.To.Bytes(), recipient.Bytes()) {
t.Fatalf("estimating with wrong recipient. wanted %x, got %x", recipient, call.To)
}
if !bytes.Equal(call.Data, txData) {
t.Fatal("estimating with wrong data")
}
return estimatedGasLimit, nil
}),
backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) {
return suggestedGasPrice, nil
}),
backendmock.WithPendingNonceAtFunc(func(ctx context.Context, account common.Address) (uint64, error) {
return nextNonce, nil
}),
),
signerMockForTransaction(signedTx, sender, t),
store,
)
if err != nil {
t.Fatal(err)
}
txHash, err := transactionService.Send(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(txHash.Bytes(), signedTx.Hash().Bytes()) {
t.Fatal("returning wrong transaction hash")
}
var storedNonce uint64
err = store.Get(nonceKey(sender), &storedNonce)
if err != nil {
t.Fatal(err)
}
if storedNonce != nextNonce+1 {
t.Fatalf("did not store nonce correctly. wanted %d, got %d", nextNonce+1, storedNonce)
}
})
t.Run("deploy", func(t *testing.T) {
signedTx := types.NewContractCreation(nonce, value, estimatedGasLimit, suggestedGasPrice, txData)
request := &transaction.TxRequest{
To: nil,
Data: txData,
Value: value,
}
transactionService, err := transaction.NewService(logger,
backendmock.New(
backendmock.WithSendTransactionFunc(func(ctx context.Context, tx *types.Transaction) error {
if tx != signedTx {
t.Fatal("not sending signed transaction")
}
return nil
}),
backendmock.WithEstimateGasFunc(func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
if call.To != nil {
t.Fatalf("estimating with recipient. wanted nil, got %x", call.To)
}
if !bytes.Equal(call.Data, txData) {
t.Fatal("estimating with wrong data")
}
return estimatedGasLimit, nil
}),
backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) {
return suggestedGasPrice, nil
}),
backendmock.WithPendingNonceAtFunc(func(ctx context.Context, account common.Address) (uint64, error) {
return nonce, nil
}),
),
signerMockForTransaction(signedTx, sender, t),
storemock.NewStateStore(),
)
if err != nil {
t.Fatal(err)
}
txHash, err := transactionService.Send(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(txHash.Bytes(), signedTx.Hash().Bytes()) {
t.Fatal("returning wrong transaction hash")
}
})
} }
func TestTransactionWaitForReceipt(t *testing.T) { func TestTransactionWaitForReceipt(t *testing.T) {
...@@ -191,6 +323,7 @@ func TestTransactionWaitForReceipt(t *testing.T) { ...@@ -191,6 +323,7 @@ func TestTransactionWaitForReceipt(t *testing.T) {
}), }),
), ),
signermock.New(), signermock.New(),
nil,
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment