Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(txpool): StatsWithMinBaseFee #884

Merged
merged 6 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Thegaram marked this conversation as resolved.
Show resolved Hide resolved
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) {
Thegaram marked this conversation as resolved.
Show resolved Hide resolved
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 1 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
Loading