diff --git a/pkg/config/chain.go b/pkg/config/chain.go index 175e9a7d7df..1f5b3355961 100644 --- a/pkg/config/chain.go +++ b/pkg/config/chain.go @@ -25,7 +25,7 @@ type ChainConfig struct { StakingAddress common.Address PostageStampAddress common.Address RedistributionAddress common.Address - SwapPriceOracleAddress common.Address + SwapPriceOracleAddress common.Address // Swap swear and swindle (S3) Contracts CurrentFactoryAddress common.Address // ABIs. diff --git a/pkg/node/chain.go b/pkg/node/chain.go index 8770a57ba43..5931c8dde8e 100644 --- a/pkg/node/chain.go +++ b/pkg/node/chain.go @@ -346,6 +346,11 @@ type noOpChainBackend struct { chainID int64 } +// BlockByNumber implements transaction.Backend. +func (m *noOpChainBackend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + return nil, postagecontract.ErrChainDisabled +} + func (m noOpChainBackend) Metrics() []prometheus.Collector { return nil } diff --git a/pkg/sctx/sctx.go b/pkg/sctx/sctx.go index 757ef36f083..e2f1d3aefd4 100644 --- a/pkg/sctx/sctx.go +++ b/pkg/sctx/sctx.go @@ -12,10 +12,8 @@ import ( "math/big" ) -var ( - // ErrTargetPrefix is returned when target prefix decoding fails. - ErrTargetPrefix = errors.New("error decoding prefix string") -) +// ErrTargetPrefix is returned when target prefix decoding fails. +var ErrTargetPrefix = errors.New("error decoding prefix string") type ( HTTPRequestIDKey struct{} @@ -60,7 +58,6 @@ func GetGasLimitWithDefault(ctx context.Context, defaultLimit uint64) uint64 { func SetGasPrice(ctx context.Context, price *big.Int) context.Context { return context.WithValue(ctx, gasPriceKey{}, price) - } func GetGasPrice(ctx context.Context) *big.Int { diff --git a/pkg/storageincentives/redistribution/redistribution.go b/pkg/storageincentives/redistribution/redistribution.go index a36e8d992d9..4657c81cbff 100644 --- a/pkg/storageincentives/redistribution/redistribution.go +++ b/pkg/storageincentives/redistribution/redistribution.go @@ -47,7 +47,6 @@ func New( incentivesContractABI abi.ABI, setGasLimit bool, ) Contract { - var gasLimit uint64 if setGasLimit { gasLimit = transaction.DefaultGasLimit @@ -118,7 +117,7 @@ func (c *contract) Claim(ctx context.Context, proofs ChunkInclusionProofs) (comm Value: big.NewInt(0), Description: "claim win transaction", } - txHash, err := c.sendAndWait(ctx, request, 50) + txHash, err := c.sendAndWait(ctx, request, transaction.RedistributionTipBoostPercent) if err != nil { return txHash, fmt.Errorf("claim: %w", err) } @@ -141,7 +140,7 @@ func (c *contract) Commit(ctx context.Context, obfusHash []byte, round uint64) ( Value: big.NewInt(0), Description: "commit transaction", } - txHash, err := c.sendAndWait(ctx, request, 50) + txHash, err := c.sendAndWait(ctx, request, transaction.RedistributionTipBoostPercent) if err != nil { return txHash, fmt.Errorf("commit: obfusHash %v: %w", common.BytesToHash(obfusHash), err) } @@ -164,7 +163,7 @@ func (c *contract) Reveal(ctx context.Context, storageDepth uint8, reserveCommit Value: big.NewInt(0), Description: "reveal transaction", } - txHash, err := c.sendAndWait(ctx, request, 50) + txHash, err := c.sendAndWait(ctx, request, transaction.RedistributionTipBoostPercent) if err != nil { return txHash, fmt.Errorf("reveal: storageDepth %d reserveCommitmentHash %v RandomNonce %v: %w", storageDepth, common.BytesToHash(reserveCommitmentHash), common.BytesToHash(RandomNonce), err) } diff --git a/pkg/storageincentives/staking/contract.go b/pkg/storageincentives/staking/contract.go index d039f7ecb67..7a91fa12266 100644 --- a/pkg/storageincentives/staking/contract.go +++ b/pkg/storageincentives/staking/contract.go @@ -73,7 +73,6 @@ func New( setGasLimit bool, height uint8, ) Contract { - var gasLimit uint64 if setGasLimit { gasLimit = transaction.DefaultGasLimit @@ -142,7 +141,6 @@ func (c *contract) ChangeStakeOverlay(ctx context.Context, nonce common.Hash) (c // UpdateHeight submits the reserve doubling height to the contract only if the height is a new value. func (c *contract) UpdateHeight(ctx context.Context) (common.Hash, bool, error) { - h, err := c.getHeight(ctx) if err != nil { return common.Hash{}, false, fmt.Errorf("staking contract: failed to read previous height: %w", err) @@ -267,7 +265,7 @@ func (c *contract) sendApproveTransaction(ctx context.Context, amount *big.Int) ) }() - txHash, err := c.transactionService.Send(ctx, request, 0) + txHash, err := c.transactionService.Send(ctx, request, transaction.DefaultTipBoostPercent) if err != nil { return nil, err } diff --git a/pkg/transaction/backend.go b/pkg/transaction/backend.go index 4b62203ed99..0bf38c35975 100644 --- a/pkg/transaction/backend.go +++ b/pkg/transaction/backend.go @@ -29,6 +29,7 @@ type Backend interface { TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) BlockNumber(ctx context.Context) (uint64, error) + BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) BalanceAt(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) diff --git a/pkg/transaction/backendmock/backend.go b/pkg/transaction/backendmock/backend.go index f7b3ed0e368..fea46f3936b 100644 --- a/pkg/transaction/backendmock/backend.go +++ b/pkg/transaction/backendmock/backend.go @@ -108,7 +108,7 @@ func (m *backendMock) BlockNumber(ctx context.Context) (uint64, error) { } func (m *backendMock) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - if m.blockNumber != nil { + if m.blockByNumber != nil { return m.blockByNumber(ctx, number) } return nil, errors.New("not implemented") @@ -136,7 +136,7 @@ func (m *backendMock) NonceAt(ctx context.Context, account common.Address, block } func (m *backendMock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - if m.suggestGasPrice != nil { + if m.suggestGasTipCap != nil { return m.suggestGasTipCap(ctx) } return nil, errors.New("not implemented") diff --git a/pkg/transaction/transaction.go b/pkg/transaction/transaction.go index fc7a4904510..2d911e27261 100644 --- a/pkg/transaction/transaction.go +++ b/pkg/transaction/transaction.go @@ -41,11 +41,14 @@ var ( ErrTransactionReverted = errors.New("transaction reverted") ErrUnknownTransaction = errors.New("unknown transaction") ErrAlreadyImported = errors.New("already imported") + ErrEIP1559NotSupported = errors.New("network does not appear to support EIP-1559 (no baseFee)") ) const ( - DefaultTipBoostPercent = 20 - DefaultGasLimit = 1_000_000 + DefaultGasLimit = 1_000_000 + DefaultTipBoostPercent = 25 + MinimumGasTipCap = 1_500_000_000 // 1.5 Gwei + RedistributionTipBoostPercent = 50 ) // TxRequest describes a request for a transaction that can be executed. @@ -226,8 +229,8 @@ func (t *transactionService) waitForPendingTx(txHash common.Hash) { t.wg.Add(1) go func() { defer t.wg.Done() - switch _, err := t.WaitForReceipt(t.ctx, txHash); { - case err == nil: + switch _, err := t.WaitForReceipt(t.ctx, txHash); err { + case nil: t.logger.Info("pending transaction confirmed", "tx", txHash) err = t.store.Delete(pendingTransactionKey(txHash)) if err != nil { @@ -286,7 +289,7 @@ func (t *transactionService) prepareTransaction(ctx context.Context, request *Tx gasLimit = request.MinEstimatedGasLimit } - gasLimit += gasLimit / 4 // add 25% on top + gasLimit += gasLimit / 2 // add 50% buffer to the estimated gas limit if gasLimit < request.MinEstimatedGasLimit { gasLimit = request.MinEstimatedGasLimit } @@ -324,28 +327,48 @@ func (t *transactionService) prepareTransaction(ctx context.Context, request *Tx } func (t *transactionService) suggestedFeeAndTip(ctx context.Context, gasPrice *big.Int, boostPercent int) (*big.Int, *big.Int, error) { - var err error + gasTipCap, err := t.backend.SuggestGasTipCap(ctx) + if err != nil { + return nil, nil, err + } + + multiplier := big.NewInt(int64(boostPercent) + 100) + gasTipCap = new(big.Int).Div(new(big.Int).Mul(gasTipCap, multiplier), big.NewInt(100)) + + minimumTip := big.NewInt(MinimumGasTipCap) + if gasTipCap.Cmp(minimumTip) < 0 { + gasTipCap = new(big.Int).Set(minimumTip) + } + + var gasFeeCap *big.Int if gasPrice == nil { - gasPrice, err = t.backend.SuggestGasPrice(ctx) + latestBlockHeader, err := t.backend.HeaderByNumber(ctx, nil) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to get latest block: %w", err) } - gasPrice = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(boostPercent)+100), gasPrice), big.NewInt(100)) - } - gasTipCap, err := t.backend.SuggestGasTipCap(ctx) - if err != nil { - return nil, nil, err + if latestBlockHeader.BaseFee == nil { + return nil, nil, ErrEIP1559NotSupported + } + + // gasFeeCap = (2 * baseFee) + gasTipCap + gasFeeCap = new(big.Int).Add( + new(big.Int).Mul(latestBlockHeader.BaseFee, big.NewInt(2)), + gasTipCap, + ) + } else { + gasFeeCap = new(big.Int).Set(gasPrice) } - gasTipCap = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(boostPercent)+100), gasTipCap), big.NewInt(100)) - gasFeeCap := new(big.Int).Add(gasTipCap, gasPrice) + if gasTipCap.Cmp(gasFeeCap) > 0 { + t.logger.Warning("gas tip cap is higher than gas fee cap, using gas fee cap as gas tip cap", "gas_tip_cap", gasTipCap, "gas_fee_cap", gasFeeCap) + gasTipCap = new(big.Int).Set(gasFeeCap) + } - t.logger.Debug("prepare transaction", "gas_price", gasPrice, "gas_max_fee", gasFeeCap, "gas_max_tip", gasTipCap) + t.logger.Debug("prepare transaction", "gas_max_fee", gasFeeCap, "gas_max_tip", gasTipCap) return gasFeeCap, gasTipCap, nil - } func storedTransactionKey(txHash common.Hash) string { @@ -371,10 +394,9 @@ func (t *transactionService) nextNonce(ctx context.Context) (uint64, error) { // PendingNonceAt returns the nonce we should use, but we will // compare this to our pending tx list, therefore the -1. - var maxNonce uint64 = onchainNonce - 1 + var maxNonce = onchainNonce - 1 for _, txHash := range pendingTxs { trx, _, err := t.backend.TransactionByHash(ctx, txHash) - if err != nil { t.logger.Error(err, "pending transaction not found", "tx", txHash) return 0, err @@ -419,7 +441,7 @@ func (t *transactionService) WatchSentTransaction(txHash common.Hash) (<-chan ty } func (t *transactionService) PendingTransactions() ([]common.Hash, error) { - var txHashes []common.Hash = make([]common.Hash, 0) + var txHashes = make([]common.Hash, 0) err := t.store.Iterate(pendingTransactionPrefix, func(key, value []byte) (stop bool, err error) { txHash := common.HexToHash(strings.TrimPrefix(string(key), pendingTransactionPrefix)) txHashes = append(txHashes, txHash) @@ -438,7 +460,6 @@ func (t *transactionService) filterPendingTransactions(ctx context.Context, txHa for _, txHash := range txHashes { _, isPending, err := t.backend.TransactionByHash(ctx, txHash) - // When error occurres consider transaction as pending (so this transaction won't be filtered out), // unless it was not found if err != nil { diff --git a/pkg/transaction/transaction_test.go b/pkg/transaction/transaction_test.go index d105738afbf..d8b830e5780 100644 --- a/pkg/transaction/transaction_test.go +++ b/pkg/transaction/transaction_test.go @@ -30,6 +30,11 @@ import ( "github.com/ethersphere/bee/v2/pkg/util/testutil" ) +var ( + minimumTip = big.NewInt(transaction.MinimumGasTipCap) + baseFee = big.NewInt(3_000_000_000) +) + func signerMockForTransaction(t *testing.T, signedTx *types.Transaction, sender common.Address, signerChainID *big.Int) crypto.Signer { t.Helper() return signermock.New( @@ -78,12 +83,12 @@ func TestTransactionSend(t *testing.T) { recipient := common.HexToAddress("0xabcd") txData := common.Hex2Bytes("0xabcdee") value := big.NewInt(1) - suggestedGasPrice := big.NewInt(1000) - suggestedGasTip := big.NewInt(100) - defaultGasFee := big.NewInt(0).Add(suggestedGasPrice, suggestedGasTip) + suggestedGasTip := minimumTip estimatedGasLimit := uint64(3) + gasLimit := estimatedGasLimit + estimatedGasLimit/2 // added 50% buffer nonce := uint64(2) chainID := big.NewInt(5) + gasFeeCap := new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), suggestedGasTip) t.Run("send", func(t *testing.T) { t.Parallel() @@ -93,8 +98,8 @@ func TestTransactionSend(t *testing.T) { Nonce: nonce, To: &recipient, Value: value, - Gas: estimatedGasLimit, - GasFeeCap: defaultGasFee, + Gas: gasLimit, + GasFeeCap: gasFeeCap, GasTipCap: suggestedGasTip, Data: txData, }) @@ -122,15 +127,15 @@ func TestTransactionSend(t *testing.T) { } 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 - 1, nil }), backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { return suggestedGasTip, nil }), + backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: baseFee}, nil + }), ), signerMockForTransaction(t, signedTx, sender, chainID), store, @@ -172,12 +177,12 @@ func TestTransactionSend(t *testing.T) { t.Fatalf("got wrong description in stored transaction. wanted %x, got %x", request.Description, storedTransaction.Description) } - if storedTransaction.GasLimit != estimatedGasLimit { - t.Fatalf("got wrong gas limit in stored transaction. wanted %d, got %d", estimatedGasLimit, storedTransaction.GasLimit) + if storedTransaction.GasLimit != gasLimit { + t.Fatalf("got wrong gas limit in stored transaction. wanted %d, got %d", gasLimit, storedTransaction.GasLimit) } - if defaultGasFee.Cmp(storedTransaction.GasPrice) != 0 { - t.Fatalf("got wrong gas price in stored transaction. wanted %d, got %d", defaultGasFee, storedTransaction.GasPrice) + if gasFeeCap.Cmp(storedTransaction.GasPrice) != 0 { + t.Fatalf("got wrong gas price in stored transaction. wanted %d, got %d", gasFeeCap, storedTransaction.GasPrice) } if storedTransaction.Nonce != nonce { @@ -205,8 +210,8 @@ func TestTransactionSend(t *testing.T) { Nonce: nonce, To: &recipient, Value: value, - Gas: estimatedGasLimit, - GasFeeCap: defaultGasFee, + Gas: gasLimit, + GasFeeCap: gasFeeCap, GasTipCap: suggestedGasTip, Data: txData, }) @@ -229,15 +234,15 @@ func TestTransactionSend(t *testing.T) { backendmock.WithEstimateGasFunc(func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { return 0, errors.New("estimate failure") }), - 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 - 1, nil }), backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { return suggestedGasTip, nil }), + backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: baseFee}, nil + }), ), signerMockForTransaction(t, signedTx, sender, chainID), store, @@ -279,12 +284,12 @@ func TestTransactionSend(t *testing.T) { t.Fatalf("got wrong description in stored transaction. wanted %x, got %x", request.Description, storedTransaction.Description) } - if storedTransaction.GasLimit != estimatedGasLimit { - t.Fatalf("got wrong gas limit in stored transaction. wanted %d, got %d", estimatedGasLimit, storedTransaction.GasLimit) + if storedTransaction.GasLimit != gasLimit { + t.Fatalf("got wrong gas limit in stored transaction. wanted %d, got %d", gasLimit, storedTransaction.GasLimit) } - if defaultGasFee.Cmp(storedTransaction.GasPrice) != 0 { - t.Fatalf("got wrong gas price in stored transaction. wanted %d, got %d", defaultGasFee, storedTransaction.GasPrice) + if gasFeeCap.Cmp(storedTransaction.GasPrice) != 0 { + t.Fatalf("got wrong gas price in stored transaction. wanted %d, got %d", gasFeeCap, storedTransaction.GasPrice) } if storedTransaction.Nonce != nonce { @@ -307,20 +312,18 @@ func TestTransactionSend(t *testing.T) { t.Run("sendWithBoost", func(t *testing.T) { t.Parallel() - tip := big.NewInt(0).Div(new(big.Int).Mul(suggestedGasTip, big.NewInt(15)), big.NewInt(10)) - fee := big.NewInt(0).Div(new(big.Int).Mul(suggestedGasPrice, big.NewInt(15)), big.NewInt(10)) - fee = fee.Add(fee, tip) - // tip is the same as suggestedGasPrice and boost is 50% - // so final gas price will be 2.5x suggestedGasPrice + multiplier := big.NewInt(int64(transaction.DefaultTipBoostPercent) + 100) + suggestedGasTipWithBoost := new(big.Int).Div(new(big.Int).Mul(suggestedGasTip, multiplier), big.NewInt(100)) + gasFeeCapWithBoost := new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), suggestedGasTipWithBoost) signedTx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainID, Nonce: nonce, To: &recipient, Value: value, - Gas: estimatedGasLimit, - GasFeeCap: fee, - GasTipCap: tip, + Gas: gasLimit, + GasFeeCap: gasFeeCapWithBoost, + GasTipCap: suggestedGasTipWithBoost, Data: txData, }) request := &transaction.TxRequest{ @@ -347,15 +350,15 @@ func TestTransactionSend(t *testing.T) { } 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 - 1, nil }), backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { return suggestedGasTip, nil }), + backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: baseFee}, nil + }), ), signerMockForTransaction(t, signedTx, sender, chainID), store, @@ -371,7 +374,7 @@ func TestTransactionSend(t *testing.T) { } testutil.CleanupCloser(t, transactionService) - txHash, err := transactionService.Send(context.Background(), request, 50) + txHash, err := transactionService.Send(context.Background(), request, transaction.DefaultTipBoostPercent) if err != nil { t.Fatal(err) } @@ -397,12 +400,12 @@ func TestTransactionSend(t *testing.T) { t.Fatalf("got wrong description in stored transaction. wanted %x, got %x", request.Description, storedTransaction.Description) } - if storedTransaction.GasLimit != estimatedGasLimit { - t.Fatalf("got wrong gas limit in stored transaction. wanted %d, got %d", estimatedGasLimit, storedTransaction.GasLimit) + if storedTransaction.GasLimit != gasLimit { + t.Fatalf("got wrong gas limit in stored transaction. wanted %d, got %d", gasLimit, storedTransaction.GasLimit) } - if fee.Cmp(storedTransaction.GasPrice) != 0 { - t.Fatalf("got wrong gas price in stored transaction. wanted %d, got %d", fee, storedTransaction.GasPrice) + if gasFeeCapWithBoost.Cmp(storedTransaction.GasPrice) != 0 { + t.Fatalf("got wrong gas price in stored transaction. wanted %d, got %d", gasFeeCapWithBoost, storedTransaction.GasPrice) } if storedTransaction.Nonce != nonce { @@ -430,9 +433,9 @@ func TestTransactionSend(t *testing.T) { Nonce: nonce, To: &recipient, Value: value, - Gas: estimatedGasLimit, + Gas: gasLimit, GasTipCap: suggestedGasTip, - GasFeeCap: defaultGasFee, + GasFeeCap: gasFeeCap, Data: txData, }) request := &transaction.TxRequest{ @@ -459,15 +462,15 @@ func TestTransactionSend(t *testing.T) { } 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 }), backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { return suggestedGasTip, nil }), + backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: baseFee}, nil + }), ), signerMockForTransaction(t, signedTx, sender, chainID), store, @@ -498,9 +501,9 @@ func TestTransactionSend(t *testing.T) { Nonce: nextNonce, To: &recipient, Value: value, - Gas: estimatedGasLimit, + Gas: gasLimit, GasTipCap: suggestedGasTip, - GasFeeCap: defaultGasFee, + GasFeeCap: gasFeeCap, Data: txData, }) request := &transaction.TxRequest{ @@ -527,8 +530,74 @@ func TestTransactionSend(t *testing.T) { } 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 + }), + backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { + return suggestedGasTip, nil + }), + backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: baseFee}, nil + }), + ), + signerMockForTransaction(t, signedTx, sender, chainID), + store, + chainID, + monitormock.New(), + ) + if err != nil { + t.Fatal(err) + } + + txHash, err := transactionService.Send(context.Background(), request, 0) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(txHash.Bytes(), signedTx.Hash().Bytes()) { + t.Fatal("returning wrong transaction hash") + } + }) + + t.Run("send higher tip than fee", func(t *testing.T) { + t.Parallel() + + customGasFeeCap := big.NewInt(1000) // smaller than tip + nextNonce := nonce + 5 + signedTx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainID, + Nonce: nextNonce, + To: &recipient, + Value: value, + Gas: gasLimit, + GasTipCap: customGasFeeCap, + GasFeeCap: customGasFeeCap, + Data: txData, + }) + request := &transaction.TxRequest{ + To: &recipient, + Data: txData, + Value: value, + GasPrice: customGasFeeCap, + } + store := storemock.NewStateStore() + + transactionService, err := transaction.NewService(logger, sender, + 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.WithPendingNonceAtFunc(func(ctx context.Context, account common.Address) (uint64, error) { return nextNonce, nil @@ -536,6 +605,9 @@ func TestTransactionSend(t *testing.T) { backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { return suggestedGasTip, nil }), + backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: baseFee}, nil + }), ), signerMockForTransaction(t, signedTx, sender, chainID), store, @@ -554,6 +626,15 @@ func TestTransactionSend(t *testing.T) { if !bytes.Equal(txHash.Bytes(), signedTx.Hash().Bytes()) { t.Fatal("returning wrong transaction hash") } + + storedTransaction, err := transactionService.StoredTransaction(txHash) + if err != nil { + t.Fatal(err) + } + + if storedTransaction.GasTipCap.Cmp(customGasFeeCap) != 0 { + t.Fatalf("got wrong gas tip in stored transaction. wanted %d, got %d", customGasFeeCap, storedTransaction.GasTipCap) + } }) } @@ -627,11 +708,11 @@ func TestTransactionResend(t *testing.T) { chainID := big.NewInt(5) nonce := uint64(10) data := []byte{1, 2, 3, 4} - gasPrice := big.NewInt(1000) gasTip := big.NewInt(100) gasFee := big.NewInt(1100) gasLimit := uint64(100000) value := big.NewInt(0) + gasFeeCap := new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), minimumTip) store := storemock.NewStateStore() testutil.CleanupCloser(t, store) @@ -643,7 +724,7 @@ func TestTransactionResend(t *testing.T) { Value: value, Gas: gasLimit, GasTipCap: gasTip, - GasFeeCap: gasFee, + GasFeeCap: gasFeeCap, Data: data, }) @@ -667,12 +748,12 @@ func TestTransactionResend(t *testing.T) { } return nil }), - backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) { - return gasPrice, nil - }), backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { return gasTip, nil }), + backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: baseFee}, nil + }), ), signerMockForTransaction(t, signedTx, recipient, chainID), store, @@ -699,7 +780,6 @@ func TestTransactionCancel(t *testing.T) { chainID := big.NewInt(5) nonce := uint64(10) data := []byte{1, 2, 3, 4} - gasPrice := big.NewInt(1000) gasTip := big.NewInt(100) gasFee := big.NewInt(1100) gasLimit := uint64(100000) @@ -732,12 +812,13 @@ func TestTransactionCancel(t *testing.T) { t.Fatal(err) } - gasTipCap := new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(10)+100), gasTip), big.NewInt(100)) - gasFeeCap := new(big.Int).Add(gasFee, gasTipCap) - t.Run("ok", func(t *testing.T) { t.Parallel() + fee := new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), minimumTip) + gasTipCap := new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(10)+100), minimumTip), big.NewInt(100)) + gasFeeCap := new(big.Int).Add(fee, gasTipCap) + cancelTx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainID, Nonce: nonce, @@ -757,12 +838,12 @@ func TestTransactionCancel(t *testing.T) { } return nil }), - backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) { - return gasPrice, nil - }), backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { return gasTip, nil }), + backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: baseFee}, nil + }), ), signerMockForTransaction(t, cancelTx, recipient, chainID), store, @@ -788,6 +869,8 @@ func TestTransactionCancel(t *testing.T) { t.Parallel() customGasPrice := big.NewInt(5) + gasTipCap := new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(10)+100), gasTip), big.NewInt(100)) + gasFeeCap := new(big.Int).Add(gasFee, gasTipCap) cancelTx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainID, @@ -808,9 +891,6 @@ func TestTransactionCancel(t *testing.T) { } return nil }), - backendmock.WithSuggestGasPriceFunc(func(ctx context.Context) (*big.Int, error) { - return gasPrice, nil - }), backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) { return gasTip, nil }), diff --git a/pkg/transaction/wrapped/metrics.go b/pkg/transaction/wrapped/metrics.go index eeb255c3d3b..cb7ce3967c3 100644 --- a/pkg/transaction/wrapped/metrics.go +++ b/pkg/transaction/wrapped/metrics.go @@ -27,6 +27,7 @@ type metrics struct { SendTransactionCalls prometheus.Counter FilterLogsCalls prometheus.Counter ChainIDCalls prometheus.Counter + BlockByNumberCalls prometheus.Counter } func newMetrics() metrics { @@ -129,6 +130,12 @@ func newMetrics() metrics { Name: "calls_chain_id", Help: "Count of eth_chainId rpc calls", }), + BlockByNumberCalls: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: m.Namespace, + Subsystem: subsystem, + Name: "calls_block_by_number", + Help: "Count of eth_getBlockByNumber rpc calls", + }), } } diff --git a/pkg/transaction/wrapped/wrapped.go b/pkg/transaction/wrapped/wrapped.go index a0728fa1d83..87ec74da7e9 100644 --- a/pkg/transaction/wrapped/wrapped.go +++ b/pkg/transaction/wrapped/wrapped.go @@ -201,6 +201,20 @@ func (b *wrappedBackend) ChainID(ctx context.Context) (*big.Int, error) { return chainID, nil } +// BlockByNumber implements transaction.Backend. +func (b *wrappedBackend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + b.metrics.TotalRPCCalls.Inc() + b.metrics.BlockByNumberCalls.Inc() + block, err := b.backend.BlockByNumber(ctx, number) + if err != nil { + if !errors.Is(err, ethereum.NotFound) { + b.metrics.TotalRPCErrors.Inc() + } + return nil, err + } + return block, nil +} + func (b *wrappedBackend) Close() error { b.backend.Close() return nil