diff --git a/core/tx_pool.go b/core/tx_pool.go index e75258ccfe5b..3ff725df23ac 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -496,6 +496,40 @@ func (pool *TxPool) stats() (int, int) { return pending, queued } +// StatsWithMinBaseFee retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions greater equal minBaseFee. +func (pool *TxPool) StatsWithMinBaseFee(minBaseFee *big.Int) (int, int) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + return pool.statsWithMinBaseFee(minBaseFee) +} + +// statsWithMinBaseFee retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions greater equal minBaseFee. +func (pool *TxPool) statsWithMinBaseFee(minBaseFee *big.Int) (int, int) { + pending := 0 + for _, list := range pool.pending { + for _, tx := range list.txs.flatten() { + if _, err := tx.EffectiveGasTip(minBaseFee); err != nil { + break // basefee too low, discard rest of txs with higher nonces from the account + } + pending++ + } + } + + queued := 0 + for _, list := range pool.queue { + for _, tx := range list.txs.flatten() { + if _, err := tx.EffectiveGasTip(minBaseFee); err != nil { + break // basefee too low, discard rest of txs with higher nonces from the account + } + queued++ + } + } + return pending, queued +} + // Content retrieves the data content of the transaction pool, returning all the // pending as well as queued transactions, grouped by account and sorted by nonce. func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 1662a57f650e..463106e9a531 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -2601,3 +2601,74 @@ func TestPoolPending(t *testing.T) { maxAccounts := 10 assert.Len(t, pool.PendingWithMax(false, maxAccounts), maxAccounts) } + +func TestStatsWithMinBaseFee(t *testing.T) { + // Create the pool to test the pricing enforcement with + pool, _ := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 4) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(5), keys[0])) // will stay pending + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[0])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[0])) + + txs = append(txs, dynamicFeeTx(0, 100000, big.NewInt(5), big.NewInt(1), keys[1])) // will stay pending + txs = append(txs, dynamicFeeTx(1, 100000, big.NewInt(3), big.NewInt(2), keys[1])) // will stay pending + txs = append(txs, dynamicFeeTx(2, 100000, big.NewInt(2), big.NewInt(1), keys[1])) + txs = append(txs, dynamicFeeTx(3, 100000, big.NewInt(4), big.NewInt(1), keys[1])) + + localTx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[3]) + + // queued + txs = append(txs, dynamicFeeTx(1, 100000, big.NewInt(3), big.NewInt(2), keys[2])) // will stay queued + txs = append(txs, dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])) + txs = append(txs, dynamicFeeTx(3, 100000, big.NewInt(2), big.NewInt(2), keys[2])) + + // Import the batch and that both pending and queued transactions match up + pool.AddRemotesSync(txs) + pool.AddLocal(localTx) + + minBaseFee := big.NewInt(3) + pool.priced.SetBaseFee(minBaseFee) + + // Check pool.Stats(), all tx should be counted + { + pending, queued := pool.Stats() + if pending != 8 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 8) + } + if queued != 3 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) + } + if err := validateEvents(events, 8); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + } + + // Check pool.StatsWithMinBaseFee(), only tx with base fee >= minBaseFee should be counted + { + pending, queued := pool.StatsWithMinBaseFee(minBaseFee) + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + } +} diff --git a/eth/api_backend.go b/eth/api_backend.go index 81b70a857363..cae5e28efb79 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -292,6 +292,10 @@ func (b *EthAPIBackend) Stats() (pending int, queued int) { return b.eth.txPool.Stats() } +func (b *EthAPIBackend) StatsWithMinBaseFee(minBaseFee *big.Int) (pending int, queued int) { + return b.eth.txPool.StatsWithMinBaseFee(minBaseFee) +} + func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { return b.eth.TxPool().Content() } diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index ae314076023e..dab0e06d3200 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -64,6 +64,7 @@ type OracleBackend interface { SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription StateAt(root common.Hash) (*state.StateDB, error) Stats() (pending int, queued int) + StatsWithMinBaseFee(minBaseFee *big.Int) (pending int, queued int) } // Oracle recommends gas prices based on the content of recent @@ -192,7 +193,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { // If pending txs are less than oracle.congestedThreshold, we consider the network to be non-congested and suggest // a minimal tip cap. This is to prevent users from overpaying for gas when the network is not congested and a few // high-priced txs are causing the suggested tip cap to be high. - pendingTxCount, _ := oracle.backend.Stats() + pendingTxCount, _ := oracle.backend.StatsWithMinBaseFee(head.BaseFee) if pendingTxCount < oracle.congestedThreshold { // Before Curie (EIP-1559), we need to return the total suggested gas price. After Curie we return 2 wei as the tip cap, // as the base fee is set separately or added manually for legacy transactions. diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 7483704b244c..16a64a2eabf4 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -101,6 +101,10 @@ func (b *testBackend) Stats() (int, int) { return b.pendingTxCount, 0 } +func (b *testBackend) StatsWithMinBaseFee(minBaseFee *big.Int) (int, int) { + return b.pendingTxCount, 0 +} + func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool, pendingTxCount int) *testBackend { var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") diff --git a/les/api_backend.go b/les/api_backend.go index 8c019cf3c3cc..0a506f6c6d45 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -225,6 +225,10 @@ func (b *LesApiBackend) Stats() (pending int, queued int) { return b.eth.txPool.Stats(), 0 } +func (b *LesApiBackend) StatsWithMinBaseFee(minBaseFee *big.Int) (pending int, queued int) { + return b.eth.txPool.StatsWithMinBaseFee(minBaseFee), 0 +} + func (b *LesApiBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { return b.eth.txPool.Content() } diff --git a/light/txpool.go b/light/txpool.go index ca3158a78dc9..2858178ddfe5 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -353,6 +353,20 @@ func (pool *TxPool) Stats() (pending int) { return } +// StatsWithMinBaseFee returns the number of currently pending (locally created) transactions and ignores the base fee. +func (pool *TxPool) StatsWithMinBaseFee(minBaseFee *big.Int) (pending int) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + for _, tx := range pool.pending { + if _, err := tx.EffectiveGasTip(minBaseFee); err == nil { + pending++ + } + } + + return pending +} + // validateTx checks whether a transaction is valid according to the consensus rules. func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error { // Validate sender diff --git a/params/version.go b/params/version.go index bdd5573ebe40..4c0bff1aad0a 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 5 // Minor version component of the current release - VersionPatch = 7 // Patch version component of the current release + VersionPatch = 8 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string )