diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 3ed698c1b18f..ba6308b4bbfa 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1446,11 +1446,15 @@ func (p *BlobPool) drop() { // // The transactions can also be pre-filtered by the dynamic fee components to // reduce allocations and load on downstream subsystems. -func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction { +func (p *BlobPool) Pending(filter txpool.PendingFilter) txpool.Pending { // If only plain transactions are requested, this pool is unsuitable as it // contains none, don't even bother. if filter.OnlyPlainTxs { - return nil + return txpool.EmptyPending + } + if filter.OnlyLocals { + // There is no notion of local accounts in the blob pool. + return txpool.EmptyPending } // Track the amount of time waiting to retrieve the list of pending blob txs // from the pool and the amount of time actually spent on assembling the data. @@ -1466,19 +1470,30 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx pendtimeHist.Update(time.Since(execStart).Nanoseconds()) }() - pending := make(map[common.Address][]*txpool.LazyTransaction, len(p.index)) + var ( + baseFee = new(uint256.Int) + heads = make(txpool.TipList, 0, len(p.index)) + tails = make(map[common.Address][]*txpool.LazyTransaction, len(p.index)) + ) + if filter.BaseFee != nil { + baseFee = filter.BaseFee + } for addr, txs := range p.index { - lazies := make([]*txpool.LazyTransaction, 0, len(txs)) - for _, tx := range txs { - // If transaction filtering was requested, discard badly priced ones - if filter.MinTip != nil && filter.BaseFee != nil { + var ( + tail []*txpool.LazyTransaction + ) + for i, tx := range txs { + var tip *uint256.Int + if filter.BaseFee != nil { if tx.execFeeCap.Lt(filter.BaseFee) { break // basefee too low, cannot be included, discard rest of txs from the account } - tip := new(uint256.Int).Sub(tx.execFeeCap, filter.BaseFee) - if tip.Gt(tx.execTipCap) { - tip = tx.execTipCap - } + } + tip = new(uint256.Int).Sub(tx.execFeeCap, baseFee) + if tip.Gt(tx.execTipCap) { + tip = tx.execTipCap + } + if filter.MinTip != nil { if tip.Lt(filter.MinTip) { break // allowed or remaining tip too low, cannot be included, discard rest of txs from the account } @@ -1488,22 +1503,48 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx break // blobfee too low, cannot be included, discard rest of txs from the account } } - // Transaction was accepted according to the filter, append to the pending list - lazies = append(lazies, &txpool.LazyTransaction{ - Pool: p, - Hash: tx.hash, - Time: execStart, // TODO(karalabe): Maybe save these and use that? - GasFeeCap: tx.execFeeCap, - GasTipCap: tx.execTipCap, - Gas: tx.execGas, - BlobGas: tx.blobGas, - }) + // Transaction was accepted according to the filter, append to the pending set + lazyTx := &txpool.LazyTransaction{ + Pool: p, + Hash: tx.hash, + Time: execStart, + Fees: *tip, + Gas: tx.execGas, + BlobGas: tx.blobGas, + } + if len(tail) == 0 { + tail = make([]*txpool.LazyTransaction, 0, len(txs)-i) + heads = append(heads, &txpool.TxTips{ + From: addr, + Tips: lazyTx.Fees, + Time: lazyTx.Time.UnixNano(), + }) + } + tail = append(tail, lazyTx) + } + if len(tail) > 0 { + tails[addr] = tail } - if len(lazies) > 0 { - pending[addr] = lazies + } + return txpool.NewPendingSet(heads, tails) +} + +// PendingHashes retrieves the hashes of all currently processable transactions. +// The returned list is grouped by origin account and sorted by nonce +func (p *BlobPool) PendingHashes(filter txpool.PendingFilter) []common.Hash { + if filter.OnlyPlainTxs || filter.OnlyLocals { + return nil + } + p.lock.RLock() + defer p.lock.RUnlock() + + var hashes = make([]common.Hash, 0, len(p.index)) + for _, txs := range p.index { + for _, tx := range txs { + hashes = append(hashes, tx.hash) } } - return pending + return hashes } // updateStorageMetrics retrieves a bunch of stats from the data store and pushes diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index f7644c1d0ab6..0eff2fdffc1d 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -1356,15 +1356,22 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) { // Benchmark assembling the pending b.ResetTimer() b.ReportAllocs() - + var p txpool.Pending for i := 0; i < b.N; i++ { - p := pool.Pending(txpool.PendingFilter{ + p = pool.Pending(txpool.PendingFilter{ MinTip: uint256.NewInt(1), BaseFee: chain.basefee, BlobFee: chain.blobfee, }) - if len(p) != int(capacity) { - b.Fatalf("have %d want %d", len(p), capacity) - } + } + b.StopTimer() + + count := 0 + for ltx, _ := p.Peek(); ltx != nil; ltx, _ = p.Peek() { + p.Shift() + count++ + } + if count != int(capacity) { + b.Fatalf("have %d want %d", count, capacity) } } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 4e1d26acf405..735eee425eae 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -522,57 +522,94 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction, // // The transactions can also be pre-filtered by the dynamic fee components to // reduce allocations and load on downstream subsystems. -func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction { +func (pool *LegacyPool) Pending(filter txpool.PendingFilter) txpool.Pending { // If only blob transactions are requested, this pool is unsuitable as it // contains none, don't even bother. if filter.OnlyBlobTxs { - return nil + return txpool.EmptyPending } pool.mu.Lock() defer pool.mu.Unlock() - // Convert the new uint256.Int types to the old big.Int ones used by the legacy pool var ( - minTipBig *big.Int - baseFeeBig *big.Int + baseFee = new(uint256.Int) + minTip = new(uint256.Int) + heads = make(txpool.TipList, 0, len(pool.pending)) + tails = make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) ) if filter.MinTip != nil { - minTipBig = filter.MinTip.ToBig() + minTip = filter.MinTip } if filter.BaseFee != nil { - baseFeeBig = filter.BaseFee.ToBig() + baseFee = filter.BaseFee } - pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) + baseFeeBig := baseFee.ToBig() for addr, list := range pool.pending { - txs := list.Flatten() - - // If the miner requests tip enforcement, cap the lists now - if minTipBig != nil && !pool.locals.contains(addr) { - for i, tx := range txs { - if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 { - txs = txs[:i] - break - } - } + if filter.NoLocals && pool.locals.contains(addr) { + continue } - if len(txs) > 0 { - lazies := make([]*txpool.LazyTransaction, len(txs)) - for i := 0; i < len(txs); i++ { - lazies[i] = &txpool.LazyTransaction{ - Pool: pool, - Hash: txs[i].Hash(), - Tx: txs[i], - Time: txs[i].Time(), - GasFeeCap: uint256.MustFromBig(txs[i].GasFeeCap()), - GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()), - Gas: txs[i].Gas(), - BlobGas: txs[i].BlobGas(), - } + if filter.OnlyLocals && !pool.locals.contains(addr) { + continue + } + var ( + tail []*txpool.LazyTransaction + txs = list.Flatten() + ) + for i, tx := range txs { + bigTip, err := tx.EffectiveGasTip(baseFeeBig) + if err != nil { + break // basefee too low, cannot be included, discard rest of txs from the account + } + tip := uint256.MustFromBig(bigTip) + if tip.Lt(minTip) { + break // allowed or remaining tip too low, cannot be included, discard rest of txs from the account } - pending[addr] = lazies + lazyTx := &txpool.LazyTransaction{ + Pool: pool, + Hash: txs[i].Hash(), + Tx: txs[i], + Time: txs[i].Time(), + Fees: *tip, + Gas: txs[i].Gas(), + BlobGas: txs[i].BlobGas(), + } + if len(tail) == 0 { + tail = make([]*txpool.LazyTransaction, 0, len(txs)-i) + heads = append(heads, &txpool.TxTips{ + From: addr, + Tips: lazyTx.Fees, + Time: lazyTx.Time.UnixNano(), + }) + } + tail = append(tail, lazyTx) + } + if len(tail) > 0 { + tails[addr] = tail + } + } + return txpool.NewPendingSet(heads, tails) +} + +// PendingHashes retrieves the hashes of all currently processable transactions. +// The returned list is grouped by origin account and sorted by nonce +func (pool *LegacyPool) PendingHashes(filter txpool.PendingFilter) []common.Hash { + if filter.OnlyBlobTxs { + return nil + } + pool.mu.Lock() + defer pool.mu.Unlock() + + var hashes = make([]common.Hash, 0, len(pool.pending)) + for addr, list := range pool.pending { + if filter.NoLocals && pool.locals.contains(addr) { + continue + } + if filter.OnlyLocals && !pool.locals.contains(addr) { + continue } + hashes = list.AppendHashes(hashes) } - return pending + return hashes } // Locals retrieves the accounts currently considered local by the pool. diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index 7db9c98ace63..3890d2565710 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -258,6 +258,25 @@ func (m *sortedMap) Flatten() types.Transactions { return txs } +// AppendHashes uses the flattened slice of transactions and appends the hashes +// to the destination slice. +func (m *sortedMap) AppendHashes(dst []common.Hash) []common.Hash { + m.cacheMu.Lock() + defer m.cacheMu.Unlock() + // If the sorting was not cached yet, create and cache it + if m.cache == nil { + m.cache = make(types.Transactions, 0, len(m.items)) + for _, tx := range m.items { + m.cache = append(m.cache, tx) + } + sort.Sort(types.TxByNonce(m.cache)) + } + for _, tx := range m.items { + dst = append(dst, tx.Hash()) + } + return dst +} + // LastElement returns the last element of a flattened list, thus, the // transaction with the highest nonce func (m *sortedMap) LastElement() *types.Transaction { @@ -453,6 +472,12 @@ func (l *list) Flatten() types.Transactions { return l.txs.Flatten() } +// AppendHashes flattens a nonce-sorted slice of transacstions, and appends +// the hashes to dst. The destination slice might be reallocated, and is returned. +func (l *list) AppendHashes(dst []common.Hash) []common.Hash { + return l.txs.AppendHashes(dst) +} + // LastElement returns the last element of a flattened list, thus, the // transaction with the highest nonce func (l *list) LastElement() *types.Transaction { diff --git a/core/txpool/pending.go b/core/txpool/pending.go new file mode 100644 index 000000000000..6e98ac1b87cc --- /dev/null +++ b/core/txpool/pending.go @@ -0,0 +1,154 @@ +// Copyright 2024 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 . + +package txpool + +import ( + "container/heap" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +type Pending interface { + // Shift replaces the current best head with the next one from the same account. + Shift() + // Peek returns the next transaction by price. + Peek() (*LazyTransaction, *uint256.Int) + + // Pop removes the best transaction, *not* replacing it with the next one from + // the same account. This should be used when a transaction cannot be executed + // and hence all subsequent ones should be discarded from the same account. + Pop() + + // Empty returns true if the set is empty. + Empty() bool + + // Clears the set + Clear() +} + +// PendingFilter is a collection of filter rules to allow retrieving a subset +// of transactions for announcement or mining. +// +// Note, the entries here are not arbitrary useful filters, rather each one has +// a very specific call site in mind and each one can be evaluated very cheaply +// by the pool implementations. Only add new ones that satisfy those constraints. +type PendingFilter struct { + MinTip *uint256.Int // Minimum miner tip required to include a transaction + BaseFee *uint256.Int // Minimum 1559 basefee needed to include a transaction + BlobFee *uint256.Int // Minimum 4844 blobfee needed to include a blob transaction + + OnlyPlainTxs bool // Return only plain EVM transactions (peer-join announces, block space filling) + OnlyBlobTxs bool // Return only blob transactions (block blob-space filling) + + OnlyLocals bool // Return only txs from 'local' addresses. + NoLocals bool // Remove all txs from 'local' addresses +} + +type TxTips struct { + From common.Address // sender + Tips uint256.Int // miner-fees earned by this transaction. + Time int64 // Time when the transaction was first seen +} + +type TipList []*TxTips + +func (f TipList) Len() int { + return len(f) +} + +func (f TipList) Less(i, j int) bool { + cmp := f[i].Tips.Cmp(&f[j].Tips) + if cmp == 0 { + return f[i].Time < f[j].Time + } + return cmp > 0 +} + +func (f TipList) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +func (f *TipList) Push(x any) { + *f = append(*f, x.(*TxTips)) +} + +func (f *TipList) Pop() any { + old := *f + n := len(old) + x := old[n-1] + old[n-1] = nil + *f = old[0 : n-1] + return x +} + +type pendingSet struct { + Tails map[common.Address][]*LazyTransaction // Per account nonce-sorted list of transactions + Heads TipList // Next transaction for each unique account (price heap) +} + +var EmptyPending = NewPendingSet(nil, nil) + +func NewPendingSet(heads TipList, tails map[common.Address][]*LazyTransaction) *pendingSet { + if len(heads) != 0 { + heap.Init(&heads) + } + return &pendingSet{ + Tails: tails, + Heads: heads, + } +} + +// Shift replaces the current best head with the next one from the same account. +func (ps *pendingSet) Shift() { + addr := ps.Heads[0].From + if txs, ok := ps.Tails[addr]; ok && len(txs) > 1 { + ps.Heads[0].Tips = txs[1].Fees + ps.Heads[0].Time = txs[1].Time.UnixNano() + ps.Tails[addr] = txs[1:] + heap.Fix(&ps.Heads, 0) + return + } + heap.Pop(&ps.Heads) +} + +// Peek returns the next transaction by price. +func (ps *pendingSet) Peek() (*LazyTransaction, *uint256.Int) { + if len(ps.Heads) == 0 { + return nil, nil + } + sender := ps.Heads[0].From + fees := ps.Heads[0].Tips + tx := ps.Tails[sender][0] + return tx, &fees +} + +func (ps *pendingSet) Clear() { + ps.Heads = nil + ps.Tails = nil +} + +func (ps *pendingSet) Empty() bool { + return len(ps.Heads) == 0 +} + +// Pop removes the best transaction, *not* replacing it with the next one from +// the same account. This should be used when a transaction cannot be executed +// and hence all subsequent ones should be discarded from the same account. +func (ps *pendingSet) Pop() { + heap.Pop(&ps.Heads) +} diff --git a/core/txpool/pending_test.go b/core/txpool/pending_test.go new file mode 100644 index 000000000000..fa70360dba1a --- /dev/null +++ b/core/txpool/pending_test.go @@ -0,0 +1,153 @@ +// Copyright 2024 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 . + +package txpool + +import ( + "container/heap" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/holiman/uint256" +) + +func initLists() (heads TipList, tails map[common.Address][]*LazyTransaction) { + tails = make(map[common.Address][]*LazyTransaction) + + for i := 0; i < 25; i++ { + var ( + addr = common.Address{byte(i)} + tail []*LazyTransaction + first = true + ) + for j := 0; j < 25; j++ { + tip := uint256.NewInt(uint64(100*i + 50 - j)) + lazyTx := &LazyTransaction{ + Pool: nil, + Hash: common.Hash{byte(i), byte(j)}, + Fees: *tip, + Gas: uint64(i), + BlobGas: uint64(j), + } + if first { + first = false + heads = append(heads, &TxTips{ + From: addr, + Tips: lazyTx.Fees, + Time: 0, + }) + } + tail = append(tail, lazyTx) + } + if len(tail) > 0 { + tails[addr] = tail + } + } + // un-sort the heads + rand.Shuffle(len(heads), func(i, j int) { + heads[i], heads[j] = heads[j], heads[i] + }) + return heads, tails +} + +// Test the sorting and the Shift operation +func TestPendingSortAndShift(t *testing.T) { + // Create the pending-set + var ( + heads, tails = initLists() + expectedCount = 25 * 25 + txset = NewPendingSet(heads, tails) + haveCount = 0 + prevFee = uint64(math.MaxInt64) + ) + if txset.Empty() { + t.Fatalf("expected non-empty") + } + for { + ltx, fee := txset.Peek() + if ltx == nil { + break + } + haveCount++ + if fee.Cmp(<x.Fees) != 0 { + t.Fatalf("error tx %d: %v != %v", haveCount, fee, ltx.Fees) + } + if fee.Uint64() > prevFee { + t.Fatalf("tx %d: fee %d > previous fee %d", haveCount, fee, prevFee) + } + prevFee = fee.Uint64() + txset.Shift() + } + if haveCount != expectedCount { + t.Errorf("expected %d transactions, found %d", expectedCount, haveCount) + } +} + +// Test the sorting and the Pop operation +func TestPendingSortAndPop(t *testing.T) { + var ( + heads, tails = initLists() + expectedCount = 25 * 1 + txset = NewPendingSet(heads, tails) + haveCount = 0 + prevFee = uint64(math.MaxInt64) + ) + for { + ltx, fee := txset.Peek() + if ltx == nil { + break + } + haveCount++ + if fee.Cmp(<x.Fees) != 0 { + t.Fatalf("error tx %d: %v != %v", haveCount, fee, ltx.Fees) + } + if fee.Uint64() > prevFee { + t.Fatalf("tx %d: fee %d > previous fee %d", haveCount, fee, prevFee) + } + prevFee = fee.Uint64() + txset.Pop() + } + if haveCount != expectedCount { + t.Errorf("expected %d transactions, found %d", expectedCount, haveCount) + } +} + +// Tests that if multiple transactions have the same price, the ones seen earlier +// are prioritized to avoid network spam attacks aiming for a specific ordering. +func TestSortingByTime(t *testing.T) { + var heads TipList + for i := 0; i < 25; i++ { + addr := common.Address{byte(i)} + heads = append(heads, &TxTips{ + From: addr, + Tips: *(uint256.NewInt(uint64(100))), + Time: int64(i), + }) + } + // un-sort the heads + rand.Shuffle(len(heads), func(i, j int) { + heads[i], heads[j] = heads[j], heads[i] + }) + heap.Init(&heads) + for want := int64(0); want < 25; want++ { + obj := heap.Pop(&heads).(*TxTips) + if have := obj.Time; have != want { + t.Fatalf("have %d want %d", have, want) + } + } +} diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 9881ed1b8f96..e6c8ff2fe48d 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -35,9 +35,8 @@ type LazyTransaction struct { Hash common.Hash // Transaction hash to pull up if needed Tx *types.Transaction // Transaction if already resolved - Time time.Time // Time when the transaction was first seen - GasFeeCap *uint256.Int // Maximum fee per gas the transaction may consume - GasTipCap *uint256.Int // Maximum miner tip per gas the transaction can pay + Time time.Time // Time when the transaction was first seen + Fees uint256.Int Gas uint64 // Amount of gas required by the transaction BlobGas uint64 // Amount of blob gas required by the transaction @@ -70,21 +69,6 @@ type LazyResolver interface { // may request (and relinquish) exclusive access to certain addresses. type AddressReserver func(addr common.Address, reserve bool) error -// PendingFilter is a collection of filter rules to allow retrieving a subset -// of transactions for announcement or mining. -// -// Note, the entries here are not arbitrary useful filters, rather each one has -// a very specific call site in mind and each one can be evaluated very cheaply -// by the pool implementations. Only add new ones that satisfy those constraints. -type PendingFilter struct { - MinTip *uint256.Int // Minimum miner tip required to include a transaction - BaseFee *uint256.Int // Minimum 1559 basefee needed to include a transaction - BlobFee *uint256.Int // Minimum 4844 blobfee needed to include a blob transaction - - OnlyPlainTxs bool // Return only plain EVM transactions (peer-join announces, block space filling) - OnlyBlobTxs bool // Return only blob transactions (block blob-space filling) -} - // SubPool represents a specialized transaction pool that lives on its own (e.g. // blob pool). Since independent of how many specialized pools we have, they do // need to be updated in lockstep and assemble into one coherent view for block @@ -133,7 +117,11 @@ type SubPool interface { // // The transactions can also be pre-filtered by the dynamic fee components to // reduce allocations and load on downstream subsystems. - Pending(filter PendingFilter) map[common.Address][]*LazyTransaction + Pending(filter PendingFilter) Pending + + // PendingHashes retrieves the hashes of all currently processable transactions. + // The returned list is grouped by origin account and sorted by nonce + PendingHashes(filter PendingFilter) []common.Hash // SubscribeTransactions subscribes to new transaction events. The subscriber // can decide whether to receive notifications only for newly seen transactions diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index be7435247d92..f2e7be880eda 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -356,14 +356,30 @@ func (p *TxPool) Add(txs []*types.Transaction, local bool, sync bool) []error { // // The transactions can also be pre-filtered by the dynamic fee components to // reduce allocations and load on downstream subsystems. -func (p *TxPool) Pending(filter PendingFilter) map[common.Address][]*LazyTransaction { - txs := make(map[common.Address][]*LazyTransaction) - for _, subpool := range p.subpools { - for addr, set := range subpool.Pending(filter) { - txs[addr] = set +func (p *TxPool) Pending(filter PendingFilter) Pending { + var pending Pending + // This code is not quite correct, it assumes that the filter requests + // _either_ blobs or legacy. + if !filter.OnlyBlobTxs && !filter.OnlyPlainTxs { + panic("please request either only-blobs or only-plain") + } + for _, sub := range p.subpools { + if pending = sub.Pending(filter); !pending.Empty() { + return pending } } - return txs + return pending +} + +// PendingHashes retrieves the hashes of all currently processable transactions. +// The returned list is grouped by origin account and sorted by nonce +func (p *TxPool) PendingHashes(filter PendingFilter) []common.Hash { + var hashes []common.Hash + for _, sub := range p.subpools { + h := sub.PendingHashes(filter) + hashes = append(hashes, h...) + } + return hashes } // SubscribeTransactions registers a subscription for new transaction events, diff --git a/eth/api_backend.go b/eth/api_backend.go index 48c46447c5a0..643392a8e866 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -289,12 +289,15 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { pending := b.eth.txPool.Pending(txpool.PendingFilter{}) var txs types.Transactions - for _, batch := range pending { - for _, lazy := range batch { - if tx := lazy.Resolve(); tx != nil { - txs = append(txs, tx) - } + for { + lazy, _ := pending.Peek() + if lazy == nil { + break } + if tx := lazy.Resolve(); tx != nil { + txs = append(txs, tx) + } + pending.Shift() } return txs, nil } diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go index 4ae60ed4907c..cf9e4007fdec 100644 --- a/eth/catalyst/simulated_beacon.go +++ b/eth/catalyst/simulated_beacon.go @@ -279,7 +279,7 @@ func (c *SimulatedBeacon) Rollback() { // Fork sets the head to the provided hash. func (c *SimulatedBeacon) Fork(parentHash common.Hash) error { - if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 { + if len(c.eth.TxPool().PendingHashes(txpool.PendingFilter{})) != 0 { return errors.New("pending block dirty") } parent := c.eth.BlockChain().GetBlockByHash(parentHash) @@ -291,7 +291,7 @@ func (c *SimulatedBeacon) Fork(parentHash common.Hash) error { // AdjustTime creates a new block with an adjusted timestamp. func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error { - if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 { + if len(c.eth.TxPool().PendingHashes(txpool.PendingFilter{})) != 0 { return errors.New("could not adjust time on non-empty block") } parent := c.eth.BlockChain().CurrentBlock() diff --git a/eth/handler.go b/eth/handler.go index a32a04e00b72..29d22710468f 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -72,9 +72,11 @@ type txPool interface { // Add should add the given transactions to the pool. Add(txs []*types.Transaction, local bool, sync bool) []error - // Pending should return pending transactions. - // The slice should be modifiable by the caller. - Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction + // Pending return pending transactions. + Pending(filter txpool.PendingFilter) txpool.Pending + + // PendingHashes returns a list of the hashes for all pending transactions. + PendingHashes(filter txpool.PendingFilter) []common.Hash // SubscribeTransactions subscribes to new transaction events. The subscriber // can decide whether to receive notifications only for newly seen transactions diff --git a/eth/handler_test.go b/eth/handler_test.go index bcc8ea30e415..7b2b39ab6d2a 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -92,7 +92,7 @@ func (p *testTxPool) Add(txs []*types.Transaction, local bool, sync bool) []erro } // Pending returns all the transactions known to the pool -func (p *testTxPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction { +func (p *testTxPool) Pending(filter txpool.PendingFilter) txpool.Pending { p.lock.RLock() defer p.lock.RUnlock() @@ -104,21 +104,62 @@ func (p *testTxPool) Pending(filter txpool.PendingFilter) map[common.Address][]* for _, batch := range batches { sort.Sort(types.TxByNonce(batch)) } - pending := make(map[common.Address][]*txpool.LazyTransaction) + var ( + tails = make(map[common.Address][]*txpool.LazyTransaction) + heads txpool.TipList + baseFee = new(uint256.Int) + ) + if filter.BaseFee != nil { + baseFee = filter.BaseFee + } + baseFeeBig := baseFee.ToBig() for addr, batch := range batches { + var tail []*txpool.LazyTransaction + for i, tx := range batch { + bigTip, err := tx.EffectiveGasTip(baseFeeBig) + if err != nil { + // to low for the given basefee + break + } + tip := uint256.MustFromBig(bigTip) + ltx := &txpool.LazyTransaction{ + Hash: tx.Hash(), + Tx: tx, + Time: tx.Time(), + Fees: *tip, + Gas: tx.Gas(), + BlobGas: tx.BlobGas(), + } + tail = append(tail, ltx) + if i == 0 { + heads = append(heads, &txpool.TxTips{ + From: addr, + Tips: *tip, + Time: ltx.Time.UnixNano(), + }) + } + } + tails[addr] = tail + } + return txpool.NewPendingSet(heads, tails) +} + +func (p *testTxPool) PendingHashes(filter txpool.PendingFilter) []common.Hash { + p.lock.RLock() + defer p.lock.RUnlock() + batches := make(map[common.Address][]*types.Transaction) + for _, tx := range p.pool { + from, _ := types.Sender(types.HomesteadSigner{}, tx) + batches[from] = append(batches[from], tx) + } + var hashes []common.Hash + for _, batch := range batches { + sort.Sort(types.TxByNonce(batch)) for _, tx := range batch { - pending[addr] = append(pending[addr], &txpool.LazyTransaction{ - Hash: tx.Hash(), - Tx: tx, - Time: tx.Time(), - GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()), - GasTipCap: uint256.MustFromBig(tx.GasTipCap()), - Gas: tx.Gas(), - BlobGas: tx.BlobGas(), - }) + hashes = append(hashes, tx.Hash()) } } - return pending + return hashes } // SubscribeTransactions should return an event subscription of NewTxsEvent and diff --git a/eth/sync.go b/eth/sync.go index 61f2b2b376cd..9c96df553542 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -17,19 +17,13 @@ package eth import ( - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/eth/protocols/eth" ) // syncTransactions starts sending all currently pending transactions to the given peer. func (h *handler) syncTransactions(p *eth.Peer) { - var hashes []common.Hash - for _, batch := range h.txpool.Pending(txpool.PendingFilter{OnlyPlainTxs: true}) { - for _, tx := range batch { - hashes = append(hashes, tx.Hash) - } - } + hashes := h.txpool.PendingHashes(txpool.PendingFilter{OnlyPlainTxs: true}) if len(hashes) == 0 { return } diff --git a/miner/ordering.go b/miner/ordering.go deleted file mode 100644 index bcf7af46e891..000000000000 --- a/miner/ordering.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2014 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 . - -package miner - -import ( - "container/heap" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/txpool" - "github.com/ethereum/go-ethereum/core/types" - "github.com/holiman/uint256" -) - -// txWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap -type txWithMinerFee struct { - tx *txpool.LazyTransaction - from common.Address - fees *uint256.Int -} - -// newTxWithMinerFee creates a wrapped transaction, calculating the effective -// miner gasTipCap if a base fee is provided. -// Returns error in case of a negative effective miner gasTipCap. -func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *uint256.Int) (*txWithMinerFee, error) { - tip := new(uint256.Int).Set(tx.GasTipCap) - if baseFee != nil { - if tx.GasFeeCap.Cmp(baseFee) < 0 { - return nil, types.ErrGasFeeCapTooLow - } - tip = new(uint256.Int).Sub(tx.GasFeeCap, baseFee) - if tip.Gt(tx.GasTipCap) { - tip = tx.GasTipCap - } - } - return &txWithMinerFee{ - tx: tx, - from: from, - fees: tip, - }, nil -} - -// txByPriceAndTime implements both the sort and the heap interface, making it useful -// for all at once sorting as well as individually adding and removing elements. -type txByPriceAndTime []*txWithMinerFee - -func (s txByPriceAndTime) Len() int { return len(s) } -func (s txByPriceAndTime) Less(i, j int) bool { - // If the prices are equal, use the time the transaction was first seen for - // deterministic sorting - cmp := s[i].fees.Cmp(s[j].fees) - if cmp == 0 { - return s[i].tx.Time.Before(s[j].tx.Time) - } - return cmp > 0 -} -func (s txByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func (s *txByPriceAndTime) Push(x interface{}) { - *s = append(*s, x.(*txWithMinerFee)) -} - -func (s *txByPriceAndTime) Pop() interface{} { - old := *s - n := len(old) - x := old[n-1] - old[n-1] = nil - *s = old[0 : n-1] - return x -} - -// transactionsByPriceAndNonce represents a set of transactions that can return -// transactions in a profit-maximizing sorted order, while supporting removing -// entire batches of transactions for non-executable accounts. -type transactionsByPriceAndNonce struct { - txs map[common.Address][]*txpool.LazyTransaction // Per account nonce-sorted list of transactions - heads txByPriceAndTime // Next transaction for each unique account (price heap) - signer types.Signer // Signer for the set of transactions - baseFee *uint256.Int // Current base fee -} - -// newTransactionsByPriceAndNonce creates a transaction set that can retrieve -// price sorted transactions in a nonce-honouring way. -// -// Note, the input map is reowned so the caller should not interact any more with -// if after providing it to the constructor. -func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address][]*txpool.LazyTransaction, baseFee *big.Int) *transactionsByPriceAndNonce { - // Convert the basefee from header format to uint256 format - var baseFeeUint *uint256.Int - if baseFee != nil { - baseFeeUint = uint256.MustFromBig(baseFee) - } - // Initialize a price and received time based heap with the head transactions - heads := make(txByPriceAndTime, 0, len(txs)) - for from, accTxs := range txs { - wrapped, err := newTxWithMinerFee(accTxs[0], from, baseFeeUint) - if err != nil { - delete(txs, from) - continue - } - heads = append(heads, wrapped) - txs[from] = accTxs[1:] - } - heap.Init(&heads) - - // Assemble and return the transaction set - return &transactionsByPriceAndNonce{ - txs: txs, - heads: heads, - signer: signer, - baseFee: baseFeeUint, - } -} - -// Peek returns the next transaction by price. -func (t *transactionsByPriceAndNonce) Peek() (*txpool.LazyTransaction, *uint256.Int) { - if len(t.heads) == 0 { - return nil, nil - } - return t.heads[0].tx, t.heads[0].fees -} - -// Shift replaces the current best head with the next one from the same account. -func (t *transactionsByPriceAndNonce) Shift() { - acc := t.heads[0].from - if txs, ok := t.txs[acc]; ok && len(txs) > 0 { - if wrapped, err := newTxWithMinerFee(txs[0], acc, t.baseFee); err == nil { - t.heads[0], t.txs[acc] = wrapped, txs[1:] - heap.Fix(&t.heads, 0) - return - } - } - heap.Pop(&t.heads) -} - -// Pop removes the best transaction, *not* replacing it with the next one from -// the same account. This should be used when a transaction cannot be executed -// and hence all subsequent ones should be discarded from the same account. -func (t *transactionsByPriceAndNonce) Pop() { - heap.Pop(&t.heads) -} - -// Empty returns if the price heap is empty. It can be used to check it simpler -// than calling peek and checking for nil return. -func (t *transactionsByPriceAndNonce) Empty() bool { - return len(t.heads) == 0 -} - -// Clear removes the entire content of the heap. -func (t *transactionsByPriceAndNonce) Clear() { - t.heads, t.txs = nil, nil -} diff --git a/miner/ordering_test.go b/miner/ordering_test.go deleted file mode 100644 index 3587a835c884..000000000000 --- a/miner/ordering_test.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2014 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 . - -package miner - -import ( - "crypto/ecdsa" - "math/big" - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/txpool" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/holiman/uint256" -) - -func TestTransactionPriceNonceSortLegacy(t *testing.T) { - t.Parallel() - testTransactionPriceNonceSort(t, nil) -} - -func TestTransactionPriceNonceSort1559(t *testing.T) { - t.Parallel() - testTransactionPriceNonceSort(t, big.NewInt(0)) - testTransactionPriceNonceSort(t, big.NewInt(5)) - testTransactionPriceNonceSort(t, big.NewInt(50)) -} - -// Tests that transactions can be correctly sorted according to their price in -// decreasing order, but at the same time with increasing nonces when issued by -// the same account. -func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) { - // Generate a batch of accounts to start with - keys := make([]*ecdsa.PrivateKey, 25) - for i := 0; i < len(keys); i++ { - keys[i], _ = crypto.GenerateKey() - } - signer := types.LatestSignerForChainID(common.Big1) - - // Generate a batch of transactions with overlapping values, but shifted nonces - groups := map[common.Address][]*txpool.LazyTransaction{} - expectedCount := 0 - for start, key := range keys { - addr := crypto.PubkeyToAddress(key.PublicKey) - count := 25 - for i := 0; i < 25; i++ { - var tx *types.Transaction - gasFeeCap := rand.Intn(50) - if baseFee == nil { - tx = types.NewTx(&types.LegacyTx{ - Nonce: uint64(start + i), - To: &common.Address{}, - Value: big.NewInt(100), - Gas: 100, - GasPrice: big.NewInt(int64(gasFeeCap)), - Data: nil, - }) - } else { - tx = types.NewTx(&types.DynamicFeeTx{ - Nonce: uint64(start + i), - To: &common.Address{}, - Value: big.NewInt(100), - Gas: 100, - GasFeeCap: big.NewInt(int64(gasFeeCap)), - GasTipCap: big.NewInt(int64(rand.Intn(gasFeeCap + 1))), - Data: nil, - }) - if count == 25 && int64(gasFeeCap) < baseFee.Int64() { - count = i - } - } - tx, err := types.SignTx(tx, signer, key) - if err != nil { - t.Fatalf("failed to sign tx: %s", err) - } - groups[addr] = append(groups[addr], &txpool.LazyTransaction{ - Hash: tx.Hash(), - Tx: tx, - Time: tx.Time(), - GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()), - GasTipCap: uint256.MustFromBig(tx.GasTipCap()), - Gas: tx.Gas(), - BlobGas: tx.BlobGas(), - }) - } - expectedCount += count - } - // Sort the transactions and cross check the nonce ordering - txset := newTransactionsByPriceAndNonce(signer, groups, baseFee) - - txs := types.Transactions{} - for tx, _ := txset.Peek(); tx != nil; tx, _ = txset.Peek() { - txs = append(txs, tx.Tx) - txset.Shift() - } - if len(txs) != expectedCount { - t.Errorf("expected %d transactions, found %d", expectedCount, len(txs)) - } - for i, txi := range txs { - fromi, _ := types.Sender(signer, txi) - - // Make sure the nonce order is valid - for j, txj := range txs[i+1:] { - fromj, _ := types.Sender(signer, txj) - if fromi == fromj && txi.Nonce() > txj.Nonce() { - t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce()) - } - } - // If the next tx has different from account, the price must be lower than the current one - if i+1 < len(txs) { - next := txs[i+1] - fromNext, _ := types.Sender(signer, next) - tip, err := txi.EffectiveGasTip(baseFee) - nextTip, nextErr := next.EffectiveGasTip(baseFee) - if err != nil || nextErr != nil { - t.Errorf("error calculating effective tip: %v, %v", err, nextErr) - } - if fromi != fromNext && tip.Cmp(nextTip) < 0 { - t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) - } - } - } -} - -// Tests that if multiple transactions have the same price, the ones seen earlier -// are prioritized to avoid network spam attacks aiming for a specific ordering. -func TestTransactionTimeSort(t *testing.T) { - t.Parallel() - // Generate a batch of accounts to start with - keys := make([]*ecdsa.PrivateKey, 5) - for i := 0; i < len(keys); i++ { - keys[i], _ = crypto.GenerateKey() - } - signer := types.HomesteadSigner{} - - // Generate a batch of transactions with overlapping prices, but different creation times - groups := map[common.Address][]*txpool.LazyTransaction{} - for start, key := range keys { - addr := crypto.PubkeyToAddress(key.PublicKey) - - tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key) - tx.SetTime(time.Unix(0, int64(len(keys)-start))) - - groups[addr] = append(groups[addr], &txpool.LazyTransaction{ - Hash: tx.Hash(), - Tx: tx, - Time: tx.Time(), - GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()), - GasTipCap: uint256.MustFromBig(tx.GasTipCap()), - Gas: tx.Gas(), - BlobGas: tx.BlobGas(), - }) - } - // Sort the transactions and cross check the nonce ordering - txset := newTransactionsByPriceAndNonce(signer, groups, nil) - - txs := types.Transactions{} - for tx, _ := txset.Peek(); tx != nil; tx, _ = txset.Peek() { - txs = append(txs, tx.Tx) - txset.Shift() - } - if len(txs) != len(keys) { - t.Errorf("expected %d transactions, found %d", len(keys), len(txs)) - } - for i, txi := range txs { - fromi, _ := types.Sender(signer, txi) - if i+1 < len(txs) { - next := txs[i+1] - fromNext, _ := types.Sender(signer, next) - - if txi.GasPrice().Cmp(next.GasPrice()) < 0 { - t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) - } - // Make sure time order is ascending if the txs have the same gas price - if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.Time().After(next.Time()) { - t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.Time(), i+1, fromNext[:4], next.Time()) - } - } - } -} diff --git a/miner/worker.go b/miner/worker.go index 7e038b0f301b..a8fd8dc0358d 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -271,7 +271,7 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* return receipt, err } -func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { +func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs txpool.Pending, interrupt *atomic.Int32) error { gasLimit := env.header.GasLimit if env.gasPool == nil { env.gasPool = new(core.GasPool).AddGas(gasLimit) @@ -298,7 +298,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran // Retrieve the next transaction and abort if all done. var ( ltx *txpool.LazyTransaction - txs *transactionsByPriceAndNonce + txs txpool.Pending ) pltx, ptip := plainTxs.Peek() bltx, btip := blobTxs.Peek() @@ -315,6 +315,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran txs, ltx = plainTxs, pltx } } + if ltx == nil { break } @@ -376,53 +377,61 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran // be customized with the plugin in the future. func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment) error { miner.confMu.RLock() - tip := miner.config.GasPrice + tip := uint256.MustFromBig(miner.config.GasPrice) miner.confMu.RUnlock() // Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees - filter := txpool.PendingFilter{ - MinTip: uint256.MustFromBig(tip), - } + var ( + baseFee *uint256.Int + blobFee *uint256.Int + ) if env.header.BaseFee != nil { - filter.BaseFee = uint256.MustFromBig(env.header.BaseFee) + baseFee = uint256.MustFromBig(env.header.BaseFee) } if env.header.ExcessBlobGas != nil { - filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(*env.header.ExcessBlobGas)) - } - filter.OnlyPlainTxs, filter.OnlyBlobTxs = true, false - pendingPlainTxs := miner.txpool.Pending(filter) - - filter.OnlyPlainTxs, filter.OnlyBlobTxs = false, true - pendingBlobTxs := miner.txpool.Pending(filter) - - // Split the pending transactions into locals and remotes. - localPlainTxs, remotePlainTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingPlainTxs - localBlobTxs, remoteBlobTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingBlobTxs - - for _, account := range miner.txpool.Locals() { - if txs := remotePlainTxs[account]; len(txs) > 0 { - delete(remotePlainTxs, account) - localPlainTxs[account] = txs - } - if txs := remoteBlobTxs[account]; len(txs) > 0 { - delete(remoteBlobTxs, account) - localBlobTxs[account] = txs - } + blobFee = uint256.MustFromBig(eip4844.CalcBlobFee(*env.header.ExcessBlobGas)) } + localPlainTxs := miner.txpool.Pending(txpool.PendingFilter{ + MinTip: nil, // no mintip for locals + BaseFee: baseFee, + BlobFee: blobFee, + OnlyPlainTxs: true, + OnlyBlobTxs: false, + OnlyLocals: true, + }) + localBlobTxs := miner.txpool.Pending(txpool.PendingFilter{ + MinTip: nil, // no mintip for locals + BaseFee: baseFee, + BlobFee: blobFee, + OnlyPlainTxs: false, + OnlyBlobTxs: true, + OnlyLocals: true, + }) + + remotePlainTxs := miner.txpool.Pending(txpool.PendingFilter{ + MinTip: tip, + BaseFee: baseFee, + BlobFee: blobFee, + OnlyPlainTxs: true, + OnlyBlobTxs: false, + OnlyLocals: false, + }) + remoteBlobTxs := miner.txpool.Pending(txpool.PendingFilter{ + MinTip: tip, + BaseFee: baseFee, + BlobFee: blobFee, + OnlyPlainTxs: false, + OnlyBlobTxs: true, + OnlyLocals: false, + }) // Fill the block with all available pending transactions. - if len(localPlainTxs) > 0 || len(localBlobTxs) > 0 { - plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, env.header.BaseFee) - blobTxs := newTransactionsByPriceAndNonce(env.signer, localBlobTxs, env.header.BaseFee) - - if err := miner.commitTransactions(env, plainTxs, blobTxs, interrupt); err != nil { + if !localPlainTxs.Empty() || !localBlobTxs.Empty() { + if err := miner.commitTransactions(env, localPlainTxs, localBlobTxs, interrupt); err != nil { return err } } - if len(remotePlainTxs) > 0 || len(remoteBlobTxs) > 0 { - plainTxs := newTransactionsByPriceAndNonce(env.signer, remotePlainTxs, env.header.BaseFee) - blobTxs := newTransactionsByPriceAndNonce(env.signer, remoteBlobTxs, env.header.BaseFee) - - if err := miner.commitTransactions(env, plainTxs, blobTxs, interrupt); err != nil { + if !remotePlainTxs.Empty() || !remoteBlobTxs.Empty() { + if err := miner.commitTransactions(env, remotePlainTxs, remoteBlobTxs, interrupt); err != nil { return err } }