mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
core/txpool: annotate tx error if it might be accepted later
This commit is contained in:
parent
80753ba147
commit
9b21e35202
15 changed files with 296 additions and 153 deletions
|
|
@ -1391,6 +1391,8 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, txpool.ErrUnderpriced):
|
case errors.Is(err, txpool.ErrUnderpriced):
|
||||||
addUnderpricedMeter.Mark(1)
|
addUnderpricedMeter.Mark(1)
|
||||||
|
case errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
||||||
|
addUnderpricedMeter.Mark(1)
|
||||||
case errors.Is(err, core.ErrNonceTooLow):
|
case errors.Is(err, core.ErrNonceTooLow):
|
||||||
addStaleMeter.Mark(1)
|
addStaleMeter.Mark(1)
|
||||||
case errors.Is(err, core.ErrNonceTooHigh):
|
case errors.Is(err, core.ErrNonceTooHigh):
|
||||||
|
|
|
||||||
|
|
@ -1484,7 +1484,7 @@ func TestAdd(t *testing.T) {
|
||||||
{ // New account, no previous txs, nonce 0, but blob fee cap too low
|
{ // New account, no previous txs, nonce 0, but blob fee cap too low
|
||||||
from: "alice",
|
from: "alice",
|
||||||
tx: makeUnsignedTx(0, 1, 1, 0),
|
tx: makeUnsignedTx(0, 1, 1, 0),
|
||||||
err: txpool.ErrUnderpriced,
|
err: txpool.ErrTxGasPriceTooLow,
|
||||||
},
|
},
|
||||||
{ // Same as above but blob fee cap equals minimum, should be accepted
|
{ // Same as above but blob fee cap equals minimum, should be accepted
|
||||||
from: "alice",
|
from: "alice",
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@
|
||||||
|
|
||||||
package txpool
|
package txpool
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrAlreadyKnown is returned if the transactions is already contained
|
// ErrAlreadyKnown is returned if the transactions is already contained
|
||||||
|
|
@ -26,14 +28,19 @@ var (
|
||||||
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
||||||
ErrInvalidSender = errors.New("invalid sender")
|
ErrInvalidSender = errors.New("invalid sender")
|
||||||
|
|
||||||
// ErrUnderpriced is returned if a transaction's gas price is below the minimum
|
// ErrUnderpriced is returned if a transaction's gas price is too low to be
|
||||||
// configured for the transaction pool.
|
// included in the pool. If the gas price is lower than the minimum configured
|
||||||
|
// one for the transaction pool, use ErrTxGasPriceTooLow instead.
|
||||||
ErrUnderpriced = errors.New("transaction underpriced")
|
ErrUnderpriced = errors.New("transaction underpriced")
|
||||||
|
|
||||||
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
|
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
|
||||||
// with a different one without the required price bump.
|
// with a different one without the required price bump.
|
||||||
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
||||||
|
|
||||||
|
// ErrTxGasPriceTooLow is returned if a transaction's gas price is below the
|
||||||
|
// minimum configured for the transaction pool.
|
||||||
|
ErrTxGasPriceTooLow = errors.New("transaction gas price below minimum")
|
||||||
|
|
||||||
// ErrAccountLimitExceeded is returned if a transaction would exceed the number
|
// ErrAccountLimitExceeded is returned if a transaction would exceed the number
|
||||||
// allowed by a pool for a single account.
|
// allowed by a pool for a single account.
|
||||||
ErrAccountLimitExceeded = errors.New("account limit exceeded")
|
ErrAccountLimitExceeded = errors.New("account limit exceeded")
|
||||||
|
|
@ -61,3 +68,30 @@ var (
|
||||||
// transactions is reached for specific accounts.
|
// transactions is reached for specific accounts.
|
||||||
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts")
|
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrTxTemporarilyRejected represents a temporarily rejected transaction,
|
||||||
|
// typically due to a full transaction pool or other transient conditions.
|
||||||
|
// The transaction may be accepted later.
|
||||||
|
type ErrTxTemporarilyRejected struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapTxTemporarilyRejected wraps the given error in an ErrTxTemporarilyRejected type.
|
||||||
|
func WrapTxTemporarilyRejected(err error) ErrTxTemporarilyRejected {
|
||||||
|
return ErrTxTemporarilyRejected{Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e ErrTxTemporarilyRejected) Error() string {
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap allows unwrapping the underlying error.
|
||||||
|
func (e ErrTxTemporarilyRejected) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is enables errors.Is to match either the wrapper or the wrapped error.
|
||||||
|
func (e ErrTxTemporarilyRejected) Is(target error) bool {
|
||||||
|
return errors.Is(e.Err, target)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -932,6 +932,39 @@ func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error {
|
||||||
return pool.Add([]*types.Transaction{tx}, true)[0]
|
return pool.Add([]*types.Transaction{tx}, true)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// annotateErrors processes the provided errors and annotate them with a
|
||||||
|
// txpool.ErrTxTemporarilyRejected wrapper, reflecting the transactions
|
||||||
|
// are temporarily rejected with some reasons but may be accepted later.
|
||||||
|
func annotateErrors(errs []error) []error {
|
||||||
|
var annotated []error
|
||||||
|
for _, err := range errs {
|
||||||
|
switch {
|
||||||
|
/*
|
||||||
|
// These two errors are not possible in case of leagcy pool
|
||||||
|
case errors.Is(err, core.ErrNonceTooHigh):
|
||||||
|
annotated = append(annotated, txpool.WrapTxTemporarilyRejected(err))
|
||||||
|
case errors.Is(err, txpool.ErrAccountLimitExceeded):
|
||||||
|
annotated = append(annotated, txpool.WrapTxTemporarilyRejected(err))
|
||||||
|
*/
|
||||||
|
case errors.Is(err, ErrOutOfOrderTxFromDelegated):
|
||||||
|
annotated = append(annotated, txpool.WrapTxTemporarilyRejected(err))
|
||||||
|
case errors.Is(err, txpool.ErrInflightTxLimitReached):
|
||||||
|
annotated = append(annotated, txpool.WrapTxTemporarilyRejected(err))
|
||||||
|
case errors.Is(err, ErrAuthorityReserved):
|
||||||
|
annotated = append(annotated, txpool.WrapTxTemporarilyRejected(err))
|
||||||
|
case errors.Is(err, txpool.ErrUnderpriced):
|
||||||
|
annotated = append(annotated, txpool.WrapTxTemporarilyRejected(err))
|
||||||
|
case errors.Is(err, ErrTxPoolOverflow):
|
||||||
|
annotated = append(annotated, txpool.WrapTxTemporarilyRejected(err))
|
||||||
|
case errors.Is(err, ErrFutureReplacePending):
|
||||||
|
annotated = append(annotated, txpool.WrapTxTemporarilyRejected(err))
|
||||||
|
default:
|
||||||
|
annotated = append(annotated, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return annotated
|
||||||
|
}
|
||||||
|
|
||||||
// Add enqueues a batch of transactions into the pool if they are valid.
|
// Add enqueues a batch of transactions into the pool if they are valid.
|
||||||
//
|
//
|
||||||
// Note, if sync is set the method will block until all internal maintenance
|
// Note, if sync is set the method will block until all internal maintenance
|
||||||
|
|
@ -983,7 +1016,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
if sync {
|
if sync {
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
return errs
|
return annotateErrors(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addTxsLocked attempts to queue a batch of transactions if they are valid.
|
// addTxsLocked attempts to queue a batch of transactions if they are valid.
|
||||||
|
|
|
||||||
|
|
@ -82,12 +82,14 @@ func TestTransactionFutureAttack(t *testing.T) {
|
||||||
// Create the pool to test the limit enforcement with
|
// Create the pool to test the limit enforcement with
|
||||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||||
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
|
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
|
||||||
|
|
||||||
config := testTxPoolConfig
|
config := testTxPoolConfig
|
||||||
config.GlobalQueue = 100
|
config.GlobalQueue = 100
|
||||||
config.GlobalSlots = 100
|
config.GlobalSlots = 100
|
||||||
pool := New(config, blockchain)
|
pool := New(config, blockchain)
|
||||||
pool.Init(config.PriceLimit, blockchain.CurrentBlock(), newReserver())
|
pool.Init(config.PriceLimit, blockchain.CurrentBlock(), newReserver())
|
||||||
defer pool.Close()
|
defer pool.Close()
|
||||||
|
|
||||||
fillPool(t, pool)
|
fillPool(t, pool)
|
||||||
pending, _ := pool.Stats()
|
pending, _ := pool.Stats()
|
||||||
// Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops
|
// Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops
|
||||||
|
|
@ -180,7 +182,9 @@ func TestTransactionZAttack(t *testing.T) {
|
||||||
ivPending := countInvalidPending()
|
ivPending := countInvalidPending()
|
||||||
t.Logf("invalid pending: %d\n", ivPending)
|
t.Logf("invalid pending: %d\n", ivPending)
|
||||||
|
|
||||||
// Now, DETER-Z attack starts, let's add a bunch of expensive non-executables (from N accounts) along with balance-overdraft txs (from one account), and see if the pending-count drops
|
// Now, DETER-Z attack starts, let's add a bunch of expensive non-executables
|
||||||
|
// (from N accounts) along with balance-overdraft txs (from one account), and
|
||||||
|
// see if the pending-count drops
|
||||||
for j := 0; j < int(pool.config.GlobalQueue); j++ {
|
for j := 0; j < int(pool.config.GlobalQueue); j++ {
|
||||||
futureTxs := types.Transactions{}
|
futureTxs := types.Transactions{}
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
|
|
|
||||||
|
|
@ -413,7 +413,7 @@ func TestInvalidTransactions(t *testing.T) {
|
||||||
|
|
||||||
tx = transaction(1, 100000, key)
|
tx = transaction(1, 100000, key)
|
||||||
pool.gasTip.Store(uint256.NewInt(1000))
|
pool.gasTip.Store(uint256.NewInt(1000))
|
||||||
if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
|
if err, want := pool.addRemote(tx), txpool.ErrTxGasPriceTooLow; !errors.Is(err, want) {
|
||||||
t.Errorf("want %v have %v", want, err)
|
t.Errorf("want %v have %v", want, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -484,7 +484,7 @@ func TestNegativeValue(t *testing.T) {
|
||||||
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key)
|
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key)
|
||||||
from, _ := deriveSender(tx)
|
from, _ := deriveSender(tx)
|
||||||
testAddBalance(pool, from, big.NewInt(1))
|
testAddBalance(pool, from, big.NewInt(1))
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrNegativeValue {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrNegativeValue) {
|
||||||
t.Error("expected", txpool.ErrNegativeValue, "got", err)
|
t.Error("expected", txpool.ErrNegativeValue, "got", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -497,7 +497,7 @@ func TestTipAboveFeeCap(t *testing.T) {
|
||||||
|
|
||||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key)
|
tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key)
|
||||||
|
|
||||||
if err := pool.addRemote(tx); err != core.ErrTipAboveFeeCap {
|
if err := pool.addRemote(tx); !errors.Is(err, core.ErrTipAboveFeeCap) {
|
||||||
t.Error("expected", core.ErrTipAboveFeeCap, "got", err)
|
t.Error("expected", core.ErrTipAboveFeeCap, "got", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -512,12 +512,12 @@ func TestVeryHighValues(t *testing.T) {
|
||||||
veryBigNumber.Lsh(veryBigNumber, 300)
|
veryBigNumber.Lsh(veryBigNumber, 300)
|
||||||
|
|
||||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key)
|
tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key)
|
||||||
if err := pool.addRemote(tx); err != core.ErrTipVeryHigh {
|
if err := pool.addRemote(tx); !errors.Is(err, core.ErrTipVeryHigh) {
|
||||||
t.Error("expected", core.ErrTipVeryHigh, "got", err)
|
t.Error("expected", core.ErrTipVeryHigh, "got", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key)
|
tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key)
|
||||||
if err := pool.addRemote(tx2); err != core.ErrFeeCapVeryHigh {
|
if err := pool.addRemote(tx2); !errors.Is(err, core.ErrFeeCapVeryHigh) {
|
||||||
t.Error("expected", core.ErrFeeCapVeryHigh, "got", err)
|
t.Error("expected", core.ErrFeeCapVeryHigh, "got", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1424,14 +1424,14 @@ func TestRepricing(t *testing.T) {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
// Check that we can't add the old transactions back
|
// Check that we can't add the old transactions back
|
||||||
if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
if err := validateEvents(events, 0); err != nil {
|
if err := validateEvents(events, 0); err != nil {
|
||||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||||
|
|
@ -1476,14 +1476,14 @@ func TestMinGasPriceEnforced(t *testing.T) {
|
||||||
tx := pricedTransaction(0, 100000, big.NewInt(2), key)
|
tx := pricedTransaction(0, 100000, big.NewInt(2), key)
|
||||||
pool.SetGasTip(big.NewInt(tx.GasPrice().Int64() + 1))
|
pool.SetGasTip(big.NewInt(tx.GasPrice().Int64() + 1))
|
||||||
|
|
||||||
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("Min tip not enforced")
|
t.Fatalf("Min tip not enforced")
|
||||||
}
|
}
|
||||||
|
|
||||||
tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), key)
|
tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), key)
|
||||||
pool.SetGasTip(big.NewInt(tx.GasTipCap().Int64() + 1))
|
pool.SetGasTip(big.NewInt(tx.GasTipCap().Int64() + 1))
|
||||||
|
|
||||||
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("Min tip not enforced")
|
t.Fatalf("Min tip not enforced")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1560,16 +1560,16 @@ func TestRepricingDynamicFee(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Check that we can't add the old transactions back
|
// Check that we can't add the old transactions back
|
||||||
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0])
|
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0])
|
||||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])
|
tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])
|
||||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])
|
tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])
|
||||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
if err := validateEvents(events, 0); err != nil {
|
if err := validateEvents(events, 0); err != nil {
|
||||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||||
|
|
@ -1673,7 +1673,7 @@ func TestUnderpricing(t *testing.T) {
|
||||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||||
}
|
}
|
||||||
// Ensure that replacing a pending transaction with a future transaction fails
|
// Ensure that replacing a pending transaction with a future transaction fails
|
||||||
if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != ErrFutureReplacePending {
|
if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); !errors.Is(err, ErrFutureReplacePending) {
|
||||||
t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending)
|
t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending)
|
||||||
}
|
}
|
||||||
pending, queued = pool.Stats()
|
pending, queued = pool.Stats()
|
||||||
|
|
@ -1995,7 +1995,7 @@ func TestReplacement(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil {
|
||||||
t.Fatalf("failed to add original cheap pending transaction: %v", err)
|
t.Fatalf("failed to add original cheap pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil {
|
||||||
|
|
@ -2008,7 +2008,7 @@ func TestReplacement(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil {
|
||||||
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil {
|
||||||
|
|
@ -2022,7 +2022,7 @@ func TestReplacement(t *testing.T) {
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil {
|
||||||
t.Fatalf("failed to add original cheap queued transaction: %v", err)
|
t.Fatalf("failed to add original cheap queued transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil {
|
||||||
|
|
@ -2032,7 +2032,7 @@ func TestReplacement(t *testing.T) {
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil {
|
||||||
t.Fatalf("failed to add original proper queued transaction: %v", err)
|
t.Fatalf("failed to add original proper queued transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil {
|
||||||
|
|
@ -2096,7 +2096,7 @@ func TestReplacementDynamicFee(t *testing.T) {
|
||||||
}
|
}
|
||||||
// 2. Don't bump tip or feecap => discard
|
// 2. Don't bump tip or feecap => discard
|
||||||
tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key)
|
tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 3. Bump both more than min => accept
|
// 3. Bump both more than min => accept
|
||||||
|
|
@ -2117,24 +2117,25 @@ func TestReplacementDynamicFee(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(tx); err != nil {
|
if err := pool.addRemoteSync(tx); err != nil {
|
||||||
t.Fatalf("failed to add original proper %s transaction: %v", stage, err)
|
t.Fatalf("failed to add original proper %s transaction: %v", stage, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Bump tip max allowed so it's still underpriced => discard
|
// 6. Bump tip max allowed so it's still underpriced => discard
|
||||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold-1), key)
|
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold-1), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 7. Bump fee cap max allowed so it's still underpriced => discard
|
// 7. Bump fee cap max allowed so it's still underpriced => discard
|
||||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key)
|
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 8. Bump tip min for acceptance => accept
|
// 8. Bump tip min for acceptance => accept
|
||||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold), key)
|
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 9. Bump fee cap min for acceptance => accept
|
// 9. Bump fee cap min for acceptance => accept
|
||||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key)
|
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 10. Check events match expected (3 new executable txs during pending, 0 during queue)
|
// 10. Check events match expected (3 new executable txs during pending, 0 during queue)
|
||||||
|
|
|
||||||
|
|
@ -74,32 +74,22 @@ func New(journalPath string, journalTime time.Duration, chainConfig *params.Chai
|
||||||
|
|
||||||
// Track adds a transaction to the tracked set.
|
// Track adds a transaction to the tracked set.
|
||||||
// Note: blob-type transactions are ignored.
|
// Note: blob-type transactions are ignored.
|
||||||
func (tracker *TxTracker) Track(tx *types.Transaction) error {
|
func (tracker *TxTracker) Track(tx *types.Transaction) {
|
||||||
return tracker.TrackAll([]*types.Transaction{tx})[0]
|
tracker.TrackAll([]*types.Transaction{tx})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrackAll adds a list of transactions to the tracked set.
|
// TrackAll adds a list of transactions to the tracked set.
|
||||||
// Note: blob-type transactions are ignored.
|
// Note: blob-type transactions are ignored.
|
||||||
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
|
||||||
tracker.mu.Lock()
|
tracker.mu.Lock()
|
||||||
defer tracker.mu.Unlock()
|
defer tracker.mu.Unlock()
|
||||||
|
|
||||||
var errors []error
|
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
if tx.Type() == types.BlobTxType {
|
if tx.Type() == types.BlobTxType {
|
||||||
errors = append(errors, nil)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Ignore the transactions which are failed for fundamental
|
|
||||||
// validation such as invalid parameters.
|
|
||||||
if err := tracker.pool.ValidateTxBasics(tx); err != nil {
|
|
||||||
log.Debug("Invalid transaction submitted", "hash", tx.Hash(), "err", err)
|
|
||||||
errors = append(errors, err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If we're already tracking it, it's a no-op
|
// If we're already tracking it, it's a no-op
|
||||||
if _, ok := tracker.all[tx.Hash()]; ok {
|
if _, ok := tracker.all[tx.Hash()]; ok {
|
||||||
errors = append(errors, nil)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Theoretically, checking the error here is unnecessary since sender recovery
|
// Theoretically, checking the error here is unnecessary since sender recovery
|
||||||
|
|
@ -108,11 +98,8 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
||||||
// Therefore, the error is still checked just in case.
|
// Therefore, the error is still checked just in case.
|
||||||
addr, err := types.Sender(tracker.signer, tx)
|
addr, err := types.Sender(tracker.signer, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
errors = append(errors, nil)
|
|
||||||
|
|
||||||
tracker.all[tx.Hash()] = tx
|
tracker.all[tx.Hash()] = tx
|
||||||
if tracker.byAddr[addr] == nil {
|
if tracker.byAddr[addr] == nil {
|
||||||
tracker.byAddr[addr] = legacypool.NewSortedMap()
|
tracker.byAddr[addr] = legacypool.NewSortedMap()
|
||||||
|
|
@ -124,7 +111,6 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localGauge.Update(int64(len(tracker.all)))
|
localGauge.Update(int64(len(tracker.all)))
|
||||||
return errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recheck checks and returns any transactions that needs to be resubmitted.
|
// recheck checks and returns any transactions that needs to be resubmitted.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package locals
|
package locals
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -91,10 +90,12 @@ func (env *testEnv) close() {
|
||||||
env.chain.Stop()
|
env.chain.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:unused
|
||||||
func (env *testEnv) setGasTip(gasTip uint64) {
|
func (env *testEnv) setGasTip(gasTip uint64) {
|
||||||
env.pool.SetGasTip(new(big.Int).SetUint64(gasTip))
|
env.pool.SetGasTip(new(big.Int).SetUint64(gasTip))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:unused
|
||||||
func (env *testEnv) makeTx(nonce uint64, gasPrice *big.Int) *types.Transaction {
|
func (env *testEnv) makeTx(nonce uint64, gasPrice *big.Int) *types.Transaction {
|
||||||
if nonce == 0 {
|
if nonce == 0 {
|
||||||
head := env.chain.CurrentHeader()
|
head := env.chain.CurrentHeader()
|
||||||
|
|
@ -121,6 +122,7 @@ func (env *testEnv) makeTxs(n int) []*types.Transaction {
|
||||||
return txs
|
return txs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:unused
|
||||||
func (env *testEnv) commit() {
|
func (env *testEnv) commit() {
|
||||||
head := env.chain.CurrentBlock()
|
head := env.chain.CurrentBlock()
|
||||||
block := env.chain.GetBlock(head.Hash(), head.Number.Uint64())
|
block := env.chain.GetBlock(head.Hash(), head.Number.Uint64())
|
||||||
|
|
@ -137,60 +139,6 @@ func (env *testEnv) commit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRejectInvalids(t *testing.T) {
|
|
||||||
env := newTestEnv(t, 10, 0, "")
|
|
||||||
defer env.close()
|
|
||||||
|
|
||||||
var cases = []struct {
|
|
||||||
gasTip uint64
|
|
||||||
tx *types.Transaction
|
|
||||||
expErr error
|
|
||||||
commit bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
tx: env.makeTx(5, nil), // stale
|
|
||||||
expErr: core.ErrNonceTooLow,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tx: env.makeTx(11, nil), // future transaction
|
|
||||||
expErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gasTip: params.GWei,
|
|
||||||
tx: env.makeTx(0, new(big.Int).SetUint64(params.GWei/2)), // low price
|
|
||||||
expErr: txpool.ErrUnderpriced,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tx: types.NewTransaction(10, common.Address{0x00}, big.NewInt(1000), params.TxGas, big.NewInt(params.GWei), nil), // invalid signature
|
|
||||||
expErr: types.ErrInvalidSig,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
commit: true,
|
|
||||||
tx: env.makeTx(10, nil), // stale
|
|
||||||
expErr: core.ErrNonceTooLow,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tx: env.makeTx(11, nil),
|
|
||||||
expErr: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, c := range cases {
|
|
||||||
if c.gasTip != 0 {
|
|
||||||
env.setGasTip(c.gasTip)
|
|
||||||
}
|
|
||||||
if c.commit {
|
|
||||||
env.commit()
|
|
||||||
}
|
|
||||||
gotErr := env.tracker.Track(c.tx)
|
|
||||||
if c.expErr == nil && gotErr != nil {
|
|
||||||
t.Fatalf("%d, unexpected error: %v", i, gotErr)
|
|
||||||
}
|
|
||||||
if c.expErr != nil && !errors.Is(gotErr, c.expErr) {
|
|
||||||
t.Fatalf("%d, unexpected error, want: %v, got: %v", i, c.expErr, gotErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResubmit(t *testing.T) {
|
func TestResubmit(t *testing.T) {
|
||||||
env := newTestEnv(t, 10, 0, "")
|
env := newTestEnv(t, 10, 0, "")
|
||||||
defer env.close()
|
defer env.close()
|
||||||
|
|
|
||||||
|
|
@ -322,31 +322,6 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
|
||||||
// rules, but does not check state-dependent validation such as sufficient balance.
|
|
||||||
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
|
|
||||||
addr, err := types.Sender(p.signer, tx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Reject transactions with stale nonce. Gapped-nonce future transactions
|
|
||||||
// are considered valid and will be handled by the subpool according to its
|
|
||||||
// internal policy.
|
|
||||||
p.stateLock.RLock()
|
|
||||||
nonce := p.state.GetNonce(addr)
|
|
||||||
p.stateLock.RUnlock()
|
|
||||||
|
|
||||||
if nonce > tx.Nonce() {
|
|
||||||
return core.ErrNonceTooLow
|
|
||||||
}
|
|
||||||
for _, subpool := range p.subpools {
|
|
||||||
if subpool.Filter(tx) {
|
|
||||||
return subpool.ValidateTxBasics(tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: received type %d", core.ErrTxTypeNotSupported, tx.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
||||||
// to the large transaction churn, add may postpone fully integrating the tx
|
// to the large transaction churn, add may postpone fully integrating the tx
|
||||||
// to a later point to batch multiple ones together.
|
// to a later point to batch multiple ones together.
|
||||||
|
|
|
||||||
|
|
@ -131,12 +131,12 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
||||||
}
|
}
|
||||||
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
||||||
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||||
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrUnderpriced, tx.GasTipCap(), opts.MinTip)
|
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
||||||
}
|
}
|
||||||
if tx.Type() == types.BlobTxType {
|
if tx.Type() == types.BlobTxType {
|
||||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||||
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
||||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrUnderpriced, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
||||||
}
|
}
|
||||||
sidecar := tx.BlobTxSidecar()
|
sidecar := tx.BlobTxSidecar()
|
||||||
if sidecar == nil {
|
if sidecar == nil {
|
||||||
|
|
|
||||||
|
|
@ -307,19 +307,24 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
||||||
locals := b.eth.localTxTracker
|
|
||||||
if locals != nil {
|
|
||||||
if err := locals.Track(signedTx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No error will be returned to user if the transaction fails stateful
|
|
||||||
// validation (e.g., no available slot), as the locally submitted transactions
|
|
||||||
// may be resubmitted later via the local tracker.
|
|
||||||
err := b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]
|
err := b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]
|
||||||
if err != nil && locals == nil {
|
|
||||||
|
// If the local transaction tracker is not configured, returns whatever
|
||||||
|
// returned from the txpool.
|
||||||
|
if b.eth.localTxTracker == nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// If the transaction fails with an error indicating it is invalid, or if there is
|
||||||
|
// very little chance it will be accepted later (e.g., the gas price is below the
|
||||||
|
// configured minimum, or the sender has insufficient funds to cover the cost),
|
||||||
|
// propagate the error to the user.
|
||||||
|
if err != nil && !errors.As(err, &txpool.ErrTxTemporarilyRejected{}) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// No error will be returned to user if the transaction fails with a temporary
|
||||||
|
// error and might be accepted later (e.g., the transaction pool is full).
|
||||||
|
// Locally submitted transactions will be resubmitted later via the local tracker.
|
||||||
|
b.eth.localTxTracker.Track(signedTx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
155
eth/api_backend_test.go
Normal file
155
eth/api_backend_test.go
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package eth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/locals"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
funds = big.NewInt(1000_000_000_000_000)
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: params.MergedTestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
address: {Balance: funds},
|
||||||
|
},
|
||||||
|
Difficulty: common.Big0,
|
||||||
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
}
|
||||||
|
signer = types.LatestSignerForChainID(gspec.Config.ChainID)
|
||||||
|
)
|
||||||
|
|
||||||
|
func initBackend(t *testing.T, withLocal bool) *EthAPIBackend {
|
||||||
|
var (
|
||||||
|
// Create a database pre-initialize with a genesis block
|
||||||
|
db = rawdb.NewMemoryDatabase()
|
||||||
|
engine = beacon.New(ethash.NewFaker())
|
||||||
|
)
|
||||||
|
chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil)
|
||||||
|
|
||||||
|
txconfig := legacypool.DefaultConfig
|
||||||
|
txconfig.Journal = "" // Don't litter the disk with test journals
|
||||||
|
|
||||||
|
blobPool := blobpool.New(blobpool.Config{Datadir: t.TempDir()}, chain, nil)
|
||||||
|
legacyPool := legacypool.New(txconfig, chain)
|
||||||
|
txpool, _ := txpool.New(txconfig.PriceLimit, chain, []txpool.SubPool{legacyPool, blobPool})
|
||||||
|
|
||||||
|
eth := &Ethereum{
|
||||||
|
blockchain: chain,
|
||||||
|
txPool: txpool,
|
||||||
|
}
|
||||||
|
if withLocal {
|
||||||
|
eth.localTxTracker = locals.New("", time.Minute, gspec.Config, txpool)
|
||||||
|
}
|
||||||
|
return &EthAPIBackend{
|
||||||
|
eth: eth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTx(nonce uint64, gasPrice *big.Int, amount *big.Int, key *ecdsa.PrivateKey) *types.Transaction {
|
||||||
|
if gasPrice == nil {
|
||||||
|
gasPrice = big.NewInt(params.GWei)
|
||||||
|
}
|
||||||
|
if amount == nil {
|
||||||
|
amount = big.NewInt(1000)
|
||||||
|
}
|
||||||
|
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{0x00}, amount, params.TxGas, gasPrice, nil), signer, key)
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsignedAuth struct {
|
||||||
|
nonce uint64
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func pricedSetCodeTx(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, unsigned []unsignedAuth) *types.Transaction {
|
||||||
|
var authList []types.SetCodeAuthorization
|
||||||
|
for _, u := range unsigned {
|
||||||
|
auth, _ := types.SignSetCode(u.key, types.SetCodeAuthorization{
|
||||||
|
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
|
||||||
|
Address: common.Address{0x42},
|
||||||
|
Nonce: u.nonce,
|
||||||
|
})
|
||||||
|
authList = append(authList, auth)
|
||||||
|
}
|
||||||
|
return pricedSetCodeTxWithAuth(nonce, gaslimit, gasFee, tip, key, authList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pricedSetCodeTxWithAuth(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, authList []types.SetCodeAuthorization) *types.Transaction {
|
||||||
|
return types.MustSignNewTx(key, signer, &types.SetCodeTx{
|
||||||
|
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
|
||||||
|
Nonce: nonce,
|
||||||
|
GasTipCap: tip,
|
||||||
|
GasFeeCap: gasFee,
|
||||||
|
Gas: gaslimit,
|
||||||
|
To: common.Address{},
|
||||||
|
Value: uint256.NewInt(100),
|
||||||
|
Data: nil,
|
||||||
|
AccessList: nil,
|
||||||
|
AuthList: authList,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalTxSend(t *testing.T) {
|
||||||
|
for _, withLocal := range []bool{false, true} {
|
||||||
|
b := initBackend(t, withLocal)
|
||||||
|
|
||||||
|
txA := pricedSetCodeTx(0, 250000, uint256.NewInt(params.GWei), uint256.NewInt(params.GWei), key, []unsignedAuth{
|
||||||
|
{
|
||||||
|
nonce: 0,
|
||||||
|
key: key,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
b.SendTx(context.Background(), txA)
|
||||||
|
|
||||||
|
txB := makeTx(1, nil, nil, key)
|
||||||
|
err := b.SendTx(context.Background(), txB)
|
||||||
|
|
||||||
|
if withLocal {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error sending tx: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||||
|
t.Fatalf("Unexpected error, want: %v, got: %v", txpool.ErrInflightTxLimitReached, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -345,7 +345,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
||||||
// Track the transaction hash if the price is too low for us.
|
// Track the transaction hash if the price is too low for us.
|
||||||
// Avoid re-request this transaction when we receive another
|
// Avoid re-request this transaction when we receive another
|
||||||
// announcement.
|
// announcement.
|
||||||
if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
f.underpriced.Add(batch[j].Hash(), batch[j].Time())
|
f.underpriced.Add(batch[j].Hash(), batch[j].Time())
|
||||||
}
|
}
|
||||||
// Track a few interesting failure types
|
// Track a few interesting failure types
|
||||||
|
|
@ -355,7 +355,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
||||||
case errors.Is(err, txpool.ErrAlreadyKnown):
|
case errors.Is(err, txpool.ErrAlreadyKnown):
|
||||||
duplicate++
|
duplicate++
|
||||||
|
|
||||||
case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced):
|
case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
||||||
underpriced++
|
underpriced++
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -1244,10 +1244,12 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
|
||||||
func(txs []*types.Transaction) []error {
|
func(txs []*types.Transaction) []error {
|
||||||
errs := make([]error, len(txs))
|
errs := make([]error, len(txs))
|
||||||
for i := 0; i < len(errs); i++ {
|
for i := 0; i < len(errs); i++ {
|
||||||
if i%2 == 0 {
|
if i%3 == 0 {
|
||||||
errs[i] = txpool.ErrUnderpriced
|
errs[i] = txpool.ErrUnderpriced
|
||||||
} else {
|
} else if i%3 == 1 {
|
||||||
errs[i] = txpool.ErrReplaceUnderpriced
|
errs[i] = txpool.ErrReplaceUnderpriced
|
||||||
|
} else {
|
||||||
|
errs[i] = txpool.ErrTxGasPriceTooLow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errs
|
return errs
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/goleak"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"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/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ bind.ContractBackend = (Client)(nil)
|
var _ bind.ContractBackend = (Client)(nil)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue