mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
Merge branch 'master' into upgrade_filter_system
This commit is contained in:
commit
4ffc292bd4
25 changed files with 460 additions and 189 deletions
|
|
@ -21,6 +21,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
|
@ -100,6 +101,10 @@ var (
|
|||
errInvalidNewChain = errors.New("invalid new chain")
|
||||
)
|
||||
|
||||
var (
|
||||
forkReadyInterval = 3 * time.Minute
|
||||
)
|
||||
|
||||
const (
|
||||
bodyCacheLimit = 256
|
||||
blockCacheLimit = 256
|
||||
|
|
@ -275,6 +280,8 @@ type BlockChain struct {
|
|||
processor Processor // Block transaction processor interface
|
||||
vmConfig vm.Config
|
||||
logger *tracing.Hooks
|
||||
|
||||
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
|
||||
}
|
||||
|
||||
// NewBlockChain returns a fully initialised block chain using information
|
||||
|
|
@ -514,19 +521,33 @@ func (bc *BlockChain) loadLastState() error {
|
|||
log.Warn("Empty database, resetting chain")
|
||||
return bc.Reset()
|
||||
}
|
||||
// Make sure the entire head block is available
|
||||
headBlock := bc.GetBlockByHash(head)
|
||||
headHeader := bc.GetHeaderByHash(head)
|
||||
if headHeader == nil {
|
||||
// Corrupt or empty database, init from scratch
|
||||
log.Warn("Head header missing, resetting chain", "hash", head)
|
||||
return bc.Reset()
|
||||
}
|
||||
|
||||
var headBlock *types.Block
|
||||
if cmp := headHeader.Number.Cmp(new(big.Int)); cmp == 1 {
|
||||
// Make sure the entire head block is available.
|
||||
headBlock = bc.GetBlockByHash(head)
|
||||
} else if cmp == 0 {
|
||||
// On a pruned node the block body might not be available. But a pruned
|
||||
// block should never be the head block. The only exception is when, as
|
||||
// a last resort, chain is reset to genesis.
|
||||
headBlock = bc.genesisBlock
|
||||
}
|
||||
if headBlock == nil {
|
||||
// Corrupt or empty database, init from scratch
|
||||
log.Warn("Head block missing, resetting chain", "hash", head)
|
||||
return bc.Reset()
|
||||
}
|
||||
// Everything seems to be fine, set as the head block
|
||||
bc.currentBlock.Store(headBlock.Header())
|
||||
bc.currentBlock.Store(headHeader)
|
||||
headBlockGauge.Update(int64(headBlock.NumberU64()))
|
||||
|
||||
// Restore the last known head header
|
||||
headHeader := headBlock.Header()
|
||||
if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) {
|
||||
if header := bc.GetHeaderByHash(head); header != nil {
|
||||
headHeader = header
|
||||
|
|
@ -620,7 +641,7 @@ func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
|
|||
if predefinedPoint == nil {
|
||||
log.Error("Chain history pruning is not supported for this network", "genesis", bc.genesisBlock.Hash())
|
||||
return fmt.Errorf("history pruning requested for unknown network")
|
||||
} else if freezerTail != predefinedPoint.BlockNumber {
|
||||
} else if freezerTail > 0 && freezerTail != predefinedPoint.BlockNumber {
|
||||
log.Error("Chain history database is pruned to unknown block", "tail", freezerTail)
|
||||
return fmt.Errorf("unexpected database tail")
|
||||
}
|
||||
|
|
@ -642,11 +663,15 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
|||
// Send chain head event to update the transaction pool
|
||||
header := bc.CurrentBlock()
|
||||
if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil {
|
||||
// This should never happen. In practice, previously currentBlock
|
||||
// contained the entire block whereas now only a "marker", so there
|
||||
// is an ever so slight chance for a race we should handle.
|
||||
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
|
||||
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
|
||||
// In a pruned node the genesis block will not exist in the freezer.
|
||||
// It should not happen that we set head to any other pruned block.
|
||||
if header.Number.Uint64() > 0 {
|
||||
// This should never happen. In practice, previously currentBlock
|
||||
// contained the entire block whereas now only a "marker", so there
|
||||
// is an ever so slight chance for a race we should handle.
|
||||
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
|
||||
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
|
||||
}
|
||||
}
|
||||
bc.chainHeadFeed.Send(ChainHeadEvent{Header: header})
|
||||
return nil
|
||||
|
|
@ -663,11 +688,15 @@ func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error {
|
|||
// Send chain head event to update the transaction pool
|
||||
header := bc.CurrentBlock()
|
||||
if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil {
|
||||
// This should never happen. In practice, previously currentBlock
|
||||
// contained the entire block whereas now only a "marker", so there
|
||||
// is an ever so slight chance for a race we should handle.
|
||||
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
|
||||
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
|
||||
// In a pruned node the genesis block will not exist in the freezer.
|
||||
// It should not happen that we set head to any other pruned block.
|
||||
if header.Number.Uint64() > 0 {
|
||||
// This should never happen. In practice, previously currentBlock
|
||||
// contained the entire block whereas now only a "marker", so there
|
||||
// is an ever so slight chance for a race we should handle.
|
||||
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
|
||||
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
|
||||
}
|
||||
}
|
||||
bc.chainHeadFeed.Send(ChainHeadEvent{Header: header})
|
||||
return nil
|
||||
|
|
@ -1862,6 +1891,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
|
|||
trieDiffNodes, trieBufNodes, _ := bc.triedb.Size()
|
||||
stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead)
|
||||
|
||||
// Print confirmation that a future fork is scheduled, but not yet active.
|
||||
bc.logForkReadiness(block)
|
||||
|
||||
if !setHead {
|
||||
// After merge we expect few side chains. Simply count
|
||||
// all blocks the CL gives us for GC processing time
|
||||
|
|
@ -1895,6 +1927,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
|
|||
"root", block.Root())
|
||||
}
|
||||
}
|
||||
|
||||
stats.ignored += it.remaining()
|
||||
return witness, it.index, err
|
||||
}
|
||||
|
|
@ -2492,6 +2525,23 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er
|
|||
log.Error(summarizeBadBlock(block, receipts, bc.Config(), err))
|
||||
}
|
||||
|
||||
// logForkReadiness will write a log when a future fork is scheduled, but not
|
||||
// active. This is useful so operators know their client is ready for the fork.
|
||||
func (bc *BlockChain) logForkReadiness(block *types.Block) {
|
||||
c := bc.Config()
|
||||
current, last := c.LatestFork(block.Time()), c.LatestFork(math.MaxUint64)
|
||||
t := c.Timestamp(last)
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
at := time.Unix(int64(*t), 0)
|
||||
if current < last && time.Now().After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
|
||||
log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822),
|
||||
"remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix())
|
||||
bc.lastForkReadyAlert = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
// summarizeBadBlock returns a string summarizing the bad block and other
|
||||
// relevant information.
|
||||
func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package filtermaps
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -39,6 +41,7 @@ type blockchain interface {
|
|||
// of the underlying blockchain, it should only possess the block headers
|
||||
// and receipts up until the expected chain view head.
|
||||
type ChainView struct {
|
||||
lock sync.Mutex
|
||||
chain blockchain
|
||||
headNumber uint64
|
||||
hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash
|
||||
|
|
@ -147,6 +150,9 @@ func (cv *ChainView) extendNonCanonical() bool {
|
|||
|
||||
// blockHash returns the given block hash without doing the head number check.
|
||||
func (cv *ChainView) blockHash(number uint64) common.Hash {
|
||||
cv.lock.Lock()
|
||||
defer cv.lock.Unlock()
|
||||
|
||||
if number+uint64(len(cv.hashes)) <= cv.headNumber {
|
||||
hash := cv.chain.GetCanonicalHash(number)
|
||||
if !cv.extendNonCanonical() {
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ func (f *FilterMaps) lastCanonicalSnapshotOfMap(mapIndex uint32) *renderedMap {
|
|||
var best *renderedMap
|
||||
for _, blockNumber := range f.renderSnapshots.Keys() {
|
||||
if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() &&
|
||||
blockNumber <= f.indexedView.headNumber && f.indexedView.getBlockId(blockNumber) == cp.lastBlockId &&
|
||||
blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId &&
|
||||
cp.mapIndex == mapIndex && (best == nil || blockNumber > best.lastBlock) {
|
||||
best = cp
|
||||
|
|
|
|||
|
|
@ -76,8 +76,10 @@ func TestCreation(t *testing.T) {
|
|||
{20000000, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block
|
||||
{20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block
|
||||
{30000000, 1710338134, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // Last Shanghai block
|
||||
{40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // First Cancun block
|
||||
{50000000, 2000000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // Future Cancun block
|
||||
{40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // First Cancun block
|
||||
{30000000, 1746022486, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // Last Cancun block
|
||||
{30000000, 1746612311, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}}, // First Prague block
|
||||
{50000000, 2000000000, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}}, // Future Prague block
|
||||
},
|
||||
},
|
||||
// Sepolia test cases
|
||||
|
|
@ -137,9 +139,11 @@ func TestCreation(t *testing.T) {
|
|||
// fork ID.
|
||||
func TestValidation(t *testing.T) {
|
||||
// Config that has not timestamp enabled
|
||||
// TODO(lightclient): this always needs to be updated when a mainnet timestamp is set.
|
||||
legacyConfig := *params.MainnetChainConfig
|
||||
legacyConfig.ShanghaiTime = nil
|
||||
legacyConfig.CancunTime = nil
|
||||
legacyConfig.PragueTime = nil
|
||||
|
||||
tests := []struct {
|
||||
config *params.ChainConfig
|
||||
|
|
@ -314,9 +318,7 @@ func TestValidation(t *testing.T) {
|
|||
|
||||
// Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote
|
||||
// is definitely out of sync. It may or may not need the Prague update, we don't know yet.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun **and** Prague is specced, update all the numbers
|
||||
//{params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
|
||||
{params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 1710338135}, nil},
|
||||
|
||||
// Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept.
|
||||
{params.MainnetChainConfig, 21000000, 1700000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}, nil},
|
||||
|
|
@ -324,8 +326,7 @@ func TestValidation(t *testing.T) {
|
|||
// Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local
|
||||
// out of sync. Local also knows about a future fork, but that is uncertain yet.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun **and** Prague is specced, update remote checksum
|
||||
//{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x00000000), Next: 0}, nil},
|
||||
{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks.
|
||||
// Remote needs software update.
|
||||
|
|
@ -342,11 +343,11 @@ func TestValidation(t *testing.T) {
|
|||
// Local is mainnet Shanghai, remote is random Shanghai.
|
||||
{params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Cancun, far in the future. Remote announces Gopherium (non existing fork)
|
||||
// Local is mainnet Prague, far in the future. Remote announces Gopherium (non existing fork)
|
||||
// at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible.
|
||||
//
|
||||
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
||||
{params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0x9f3d2254), Next: 8888888888}, ErrLocalIncompatibleOrStale},
|
||||
{params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0xc376cf8b), Next: 8888888888}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing
|
||||
// fork) at timestamp 1668000000, before Cancun. Local is incompatible.
|
||||
|
|
|
|||
|
|
@ -196,6 +196,19 @@ func (indexer *txIndexer) repair(head uint64) {
|
|||
}
|
||||
}
|
||||
|
||||
// resolveHead resolves the block number of the current chain head.
|
||||
func (indexer *txIndexer) resolveHead() uint64 {
|
||||
headBlockHash := rawdb.ReadHeadBlockHash(indexer.db)
|
||||
if headBlockHash == (common.Hash{}) {
|
||||
return 0
|
||||
}
|
||||
headBlockNumber := rawdb.ReadHeaderNumber(indexer.db, headBlockHash)
|
||||
if headBlockNumber == nil {
|
||||
return 0
|
||||
}
|
||||
return *headBlockNumber
|
||||
}
|
||||
|
||||
// loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending
|
||||
// on the received chain event.
|
||||
func (indexer *txIndexer) loop(chain *BlockChain) {
|
||||
|
|
@ -203,9 +216,9 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
|
|||
|
||||
// Listening to chain events and manipulate the transaction indexes.
|
||||
var (
|
||||
stop chan struct{} // Non-nil if background routine is active
|
||||
done chan struct{} // Non-nil if background routine is active
|
||||
head = rawdb.ReadHeadBlock(indexer.db).NumberU64() // The latest announced chain head
|
||||
stop chan struct{} // Non-nil if background routine is active
|
||||
done chan struct{} // Non-nil if background routine is active
|
||||
head = indexer.resolveHead() // The latest announced chain head
|
||||
|
||||
headCh = make(chan ChainHeadEvent)
|
||||
sub = chain.SubscribeChainHeadEvent(headCh)
|
||||
|
|
|
|||
|
|
@ -1391,6 +1391,8 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
|||
switch {
|
||||
case errors.Is(err, txpool.ErrUnderpriced):
|
||||
addUnderpricedMeter.Mark(1)
|
||||
case errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
||||
addUnderpricedMeter.Mark(1)
|
||||
case errors.Is(err, core.ErrNonceTooLow):
|
||||
addStaleMeter.Mark(1)
|
||||
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
|
||||
from: "alice",
|
||||
tx: makeUnsignedTx(0, 1, 1, 0),
|
||||
err: txpool.ErrUnderpriced,
|
||||
err: txpool.ErrTxGasPriceTooLow,
|
||||
},
|
||||
{ // Same as above but blob fee cap equals minimum, should be accepted
|
||||
from: "alice",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
package txpool
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// 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 = errors.New("invalid sender")
|
||||
|
||||
// ErrUnderpriced is returned if a transaction's gas price is below the minimum
|
||||
// configured for the transaction pool.
|
||||
// ErrUnderpriced is returned if a transaction's gas price is too low to be
|
||||
// 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")
|
||||
|
||||
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
|
||||
// with a different one without the required price bump.
|
||||
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
|
||||
// allowed by a pool for a single account.
|
||||
ErrAccountLimitExceeded = errors.New("account limit exceeded")
|
||||
|
|
|
|||
|
|
@ -1827,6 +1827,16 @@ func (t *lookup) Remove(hash common.Hash) {
|
|||
delete(t.txs, hash)
|
||||
}
|
||||
|
||||
// Clear resets the lookup structure, removing all stored entries.
|
||||
func (t *lookup) Clear() {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.slots = 0
|
||||
t.txs = make(map[common.Hash]*types.Transaction)
|
||||
t.auths = make(map[common.Address][]common.Hash)
|
||||
}
|
||||
|
||||
// TxsBelowTip finds all remote transactions below the given tip threshold.
|
||||
func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions {
|
||||
found := make(types.Transactions, 0, 128)
|
||||
|
|
@ -1923,7 +1933,7 @@ func (pool *LegacyPool) Clear() {
|
|||
for addr := range pool.queue {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
pool.all = newLookup()
|
||||
pool.all.Clear()
|
||||
pool.priced = newPricedList(pool.all)
|
||||
pool.pending = make(map[common.Address]*list)
|
||||
pool.queue = make(map[common.Address]*list)
|
||||
|
|
|
|||
|
|
@ -82,12 +82,14 @@ func TestTransactionFutureAttack(t *testing.T) {
|
|||
// Create the pool to test the limit enforcement with
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
|
||||
|
||||
config := testTxPoolConfig
|
||||
config.GlobalQueue = 100
|
||||
config.GlobalSlots = 100
|
||||
pool := New(config, blockchain)
|
||||
pool.Init(config.PriceLimit, blockchain.CurrentBlock(), newReserver())
|
||||
defer pool.Close()
|
||||
|
||||
fillPool(t, pool)
|
||||
pending, _ := pool.Stats()
|
||||
// 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()
|
||||
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++ {
|
||||
futureTxs := types.Transactions{}
|
||||
key, _ := crypto.GenerateKey()
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ func TestInvalidTransactions(t *testing.T) {
|
|||
|
||||
tx = transaction(1, 100000, key)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
from, _ := deriveSender(tx)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -497,7 +497,7 @@ func TestTipAboveFeeCap(t *testing.T) {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -512,12 +512,12 @@ func TestVeryHighValues(t *testing.T) {
|
|||
veryBigNumber.Lsh(veryBigNumber, 300)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1424,14 +1424,14 @@ func TestRepricing(t *testing.T) {
|
|||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// 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) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", 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.ErrTxGasPriceTooLow)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", 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.ErrTxGasPriceTooLow)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", 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.ErrTxGasPriceTooLow)
|
||||
}
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
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)
|
||||
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")
|
||||
}
|
||||
|
||||
tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), key)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
@ -1560,16 +1560,16 @@ func TestRepricingDynamicFee(t *testing.T) {
|
|||
}
|
||||
// Check that we can't add the old transactions back
|
||||
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0])
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", 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.ErrTxGasPriceTooLow)
|
||||
}
|
||||
tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", 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.ErrTxGasPriceTooLow)
|
||||
}
|
||||
tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", 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.ErrTxGasPriceTooLow)
|
||||
}
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
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)
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
// 3. Bump both more than min => accept
|
||||
|
|
@ -2117,24 +2117,25 @@ func TestReplacementDynamicFee(t *testing.T) {
|
|||
if err := pool.addRemoteSync(tx); err != nil {
|
||||
t.Fatalf("failed to add original proper %s transaction: %v", stage, err)
|
||||
}
|
||||
|
||||
// 6. Bump tip max allowed so it's still underpriced => discard
|
||||
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)
|
||||
}
|
||||
// 7. Bump fee cap max allowed so it's still underpriced => discard
|
||||
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)
|
||||
}
|
||||
// 8. Bump tip min for acceptance => accept
|
||||
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)
|
||||
}
|
||||
// 9. Bump fee cap min for acceptance => accept
|
||||
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)
|
||||
}
|
||||
// 10. Check events match expected (3 new executable txs during pending, 0 during queue)
|
||||
|
|
|
|||
46
core/txpool/locals/errors.go
Normal file
46
core/txpool/locals/errors.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// 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 locals
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||
)
|
||||
|
||||
// IsTemporaryReject determines whether the given error indicates a temporary
|
||||
// reason to reject a transaction from being included in the txpool. The result
|
||||
// may change if the txpool's state changes later.
|
||||
func IsTemporaryReject(err error) bool {
|
||||
switch {
|
||||
case errors.Is(err, legacypool.ErrOutOfOrderTxFromDelegated):
|
||||
return true
|
||||
case errors.Is(err, txpool.ErrInflightTxLimitReached):
|
||||
return true
|
||||
case errors.Is(err, legacypool.ErrAuthorityReserved):
|
||||
return true
|
||||
case errors.Is(err, txpool.ErrUnderpriced):
|
||||
return true
|
||||
case errors.Is(err, legacypool.ErrTxPoolOverflow):
|
||||
return true
|
||||
case errors.Is(err, legacypool.ErrFutureReplacePending):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -74,32 +74,22 @@ func New(journalPath string, journalTime time.Duration, chainConfig *params.Chai
|
|||
|
||||
// Track adds a transaction to the tracked set.
|
||||
// Note: blob-type transactions are ignored.
|
||||
func (tracker *TxTracker) Track(tx *types.Transaction) error {
|
||||
return tracker.TrackAll([]*types.Transaction{tx})[0]
|
||||
func (tracker *TxTracker) Track(tx *types.Transaction) {
|
||||
tracker.TrackAll([]*types.Transaction{tx})
|
||||
}
|
||||
|
||||
// TrackAll adds a list of transactions to the tracked set.
|
||||
// Note: blob-type transactions are ignored.
|
||||
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
||||
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
|
||||
var errors []error
|
||||
for _, tx := range txs {
|
||||
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
|
||||
}
|
||||
// If we're already tracking it, it's a no-op
|
||||
if _, ok := tracker.all[tx.Hash()]; ok {
|
||||
errors = append(errors, nil)
|
||||
continue
|
||||
}
|
||||
// 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.
|
||||
addr, err := types.Sender(tracker.signer, tx)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
errors = append(errors, nil)
|
||||
|
||||
tracker.all[tx.Hash()] = tx
|
||||
if tracker.byAddr[addr] == nil {
|
||||
tracker.byAddr[addr] = legacypool.NewSortedMap()
|
||||
|
|
@ -124,7 +111,6 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
|||
}
|
||||
}
|
||||
localGauge.Update(int64(len(tracker.all)))
|
||||
return errors
|
||||
}
|
||||
|
||||
// recheck checks and returns any transactions that needs to be resubmitted.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package locals
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -91,10 +90,12 @@ func (env *testEnv) close() {
|
|||
env.chain.Stop()
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func (env *testEnv) setGasTip(gasTip uint64) {
|
||||
env.pool.SetGasTip(new(big.Int).SetUint64(gasTip))
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func (env *testEnv) makeTx(nonce uint64, gasPrice *big.Int) *types.Transaction {
|
||||
if nonce == 0 {
|
||||
head := env.chain.CurrentHeader()
|
||||
|
|
@ -121,6 +122,7 @@ func (env *testEnv) makeTxs(n int) []*types.Transaction {
|
|||
return txs
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func (env *testEnv) commit() {
|
||||
head := env.chain.CurrentBlock()
|
||||
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) {
|
||||
env := newTestEnv(t, 10, 0, "")
|
||||
defer env.close()
|
||||
|
|
|
|||
|
|
@ -186,13 +186,15 @@ func (p *TxPool) loop(head *types.Header) {
|
|||
// Try to inject a busy marker and start a reset if successful
|
||||
select {
|
||||
case resetBusy <- struct{}{}:
|
||||
statedb, err := p.chain.StateAt(newHead.Root)
|
||||
if err != nil {
|
||||
log.Crit("Failed to reset txpool state", "err", err)
|
||||
// Updates the statedb with the new chain head. The head state may be
|
||||
// unavailable if the initial state sync has not yet completed.
|
||||
if statedb, err := p.chain.StateAt(newHead.Root); err != nil {
|
||||
log.Error("Failed to reset txpool state", "err", err)
|
||||
} else {
|
||||
p.stateLock.Lock()
|
||||
p.state = statedb
|
||||
p.stateLock.Unlock()
|
||||
}
|
||||
p.stateLock.Lock()
|
||||
p.state = statedb
|
||||
p.stateLock.Unlock()
|
||||
|
||||
// Busy marker injected, start a new subpool reset
|
||||
go func(oldHead, newHead *types.Header) {
|
||||
|
|
@ -322,31 +324,6 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
|
|||
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
|
||||
// to the large transaction churn, add may postpone fully integrating the tx
|
||||
// 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
|
||||
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 {
|
||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||
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()
|
||||
if sidecar == nil {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"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/eth/gasprice"
|
||||
|
|
@ -307,19 +308,24 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
|
|||
}
|
||||
|
||||
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]
|
||||
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
|
||||
}
|
||||
// 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 && !locals.IsTemporaryReject(err) {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
157
eth/api_backend_test.go
Normal file
157
eth/api_backend_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// 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(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: ""}, 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 TestSendTx(t *testing.T) {
|
||||
testSendTx(t, false)
|
||||
testSendTx(t, true)
|
||||
}
|
||||
|
||||
func testSendTx(t *testing.T, withLocal bool) {
|
||||
b := initBackend(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -124,9 +125,13 @@ func NewSimulatedBeacon(period uint64, feeRecipient common.Address, eth *eth.Eth
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// cap the dev mode period to a reasonable maximum value to avoid
|
||||
// overflowing the time.Duration (int64) that it will occupy
|
||||
const maxPeriod = uint64(math.MaxInt64 / time.Second)
|
||||
return &SimulatedBeacon{
|
||||
eth: eth,
|
||||
period: period,
|
||||
period: min(period, maxPeriod),
|
||||
shutdownCh: make(chan struct{}),
|
||||
engineAPI: engineAPI,
|
||||
lastBlockTime: block.Time,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
// Avoid re-request this transaction when we receive another
|
||||
// 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())
|
||||
}
|
||||
// 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):
|
||||
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++
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1244,10 +1244,12 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
|
|||
func(txs []*types.Transaction) []error {
|
||||
errs := make([]error, len(txs))
|
||||
for i := 0; i < len(errs); i++ {
|
||||
if i%2 == 0 {
|
||||
if i%3 == 0 {
|
||||
errs[i] = txpool.ErrUnderpriced
|
||||
} else {
|
||||
} else if i%3 == 1 {
|
||||
errs[i] = txpool.ErrReplaceUnderpriced
|
||||
} else {
|
||||
errs[i] = txpool.ErrTxGasPriceTooLow
|
||||
}
|
||||
}
|
||||
return errs
|
||||
|
|
|
|||
|
|
@ -484,7 +484,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
|
|||
total := new(big.Int).Exp(direct, big.NewInt(2), nil) // Stabilise total peer count a bit based on sqrt peers
|
||||
|
||||
var (
|
||||
signer = types.LatestSignerForChainID(h.chain.Config().ChainID) // Don't care about chain status, we just need *a* sender
|
||||
signer = types.LatestSigner(h.chain.Config()) // Don't care about chain status, we just need *a* sender
|
||||
hasher = crypto.NewKeccakState()
|
||||
hash = make([]byte, 32)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,16 +25,14 @@ import (
|
|||
"testing"
|
||||
"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/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
var _ bind.ContractBackend = (Client)(nil)
|
||||
|
|
|
|||
|
|
@ -60,10 +60,12 @@ var (
|
|||
TerminalTotalDifficulty: MainnetTerminalTotalDifficulty, // 58_750_000_000_000_000_000_000
|
||||
ShanghaiTime: newUint64(1681338455),
|
||||
CancunTime: newUint64(1710338135),
|
||||
PragueTime: newUint64(1746612311),
|
||||
DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"),
|
||||
Ethash: new(EthashConfig),
|
||||
BlobScheduleConfig: &BlobScheduleConfig{
|
||||
Cancun: DefaultCancunBlobConfig,
|
||||
Prague: DefaultPragueBlobConfig,
|
||||
},
|
||||
}
|
||||
// HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network.
|
||||
|
|
@ -907,6 +909,23 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
|
|||
}
|
||||
}
|
||||
|
||||
// Timestamp returns the timestamp associated with the fork or returns nil if
|
||||
// the fork isn't defined or isn't a time-based fork.
|
||||
func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
|
||||
switch {
|
||||
case fork == forks.Osaka:
|
||||
return c.OsakaTime
|
||||
case fork == forks.Prague:
|
||||
return c.PragueTime
|
||||
case fork == forks.Cancun:
|
||||
return c.CancunTime
|
||||
case fork == forks.Shanghai:
|
||||
return c.ShanghaiTime
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
|
||||
// rescheduled to block s2 because head is already past the fork.
|
||||
func isForkBlockIncompatible(s1, s2, head *big.Int) bool {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ package forks
|
|||
type Fork int
|
||||
|
||||
const (
|
||||
Frontier = iota
|
||||
Frontier Fork = iota
|
||||
FrontierThawing
|
||||
Homestead
|
||||
DAO
|
||||
|
|
@ -41,3 +41,35 @@ const (
|
|||
Prague
|
||||
Osaka
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (f Fork) String() string {
|
||||
s, ok := forkToString[f]
|
||||
if !ok {
|
||||
return "Unknown fork"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var forkToString = map[Fork]string{
|
||||
Frontier: "Frontier",
|
||||
FrontierThawing: "Frontier Thawing",
|
||||
Homestead: "Homestead",
|
||||
DAO: "DAO",
|
||||
TangerineWhistle: "Tangerine Whistle",
|
||||
SpuriousDragon: "Spurious Dragon",
|
||||
Byzantium: "Byzantium",
|
||||
Constantinople: "Constantinople",
|
||||
Petersburg: "Petersburg",
|
||||
Istanbul: "Istanbul",
|
||||
MuirGlacier: "Muir Glacier",
|
||||
Berlin: "Berlin",
|
||||
London: "London",
|
||||
ArrowGlacier: "Arrow Glacier",
|
||||
GrayGlacier: "Gray Glacier",
|
||||
Paris: "Paris",
|
||||
Shanghai: "Shanghai",
|
||||
Cancun: "Cancun",
|
||||
Prague: "Prague",
|
||||
Osaka: "Osaka",
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue