Skip to content

Commit

Permalink
feat(txpool): StatsWithMinBaseFee (#884)
Browse files Browse the repository at this point in the history
* feat(txpool): implement StatsWithMinBaseFee

* feat(gpo): use StatsWithMinBaseFee to filter out tx below the current blocks base fee

* feat(les/txpool): implement StatsWithMinBaseFee

* use flatten() (lowercase) to avoid copying of tx list

* chore: auto version bump [bot]

---------

Co-authored-by: jonastheis <[email protected]>
  • Loading branch information
jonastheis and jonastheis authored Jul 10, 2024
1 parent ae45e12 commit 8c7a3ae
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 2 deletions.
34 changes: 34 additions & 0 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
71 changes: 71 additions & 0 deletions core/tx_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
4 changes: 4 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
3 changes: 2 additions & 1 deletion eth/gasprice/gasprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions eth/gasprice/gasprice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions les/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
14 changes: 14 additions & 0 deletions light/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down

0 comments on commit 8c7a3ae

Please sign in to comment.