From dfb046e852bfcd5745a6307c67a23e47b8a3c396 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 16 Jul 2024 05:49:06 +0100 Subject: [PATCH 01/57] txpool: incomplete pool2 and pool3 --- core/events.go | 8 +- core/txpool/legacypool/buffer.go | 70 +++++++ core/txpool/legacypool/legacypool.go | 292 ++++++++++++++++++++++++--- core/txpool/legacypool/list.go | 14 +- eth/handler.go | 10 +- eth/protocols/eth/peer.go | 37 +++- eth/sync.go | 2 +- 7 files changed, 380 insertions(+), 53 deletions(-) create mode 100644 core/txpool/legacypool/buffer.go diff --git a/core/events.go b/core/events.go index ce8bcca744..c67ad58962 100644 --- a/core/events.go +++ b/core/events.go @@ -22,7 +22,13 @@ import ( ) // NewTxsEvent is posted when a batch of transactions enters the transaction pool. -type NewTxsEvent struct{ Txs []*types.Transaction } +type NewTxsEvent struct { + Txs []*types.Transaction + // todo Static bool is Whether to send to only Static peer or not. + // This is because at high traffic we still want to broadcast transactions to at least some peers so that we + // minimize the transaction lost. + Static bool +} // ReannoTxsEvent is posted when a batch of local pending transactions exceed a specified duration. type ReannoTxsEvent struct{ Txs []*types.Transaction } diff --git a/core/txpool/legacypool/buffer.go b/core/txpool/legacypool/buffer.go new file mode 100644 index 0000000000..ccf9b1161d --- /dev/null +++ b/core/txpool/legacypool/buffer.go @@ -0,0 +1,70 @@ +package legacypool + +import ( + containerList "container/list" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type LRUBuffer struct { + capacity int + buffer *containerList.List + index map[common.Hash]*containerList.Element + mu sync.Mutex +} + +func NewLRUBuffer(capacity int) *LRUBuffer { + return &LRUBuffer{ + capacity: capacity, + buffer: containerList.New(), + index: make(map[common.Hash]*containerList.Element), + } +} + +func (lru *LRUBuffer) Add(tx *types.Transaction) { + lru.mu.Lock() + defer lru.mu.Unlock() + + if elem, ok := lru.index[tx.Hash()]; ok { + lru.buffer.MoveToFront(elem) + return + } + + if lru.buffer.Len() >= lru.capacity { + back := lru.buffer.Back() + lru.buffer.Remove(back) + delete(lru.index, back.Value.(*types.Transaction).Hash()) + } + + elem := lru.buffer.PushFront(tx) + lru.index[tx.Hash()] = elem +} + +func (lru *LRUBuffer) Get(hash common.Hash) (*types.Transaction, bool) { + lru.mu.Lock() + defer lru.mu.Unlock() + + if elem, ok := lru.index[hash]; ok { + lru.buffer.MoveToFront(elem) + return elem.Value.(*types.Transaction), true + } + return nil, false +} + +func (lru *LRUBuffer) Flush(maxTransactions int) []*types.Transaction { + lru.mu.Lock() + defer lru.mu.Unlock() + + txs := make([]*types.Transaction, 0, maxTransactions) + count := 0 + for count < maxTransactions && lru.buffer.Len() > 0 { + back := lru.buffer.Back() + txs = append(txs, back.Value.(*types.Transaction)) + lru.buffer.Remove(back) + delete(lru.index, back.Value.(*types.Transaction).Hash()) + count++ + } + return txs +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 91cd01e7b4..b09d803456 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -55,6 +55,9 @@ const ( // txReannoMaxNum is the maximum number of transactions a reannounce action can include. txReannoMaxNum = 1024 + + pool2Size = 10000 // todo might have to set it in config + pool3Size = 50000 ) var ( @@ -235,9 +238,12 @@ type LegacyPool struct { all *lookup // All transactions to allow lookups priced *pricedList // All transactions sorted by price + criticalPathPool map[common.Address]*list // Critical path transactions (Pool 2) + localBufferPool *LRUBuffer // Local buffer transactions (Pool 3) + reqResetCh chan *txpoolResetRequest reqPromoteCh chan *accountSet - queueTxEventCh chan *types.Transaction + queueTxEventCh chan QueueTxEventCh reorgDoneCh chan chan struct{} reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop wg sync.WaitGroup // tracks loop, scheduleReorgLoop @@ -246,6 +252,17 @@ type LegacyPool struct { changesSinceReorg int // A counter for how many drops we've performed in-between reorg. } +type QueueTxEventCh struct { + tx *types.Transaction + static bool +} + +func newQueueTxEventCh() QueueTxEventCh { + return QueueTxEventCh{ + tx: new(types.Transaction), + } +} + type txpoolResetRequest struct { oldHead, newHead *types.Header } @@ -258,20 +275,22 @@ func New(config Config, chain BlockChain) *LegacyPool { // Create the transaction pool with its initial settings pool := &LegacyPool{ - config: config, - chain: chain, - chainconfig: chain.Config(), - signer: types.LatestSigner(chain.Config()), - pending: make(map[common.Address]*list), - queue: make(map[common.Address]*list), - beats: make(map[common.Address]time.Time), - all: newLookup(), - reqResetCh: make(chan *txpoolResetRequest), - reqPromoteCh: make(chan *accountSet), - queueTxEventCh: make(chan *types.Transaction), - reorgDoneCh: make(chan chan struct{}), - reorgShutdownCh: make(chan struct{}), - initDoneCh: make(chan struct{}), + config: config, + chain: chain, + chainconfig: chain.Config(), + signer: types.LatestSigner(chain.Config()), + pending: make(map[common.Address]*list), + queue: make(map[common.Address]*list), + beats: make(map[common.Address]time.Time), + all: newLookup(), + reqResetCh: make(chan *txpoolResetRequest), + reqPromoteCh: make(chan *accountSet), + queueTxEventCh: make(chan QueueTxEventCh), + reorgDoneCh: make(chan chan struct{}), + reorgShutdownCh: make(chan struct{}), + initDoneCh: make(chan struct{}), + criticalPathPool: make(map[common.Address]*list), + localBufferPool: NewLRUBuffer(pool3Size), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -283,6 +302,9 @@ func New(config Config, chain BlockChain) *LegacyPool { if !config.NoLocals && config.Journal != "" { pool.journal = newTxJournal(config.Journal) } + + pool.startPeriodicTransfer() // todo (incomplete) Start the periodic transfer routine + return pool } @@ -746,6 +768,20 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e knownTxMeter.Mark(1) return false, txpool.ErrAlreadyKnown } + + pool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue + //txPoolSizeBeforeCurrentTx := uint64(pool.all.Slots()) + txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) + // pool2Size, pool3Size + var includePool1, includePool2, includePool3 bool + if txPoolSizeAfterCurrentTx < pool1Size { + includePool1 = true + } else if (txPoolSizeAfterCurrentTx > pool1Size) && (txPoolSizeAfterCurrentTx <= pool2Size) { + includePool2 = true + } else { + includePool3 = true + } + // Make the local flag. If it's from local source or it's from the network but // the sender is marked as local previously, treat it as the local transaction. isLocal := local || pool.locals.containsTx(tx) @@ -781,13 +817,23 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } }() } + // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { + if uint64(pool.all.Slots()+numSlots(tx)) > pool1Size { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { - log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) - underpricedTxMeter.Mark(1) - return false, txpool.ErrUnderpriced + // todo maybe here pool2 can come into picture. + // lets say it does. Then how can we transfer it to pool2? + // todo: check if pool2 can include it. If it can then include or else pool3 + addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, includePool1, includePool2, includePool3) + if !addedToAnyPool { + log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) + underpricedTxMeter.Mark(1) + return false, err + //return false, txpool.ErrUnderpriced + } + return true, nil + } // We're about to replace a transaction. The reorg does a more thorough @@ -848,11 +894,14 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Nonce already pending, check if required price bump is met inserted, old := list.Add(tx, pool.config.PriceBump) if !inserted { - pendingDiscardMeter.Mark(1) - return false, txpool.ErrReplaceUnderpriced + // todo maybe here we can add to pool2 or pool3? + addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, false, includePool2, includePool3) + //pendingDiscardMeter.Mark(1) + return addedToAnyPool, err } // New transaction is better, replace old one if old != nil { + // todo maybe here we can add the old to pool2 or pool3? pool.all.Remove(old.Hash()) pool.priced.Removed(1) pendingReplaceMeter.Mark(1) @@ -860,18 +909,31 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.all.Add(tx, isLocal) pool.priced.Put(tx, isLocal) pool.journalTx(from, tx) - pool.queueTxEvent(tx) + pool.queueTxEvent(tx, false) // todo putting false as placeholder now log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat pool.beats[from] = time.Now() return old != nil, nil } + // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.enqueueTx(hash, tx, isLocal, true) + replaced, err = pool.addToPool2OrPool3(tx, from, includePool1, includePool2, includePool3) + + //replaced, err = pool.enqueueTx(hash, tx, isLocal, true) if err != nil { return false, err } + + //if uint64(len(pool.pending)) < pool1Size { + // replaced, err = pool.enqueueTx(hash, tx, isLocal, true) + //} else if len(pool.criticalPathPool) < pool2Size { + // replaced, err = pool.enqueueTxToCriticalPath(hash, tx, isLocal, true) + //} else { + // pool.localBufferPool.Add(tx) + // replaced, err = false, nil + //} + // Mark local addresses and journal local transactions if local && !pool.locals.contains(from) { log.Info("Setting new local account", "address", from) @@ -887,6 +949,40 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return replaced, nil } +func (pool *LegacyPool) addToPool2OrPool3(tx *types.Transaction, from common.Address, pool1, pool2, pool3 bool) (bool, error) { + if pool1 { + // todo (check) logic for pool1 related + pool.all.Add(tx, pool2) + pool.priced.Put(tx, pool2) + pool.journalTx(from, tx) + pool.queueTxEvent(tx, false) + log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) + + // Successful promotion, bump the heartbeat + pool.beats[from] = time.Now() + + return true, nil + } + if pool2 { + // todo (check) logic for pool2 related + pool.all.Add(tx, pool2) + pool.priced.Put(tx, pool2) + pool.journalTx(from, tx) + pool.queueTxEvent(tx, true) + log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) + + // Successful promotion, bump the heartbeat + pool.beats[from] = time.Now() + return true, nil + } + if pool3 { + // todo (check) logic for pool3 + pool.localBufferPool.Add(tx) + return true, nil + } + return false, errors.New("could not add to any pool") +} + // isGapped reports whether the given transaction is immediately executable. func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) bool { // Short circuit if transaction falls within the scope of the pending list @@ -1248,9 +1344,13 @@ func (pool *LegacyPool) requestPromoteExecutables(set *accountSet) chan struct{} } // queueTxEvent enqueues a transaction event to be sent in the next reorg run. -func (pool *LegacyPool) queueTxEvent(tx *types.Transaction) { +func (pool *LegacyPool) queueTxEvent(tx *types.Transaction, static bool) { + event := QueueTxEventCh{ + tx: tx, + static: static, + } select { - case pool.queueTxEventCh <- tx: + case pool.queueTxEventCh <- event: case <-pool.reorgShutdownCh: } } @@ -1304,14 +1404,14 @@ func (pool *LegacyPool) scheduleReorgLoop() { launchNextRun = true pool.reorgDoneCh <- nextDone - case tx := <-pool.queueTxEventCh: + case queue := <-pool.queueTxEventCh: // Queue up the event, but don't schedule a reorg. It's up to the caller to // request one later if they want the events sent. - addr, _ := types.Sender(pool.signer, tx) + addr, _ := types.Sender(pool.signer, queue.tx) if _, ok := queuedEvents[addr]; !ok { queuedEvents[addr] = newSortedMap() } - queuedEvents[addr].Put(tx) + queuedEvents[addr].Put(queue.tx, queue.static) case <-curDone: curDone = nil @@ -1397,14 +1497,21 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, if _, ok := events[addr]; !ok { events[addr] = newSortedMap() } - events[addr].Put(tx) + events[addr].Put(tx, false) // todo putting false as placeholder for now } if len(events) > 0 { - var txs []*types.Transaction + //var txs []*types.Transaction for _, set := range events { - txs = append(txs, set.Flatten()...) + // todo problem is that here all transactions are sent at once. Maybe send them in series? But not sure how + // performance will be affected + // todo Maybe do the Send() only twice (instead of all events): + // 1. For Static + // 2. For non-Static + pool.txFeed.Send(core.NewTxsEvent{Txs: set.Flatten(), Static: set.staticOnly}) + //txs = append(txs, set.Flatten()...) } - pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) + + //pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) } } @@ -1589,6 +1696,13 @@ func (pool *LegacyPool) truncatePending() { return } + // todo maybe check here if the extra numbers are within pool1 and (pool1+pool2) and in that case + // we can only broadcast to static peers. + pool1Size := pool.config.GlobalSlots + if (pending > pool1Size) && pending < (pool1Size+pool2Size) { + + } + pendingBeforeCap := pending // Assemble a spam order to penalize large transactors first spammers := prque.New[int64, common.Address](nil) @@ -2038,3 +2152,117 @@ func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions { func numSlots(tx *types.Transaction) int { return int((tx.Size() + txSlotSize - 1) / txSlotSize) } + +func (pool *LegacyPool) enqueueTxToCriticalPath(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { + from, _ := types.Sender(pool.signer, tx) + if pool.criticalPathPool[from] == nil { + pool.criticalPathPool[from] = newList(false) + } + inserted, old := pool.criticalPathPool[from].Add(tx, pool.config.PriceBump) + if !inserted { + queuedDiscardMeter.Mark(1) + return false, txpool.ErrReplaceUnderpriced + } + if old != nil { + pool.all.Remove(old.Hash()) + pool.priced.Removed(1) + queuedReplaceMeter.Mark(1) + } else { + queuedGauge.Inc(1) + } + if pool.all.Get(hash) == nil && !addAll { + log.Error("Missing transaction in lookup set, please report the issue", "hash", hash) + } + if addAll { + pool.all.Add(tx, local) + pool.priced.Put(tx, local) + } + if _, exist := pool.beats[from]; !exist { + pool.beats[from] = time.Now() + } + return old != nil, nil +} + +//func getPool2Peers(allPeers, staticPeers []Peer) []Peer { +// allWithoutStatic := difference(allPeers, staticPeers) +// randomNonStatic := getRandomPeers(allWithoutStatic, MaxNonStaticPeer) +// return append(staticPeers, randomNonStatic...) +//} +// +//func broadcastToPeers(tx *types.Transaction, peers []Peer) { +// for _, peer := range peers { +// peer.SendTransaction(tx) +// } +//} +// +//func difference(slice1, slice2 []Peer) []Peer { +// m := make(map[Peer]bool) +// for _, item := range slice2 { +// m[item] = true +// } +// var diff []Peer +// for _, item := range slice1 { +// if _, ok := m[item]; !ok { +// diff = append(diff, item) +// } +// } +// return diff +//} +// +//func getRandomPeers(peers []Peer, max int) []Peer { +// rand.Seed(time.Now().UnixNano()) +// rand.Shuffle(len(peers), func(i, j int) { peers[i], peers[j] = peers[j], peers[i] }) +// if len(peers) > max { +// return peers[:max] +// } +// return peers +//} + +func (pool *LegacyPool) startPeriodicTransfer() { + ticker := time.NewTicker(time.Minute) // Adjust the interval as needed + go func() { + for { + select { + case <-ticker.C: + pool.mu.Lock() + pool.transferTransactions() + pool.mu.Unlock() + case <-pool.reorgShutdownCh: + ticker.Stop() + return + } + } + }() +} + +func (pool *LegacyPool) transferTransactions() { + // todo (incomplete) here we check all the transfer logic. So + // if len(pool12) > maxPool1Size && len(pool12) < (maxPool1Size + maxPool2Size) then only broadcast to static peers. + // if len(pool12) > (maxPool1Size + maxPool2Size) then add a new transaction to pool3 only + + maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue + + // Transfer transactions from LocalBufferPool (Pool 3) to CriticalPathPool (Pool 2) + if len(pool.criticalPathPool) < pool2Size { + txs := pool.localBufferPool.Flush(pool2Size - len(pool.criticalPathPool)) + for _, tx := range txs { + _, err := pool.enqueueTxToCriticalPath(tx.Hash(), tx, false, true) + if err != nil { + log.Error("Failed to transfer transaction from Pool 3 to Pool 2", "err", err) + } + } + } + + // Transfer transactions from CriticalPathPool (Pool 2) to LegacyPool (Pool 1) + if uint64(len(pool.pending)) < maxPool1Size { + for addr, list := range pool.criticalPathPool { + for _, tx := range list.Flatten() { + _, err := pool.enqueueTx(tx.Hash(), tx, false, true) + if err != nil { + log.Error("Failed to transfer transaction from Pool 2 to Pool 1", "err", err) + } + } + delete(pool.criticalPathPool, addr) + } + } +} diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index 582ef85d07..0fb69f8934 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -63,10 +63,11 @@ func (h *nonceHeap) Pop() interface{} { // sortedMap is a nonce->transaction hash map with a heap based index to allow // iterating over the contents in a nonce-incrementing way. type sortedMap struct { - items map[uint64]*types.Transaction // Hash map storing the transaction data - index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) - cache types.Transactions // Cache of the transactions already sorted - cacheMu sync.Mutex // Mutex covering the cache + items map[uint64]*types.Transaction // Hash map storing the transaction data + index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) + cache types.Transactions // Cache of the transactions already sorted + cacheMu sync.Mutex // Mutex covering the cache + staticOnly bool // only send this transaction to static peers } // newSortedMap creates a new nonce-sorted transaction map. @@ -84,7 +85,7 @@ func (m *sortedMap) Get(nonce uint64) *types.Transaction { // Put inserts a new transaction into the map, also updating the map's nonce // index. If a transaction already exists with the same nonce, it's overwritten. -func (m *sortedMap) Put(tx *types.Transaction) { +func (m *sortedMap) Put(tx *types.Transaction, static bool) { nonce := tx.Nonce() if m.items[nonce] == nil { heap.Push(m.index, nonce) @@ -94,6 +95,7 @@ func (m *sortedMap) Put(tx *types.Transaction) { txSortedMapPool.Put(m.cache) } m.items[nonce], m.cache = tx, nil + m.staticOnly = static m.cacheMu.Unlock() } @@ -360,7 +362,7 @@ func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transa l.totalcost.Add(l.totalcost, cost) // Otherwise overwrite the old transaction with the current one - l.txs.Put(tx) + l.txs.Put(tx, false) // todo putting false as a placeholder if l.costcap.Cmp(cost) < 0 { l.costcap = cost } diff --git a/eth/handler.go b/eth/handler.go index 23dba9e14d..a733b134c6 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -838,7 +838,7 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { // - To a square root of all peers for non-blob transactions // - And, separately, as announcements to all peers which are not known to // already have the given transaction. -func (h *handler) BroadcastTransactions(txs types.Transactions) { +func (h *handler) BroadcastTransactions(txs types.Transactions, staticOnly bool) { var ( blobTxs int // Number of blob transactions to announce only largeTxs int // Number of large transactions to announce only @@ -876,12 +876,12 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { for peer, hashes := range txset { directPeers++ directCount += len(hashes) - peer.AsyncSendTransactions(hashes) + peer.AsyncSendTransactions(hashes, staticOnly) } for peer, hashes := range annos { annPeers++ annCount += len(hashes) - peer.AsyncSendPooledTransactionHashes(hashes) + peer.AsyncSendPooledTransactionHashes(hashes, staticOnly) } log.Debug("Distributed transactions", "plaintxs", len(txs)-blobTxs-largeTxs, "blobtxs", blobTxs, "largetxs", largeTxs, "bcastpeers", directPeers, "bcastcount", directCount, "annpeers", annPeers, "anncount", annCount) @@ -899,7 +899,7 @@ func (h *handler) ReannounceTransactions(txs types.Transactions) { peersCount := uint(math.Sqrt(float64(h.peers.len()))) peers := h.peers.headPeers(peersCount) for _, peer := range peers { - peer.AsyncSendPooledTransactionHashes(hashes) + peer.AsyncSendPooledTransactionHashes(hashes, false) // todo keeping it false for now. confirm it. } log.Debug("Transaction reannounce", "txs", len(txs), "announce packs", peersCount, "announced hashes", peersCount*uint(len(hashes))) @@ -962,7 +962,7 @@ func (h *handler) txBroadcastLoop() { for { select { case event := <-h.txsCh: - h.BroadcastTransactions(event.Txs) + h.BroadcastTransactions(event.Txs, event.Static) // todo should Static bool be a slice of bools? case <-h.txsSub.Err(): return case <-h.stopCh: diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 7720d9bd44..44b55dcfc9 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -222,15 +222,26 @@ func (p *Peer) SendTransactions(txs types.Transactions) error { // AsyncSendTransactions queues a list of transactions (by hash) to eventually // propagate to a remote peer. The number of pending sends are capped (new ones // will force old sends to be dropped) -func (p *Peer) AsyncSendTransactions(hashes []common.Hash) { +func (p *Peer) AsyncSendTransactions(hashes []common.Hash, staticOnly bool) { + // todo p.Peer.Info().Network.Static bool decides if pool2 transaction will be broadcasted to that peer or not select { - case p.txBroadcast <- hashes: - // Mark all the transactions as known, but ensure we don't overflow our limits - p.knownTxs.Add(hashes...) case <-p.txTerm: p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) case <-p.term: p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) + default: + if (staticOnly && p.Peer.Info().Network.Static) || !staticOnly { + select { + case p.txBroadcast <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + p.knownTxs.Add(hashes...) + default: + // Handle the case when the channel is full or not ready + p.Log().Debug("Unable to send transactions, channel full or not ready", "count", len(hashes)) + } + } else { + p.Log().Debug("Not sending transactions as peer not static", "count", len(hashes)) + } } } @@ -250,15 +261,25 @@ func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash, types []byte, s // AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually // announce to a remote peer. The number of pending sends are capped (new ones // will force old sends to be dropped) -func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { +func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash, staticOnly bool) { select { - case p.txAnnounce <- hashes: - // Mark all the transactions as known, but ensure we don't overflow our limits - p.knownTxs.Add(hashes...) case <-p.txTerm: p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) case <-p.term: p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) + default: + if (staticOnly && p.Peer.Info().Network.Static) || !staticOnly { + select { + case p.txAnnounce <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + p.knownTxs.Add(hashes...) + default: + // Handle the case when the channel is full or not ready + p.Log().Debug("Unable to send transactions, channel full or not ready", "count", len(hashes)) + } + } else { + p.Log().Debug("Not sending transactions as peer not static", "count", len(hashes)) + } } } diff --git a/eth/sync.go b/eth/sync.go index 3b04d09920..b09d54aff0 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -44,7 +44,7 @@ func (h *handler) syncTransactions(p *eth.Peer) { if len(hashes) == 0 { return } - p.AsyncSendPooledTransactionHashes(hashes) + p.AsyncSendPooledTransactionHashes(hashes, false) // todo confirm if false is fine } // syncVotes starts sending all currently pending votes to the given peer. From e30248be3a8c9c28764dcec73b606b744cc634f3 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Mon, 29 Jul 2024 11:51:02 +0100 Subject: [PATCH 02/57] pool: optimise sending in reorg --- core/txpool/legacypool/legacypool.go | 57 +++++++++++++++++----------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b09d803456..fe7dcdb427 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -822,9 +822,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e if uint64(pool.all.Slots()+numSlots(tx)) > pool1Size { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { - // todo maybe here pool2 can come into picture. - // lets say it does. Then how can we transfer it to pool2? - // todo: check if pool2 can include it. If it can then include or else pool3 addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, includePool1, includePool2, includePool3) if !addedToAnyPool { log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) @@ -848,6 +845,8 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // New transaction is better than our worse ones, make room for it. // If it's a local transaction, forcibly discard all available transactions. // Otherwise if we can't make enough room for new one, abort the operation. + // todo maybe we don't need to Discard if we can include it in pool2 or pool3 + // OR get the discarded transactions and make them part of pool2 or pool3 drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) // Special case, we still can't make the room for the new remote one. @@ -858,6 +857,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the new transaction is a future transaction it should never churn pending transactions + // todo WHY THIS CHECK HAPPENS AFTER CALLING DISCARD()?? if !isLocal && pool.isGapped(from, tx) { var replacesPending bool for _, dropTx := range drop { @@ -879,6 +879,8 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Kick out the underpriced remote transactions. for _, tx := range drop { + // todo pool2 or pool3. Because the new tx is better than transactions of drop but drop transactions still + // deserve to be part of at least pool2 or pool3. log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) underpricedTxMeter.Mark(1) @@ -894,9 +896,8 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Nonce already pending, check if required price bump is met inserted, old := list.Add(tx, pool.config.PriceBump) if !inserted { - // todo maybe here we can add to pool2 or pool3? addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, false, includePool2, includePool3) - //pendingDiscardMeter.Mark(1) + //pendingDiscardMeter.Mark(1) // todo do we need to mark the meter here? return addedToAnyPool, err } // New transaction is better, replace old one @@ -909,7 +910,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.all.Add(tx, isLocal) pool.priced.Put(tx, isLocal) pool.journalTx(from, tx) - pool.queueTxEvent(tx, false) // todo putting false as placeholder now + pool.queueTxEvent(tx, includePool2) // todo putting includePool2 for now, check and confirm log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -925,15 +926,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return false, err } - //if uint64(len(pool.pending)) < pool1Size { - // replaced, err = pool.enqueueTx(hash, tx, isLocal, true) - //} else if len(pool.criticalPathPool) < pool2Size { - // replaced, err = pool.enqueueTxToCriticalPath(hash, tx, isLocal, true) - //} else { - // pool.localBufferPool.Add(tx) - // replaced, err = false, nil - //} - // Mark local addresses and journal local transactions if local && !pool.locals.contains(from) { log.Info("Setting new local account", "address", from) @@ -1500,16 +1492,35 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, events[addr].Put(tx, false) // todo putting false as placeholder for now } if len(events) > 0 { - //var txs []*types.Transaction + staticTxs := make([]*types.Transaction, 0) + nonStaticTxs := make([]*types.Transaction, 0) for _, set := range events { - // todo problem is that here all transactions are sent at once. Maybe send them in series? But not sure how - // performance will be affected - // todo Maybe do the Send() only twice (instead of all events): - // 1. For Static - // 2. For non-Static - pool.txFeed.Send(core.NewTxsEvent{Txs: set.Flatten(), Static: set.staticOnly}) - //txs = append(txs, set.Flatten()...) + flattenedTxs := set.Flatten() + if set.staticOnly { + staticTxs = append(staticTxs, flattenedTxs...) + } else { + nonStaticTxs = append(nonStaticTxs, flattenedTxs...) + } + } + // Send static transactions + if len(staticTxs) > 0 { + pool.txFeed.Send(core.NewTxsEvent{Txs: staticTxs, Static: true}) + } + + // Send dynamic transactions + if len(nonStaticTxs) > 0 { + pool.txFeed.Send(core.NewTxsEvent{Txs: nonStaticTxs, Static: false}) } + //for _, set := range events { + // // todo problem is that here all transactions are sent at once. Maybe send them in series? But not sure how + // // performance will be affected + // // todo Maybe do the Send() only twice (instead of all events): + // // 1. For Static + // // 2. For non-Static + // pool.txFeed.Send(core.NewTxsEvent{Txs: set.Flatten(), Static: set.staticOnly}) + // // todo what if pool1 and pool2 are full at this point. Should we include it to pool3?? Probably NO + // //txs = append(txs, set.Flatten()...) + //} //pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) } From f80ac01c95aefc0c175748ae73b20304e4dc879a Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 1 Aug 2024 14:36:47 +0100 Subject: [PATCH 03/57] pool: add static info and simplify transfer --- core/txpool/legacypool/legacypool.go | 136 ++++++++++++++++++--------- core/txpool/legacypool/list.go | 4 +- 2 files changed, 93 insertions(+), 47 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index fe7dcdb427..5daadff6bc 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -56,7 +56,7 @@ const ( // txReannoMaxNum is the maximum number of transactions a reannounce action can include. txReannoMaxNum = 1024 - pool2Size = 10000 // todo might have to set it in config + pool2Size = 10000 // todo might have to set it in config // This is in slots and not in no of transactions pool3Size = 50000 ) @@ -894,7 +894,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Try to replace an existing transaction in the pending pool if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { // Nonce already pending, check if required price bump is met - inserted, old := list.Add(tx, pool.config.PriceBump) + inserted, old := list.Add(tx, pool.config.PriceBump, includePool2) if !inserted { addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, false, includePool2, includePool3) //pendingDiscardMeter.Mark(1) // todo do we need to mark the meter here? @@ -941,6 +941,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return replaced, nil } +// addToPool2OrPool3 adds a transaction to pool1 or pool2 or pool3 depending on which one is asked for func (pool *LegacyPool) addToPool2OrPool3(tx *types.Transaction, from common.Address, pool1, pool2, pool3 bool) (bool, error) { if pool1 { // todo (check) logic for pool1 related @@ -1008,7 +1009,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local if pool.queue[from] == nil { pool.queue[from] = newList(false) } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) + inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, false) if !inserted { // An older transaction was better, discard this queuedDiscardMeter.Mark(1) @@ -1062,7 +1063,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ } list := pool.pending[addr] - inserted, old := list.Add(tx, pool.config.PriceBump) + inserted, old := list.Add(tx, pool.config.PriceBump, false) // todo check and confirm if !inserted { // An older transaction was better, discard this pool.all.Remove(hash) @@ -1511,18 +1512,6 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, if len(nonStaticTxs) > 0 { pool.txFeed.Send(core.NewTxsEvent{Txs: nonStaticTxs, Static: false}) } - //for _, set := range events { - // // todo problem is that here all transactions are sent at once. Maybe send them in series? But not sure how - // // performance will be affected - // // todo Maybe do the Send() only twice (instead of all events): - // // 1. For Static - // // 2. For non-Static - // pool.txFeed.Send(core.NewTxsEvent{Txs: set.Flatten(), Static: set.staticOnly}) - // // todo what if pool1 and pool2 are full at this point. Should we include it to pool3?? Probably NO - // //txs = append(txs, set.Flatten()...) - //} - - //pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) } } @@ -1707,12 +1696,14 @@ func (pool *LegacyPool) truncatePending() { return } - // todo maybe check here if the extra numbers are within pool1 and (pool1+pool2) and in that case - // we can only broadcast to static peers. - pool1Size := pool.config.GlobalSlots - if (pending > pool1Size) && pending < (pool1Size+pool2Size) { - - } + //var addToPool2OrPool3 bool + // + //// todo maybe check here if the extra numbers are within pool1 and (pool1+pool2) and in that case + //// we can only broadcast to static peers. + //pool1Size := pool.config.GlobalSlots + //if (pending > pool1Size) && pending < (pool1Size+pool2Size) { + // addToPool2OrPool3 = true + //} pendingBeforeCap := pending // Assemble a spam order to penalize large transactors first @@ -2169,7 +2160,7 @@ func (pool *LegacyPool) enqueueTxToCriticalPath(hash common.Hash, tx *types.Tran if pool.criticalPathPool[from] == nil { pool.criticalPathPool[from] = newList(false) } - inserted, old := pool.criticalPathPool[from].Add(tx, pool.config.PriceBump) + inserted, old := pool.criticalPathPool[from].Add(tx, pool.config.PriceBump, false) if !inserted { queuedDiscardMeter.Mark(1) return false, txpool.ErrReplaceUnderpriced @@ -2246,34 +2237,89 @@ func (pool *LegacyPool) startPeriodicTransfer() { }() } +// transferTransactions mainly moves from pool 3 to pool 2 func (pool *LegacyPool) transferTransactions() { - // todo (incomplete) here we check all the transfer logic. So - // if len(pool12) > maxPool1Size && len(pool12) < (maxPool1Size + maxPool2Size) then only broadcast to static peers. - // if len(pool12) > (maxPool1Size + maxPool2Size) then add a new transaction to pool3 only maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue + maxPool1Pool2CombinedSize := maxPool1Size + pool2Size + extraSizePool2Pool1 := uint64(len(pool.pending)) + uint64(len(pool.queue)) - pool2Size - maxPool1Size + if extraSizePool2Pool1 <= 0 { + return + } - // Transfer transactions from LocalBufferPool (Pool 3) to CriticalPathPool (Pool 2) - if len(pool.criticalPathPool) < pool2Size { - txs := pool.localBufferPool.Flush(pool2Size - len(pool.criticalPathPool)) - for _, tx := range txs { - _, err := pool.enqueueTxToCriticalPath(tx.Hash(), tx, false, true) - if err != nil { - log.Error("Failed to transfer transaction from Pool 3 to Pool 2", "err", err) - } - } + currentPool1Pool2Size := pool.all.Slots() + canTransferPool3ToPool2 := maxPool1Pool2CombinedSize > uint64(currentPool1Pool2Size) + if !canTransferPool3ToPool2 { + return + } + extraSlots := maxPool1Pool2CombinedSize - uint64(currentPool1Pool2Size) + extraTransactions := extraSlots / 4 // Since maximum slots per transaction is 4 + // So now we can take out extraTransactions number of transactions from pool3 and put in pool2 + if extraTransactions < 1 { + return } - // Transfer transactions from CriticalPathPool (Pool 2) to LegacyPool (Pool 1) - if uint64(len(pool.pending)) < maxPool1Size { - for addr, list := range pool.criticalPathPool { - for _, tx := range list.Flatten() { - _, err := pool.enqueueTx(tx.Hash(), tx, false, true) - if err != nil { - log.Error("Failed to transfer transaction from Pool 2 to Pool 1", "err", err) - } - } - delete(pool.criticalPathPool, addr) + tx := pool.localBufferPool.Flush(int(extraTransactions)) + if len(tx) == 0 { + return + } + + for _, transaction := range tx { + if !(uint64(pool.all.Slots()) < maxPool1Pool2CombinedSize) { + break + } + from, _ := types.Sender(pool.signer, transaction) + + // use addToPool2OrPool3() function to transfer from pool3 to pool2 + _, err := pool.addToPool2OrPool3(transaction, from, false, true, false) + if err != nil { + return } } + + // // todo logic: If len(pending + queue) or len(all) < (pool1Max+pool2Max) then we have room to put pool3 into pool1/pool2 + // // In the above case, transfer from pool3 to pool2. Number of transferred transactions = (pool1Max+pool2Max) - len(all) + // // Pool3 to only pool2 can be transferred. So obviously those transactions are static true. + // // Pool2 to pool1 is unclear + // + // // todo flush from pool3 and if it never gets added to anything then add it back + // // otherwise determine exactly how many transctions we can accommodate and then flush accordingly + // //for _, transaction := range tx { + // txPoolSizeBeforeCurrentTx := uint64(pool.all.Slots()) + // // todo keep extracting from pool3 until txPoolSizeBeforeCurrentTx + numSlots(extractedTransactions) >= (maxPool1Size + maxPool2Size) + // numSlotsExtractedTransactions := 0 + // for txPoolSizeBeforeCurrentTx+uint64(numSlotsExtractedTransactions) < (maxPool1Size + uint64(pool2Size)) { + // tx := pool.localBufferPool.Flush(1) + // if len(tx) == 0 { + // break + // } + // numSlotsExtractedTransactions += numSlots(tx[0]) + // } + // txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx[0])) + // // pool2Size, pool3Size + // var includePool1, includePool2, includePool3 bool + // if txPoolSizeAfterCurrentTx < maxPool1Size { + // includePool1 = true + // } else if (txPoolSizeAfterCurrentTx > maxPool1Size) && (txPoolSizeAfterCurrentTx <= (maxPool1Size + pool2Size)) { + // includePool2 = true + // } else if txPoolSizeAfterCurrentTx > (maxPool1Size+pool2Size) && txPoolSizeAfterCurrentTx < (maxPool1Size+pool2Size+pool3Size) { + // includePool3 = true + // } else { + // return + // } + // + // // already validated by this point + // from, _ := types.Sender(pool.signer, tx[0]) + // + // // use addToPool2OrPool3() function to transfer from pool3 to pool2 + // _, err := pool.addToPool2OrPool3(tx[0], from, includePool1, includePool2, includePool3) + // if err != nil { + // return + // } + //} + //} + //} + + // todo do things in terms of slots rather than number of transactions? + } diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index 0fb69f8934..df6ae8b5f6 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -328,7 +328,7 @@ func (l *list) Contains(nonce uint64) bool { // // If the new transaction is accepted into the list, the lists' cost and gas // thresholds are also potentially updated. -func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { +func (l *list) Add(tx *types.Transaction, priceBump uint64, static bool) (bool, *types.Transaction) { // If there's an older better transaction, abort old := l.txs.Get(tx.Nonce()) if old != nil { @@ -362,7 +362,7 @@ func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transa l.totalcost.Add(l.totalcost, cost) // Otherwise overwrite the old transaction with the current one - l.txs.Put(tx, false) // todo putting false as a placeholder + l.txs.Put(tx, static) // todo putting false as a placeholder if l.costcap.Cmp(cost) < 0 { l.costcap = cost } From 8655d30cb9950977fab8e9a9703ff8618f1dc6d2 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 1 Aug 2024 14:51:18 +0100 Subject: [PATCH 04/57] pool: remove comments, use queue not pool23 --- core/txpool/legacypool/legacypool.go | 106 ++------------------------- 1 file changed, 7 insertions(+), 99 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 5daadff6bc..387b36f0a7 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -896,13 +896,11 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Nonce already pending, check if required price bump is met inserted, old := list.Add(tx, pool.config.PriceBump, includePool2) if !inserted { - addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, false, includePool2, includePool3) - //pendingDiscardMeter.Mark(1) // todo do we need to mark the meter here? - return addedToAnyPool, err + pendingDiscardMeter.Mark(1) + return false, txpool.ErrReplaceUnderpriced } // New transaction is better, replace old one if old != nil { - // todo maybe here we can add the old to pool2 or pool3? pool.all.Remove(old.Hash()) pool.priced.Removed(1) pendingReplaceMeter.Mark(1) @@ -910,7 +908,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.all.Add(tx, isLocal) pool.priced.Put(tx, isLocal) pool.journalTx(from, tx) - pool.queueTxEvent(tx, includePool2) // todo putting includePool2 for now, check and confirm + pool.queueTxEvent(tx, false) // At this point pool1 can incorporate this. So no need for pool2 or pool3 log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -919,9 +917,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.addToPool2OrPool3(tx, from, includePool1, includePool2, includePool3) - - //replaced, err = pool.enqueueTx(hash, tx, isLocal, true) + replaced, err = pool.enqueueTx(hash, tx, isLocal, true) // At this point pool1 can incorporate this. So no need for pool2 or pool3 if err != nil { return false, err } @@ -1696,15 +1692,6 @@ func (pool *LegacyPool) truncatePending() { return } - //var addToPool2OrPool3 bool - // - //// todo maybe check here if the extra numbers are within pool1 and (pool1+pool2) and in that case - //// we can only broadcast to static peers. - //pool1Size := pool.config.GlobalSlots - //if (pending > pool1Size) && pending < (pool1Size+pool2Size) { - // addToPool2OrPool3 = true - //} - pendingBeforeCap := pending // Assemble a spam order to penalize large transactors first spammers := prque.New[int64, common.Address](nil) @@ -2185,41 +2172,6 @@ func (pool *LegacyPool) enqueueTxToCriticalPath(hash common.Hash, tx *types.Tran return old != nil, nil } -//func getPool2Peers(allPeers, staticPeers []Peer) []Peer { -// allWithoutStatic := difference(allPeers, staticPeers) -// randomNonStatic := getRandomPeers(allWithoutStatic, MaxNonStaticPeer) -// return append(staticPeers, randomNonStatic...) -//} -// -//func broadcastToPeers(tx *types.Transaction, peers []Peer) { -// for _, peer := range peers { -// peer.SendTransaction(tx) -// } -//} -// -//func difference(slice1, slice2 []Peer) []Peer { -// m := make(map[Peer]bool) -// for _, item := range slice2 { -// m[item] = true -// } -// var diff []Peer -// for _, item := range slice1 { -// if _, ok := m[item]; !ok { -// diff = append(diff, item) -// } -// } -// return diff -//} -// -//func getRandomPeers(peers []Peer, max int) []Peer { -// rand.Seed(time.Now().UnixNano()) -// rand.Shuffle(len(peers), func(i, j int) { peers[i], peers[j] = peers[j], peers[i] }) -// if len(peers) > max { -// return peers[:max] -// } -// return peers -//} - func (pool *LegacyPool) startPeriodicTransfer() { ticker := time.NewTicker(time.Minute) // Adjust the interval as needed go func() { @@ -2273,53 +2225,9 @@ func (pool *LegacyPool) transferTransactions() { // use addToPool2OrPool3() function to transfer from pool3 to pool2 _, err := pool.addToPool2OrPool3(transaction, from, false, true, false) if err != nil { - return + // if it never gets added to anything then add it back + pool.addToPool2OrPool3(transaction, from, false, false, true) + continue } } - - // // todo logic: If len(pending + queue) or len(all) < (pool1Max+pool2Max) then we have room to put pool3 into pool1/pool2 - // // In the above case, transfer from pool3 to pool2. Number of transferred transactions = (pool1Max+pool2Max) - len(all) - // // Pool3 to only pool2 can be transferred. So obviously those transactions are static true. - // // Pool2 to pool1 is unclear - // - // // todo flush from pool3 and if it never gets added to anything then add it back - // // otherwise determine exactly how many transctions we can accommodate and then flush accordingly - // //for _, transaction := range tx { - // txPoolSizeBeforeCurrentTx := uint64(pool.all.Slots()) - // // todo keep extracting from pool3 until txPoolSizeBeforeCurrentTx + numSlots(extractedTransactions) >= (maxPool1Size + maxPool2Size) - // numSlotsExtractedTransactions := 0 - // for txPoolSizeBeforeCurrentTx+uint64(numSlotsExtractedTransactions) < (maxPool1Size + uint64(pool2Size)) { - // tx := pool.localBufferPool.Flush(1) - // if len(tx) == 0 { - // break - // } - // numSlotsExtractedTransactions += numSlots(tx[0]) - // } - // txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx[0])) - // // pool2Size, pool3Size - // var includePool1, includePool2, includePool3 bool - // if txPoolSizeAfterCurrentTx < maxPool1Size { - // includePool1 = true - // } else if (txPoolSizeAfterCurrentTx > maxPool1Size) && (txPoolSizeAfterCurrentTx <= (maxPool1Size + pool2Size)) { - // includePool2 = true - // } else if txPoolSizeAfterCurrentTx > (maxPool1Size+pool2Size) && txPoolSizeAfterCurrentTx < (maxPool1Size+pool2Size+pool3Size) { - // includePool3 = true - // } else { - // return - // } - // - // // already validated by this point - // from, _ := types.Sender(pool.signer, tx[0]) - // - // // use addToPool2OrPool3() function to transfer from pool3 to pool2 - // _, err := pool.addToPool2OrPool3(tx[0], from, includePool1, includePool2, includePool3) - // if err != nil { - // return - // } - //} - //} - //} - - // todo do things in terms of slots rather than number of transactions? - } From 4538e92dbbb98bacef1ee8226285ae934b06edec Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 1 Aug 2024 14:54:07 +0100 Subject: [PATCH 05/57] pool: remove unused function --- core/txpool/legacypool/legacypool.go | 30 ---------------------------- 1 file changed, 30 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 387b36f0a7..942fee758c 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -2142,36 +2142,6 @@ func numSlots(tx *types.Transaction) int { return int((tx.Size() + txSlotSize - 1) / txSlotSize) } -func (pool *LegacyPool) enqueueTxToCriticalPath(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { - from, _ := types.Sender(pool.signer, tx) - if pool.criticalPathPool[from] == nil { - pool.criticalPathPool[from] = newList(false) - } - inserted, old := pool.criticalPathPool[from].Add(tx, pool.config.PriceBump, false) - if !inserted { - queuedDiscardMeter.Mark(1) - return false, txpool.ErrReplaceUnderpriced - } - if old != nil { - pool.all.Remove(old.Hash()) - pool.priced.Removed(1) - queuedReplaceMeter.Mark(1) - } else { - queuedGauge.Inc(1) - } - if pool.all.Get(hash) == nil && !addAll { - log.Error("Missing transaction in lookup set, please report the issue", "hash", hash) - } - if addAll { - pool.all.Add(tx, local) - pool.priced.Put(tx, local) - } - if _, exist := pool.beats[from]; !exist { - pool.beats[from] = time.Now() - } - return old != nil, nil -} - func (pool *LegacyPool) startPeriodicTransfer() { ticker := time.NewTicker(time.Minute) // Adjust the interval as needed go func() { From 4369e3d997498a04f1b728e691205a176f33f31c Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 1 Aug 2024 15:01:28 +0100 Subject: [PATCH 06/57] pool: refactor and bugfix --- core/txpool/legacypool/legacypool.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 942fee758c..765f070d60 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -56,8 +56,8 @@ const ( // txReannoMaxNum is the maximum number of transactions a reannounce action can include. txReannoMaxNum = 1024 - pool2Size = 10000 // todo might have to set it in config // This is in slots and not in no of transactions - pool3Size = 50000 + maxPool2Size = 10000 // todo might have to set it in config // This is in slots and not in no of transactions + maxPool3Size = 50000 ) var ( @@ -290,7 +290,7 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), criticalPathPool: make(map[common.Address]*list), - localBufferPool: NewLRUBuffer(pool3Size), + localBufferPool: NewLRUBuffer(maxPool3Size), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -769,14 +769,13 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return false, txpool.ErrAlreadyKnown } - pool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue + maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue //txPoolSizeBeforeCurrentTx := uint64(pool.all.Slots()) txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) - // pool2Size, pool3Size var includePool1, includePool2, includePool3 bool - if txPoolSizeAfterCurrentTx < pool1Size { + if txPoolSizeAfterCurrentTx < maxPool1Size { includePool1 = true - } else if (txPoolSizeAfterCurrentTx > pool1Size) && (txPoolSizeAfterCurrentTx <= pool2Size) { + } else if (txPoolSizeAfterCurrentTx > maxPool1Size) && (txPoolSizeAfterCurrentTx <= (maxPool1Size + maxPool2Size)) { includePool2 = true } else { includePool3 = true @@ -819,7 +818,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Slots()+numSlots(tx)) > pool1Size { + if uint64(pool.all.Slots()+numSlots(tx)) > maxPool1Size { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, includePool1, includePool2, includePool3) @@ -2163,8 +2162,8 @@ func (pool *LegacyPool) startPeriodicTransfer() { func (pool *LegacyPool) transferTransactions() { maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue - maxPool1Pool2CombinedSize := maxPool1Size + pool2Size - extraSizePool2Pool1 := uint64(len(pool.pending)) + uint64(len(pool.queue)) - pool2Size - maxPool1Size + maxPool1Pool2CombinedSize := maxPool1Size + maxPool2Size + extraSizePool2Pool1 := uint64(len(pool.pending)) + uint64(len(pool.queue)) - maxPool2Size - maxPool1Size if extraSizePool2Pool1 <= 0 { return } From d0d6a272f05ed2317b4f32de70555c2d8028d8a5 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Fri, 2 Aug 2024 13:03:35 +0100 Subject: [PATCH 07/57] pool: buffer test and size logic --- core/txpool/legacypool/buffer.go | 25 ++++- core/txpool/legacypool/buffer_test.go | 126 ++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 core/txpool/legacypool/buffer_test.go diff --git a/core/txpool/legacypool/buffer.go b/core/txpool/legacypool/buffer.go index ccf9b1161d..cbdba51a2e 100644 --- a/core/txpool/legacypool/buffer.go +++ b/core/txpool/legacypool/buffer.go @@ -13,6 +13,7 @@ type LRUBuffer struct { buffer *containerList.List index map[common.Hash]*containerList.Element mu sync.Mutex + size int // Total number of slots used } func NewLRUBuffer(capacity int) *LRUBuffer { @@ -20,6 +21,7 @@ func NewLRUBuffer(capacity int) *LRUBuffer { capacity: capacity, buffer: containerList.New(), index: make(map[common.Hash]*containerList.Element), + size: 0, // Initialize size to 0 } } @@ -32,14 +34,20 @@ func (lru *LRUBuffer) Add(tx *types.Transaction) { return } - if lru.buffer.Len() >= lru.capacity { + txSlots := numSlots(tx) + + // Remove elements until there is enough capacity + for lru.size+txSlots > lru.capacity && lru.buffer.Len() > 0 { back := lru.buffer.Back() + removedTx := back.Value.(*types.Transaction) lru.buffer.Remove(back) - delete(lru.index, back.Value.(*types.Transaction).Hash()) + delete(lru.index, removedTx.Hash()) + lru.size -= numSlots(removedTx) // Decrease size by the slots of the removed transaction } elem := lru.buffer.PushFront(tx) lru.index[tx.Hash()] = elem + lru.size += txSlots // Increase size by the slots of the new transaction } func (lru *LRUBuffer) Get(hash common.Hash) (*types.Transaction, bool) { @@ -61,10 +69,19 @@ func (lru *LRUBuffer) Flush(maxTransactions int) []*types.Transaction { count := 0 for count < maxTransactions && lru.buffer.Len() > 0 { back := lru.buffer.Back() - txs = append(txs, back.Value.(*types.Transaction)) + removedTx := back.Value.(*types.Transaction) + txs = append(txs, removedTx) lru.buffer.Remove(back) - delete(lru.index, back.Value.(*types.Transaction).Hash()) + delete(lru.index, removedTx.Hash()) + lru.size -= numSlots(removedTx) // Decrease size by the slots of the removed transaction count++ } return txs } + +// New method to get the current size of the buffer in terms of slots +func (lru *LRUBuffer) Size() int { + lru.mu.Lock() + defer lru.mu.Unlock() + return lru.size +} diff --git a/core/txpool/legacypool/buffer_test.go b/core/txpool/legacypool/buffer_test.go new file mode 100644 index 0000000000..49c1b3603a --- /dev/null +++ b/core/txpool/legacypool/buffer_test.go @@ -0,0 +1,126 @@ +package legacypool + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Helper function to create a dummy transaction of specified size +func createDummyTransaction(size int) *types.Transaction { + data := make([]byte, size) + return types.NewTransaction(0, common.Address{}, nil, 0, nil, data) +} + +func TestNewLRUBuffer(t *testing.T) { + capacity := 10 + lru := NewLRUBuffer(capacity) + if lru.capacity != capacity { + t.Errorf("expected capacity %d, got %d", capacity, lru.capacity) + } + if lru.buffer.Len() != 0 { + t.Errorf("expected buffer length 0, got %d", lru.buffer.Len()) + } + if len(lru.index) != 0 { + t.Errorf("expected index length 0, got %d", len(lru.index)) + } + if lru.size != 0 { + t.Errorf("expected size 0, got %d", lru.size) + } +} + +func TestAddAndGet(t *testing.T) { + lru := NewLRUBuffer(10) + + tx1 := createDummyTransaction(500) + tx2 := createDummyTransaction(1500) + + lru.Add(tx1) + lru.Add(tx2) + + if lru.Size() != 2 { + t.Errorf("expected size 2, got %d", lru.Size()) + } + + retrievedTx, ok := lru.Get(tx1.Hash()) + if !ok || retrievedTx.Hash() != tx1.Hash() { + t.Errorf("failed to retrieve tx1") + } + + retrievedTx, ok = lru.Get(tx2.Hash()) + if !ok || retrievedTx.Hash() != tx2.Hash() { + t.Errorf("failed to retrieve tx2") + } +} + +func TestBufferCapacity(t *testing.T) { + lru := NewLRUBuffer(2) // Capacity in slots + + tx1 := createDummyTransaction(500) // 1 slot + tx2 := createDummyTransaction(1500) // 1 slot + tx3 := createDummyTransaction(1000) // 1 slot + + lru.Add(tx1) + lru.Add(tx2) + + if lru.Size() != 2 { + t.Errorf("expected size 2, got %d", lru.Size()) + } + + lru.Add(tx3) + + if lru.Size() != 2 { + t.Errorf("expected size 2 after adding tx3, got %d", lru.Size()) + } + + if _, ok := lru.Get(tx1.Hash()); ok { + t.Errorf("expected tx1 to be evicted") + } +} + +func TestFlush(t *testing.T) { + lru := NewLRUBuffer(10) + + tx1 := createDummyTransaction(500) + tx2 := createDummyTransaction(1500) + tx3 := createDummyTransaction(1000) + + lru.Add(tx1) + lru.Add(tx2) + lru.Add(tx3) + + flushedTxs := lru.Flush(2) + + if len(flushedTxs) != 2 { + t.Errorf("expected to flush 2 transactions, got %d", len(flushedTxs)) + } + + expectedSize := 1 + actualSize := lru.Size() + if expectedSize != actualSize { + t.Errorf("expected size after flush %d, got %d", expectedSize, actualSize) + } +} + +func TestSize(t *testing.T) { + lru := NewLRUBuffer(10) + + tx1 := createDummyTransaction(500) // 1 slot + tx2 := createDummyTransaction(1500) // 2 slots + + lru.Add(tx1) + if lru.Size() != 1 { + t.Errorf("expected size 1, got %d", lru.Size()) + } + + lru.Add(tx2) + if lru.Size() != 2 { + t.Errorf("expected size 2, got %d", lru.Size()) + } + + lru.Flush(1) + if lru.Size() != 1 { + t.Errorf("expected size 1 after flush, got %d", lru.Size()) + } +} From 5faf41359ae489faa61451e05e10178190cc0425 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Fri, 2 Aug 2024 13:05:05 +0100 Subject: [PATCH 08/57] pool: add discarded ones to pool3 by default. --- core/txpool/legacypool/legacypool.go | 58 ++++++++++++++++++++++++---- core/txpool/legacypool/list_test.go | 8 ++-- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 765f070d60..254d5232ae 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -821,14 +821,14 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e if uint64(pool.all.Slots()+numSlots(tx)) > maxPool1Size { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { - addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, includePool1, includePool2, includePool3) + addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) if !addedToAnyPool { log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) underpricedTxMeter.Mark(1) return false, err //return false, txpool.ErrUnderpriced } - return true, nil + return false, nil // since it is not replacing a transaction } @@ -872,14 +872,35 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.priced.Put(dropTx, false) } log.Trace("Discarding future transaction replacing pending tx", "hash", hash) - return false, txpool.ErrFutureReplacePending + return false, txpool.ErrFutureReplacePending // todo 1 maybe in this case the future transaction can be part of pool3! } } + // todo calculate total number of slots in drop. Accordingly add them to pool3 (if there is space) or actually drop them + // helper function for current availability in any pool + availableSlotsPool3 := pool.availableSlotsPool3() + var remainingDrop []*types.Transaction + if availableSlotsPool3 > 0 { + // transfer availableSlotsPool3 number of transactions slots from drop to pool3. Actually drop the rest + currentSlotsUsed := 0 + for _, tx := range drop { + txSlots := numSlots(tx) + if currentSlotsUsed+txSlots <= availableSlotsPool3 { + from, _ := types.Sender(pool.signer, tx) + pool.addToPool2OrPool3(tx, from, isLocal, false, false, true) + currentSlotsUsed += txSlots + } else { + remainingDrop = append(remainingDrop, tx) + } + } + + } + // Kick out the underpriced remote transactions. - for _, tx := range drop { - // todo pool2 or pool3. Because the new tx is better than transactions of drop but drop transactions still + for _, tx := range remainingDrop { + // todo 2 pool2 or pool3. Because the new tx is better than transactions of drop but drop transactions still // deserve to be part of at least pool2 or pool3. + // At this point if remainingDrop is non nil then pool3 is full. So these will need to be deleted anyway log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) underpricedTxMeter.Mark(1) @@ -937,13 +958,18 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // addToPool2OrPool3 adds a transaction to pool1 or pool2 or pool3 depending on which one is asked for -func (pool *LegacyPool) addToPool2OrPool3(tx *types.Transaction, from common.Address, pool1, pool2, pool3 bool) (bool, error) { +func (pool *LegacyPool) addToPool2OrPool3(tx *types.Transaction, from common.Address, isLocal bool, pool1, pool2, pool3 bool) (bool, error) { if pool1 { // todo (check) logic for pool1 related pool.all.Add(tx, pool2) pool.priced.Put(tx, pool2) pool.journalTx(from, tx) pool.queueTxEvent(tx, false) + // todo check if enqueueTx should be used + _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true) // At this point pool1 can incorporate this. So no need for pool2 or pool3 + if err != nil { + return false, err + } log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -2192,11 +2218,27 @@ func (pool *LegacyPool) transferTransactions() { from, _ := types.Sender(pool.signer, transaction) // use addToPool2OrPool3() function to transfer from pool3 to pool2 - _, err := pool.addToPool2OrPool3(transaction, from, false, true, false) + _, err := pool.addToPool2OrPool3(transaction, from, true, false, true, false) // todo by default all pool3 transactions are considered local if err != nil { // if it never gets added to anything then add it back - pool.addToPool2OrPool3(transaction, from, false, false, true) + pool.addToPool2OrPool3(transaction, from, true, false, false, true) continue } } } + +func (pool *LegacyPool) availableSlotsPool3() int { + availableSlots := maxPool3Size - pool.localBufferPool.Size() + if availableSlots > 0 { + return availableSlots + } + return 0 +} + +//func (pool *LegacyPool) availableSlotsPool2() uin64 { +// availableSlots := maxPool2Size - pool.localBufferPool.Size() +// if availableSlots > 0 { +// return availableSlots +// } +// return 0 +//} diff --git a/core/txpool/legacypool/list_test.go b/core/txpool/legacypool/list_test.go index 8587c66f7d..4411d788f2 100644 --- a/core/txpool/legacypool/list_test.go +++ b/core/txpool/legacypool/list_test.go @@ -40,7 +40,7 @@ func TestStrictListAdd(t *testing.T) { // Insert the transactions in a random order list := newList(true) for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultConfig.PriceBump) + list.Add(txs[v], DefaultConfig.PriceBump, false) } // Verify internal state if len(list.txs.items) != len(txs) { @@ -64,7 +64,7 @@ func TestListAddVeryExpensive(t *testing.T) { gaslimit := uint64(i) tx, _ := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, value, gaslimit, gasprice, nil), types.HomesteadSigner{}, key) t.Logf("cost: %x bitlen: %d\n", tx.Cost(), tx.Cost().BitLen()) - list.Add(tx, DefaultConfig.PriceBump) + list.Add(tx, DefaultConfig.PriceBump, false) } } @@ -82,7 +82,7 @@ func BenchmarkListAdd(b *testing.B) { for i := 0; i < b.N; i++ { list := newList(true) for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultConfig.PriceBump) + list.Add(txs[v], DefaultConfig.PriceBump, false) list.Filter(priceLimit, DefaultConfig.PriceBump) } } @@ -102,7 +102,7 @@ func BenchmarkListCapOneTx(b *testing.B) { list := newList(true) // Insert the transactions in a random order for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultConfig.PriceBump) + list.Add(txs[v], DefaultConfig.PriceBump, false) } b.StartTimer() list.Cap(list.Len() - 1) From 5bb78b34be1bd10fe6b165fcc578021312e6c76c Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 20 Aug 2024 07:46:22 +0100 Subject: [PATCH 09/57] pool: minor refactor --- core/txpool/legacypool/legacypool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 254d5232ae..3904541d2d 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -2189,7 +2189,7 @@ func (pool *LegacyPool) transferTransactions() { maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue maxPool1Pool2CombinedSize := maxPool1Size + maxPool2Size - extraSizePool2Pool1 := uint64(len(pool.pending)) + uint64(len(pool.queue)) - maxPool2Size - maxPool1Size + extraSizePool2Pool1 := uint64(len(pool.pending)) + uint64(len(pool.queue)) - maxPool1Pool2CombinedSize if extraSizePool2Pool1 <= 0 { return } From 6b4e16ba7b69bc8105339003cb2a790e7d897249 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 21 Aug 2024 15:50:59 +0100 Subject: [PATCH 10/57] pool: make slots config --- core/txpool/legacypool/legacypool.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 3904541d2d..93ae3c361f 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -140,6 +140,8 @@ type Config struct { GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts + Pool2Slots uint64 + Pool3Slots uint64 Lifetime time.Duration // Maximum amount of time non-executable transaction are queued ReannounceTime time.Duration // Duration for announcing local pending transactions again @@ -272,6 +274,7 @@ type txpoolResetRequest struct { func New(config Config, chain BlockChain) *LegacyPool { // Sanitize the input to ensure no vulnerable gas prices are set config = (&config).sanitize() + maxPool3Size := config.Pool3Slots // Create the transaction pool with its initial settings pool := &LegacyPool{ @@ -290,7 +293,7 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), criticalPathPool: make(map[common.Address]*list), - localBufferPool: NewLRUBuffer(maxPool3Size), + localBufferPool: NewLRUBuffer(int(maxPool3Size)), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -770,6 +773,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue + maxPool2Size := pool.config.Pool2Slots //txPoolSizeBeforeCurrentTx := uint64(pool.all.Slots()) txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) var includePool1, includePool2, includePool3 bool @@ -818,9 +822,9 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Slots()+numSlots(tx)) > maxPool1Size { + if uint64(pool.all.Slots()+numSlots(tx)) > maxPool1Size { // todo 3 maybe a check here for pool2? // try addToPool2OrPool3() and only if unsuccessful then do the other things!!! // If the new transaction is underpriced, don't accept it - if !isLocal && pool.priced.Underpriced(tx) { + if true || (!isLocal && pool.priced.Underpriced(tx)) { addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) if !addedToAnyPool { log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) @@ -2188,6 +2192,7 @@ func (pool *LegacyPool) startPeriodicTransfer() { func (pool *LegacyPool) transferTransactions() { maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue + maxPool2Size := pool.config.Pool2Slots maxPool1Pool2CombinedSize := maxPool1Size + maxPool2Size extraSizePool2Pool1 := uint64(len(pool.pending)) + uint64(len(pool.queue)) - maxPool1Pool2CombinedSize if extraSizePool2Pool1 <= 0 { @@ -2228,6 +2233,7 @@ func (pool *LegacyPool) transferTransactions() { } func (pool *LegacyPool) availableSlotsPool3() int { + maxPool3Size := int(pool.config.Pool3Slots) availableSlots := maxPool3Size - pool.localBufferPool.Size() if availableSlots > 0 { return availableSlots From d03f7e5e944e025f13345758109ceaddab362de6 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 21 Aug 2024 15:51:21 +0100 Subject: [PATCH 11/57] pool: initialise pool3 slots --- core/txpool/legacypool/legacypool_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 5f7a625f13..5954783313 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2032,11 +2032,14 @@ func TestUnderpricingDynamicFee(t *testing.T) { func TestDualHeapEviction(t *testing.T) { t.Parallel() + testTxPoolConfig.Pool3Slots = 5 pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() pool.config.GlobalSlots = 10 pool.config.GlobalQueue = 10 + pool.config.Pool2Slots = 5 + pool.config.Pool3Slots = 5 var ( highTip, highCap *types.Transaction @@ -2050,7 +2053,7 @@ func TestDualHeapEviction(t *testing.T) { } add := func(urgent bool) { - for i := 0; i < 20; i++ { + for i := 0; i < 25; i++ { var tx *types.Transaction // Create a test accounts and fund it key, _ := crypto.GenerateKey() @@ -2066,7 +2069,7 @@ func TestDualHeapEviction(t *testing.T) { } pending, queued := pool.Stats() if pending+queued != 20 { - t.Fatalf("transaction count mismatch: have %d, want %d", pending+queued, 10) + t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d", pending+queued, 10, pending, queued) } } From 94a60a9006c7334fab9a3807cfd709cb8e0155b0 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Fri, 23 Aug 2024 09:11:59 +0100 Subject: [PATCH 12/57] pool: add underpriced to pool2 or 3 --- core/txpool/legacypool/legacypool.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 93ae3c361f..b7d085c7f8 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -822,18 +822,23 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Slots()+numSlots(tx)) > maxPool1Size { // todo 3 maybe a check here for pool2? // try addToPool2OrPool3() and only if unsuccessful then do the other things!!! + if uint64(pool.all.Slots()+numSlots(tx)) > maxPool1Size { + // todo 3 -> done maybe a check here for pool2? // try addToPool2OrPool3() and only if unsuccessful then do the other things!!! + // Try adding to pool2 or pool3 first // If the new transaction is underpriced, don't accept it - if true || (!isLocal && pool.priced.Underpriced(tx)) { + // todo 5 actually if underpriced then add to pool2 or 3. Otherwise find out which one will be replaced with and that one will go to pool2 or 3 + if !isLocal && pool.priced.Underpriced(tx) { addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) - if !addedToAnyPool { - log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) - underpricedTxMeter.Mark(1) - return false, err - //return false, txpool.ErrUnderpriced + if addedToAnyPool { + return false, nil + } + if err != nil { + log.Error("Error while trying to add to pool2 or pool3", "error", err) } - return false, nil // since it is not replacing a transaction + log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) + underpricedTxMeter.Mark(1) + return false, txpool.ErrUnderpriced } // We're about to replace a transaction. The reorg does a more thorough @@ -915,7 +920,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } } - // Try to replace an existing transaction in the pending pool + // Try to replace an existing transaction in the pending pool // todo 4 why we are replacing a pending tx if the we can put in pool3 or so? if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { // Nonce already pending, check if required price bump is met inserted, old := list.Add(tx, pool.config.PriceBump, includePool2) From ed2d1d7f8f440ccc51d375a024168d84aaf646df Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Sun, 25 Aug 2024 19:24:20 +0100 Subject: [PATCH 13/57] pool: enqueue in pool2 & drop properly --- core/txpool/legacypool/buffer.go | 24 ++++++++ core/txpool/legacypool/legacypool.go | 41 ++++++++++---- core/txpool/legacypool/legacypool_test.go | 67 ++++++++++++++++------- 3 files changed, 101 insertions(+), 31 deletions(-) diff --git a/core/txpool/legacypool/buffer.go b/core/txpool/legacypool/buffer.go index cbdba51a2e..78a54f98f2 100644 --- a/core/txpool/legacypool/buffer.go +++ b/core/txpool/legacypool/buffer.go @@ -2,6 +2,7 @@ package legacypool import ( containerList "container/list" + "fmt" "sync" "github.com/ethereum/go-ethereum/common" @@ -85,3 +86,26 @@ func (lru *LRUBuffer) Size() int { defer lru.mu.Unlock() return lru.size } + +// New iterator method to iterate over all transactions +func (lru *LRUBuffer) Iterate() <-chan *types.Transaction { + ch := make(chan *types.Transaction) + go func() { + lru.mu.Lock() + defer lru.mu.Unlock() + defer close(ch) + + for e := lru.buffer.Front(); e != nil; e = e.Next() { + ch <- e.Value.(*types.Transaction) + } + }() + return ch +} + +func (lru *LRUBuffer) PrintTxStats() { + // Iterating over the transactions + for tx := range lru.Iterate() { + // Print transaction details or process them as needed + fmt.Println(tx.Hash().String(), tx.GasFeeCap().String(), tx.GasTipCap().String()) + } +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b7d085c7f8..8858e35ba4 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -19,6 +19,7 @@ package legacypool import ( "errors" + "fmt" "math" "math/big" "sort" @@ -892,13 +893,13 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e if availableSlotsPool3 > 0 { // transfer availableSlotsPool3 number of transactions slots from drop to pool3. Actually drop the rest currentSlotsUsed := 0 - for _, tx := range drop { + for _, tx := range drop { // todo 6 maybe heapify this so that important txs from drop are included first! -> may not be necessary txSlots := numSlots(tx) if currentSlotsUsed+txSlots <= availableSlotsPool3 { from, _ := types.Sender(pool.signer, tx) pool.addToPool2OrPool3(tx, from, isLocal, false, false, true) currentSlotsUsed += txSlots - } else { + //} else { remainingDrop = append(remainingDrop, tx) } } @@ -946,7 +947,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.enqueueTx(hash, tx, isLocal, true) // At this point pool1 can incorporate this. So no need for pool2 or pool3 + replaced, err = pool.enqueueTx(hash, tx, isLocal, true, includePool2) // At this point pool1 can incorporate this. So no need for pool2 or pool3 if err != nil { return false, err } @@ -962,6 +963,10 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } pool.journalTx(from, tx) + if len(pool.pending) > int(maxPool1Size+maxPool2Size) { + fmt.Println("pending size exceeded") + } + log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) return replaced, nil } @@ -975,7 +980,7 @@ func (pool *LegacyPool) addToPool2OrPool3(tx *types.Transaction, from common.Add pool.journalTx(from, tx) pool.queueTxEvent(tx, false) // todo check if enqueueTx should be used - _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true) // At this point pool1 can incorporate this. So no need for pool2 or pool3 + _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true, false) // At this point pool1 can incorporate this. So no need for pool2 or pool3 if err != nil { return false, err } @@ -987,11 +992,15 @@ func (pool *LegacyPool) addToPool2OrPool3(tx *types.Transaction, from common.Add return true, nil } if pool2 { - // todo (check) logic for pool2 related + // todo (check) logic for pool2 related , should we put enqueueTx()?? -> Yes we should pool.all.Add(tx, pool2) pool.priced.Put(tx, pool2) pool.journalTx(from, tx) pool.queueTxEvent(tx, true) + _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true, true) + if err != nil { + return false, err + } log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -1033,13 +1042,13 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo // enqueueTx inserts a new transaction into the non-executable transaction queue. // // Note, this method assumes the pool lock is held! -func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { +func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool, static bool) (bool, error) { // Try to insert the transaction into the future queue from, _ := types.Sender(pool.signer, tx) // already validated if pool.queue[from] == nil { pool.queue[from] = newList(false) } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, false) + inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, static) if !inserted { // An older transaction was better, discard this queuedDiscardMeter.Mark(1) @@ -1321,7 +1330,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo // Postpone any invalidated transactions for _, tx := range invalids { // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(tx.Hash(), tx, false, false) + pool.enqueueTx(tx.Hash(), tx, false, false, false) } // Update the account nonce if needed pool.pendingNonces.setIfLower(addr, tx.Nonce()) @@ -1880,7 +1889,7 @@ func (pool *LegacyPool) demoteUnexecutables() { log.Trace("Demoting pending transaction", "hash", hash) // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(hash, tx, false, false) + pool.enqueueTx(hash, tx, false, false, false) } pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) if pool.locals.contains(addr) { @@ -1894,7 +1903,7 @@ func (pool *LegacyPool) demoteUnexecutables() { log.Error("Demoting invalidated transaction", "hash", hash) // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(hash, tx, false, false) + pool.enqueueTx(hash, tx, false, false, false) } pendingGauge.Dec(int64(len(gapped))) } @@ -2253,3 +2262,15 @@ func (pool *LegacyPool) availableSlotsPool3() int { // } // return 0 //} + +func (pool *LegacyPool) printTxStats() { + + for _, l := range pool.pending { + for _, transaction := range l.txs.items { + fmt.Println("Pending:", transaction.Hash().String(), transaction.GasFeeCap(), transaction.GasTipCap()) + } + } + + pool.localBufferPool.PrintTxStats() + fmt.Println("----------------------------------------------------") +} diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 5954783313..ce5770cce3 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -366,7 +366,7 @@ func TestQueue(t *testing.T) { testAddBalance(pool, from, big.NewInt(1000)) <-pool.requestReset(nil, nil) - pool.enqueueTx(tx.Hash(), tx, false, true) + pool.enqueueTx(tx.Hash(), tx, false, true, false) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) if len(pool.pending) != 1 { t.Error("expected valid txs to be 1 is", len(pool.pending)) @@ -375,7 +375,7 @@ func TestQueue(t *testing.T) { tx = transaction(1, 100, key) from, _ = deriveSender(tx) testSetNonce(pool, from, 2) - pool.enqueueTx(tx.Hash(), tx, false, true) + pool.enqueueTx(tx.Hash(), tx, false, true, false) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { @@ -399,9 +399,9 @@ func TestQueue2(t *testing.T) { testAddBalance(pool, from, big.NewInt(1000)) pool.reset(nil, nil) - pool.enqueueTx(tx1.Hash(), tx1, false, true) - pool.enqueueTx(tx2.Hash(), tx2, false, true) - pool.enqueueTx(tx3.Hash(), tx3, false, true) + pool.enqueueTx(tx1.Hash(), tx1, false, true, false) + pool.enqueueTx(tx2.Hash(), tx2, false, true, false) + pool.enqueueTx(tx3.Hash(), tx3, false, true, false) pool.promoteExecutables([]common.Address{from}) if len(pool.pending) != 1 { @@ -619,9 +619,9 @@ func TestDropping(t *testing.T) { pool.priced.Put(tx2, false) pool.promoteTx(account, tx2.Hash(), tx2) - pool.enqueueTx(tx10.Hash(), tx10, false, true) - pool.enqueueTx(tx11.Hash(), tx11, false, true) - pool.enqueueTx(tx12.Hash(), tx12, false, true) + pool.enqueueTx(tx10.Hash(), tx10, false, true, false) + pool.enqueueTx(tx11.Hash(), tx11, false, true, false) + pool.enqueueTx(tx12.Hash(), tx12, false, true, false) // Check that pre and post validations leave the pool as is if pool.pending[account].Len() != 3 { @@ -1739,6 +1739,7 @@ func TestRepricingKeepsLocals(t *testing.T) { // Note, local transactions are never allowed to be dropped. func TestUnderpricing(t *testing.T) { t.Parallel() + testTxPoolConfig.Pool3Slots = 5 // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) @@ -1792,7 +1793,8 @@ func TestUnderpricing(t *testing.T) { } // Ensure that adding an underpriced transaction on block limit fails if err := pool.addRemoteSync(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) + // todo + //t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } // Replace a future transaction with a future transaction if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(2), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1 @@ -1810,17 +1812,21 @@ func TestUnderpricing(t *testing.T) { } // Ensure that replacing a pending transaction with a future transaction fails if err := pool.addRemote(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != txpool.ErrFutureReplacePending { - t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending) + // todo + //t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending) } pending, queued = pool.Stats() if pending != 2 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + // todo + //t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } if queued != 2 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + // todo + //t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } if err := validateEvents(events, 2); err != nil { - t.Fatalf("additional event firing failed: %v", err) + // todo + //t.Fatalf("additional event firing failed: %v", err) } if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) @@ -2036,9 +2042,9 @@ func TestDualHeapEviction(t *testing.T) { pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() - pool.config.GlobalSlots = 10 - pool.config.GlobalQueue = 10 - pool.config.Pool2Slots = 5 + pool.config.GlobalSlots = 2 + pool.config.GlobalQueue = 2 + pool.config.Pool2Slots = 1 pool.config.Pool3Slots = 5 var ( @@ -2048,37 +2054,56 @@ func TestDualHeapEviction(t *testing.T) { check := func(tx *types.Transaction, name string) { if pool.all.GetRemote(tx.Hash()) == nil { - t.Fatalf("highest %s transaction evicted from the pool", name) + fmt.Println(highCap.GasFeeCap().String(), highCap.GasTipCap().String(), highTip.GasFeeCap().String(), highTip.GasTipCap().String(), len(pool.pending), len(pool.queue), pool.localBufferPool.size) + t.Fatalf("highest %s transaction evicted from the pool, gasPrice: %s", name, highTip.GasPrice().String()) } } add := func(urgent bool) { - for i := 0; i < 25; i++ { + for i := 0; i < 5; i++ { + fmt.Println("i= ", i) var tx *types.Transaction // Create a test accounts and fund it key, _ := crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000000)) if urgent { + fmt.Printf("i = %d gasFee: %v \n", i, int64(baseFee+1+i)) tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+1+i)), big.NewInt(int64(1+i)), key) highTip = tx } else { + fmt.Printf("i = %d gasFee: %v \n", i, int64(baseFee+200+i)) tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) highCap = tx } pool.addRemotesSync([]*types.Transaction{tx}) + pool.printTxStats() + if len(pool.pending) > 5 { + fmt.Println("pending has more than 5 elements, i: ", i) + } } pending, queued := pool.Stats() - if pending+queued != 20 { - t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d", pending+queued, 10, pending, queued) + if pending+queued != 5 { + //t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, pool3 %d", pending+queued, 10, pending, queued, pool.localBufferPool.size) } } + fmt.Println("add called: 1") add(false) for baseFee = 0; baseFee <= 1000; baseFee += 100 { + //fmt.Println(highCap.GasPrice().String()) + //fmt.Println("about to check: 0") + //check(highCap, "fee cap") + pool.priced.SetBaseFee(big.NewInt(int64(baseFee))) + fmt.Println("add called: 2") add(true) + + fmt.Println("about to check: 1", highCap.GasFeeCap().String(), highCap.GasTipCap().String()) check(highCap, "fee cap") + fmt.Println("add called: 3") add(false) + + fmt.Println("about to check: 2") check(highTip, "effective tip") } @@ -2616,7 +2641,7 @@ func benchmarkFuturePromotion(b *testing.B, size int) { for i := 0; i < size; i++ { tx := transaction(uint64(1+i), 100000, key) - pool.enqueueTx(tx.Hash(), tx, false, true) + pool.enqueueTx(tx.Hash(), tx, false, true, false) } // Benchmark the speed of pool validation b.ResetTimer() From 6daecfb105f11de1562017970d28949192515d0a Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 27 Aug 2024 07:24:00 +0100 Subject: [PATCH 14/57] pool: bugfix:always drop drop and pool2 size --- core/txpool/legacypool/legacypool.go | 42 ++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 8858e35ba4..edaf9f3f00 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -57,8 +57,8 @@ const ( // txReannoMaxNum is the maximum number of transactions a reannounce action can include. txReannoMaxNum = 1024 - maxPool2Size = 10000 // todo might have to set it in config // This is in slots and not in no of transactions - maxPool3Size = 50000 + //maxPool2Size = 10000 // todo might have to set it in config // This is in slots and not in no of transactions + //maxPool3Size = 50000 ) var ( @@ -778,7 +778,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e //txPoolSizeBeforeCurrentTx := uint64(pool.all.Slots()) txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) var includePool1, includePool2, includePool3 bool - if txPoolSizeAfterCurrentTx < maxPool1Size { + if txPoolSizeAfterCurrentTx <= maxPool1Size { includePool1 = true } else if (txPoolSizeAfterCurrentTx > maxPool1Size) && (txPoolSizeAfterCurrentTx <= (maxPool1Size + maxPool2Size)) { includePool2 = true @@ -823,13 +823,13 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Slots()+numSlots(tx)) > maxPool1Size { - // todo 3 -> done maybe a check here for pool2? // try addToPool2OrPool3() and only if unsuccessful then do the other things!!! + if uint64(pool.all.Slots()+numSlots(tx)) > (maxPool1Size + maxPool2Size) { + // todo 3 -> done maybe a check here for pool2? // try addToPool12OrPool3() and only if unsuccessful then do the other things!!! // Try adding to pool2 or pool3 first // If the new transaction is underpriced, don't accept it // todo 5 actually if underpriced then add to pool2 or 3. Otherwise find out which one will be replaced with and that one will go to pool2 or 3 if !isLocal && pool.priced.Underpriced(tx) { - addedToAnyPool, err := pool.addToPool2OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) + addedToAnyPool, err := pool.addToPool12OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) if addedToAnyPool { return false, nil } @@ -856,7 +856,13 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Otherwise if we can't make enough room for new one, abort the operation. // todo maybe we don't need to Discard if we can include it in pool2 or pool3 // OR get the discarded transactions and make them part of pool2 or pool3 - drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) + toBeDiscarded := pool.all.Slots() - int(pool.config.GlobalSlots+pool.config.GlobalQueue+pool.config.Pool2Slots) + numSlots(tx) + drop, success := pool.priced.Discard(toBeDiscarded, isLocal) + //if !success { + // // If this Discard fails then we can try at least replacing the slots which is the tx worth so that we may still be over the limit but we can accommodate a deserving transaction + // toBeDiscarded = numSlots(tx) + // drop, success = pool.priced.Discard(toBeDiscarded, isLocal) + //} // Special case, we still can't make the room for the new remote one. if !isLocal && !success { @@ -897,17 +903,24 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e txSlots := numSlots(tx) if currentSlotsUsed+txSlots <= availableSlotsPool3 { from, _ := types.Sender(pool.signer, tx) - pool.addToPool2OrPool3(tx, from, isLocal, false, false, true) + added, _ := pool.addToPool12OrPool3(tx, from, isLocal, false, false, true) // todo once they are removed they should be deleted from the pool1/2 because drop HAS to be dropped from pool1/2 irrespective of if they get added to pool3 or not! currentSlotsUsed += txSlots //} else { + //remainingDrop = append(remainingDrop, tx) + if !added { + remainingDrop = append(remainingDrop, tx) + } + } else { remainingDrop = append(remainingDrop, tx) } } + } else { + remainingDrop = drop } // Kick out the underpriced remote transactions. - for _, tx := range remainingDrop { + for _, tx := range drop { // todo 2 pool2 or pool3. Because the new tx is better than transactions of drop but drop transactions still // deserve to be part of at least pool2 or pool3. // At this point if remainingDrop is non nil then pool3 is full. So these will need to be deleted anyway @@ -971,8 +984,8 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return replaced, nil } -// addToPool2OrPool3 adds a transaction to pool1 or pool2 or pool3 depending on which one is asked for -func (pool *LegacyPool) addToPool2OrPool3(tx *types.Transaction, from common.Address, isLocal bool, pool1, pool2, pool3 bool) (bool, error) { +// addToPool12OrPool3 adds a transaction to pool1 or pool2 or pool3 depending on which one is asked for +func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Address, isLocal bool, pool1, pool2, pool3 bool) (bool, error) { if pool1 { // todo (check) logic for pool1 related pool.all.Add(tx, pool2) @@ -2236,11 +2249,11 @@ func (pool *LegacyPool) transferTransactions() { } from, _ := types.Sender(pool.signer, transaction) - // use addToPool2OrPool3() function to transfer from pool3 to pool2 - _, err := pool.addToPool2OrPool3(transaction, from, true, false, true, false) // todo by default all pool3 transactions are considered local + // use addToPool12OrPool3() function to transfer from pool3 to pool2 + _, err := pool.addToPool12OrPool3(transaction, from, true, false, true, false) // todo by default all pool3 transactions are considered local if err != nil { // if it never gets added to anything then add it back - pool.addToPool2OrPool3(transaction, from, true, false, false, true) + pool.addToPool12OrPool3(transaction, from, true, false, false, true) continue } } @@ -2272,5 +2285,6 @@ func (pool *LegacyPool) printTxStats() { } pool.localBufferPool.PrintTxStats() + fmt.Println("length of all: ", pool.all.Slots()) fmt.Println("----------------------------------------------------") } From 9d7298f0c1430dfb5fd629a292df0396ff5e11de Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 27 Aug 2024 07:33:23 +0100 Subject: [PATCH 15/57] pool: TestDualHeapEviction passing partly --- core/txpool/legacypool/legacypool_test.go | 24 ++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index ce5770cce3..44540b52cc 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2050,12 +2050,15 @@ func TestDualHeapEviction(t *testing.T) { var ( highTip, highCap *types.Transaction baseFee int + highCapValue int64 + highTipValue int64 ) check := func(tx *types.Transaction, name string) { if pool.all.GetRemote(tx.Hash()) == nil { fmt.Println(highCap.GasFeeCap().String(), highCap.GasTipCap().String(), highTip.GasFeeCap().String(), highTip.GasTipCap().String(), len(pool.pending), len(pool.queue), pool.localBufferPool.size) - t.Fatalf("highest %s transaction evicted from the pool, gasPrice: %s", name, highTip.GasPrice().String()) + pool.printTxStats() + t.Fatalf("highest %s transaction evicted from the pool, gasTip: %s, gasFeeCap: %s, hash: %s", name, highTip.GasTipCap().String(), highCap.GasFeeCap().String(), tx.Hash().String()) } } @@ -2069,11 +2072,22 @@ func TestDualHeapEviction(t *testing.T) { if urgent { fmt.Printf("i = %d gasFee: %v \n", i, int64(baseFee+1+i)) tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+1+i)), big.NewInt(int64(1+i)), key) - highTip = tx + if int64(1+i) > highTipValue || (int64(1+i) == highTipValue && int64(baseFee+1+i) > highTip.GasFeeCap().Int64()) { + fmt.Println("highTip updated. tip=", int64(1+i), highTipValue) + highTipValue = int64(1 + i) + highTip = tx + } + //highTip = tx } else { fmt.Printf("i = %d gasFee: %v \n", i, int64(baseFee+200+i)) tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) - highCap = tx + if int64(baseFee+200+i) > highCapValue { + fmt.Println("highCap updated. gasFee=", int64(baseFee+200+i), highCapValue) + highCapValue = int64(baseFee + 200 + i) + highCap = tx + } + //highCap = tx + //fmt.Println("highCap updated. gasFee=", int64(baseFee+200+i)) } pool.addRemotesSync([]*types.Transaction{tx}) pool.printTxStats() @@ -2082,7 +2096,7 @@ func TestDualHeapEviction(t *testing.T) { } } pending, queued := pool.Stats() - if pending+queued != 5 { + if !(pending+queued == 5 || pending+queued == 4) { //t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, pool3 %d", pending+queued, 10, pending, queued, pool.localBufferPool.size) } } @@ -2095,7 +2109,7 @@ func TestDualHeapEviction(t *testing.T) { //check(highCap, "fee cap") pool.priced.SetBaseFee(big.NewInt(int64(baseFee))) - fmt.Println("add called: 2") + fmt.Println("add called: 2, baseFee=", baseFee) add(true) fmt.Println("about to check: 1", highCap.GasFeeCap().String(), highCap.GasTipCap().String()) From a1a25e91a4cac7f326b378c397354c5c01c5f9f6 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 27 Aug 2024 07:38:02 +0100 Subject: [PATCH 16/57] pool: TestDualHeapEviction fully pass --- core/txpool/legacypool/legacypool_test.go | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 44540b52cc..c88be3a996 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2077,47 +2077,33 @@ func TestDualHeapEviction(t *testing.T) { highTipValue = int64(1 + i) highTip = tx } - //highTip = tx } else { fmt.Printf("i = %d gasFee: %v \n", i, int64(baseFee+200+i)) tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) if int64(baseFee+200+i) > highCapValue { - fmt.Println("highCap updated. gasFee=", int64(baseFee+200+i), highCapValue) + //fmt.Println("highCap updated. gasFee=", int64(baseFee+200+i), highCapValue) highCapValue = int64(baseFee + 200 + i) highCap = tx } - //highCap = tx - //fmt.Println("highCap updated. gasFee=", int64(baseFee+200+i)) } pool.addRemotesSync([]*types.Transaction{tx}) - pool.printTxStats() + //pool.printTxStats() if len(pool.pending) > 5 { fmt.Println("pending has more than 5 elements, i: ", i) } } pending, queued := pool.Stats() - if !(pending+queued == 5 || pending+queued == 4) { - //t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, pool3 %d", pending+queued, 10, pending, queued, pool.localBufferPool.size) + if pending+queued != 5 { + t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, pool3 %d", pending+queued, 5, pending, queued, pool.localBufferPool.size) } } - fmt.Println("add called: 1") add(false) for baseFee = 0; baseFee <= 1000; baseFee += 100 { - //fmt.Println(highCap.GasPrice().String()) - //fmt.Println("about to check: 0") - //check(highCap, "fee cap") - pool.priced.SetBaseFee(big.NewInt(int64(baseFee))) - fmt.Println("add called: 2, baseFee=", baseFee) add(true) - - fmt.Println("about to check: 1", highCap.GasFeeCap().String(), highCap.GasTipCap().String()) check(highCap, "fee cap") - fmt.Println("add called: 3") add(false) - - fmt.Println("about to check: 2") check(highTip, "effective tip") } From 253d9a566bf76bd8385e8fc13bf685c5350e3ce5 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 27 Aug 2024 07:58:38 +0100 Subject: [PATCH 17/57] pool: some cleanups --- core/txpool/legacypool/legacypool.go | 48 +++++------------------ core/txpool/legacypool/legacypool_test.go | 5 +-- 2 files changed, 11 insertions(+), 42 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index edaf9f3f00..3359727ab3 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -775,7 +775,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue maxPool2Size := pool.config.Pool2Slots - //txPoolSizeBeforeCurrentTx := uint64(pool.all.Slots()) txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) var includePool1, includePool2, includePool3 bool if txPoolSizeAfterCurrentTx <= maxPool1Size { @@ -823,11 +822,8 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Slots()+numSlots(tx)) > (maxPool1Size + maxPool2Size) { - // todo 3 -> done maybe a check here for pool2? // try addToPool12OrPool3() and only if unsuccessful then do the other things!!! - // Try adding to pool2 or pool3 first + if txPoolSizeAfterCurrentTx > (maxPool1Size + maxPool2Size) { // If the new transaction is underpriced, don't accept it - // todo 5 actually if underpriced then add to pool2 or 3. Otherwise find out which one will be replaced with and that one will go to pool2 or 3 if !isLocal && pool.priced.Underpriced(tx) { addedToAnyPool, err := pool.addToPool12OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) if addedToAnyPool { @@ -854,15 +850,8 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // New transaction is better than our worse ones, make room for it. // If it's a local transaction, forcibly discard all available transactions. // Otherwise if we can't make enough room for new one, abort the operation. - // todo maybe we don't need to Discard if we can include it in pool2 or pool3 - // OR get the discarded transactions and make them part of pool2 or pool3 toBeDiscarded := pool.all.Slots() - int(pool.config.GlobalSlots+pool.config.GlobalQueue+pool.config.Pool2Slots) + numSlots(tx) drop, success := pool.priced.Discard(toBeDiscarded, isLocal) - //if !success { - // // If this Discard fails then we can try at least replacing the slots which is the tx worth so that we may still be over the limit but we can accommodate a deserving transaction - // toBeDiscarded = numSlots(tx) - // drop, success = pool.priced.Discard(toBeDiscarded, isLocal) - //} // Special case, we still can't make the room for the new remote one. if !isLocal && !success { @@ -892,38 +881,25 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } } - // todo calculate total number of slots in drop. Accordingly add them to pool3 (if there is space) or actually drop them - // helper function for current availability in any pool + // calculate total number of slots in drop. Accordingly add them to pool3 (if there is space) + // all members of drop will be dropped from pool1/2 regardless of whether they get added to pool3 or not availableSlotsPool3 := pool.availableSlotsPool3() - var remainingDrop []*types.Transaction if availableSlotsPool3 > 0 { - // transfer availableSlotsPool3 number of transactions slots from drop to pool3. Actually drop the rest + // transfer availableSlotsPool3 number of transactions slots from drop to pool3 currentSlotsUsed := 0 - for _, tx := range drop { // todo 6 maybe heapify this so that important txs from drop are included first! -> may not be necessary + for _, tx := range drop { txSlots := numSlots(tx) if currentSlotsUsed+txSlots <= availableSlotsPool3 { from, _ := types.Sender(pool.signer, tx) - added, _ := pool.addToPool12OrPool3(tx, from, isLocal, false, false, true) // todo once they are removed they should be deleted from the pool1/2 because drop HAS to be dropped from pool1/2 irrespective of if they get added to pool3 or not! + pool.addToPool12OrPool3(tx, from, isLocal, false, false, true) currentSlotsUsed += txSlots - //} else { - //remainingDrop = append(remainingDrop, tx) - if !added { - remainingDrop = append(remainingDrop, tx) - } - } else { - remainingDrop = append(remainingDrop, tx) } } - } else { - remainingDrop = drop } // Kick out the underpriced remote transactions. for _, tx := range drop { - // todo 2 pool2 or pool3. Because the new tx is better than transactions of drop but drop transactions still - // deserve to be part of at least pool2 or pool3. - // At this point if remainingDrop is non nil then pool3 is full. So these will need to be deleted anyway log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) underpricedTxMeter.Mark(1) @@ -934,7 +910,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } } - // Try to replace an existing transaction in the pending pool // todo 4 why we are replacing a pending tx if the we can put in pool3 or so? + // Try to replace an existing transaction in the pending pool if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { // Nonce already pending, check if required price bump is met inserted, old := list.Add(tx, pool.config.PriceBump, includePool2) @@ -976,9 +952,9 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } pool.journalTx(from, tx) - if len(pool.pending) > int(maxPool1Size+maxPool2Size) { - fmt.Println("pending size exceeded") - } + //if len(pool.pending) > int(maxPool1Size+maxPool2Size) { + // fmt.Println("pending size exceeded") + //} log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) return replaced, nil @@ -992,7 +968,6 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad pool.priced.Put(tx, pool2) pool.journalTx(from, tx) pool.queueTxEvent(tx, false) - // todo check if enqueueTx should be used _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true, false) // At this point pool1 can incorporate this. So no need for pool2 or pool3 if err != nil { return false, err @@ -1005,7 +980,6 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad return true, nil } if pool2 { - // todo (check) logic for pool2 related , should we put enqueueTx()?? -> Yes we should pool.all.Add(tx, pool2) pool.priced.Put(tx, pool2) pool.journalTx(from, tx) @@ -1021,7 +995,6 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad return true, nil } if pool3 { - // todo (check) logic for pool3 pool.localBufferPool.Add(tx) return true, nil } @@ -2217,7 +2190,6 @@ func (pool *LegacyPool) startPeriodicTransfer() { // transferTransactions mainly moves from pool 3 to pool 2 func (pool *LegacyPool) transferTransactions() { - maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue maxPool2Size := pool.config.Pool2Slots maxPool1Pool2CombinedSize := maxPool1Size + maxPool2Size diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index c88be3a996..9e615429a0 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2064,21 +2064,18 @@ func TestDualHeapEviction(t *testing.T) { add := func(urgent bool) { for i := 0; i < 5; i++ { - fmt.Println("i= ", i) var tx *types.Transaction // Create a test accounts and fund it key, _ := crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000000)) if urgent { - fmt.Printf("i = %d gasFee: %v \n", i, int64(baseFee+1+i)) tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+1+i)), big.NewInt(int64(1+i)), key) if int64(1+i) > highTipValue || (int64(1+i) == highTipValue && int64(baseFee+1+i) > highTip.GasFeeCap().Int64()) { - fmt.Println("highTip updated. tip=", int64(1+i), highTipValue) + //fmt.Println("highTip updated. tip=", int64(1+i), highTipValue) highTipValue = int64(1 + i) highTip = tx } } else { - fmt.Printf("i = %d gasFee: %v \n", i, int64(baseFee+200+i)) tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) if int64(baseFee+200+i) > highCapValue { //fmt.Println("highCap updated. gasFee=", int64(baseFee+200+i), highCapValue) From 0692a99ea9c2d62406a928086be464cf7e58a1e7 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 28 Aug 2024 14:26:16 +0100 Subject: [PATCH 18/57] pool: fix the TestTransactionFutureAttack test --- core/txpool/errors.go | 4 ++++ core/txpool/legacypool/legacypool.go | 13 ++++++++++++- core/txpool/legacypool/legacypool_test.go | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/txpool/errors.go b/core/txpool/errors.go index 8298a15993..52991a4890 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -30,6 +30,10 @@ var ( // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") + // ErrUnderpriced is returned if a transaction's gas price is below the minimum + // configured for the transaction pool. + ErrUnderpricedTransferredtoAnotherPool = errors.New("transaction underpriced, so it is either in pool2 or pool3") + // 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") diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 3359727ab3..b8d4b24f9d 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -797,6 +797,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // already validated by this point from, _ := types.Sender(pool.signer, tx) + fmt.Println("from address: ", from.String()) // If the address is not yet known, request exclusivity to track the account // only by this subpool until all transactions are evicted @@ -816,6 +817,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // by a return statement before running deferred methods. Take care with // removing or subscoping err as it will break this clause. if err != nil { + fmt.Println("Just kicking out reserved address: ", from.String()) pool.reserve(from, false) } }() @@ -827,7 +829,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e if !isLocal && pool.priced.Underpriced(tx) { addedToAnyPool, err := pool.addToPool12OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) if addedToAnyPool { - return false, nil + return false, txpool.ErrUnderpricedTransferredtoAnotherPool // todo change it to return no error } if err != nil { log.Error("Error while trying to add to pool2 or pool3", "error", err) @@ -1216,12 +1218,21 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) { dirty := newAccountSet(pool.signer) errs := make([]error, len(txs)) + oldAddress := common.Address{} + newAddress := common.Address{} for i, tx := range txs { + newAddress, _ = types.Sender(pool.signer, tx) + if oldAddress == newAddress { + fmt.Println("address repeat ", i) + } else { + fmt.Println("old address: ", oldAddress.String(), "new address: ", newAddress.String(), "i= ", i) + } replaced, err := pool.add(tx, local) errs[i] = err if err == nil && !replaced { dirty.addTx(tx) } + oldAddress = newAddress } validTxMeter.Mark(int64(len(dirty.accounts))) return errs, dirty diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 9e615429a0..2524f2dd5d 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -141,15 +141,18 @@ func makeAddressReserver() txpool.AddressReserver { _, exists := reserved[addr] if reserve { if exists { + fmt.Println(addr.String()) panic("already reserved") } reserved[addr] = struct{}{} + fmt.Println("Just reserved: ", addr.String()) return nil } if !exists { panic("not reserved") } delete(reserved, addr) + fmt.Println("kicked out reserved address: ", addr.String()) return nil } } From 40dcfcd776265874adc8f8347b396864a887a6fe Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 28 Aug 2024 14:29:15 +0100 Subject: [PATCH 19/57] pool: cleanup debug logs --- core/txpool/legacypool/legacypool.go | 16 +--------------- core/txpool/legacypool/legacypool_test.go | 5 +---- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b8d4b24f9d..1e5582a23a 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -797,7 +797,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // already validated by this point from, _ := types.Sender(pool.signer, tx) - fmt.Println("from address: ", from.String()) // If the address is not yet known, request exclusivity to track the account // only by this subpool until all transactions are evicted @@ -817,7 +816,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // by a return statement before running deferred methods. Take care with // removing or subscoping err as it will break this clause. if err != nil { - fmt.Println("Just kicking out reserved address: ", from.String()) pool.reserve(from, false) } }() @@ -954,10 +952,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } pool.journalTx(from, tx) - //if len(pool.pending) > int(maxPool1Size+maxPool2Size) { - // fmt.Println("pending size exceeded") - //} - log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) return replaced, nil } @@ -1218,21 +1212,13 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) { dirty := newAccountSet(pool.signer) errs := make([]error, len(txs)) - oldAddress := common.Address{} - newAddress := common.Address{} + for i, tx := range txs { - newAddress, _ = types.Sender(pool.signer, tx) - if oldAddress == newAddress { - fmt.Println("address repeat ", i) - } else { - fmt.Println("old address: ", oldAddress.String(), "new address: ", newAddress.String(), "i= ", i) - } replaced, err := pool.add(tx, local) errs[i] = err if err == nil && !replaced { dirty.addTx(tx) } - oldAddress = newAddress } validTxMeter.Mark(int64(len(dirty.accounts))) return errs, dirty diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 2524f2dd5d..696d3e335a 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -141,18 +141,15 @@ func makeAddressReserver() txpool.AddressReserver { _, exists := reserved[addr] if reserve { if exists { - fmt.Println(addr.String()) panic("already reserved") } reserved[addr] = struct{}{} - fmt.Println("Just reserved: ", addr.String()) return nil } if !exists { panic("not reserved") } delete(reserved, addr) - fmt.Println("kicked out reserved address: ", addr.String()) return nil } } @@ -2059,7 +2056,7 @@ func TestDualHeapEviction(t *testing.T) { check := func(tx *types.Transaction, name string) { if pool.all.GetRemote(tx.Hash()) == nil { - fmt.Println(highCap.GasFeeCap().String(), highCap.GasTipCap().String(), highTip.GasFeeCap().String(), highTip.GasTipCap().String(), len(pool.pending), len(pool.queue), pool.localBufferPool.size) + //fmt.Println(highCap.GasFeeCap().String(), highCap.GasTipCap().String(), highTip.GasFeeCap().String(), highTip.GasTipCap().String(), len(pool.pending), len(pool.queue), pool.localBufferPool.size) pool.printTxStats() t.Fatalf("highest %s transaction evicted from the pool, gasTip: %s, gasFeeCap: %s, hash: %s", name, highTip.GasTipCap().String(), highCap.GasFeeCap().String(), tx.Hash().String()) } From 6673f3e75abcfd7d1b0e70867775d8a89e92a52c Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 28 Aug 2024 15:49:07 +0100 Subject: [PATCH 20/57] pool: fix TestUnderpricingDynamicFee based on new pool --- core/txpool/legacypool/legacypool_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 696d3e335a..4b416d44eb 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1793,8 +1793,7 @@ func TestUnderpricing(t *testing.T) { } // Ensure that adding an underpriced transaction on block limit fails if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) { - // todo - //t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } // Replace a future transaction with a future transaction if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(2), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1 @@ -1813,20 +1812,20 @@ func TestUnderpricing(t *testing.T) { // Ensure that replacing a pending transaction with a future transaction fails if err := pool.addRemote(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != txpool.ErrFutureReplacePending { // todo - //t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending) + t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending) } pending, queued = pool.Stats() if pending != 2 { // todo - //t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } if queued != 2 { // todo - //t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } if err := validateEvents(events, 2); err != nil { // todo - //t.Fatalf("additional event firing failed: %v", err) + t.Fatalf("additional event firing failed: %v", err) } if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) @@ -1978,8 +1977,8 @@ func TestUnderpricingDynamicFee(t *testing.T) { // Ensure that adding an underpriced transaction fails tx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]) - if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { // Pend K0:0, K0:1, K2:0; Que K1:1 - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpricedTransferredtoAnotherPool) { // Pend K0:0, K0:1, K2:0; Que K1:1 + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpricedTransferredtoAnotherPool) } // Ensure that adding high priced transactions drops cheap ones, but not own From ebd8f590a367589b409fc678fb7a2b9dc493854e Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 28 Aug 2024 15:52:41 +0100 Subject: [PATCH 21/57] pool: fix all old tests --- core/txpool/legacypool/legacypool_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 4b416d44eb..1dccf01acc 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1792,8 +1792,8 @@ func TestUnderpricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Ensure that adding an underpriced transaction on block limit fails - if err := pool.addRemoteSync(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.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpricedTransferredtoAnotherPool) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpricedTransferredtoAnotherPool) } // Replace a future transaction with a future transaction if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(2), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1 @@ -1811,20 +1811,16 @@ func TestUnderpricing(t *testing.T) { } // Ensure that replacing a pending transaction with a future transaction fails if err := pool.addRemote(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != txpool.ErrFutureReplacePending { - // todo t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending) } pending, queued = pool.Stats() if pending != 2 { - // todo t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } if queued != 2 { - // todo t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } if err := validateEvents(events, 2); err != nil { - // todo t.Fatalf("additional event firing failed: %v", err) } if err := validatePoolInternals(pool); err != nil { From bdb4cc2acd2661bed05bc0d98220c87a9d3a856e Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 29 Aug 2024 06:49:28 +0100 Subject: [PATCH 22/57] pool: lint --- core/txpool/legacypool/legacypool.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 1e5582a23a..c8d60211b4 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -260,12 +260,6 @@ type QueueTxEventCh struct { static bool } -func newQueueTxEventCh() QueueTxEventCh { - return QueueTxEventCh{ - tx: new(types.Transaction), - } -} - type txpoolResetRequest struct { oldHead, newHead *types.Header } @@ -895,7 +889,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e currentSlotsUsed += txSlots } } - } // Kick out the underpriced remote transactions. @@ -2237,16 +2230,7 @@ func (pool *LegacyPool) availableSlotsPool3() int { return 0 } -//func (pool *LegacyPool) availableSlotsPool2() uin64 { -// availableSlots := maxPool2Size - pool.localBufferPool.Size() -// if availableSlots > 0 { -// return availableSlots -// } -// return 0 -//} - func (pool *LegacyPool) printTxStats() { - for _, l := range pool.pending { for _, transaction := range l.txs.items { fmt.Println("Pending:", transaction.Hash().String(), transaction.GasFeeCap(), transaction.GasTipCap()) From e45e7eb9c698e3db76532fa069f8b786ce1b5f13 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 29 Aug 2024 08:03:45 +0100 Subject: [PATCH 23/57] pool: include static in flatten --- core/events.go | 2 +- core/txpool/legacypool/legacypool.go | 39 ++++++++++++++++------------ core/txpool/legacypool/list.go | 22 ++++++++-------- eth/handler.go | 4 +-- eth/protocols/eth/peer.go | 2 +- eth/sync.go | 2 +- 6 files changed, 38 insertions(+), 33 deletions(-) diff --git a/core/events.go b/core/events.go index c67ad58962..7a8fc70faa 100644 --- a/core/events.go +++ b/core/events.go @@ -24,7 +24,7 @@ import ( // NewTxsEvent is posted when a batch of transactions enters the transaction pool. type NewTxsEvent struct { Txs []*types.Transaction - // todo Static bool is Whether to send to only Static peer or not. + // Static bool is Whether to send to only Static peer or not. // This is because at high traffic we still want to broadcast transactions to at least some peers so that we // minimize the transaction lost. Static bool diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index c8d60211b4..4f4ac785b6 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -411,7 +411,7 @@ func (pool *LegacyPool) loop() { } // Any non-locals old enough should be removed if time.Since(pool.beats[addr]) > pool.config.Lifetime { - list := pool.queue[addr].Flatten() + list, _ := pool.queue[addr].Flatten() for _, tx := range list { pool.removeTx(tx.Hash(), true, true) } @@ -422,25 +422,27 @@ func (pool *LegacyPool) loop() { case <-reannounce.C: pool.mu.RLock() - reannoTxs := func() []*types.Transaction { + reannoTxs, _ := func() ([]*types.Transaction, []bool) { txs := make([]*types.Transaction, 0) + statics := make([]bool, 0) for addr, list := range pool.pending { if !pool.locals.contains(addr) { continue } - - for _, tx := range list.Flatten() { + transactions, static := list.Flatten() + for _, tx := range transactions { // Default ReannounceTime is 10 years, won't announce by default. if time.Since(tx.Time()) < pool.config.ReannounceTime { break } txs = append(txs, tx) + statics = append(statics, static) if len(txs) >= txReannoMaxNum { - return txs + return txs, statics } } } - return txs + return txs, statics }() pool.mu.RUnlock() if len(reannoTxs) > 0 { @@ -559,11 +561,11 @@ func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[ pending := make(map[common.Address][]*types.Transaction, len(pool.pending)) for addr, list := range pool.pending { - pending[addr] = list.Flatten() + pending[addr], _ = list.Flatten() } queued := make(map[common.Address][]*types.Transaction, len(pool.queue)) for addr, list := range pool.queue { - queued[addr] = list.Flatten() + queued[addr], _ = list.Flatten() } return pending, queued } @@ -576,11 +578,11 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction, var pending []*types.Transaction if list, ok := pool.pending[addr]; ok { - pending = list.Flatten() + pending, _ = list.Flatten() } var queued []*types.Transaction if list, ok := pool.queue[addr]; ok { - queued = list.Flatten() + queued, _ = list.Flatten() } return pending, queued } @@ -612,7 +614,7 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address] } pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) for addr, list := range pool.pending { - txs := list.Flatten() + txs, _ := list.Flatten() // If the miner requests tip enforcement, cap the lists now if minTipBig != nil && !pool.locals.contains(addr) { @@ -658,10 +660,12 @@ func (pool *LegacyPool) local() map[common.Address]types.Transactions { txs := make(map[common.Address]types.Transactions) for addr := range pool.locals.accounts { if pending := pool.pending[addr]; pending != nil { - txs[addr] = append(txs[addr], pending.Flatten()...) + transactions, _ := pending.Flatten() + txs[addr] = append(txs[addr], transactions...) } if queued := pool.queue[addr]; queued != nil { - txs[addr] = append(txs[addr], queued.Flatten()...) + transactions, _ := queued.Flatten() + txs[addr] = append(txs[addr], transactions...) } } return txs @@ -1486,7 +1490,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, // Update all accounts to the latest known pending nonce nonces := make(map[common.Address]uint64, len(pool.pending)) for addr, list := range pool.pending { - highestPending := list.LastElement() + highestPending, _ := list.LastElement() nonces[addr] = highestPending.Nonce() + 1 } pool.pendingNonces.setAll(nonces) @@ -1511,7 +1515,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, staticTxs := make([]*types.Transaction, 0) nonStaticTxs := make([]*types.Transaction, 0) for _, set := range events { - flattenedTxs := set.Flatten() + flattenedTxs, _ := set.Flatten() if set.staticOnly { staticTxs = append(staticTxs, flattenedTxs...) } else { @@ -1814,7 +1818,8 @@ func (pool *LegacyPool) truncateQueue() { // Drop all transactions if they are less than the overflow if size := uint64(list.Len()); size <= drop { - for _, tx := range list.Flatten() { + transactions, _ := list.Flatten() + for _, tx := range transactions { pool.removeTx(tx.Hash(), true, true) } drop -= size @@ -1822,7 +1827,7 @@ func (pool *LegacyPool) truncateQueue() { continue } // Otherwise drop only last few transactions - txs := list.Flatten() + txs, _ := list.Flatten() for i := len(txs) - 1; i >= 0 && drop > 0; i-- { pool.removeTx(txs[i].Hash(), true, true) drop-- diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index df6ae8b5f6..c14d613b07 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -255,7 +255,7 @@ func (m *sortedMap) Len() int { return len(m.items) } -func (m *sortedMap) flatten() types.Transactions { +func (m *sortedMap) flatten() (types.Transactions, bool) { m.cacheMu.Lock() defer m.cacheMu.Unlock() // If the sorting was not cached yet, create and cache it @@ -272,25 +272,25 @@ func (m *sortedMap) flatten() types.Transactions { } sort.Sort(types.TxByNonce(m.cache)) } - return m.cache + return m.cache, m.staticOnly } // Flatten creates a nonce-sorted slice of transactions based on the loosely // sorted internal representation. The result of the sorting is cached in case // it's requested again before any modifications are made to the contents. -func (m *sortedMap) Flatten() types.Transactions { - cache := m.flatten() +func (m *sortedMap) Flatten() (types.Transactions, bool) { + cache, static := m.flatten() // Copy the cache to prevent accidental modification txs := make(types.Transactions, len(cache)) copy(txs, cache) - return txs + return txs, static } // LastElement returns the last element of a flattened list, thus, the // transaction with the highest nonce -func (m *sortedMap) LastElement() *types.Transaction { - cache := m.flatten() - return cache[len(cache)-1] +func (m *sortedMap) LastElement() (*types.Transaction, bool) { + cache, static := m.flatten() + return cache[len(cache)-1], static } // list is a "list" of transactions belonging to an account, sorted by account @@ -362,7 +362,7 @@ func (l *list) Add(tx *types.Transaction, priceBump uint64, static bool) (bool, l.totalcost.Add(l.totalcost, cost) // Otherwise overwrite the old transaction with the current one - l.txs.Put(tx, static) // todo putting false as a placeholder + l.txs.Put(tx, static) if l.costcap.Cmp(cost) < 0 { l.costcap = cost } @@ -477,13 +477,13 @@ func (l *list) Empty() bool { // Flatten creates a nonce-sorted slice of transactions based on the loosely // sorted internal representation. The result of the sorting is cached in case // it's requested again before any modifications are made to the contents. -func (l *list) Flatten() types.Transactions { +func (l *list) Flatten() (types.Transactions, bool) { return l.txs.Flatten() } // LastElement returns the last element of a flattened list, thus, the // transaction with the highest nonce -func (l *list) LastElement() *types.Transaction { +func (l *list) LastElement() (*types.Transaction, bool) { return l.txs.LastElement() } diff --git a/eth/handler.go b/eth/handler.go index a733b134c6..80bede11ec 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -899,7 +899,7 @@ func (h *handler) ReannounceTransactions(txs types.Transactions) { peersCount := uint(math.Sqrt(float64(h.peers.len()))) peers := h.peers.headPeers(peersCount) for _, peer := range peers { - peer.AsyncSendPooledTransactionHashes(hashes, false) // todo keeping it false for now. confirm it. + peer.AsyncSendPooledTransactionHashes(hashes, false) // todo keeping it false for now. Reannounce never really happens } log.Debug("Transaction reannounce", "txs", len(txs), "announce packs", peersCount, "announced hashes", peersCount*uint(len(hashes))) @@ -962,7 +962,7 @@ func (h *handler) txBroadcastLoop() { for { select { case event := <-h.txsCh: - h.BroadcastTransactions(event.Txs, event.Static) // todo should Static bool be a slice of bools? + h.BroadcastTransactions(event.Txs, event.Static) case <-h.txsSub.Err(): return case <-h.stopCh: diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 44b55dcfc9..732da20a77 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -223,7 +223,7 @@ func (p *Peer) SendTransactions(txs types.Transactions) error { // propagate to a remote peer. The number of pending sends are capped (new ones // will force old sends to be dropped) func (p *Peer) AsyncSendTransactions(hashes []common.Hash, staticOnly bool) { - // todo p.Peer.Info().Network.Static bool decides if pool2 transaction will be broadcasted to that peer or not + // p.Peer.Info().Network.Static bool decides if pool2 transaction will be broadcasted to that peer or not select { case <-p.txTerm: p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) diff --git a/eth/sync.go b/eth/sync.go index b09d54aff0..78e9b61cbf 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -44,7 +44,7 @@ func (h *handler) syncTransactions(p *eth.Peer) { if len(hashes) == 0 { return } - p.AsyncSendPooledTransactionHashes(hashes, false) // todo confirm if false is fine + p.AsyncSendPooledTransactionHashes(hashes, false) // todo bring the static bool from Pending } // syncVotes starts sending all currently pending votes to the given peer. From 069eaf22f7ddad8dd14343d2d1e98f00f817332c Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 29 Aug 2024 15:57:30 +0100 Subject: [PATCH 24/57] pool: proper use of AsyncSendPooledTransactionHashes --- core/txpool/legacypool/legacypool.go | 3 ++- core/txpool/subpool.go | 2 ++ eth/sync.go | 20 +++++++++++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 4f4ac785b6..a20d22f818 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -614,7 +614,7 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address] } pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) for addr, list := range pool.pending { - txs, _ := list.Flatten() + txs, static := list.Flatten() // If the miner requests tip enforcement, cap the lists now if minTipBig != nil && !pool.locals.contains(addr) { @@ -637,6 +637,7 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address] GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()), Gas: txs[i].Gas(), BlobGas: txs[i].BlobGas(), + Static: static, } } pending[addr] = lazies diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index be5f6840d3..0947150c97 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -41,6 +41,8 @@ type LazyTransaction struct { Gas uint64 // Amount of gas required by the transaction BlobGas uint64 // Amount of blob gas required by the transaction + + Static bool // To specify whether to broadcast it to static peers or not } // Resolve retrieves the full transaction belonging to a lazy handle if it is still diff --git a/eth/sync.go b/eth/sync.go index 78e9b61cbf..7dd8f1a8f3 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -35,16 +35,26 @@ const ( // syncTransactions starts sending all currently pending transactions to the given peer. func (h *handler) syncTransactions(p *eth.Peer) { - var hashes []common.Hash + var hashesFalse []common.Hash + var hashesTrue []common.Hash + for _, batch := range h.txpool.Pending(txpool.PendingFilter{OnlyPlainTxs: true}) { for _, tx := range batch { - hashes = append(hashes, tx.Hash) + if tx.Static { + hashesTrue = append(hashesTrue, tx.Hash) + } else { + hashesFalse = append(hashesFalse, tx.Hash) + } } } - if len(hashes) == 0 { - return + + if len(hashesFalse) > 0 { + p.AsyncSendPooledTransactionHashes(hashesFalse, false) + } + + if len(hashesTrue) > 0 { + p.AsyncSendPooledTransactionHashes(hashesTrue, true) } - p.AsyncSendPooledTransactionHashes(hashes, false) // todo bring the static bool from Pending } // syncVotes starts sending all currently pending votes to the given peer. From 70ece9355a23ab57b8297c68ea4c2569964d3773 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 29 Aug 2024 16:10:59 +0100 Subject: [PATCH 25/57] pool: flags for pool2 and 3 capacity --- cmd/geth/main.go | 2 ++ cmd/utils/flags.go | 20 ++++++++++++++++++++ core/txpool/legacypool/legacypool.go | 23 +++++++++++++++-------- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 9763794f7e..77fbafe18e 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -91,6 +91,8 @@ var ( utils.TxPoolGlobalSlotsFlag, utils.TxPoolAccountQueueFlag, utils.TxPoolGlobalQueueFlag, + utils.TxPoolPool2SlotsFlag, + utils.TxPoolPool3SlotsFlag, utils.TxPoolLifetimeFlag, utils.TxPoolReannounceTimeFlag, utils.BlobPoolDataDirFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8707840692..ccae914fda 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -449,6 +449,18 @@ var ( Value: ethconfig.Defaults.TxPool.GlobalQueue, Category: flags.TxPoolCategory, } + TxPoolPool2SlotsFlag = &cli.Uint64Flag{ + Name: "txpool.pool2slots", + Usage: "Maximum number of transaction slots in pool 2", + Value: ethconfig.Defaults.TxPool.Pool2Slots, + Category: flags.TxPoolCategory, + } + TxPoolPool3SlotsFlag = &cli.Uint64Flag{ + Name: "txpool.pool3slots", + Usage: "Maximum number of transaction slots in pool 3", + Value: ethconfig.Defaults.TxPool.Pool3Slots, + Category: flags.TxPoolCategory, + } TxPoolLifetimeFlag = &cli.DurationFlag{ Name: "txpool.lifetime", Usage: "Maximum amount of time non-executable transaction are queued", @@ -1762,6 +1774,12 @@ func setTxPool(ctx *cli.Context, cfg *legacypool.Config) { if ctx.IsSet(TxPoolGlobalQueueFlag.Name) { cfg.GlobalQueue = ctx.Uint64(TxPoolGlobalQueueFlag.Name) } + if ctx.IsSet(TxPoolPool2SlotsFlag.Name) { + cfg.Pool2Slots = ctx.Uint64(TxPoolPool2SlotsFlag.Name) + } + if ctx.IsSet(TxPoolPool3SlotsFlag.Name) { + cfg.Pool3Slots = ctx.Uint64(TxPoolPool3SlotsFlag.Name) + } if ctx.IsSet(TxPoolLifetimeFlag.Name) { cfg.Lifetime = ctx.Duration(TxPoolLifetimeFlag.Name) } @@ -2292,6 +2310,8 @@ func EnableNodeInfo(poolConfig *legacypool.Config, nodeInfo *p2p.NodeInfo) Setup "GlobalSlots": poolConfig.GlobalSlots, "AccountQueue": poolConfig.AccountQueue, "GlobalQueue": poolConfig.GlobalQueue, + "Pool2Slots": poolConfig.Pool2Slots, + "Pool3Slots": poolConfig.Pool3Slots, "Lifetime": poolConfig.Lifetime, }) } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index a20d22f818..98efdbc868 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -56,9 +56,6 @@ const ( // txReannoMaxNum is the maximum number of transactions a reannounce action can include. txReannoMaxNum = 1024 - - //maxPool2Size = 10000 // todo might have to set it in config // This is in slots and not in no of transactions - //maxPool3Size = 50000 ) var ( @@ -141,8 +138,8 @@ type Config struct { GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts - Pool2Slots uint64 - Pool3Slots uint64 + Pool2Slots uint64 // Maximum number of transaction slots in pool 2 + Pool3Slots uint64 // Maximum number of transaction slots in pool 3 Lifetime time.Duration // Maximum amount of time non-executable transaction are queued ReannounceTime time.Duration // Duration for announcing local pending transactions again @@ -160,6 +157,8 @@ var DefaultConfig = Config{ GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio AccountQueue: 64, GlobalQueue: 1024, + Pool2Slots: 1024, + Pool3Slots: 1024, Lifetime: 3 * time.Hour, ReannounceTime: 10 * 365 * 24 * time.Hour, @@ -197,6 +196,14 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) conf.GlobalQueue = DefaultConfig.GlobalQueue } + if conf.Pool2Slots < 1 { + log.Warn("Sanitizing invalid txpool pool 2 slots", "provided", conf.Pool2Slots, "updated", DefaultConfig.Pool2Slots) + conf.Pool2Slots = DefaultConfig.Pool2Slots + } + if conf.Pool3Slots < 1 { + log.Warn("Sanitizing invalid txpool pool 3 slots", "provided", conf.Pool3Slots, "updated", DefaultConfig.Pool3Slots) + conf.Pool3Slots = DefaultConfig.Pool3Slots + } if conf.Lifetime < 1 { log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) conf.Lifetime = DefaultConfig.Lifetime @@ -826,7 +833,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e if !isLocal && pool.priced.Underpriced(tx) { addedToAnyPool, err := pool.addToPool12OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) if addedToAnyPool { - return false, txpool.ErrUnderpricedTransferredtoAnotherPool // todo change it to return no error + return false, txpool.ErrUnderpricedTransferredtoAnotherPool // The reserve code expects named error formatting } if err != nil { log.Error("Error while trying to add to pool2 or pool3", "error", err) @@ -876,7 +883,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.priced.Put(dropTx, false) } log.Trace("Discarding future transaction replacing pending tx", "hash", hash) - return false, txpool.ErrFutureReplacePending // todo 1 maybe in this case the future transaction can be part of pool3! + return false, txpool.ErrFutureReplacePending // todo 1 maybe in this case the future transaction can be part of pool3? } } @@ -2218,7 +2225,7 @@ func (pool *LegacyPool) transferTransactions() { from, _ := types.Sender(pool.signer, transaction) // use addToPool12OrPool3() function to transfer from pool3 to pool2 - _, err := pool.addToPool12OrPool3(transaction, from, true, false, true, false) // todo by default all pool3 transactions are considered local + _, err := pool.addToPool12OrPool3(transaction, from, true, false, true, false) // by default all pool3 transactions are considered local if err != nil { // if it never gets added to anything then add it back pool.addToPool12OrPool3(transaction, from, true, false, false, true) From e7d0a16bc46282a56bdd4aaee0a5267aefbb92c5 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Fri, 30 Aug 2024 13:36:09 +0100 Subject: [PATCH 26/57] pool: fix test as now by default pool2 and pool3 aren't empty --- core/txpool/legacypool/legacypool_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 1dccf01acc..0aec80c17e 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1930,7 +1930,10 @@ func TestUnderpricingDynamicFee(t *testing.T) { defer pool.Close() pool.config.GlobalSlots = 2 - pool.config.GlobalQueue = 2 + pool.config.GlobalQueue = 1 + + pool.config.Pool2Slots = 1 + pool.config.Pool3Slots = 1 // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) @@ -1995,8 +1998,8 @@ func TestUnderpricingDynamicFee(t *testing.T) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 2 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } if err := validateEvents(events, 2); err != nil { t.Fatalf("additional event firing failed: %v", err) From 0f8a1b5c5a982c7c37c26d6aa9f302086bae2fe5 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 3 Sep 2024 12:41:54 +0100 Subject: [PATCH 27/57] pool: test for transfer --- core/txpool/legacypool/legacypool_test.go | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 0aec80c17e..09d5b7c933 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2254,6 +2254,38 @@ func TestReplacement(t *testing.T) { } } +func TestTransferTransactions(t *testing.T) { + t.Parallel() + + pool, _ := setupPoolWithConfig(eip1559Config) + defer pool.Close() + + pool.config.GlobalSlots = 2 + pool.config.GlobalQueue = 1 + + pool.config.Pool2Slots = 1 + pool.config.Pool3Slots = 1 + + // 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)) + } + + tx := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[0]) + from, _ := types.Sender(pool.signer, tx) + pool.addToPool12OrPool3(tx, from, true, false, false, true) + time.Sleep(2 * time.Minute) + pending, queue := pool.Stats() + if pending != 0 { + t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queue != 1 { + t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) + } +} + // Tests that the pool rejects replacement dynamic fee transactions that don't // meet the minimum price bump required. func TestReplacementDynamicFee(t *testing.T) { From 76d157d08b46db62de3e0a014e8802c7745352ad Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 3 Sep 2024 12:42:11 +0100 Subject: [PATCH 28/57] pool: set transfer time in config --- core/txpool/legacypool/legacypool.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 98efdbc868..bee619e538 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -141,8 +141,9 @@ type Config struct { Pool2Slots uint64 // Maximum number of transaction slots in pool 2 Pool3Slots uint64 // Maximum number of transaction slots in pool 3 - Lifetime time.Duration // Maximum amount of time non-executable transaction are queued - ReannounceTime time.Duration // Duration for announcing local pending transactions again + Lifetime time.Duration // Maximum amount of time non-executable transaction are queued + ReannounceTime time.Duration // Duration for announcing local pending transactions again + InterPoolTransferTime time.Duration // Attempt to transfer from pool3 to pool2 every this much time } // DefaultConfig contains the default configurations for the transaction pool. @@ -160,8 +161,9 @@ var DefaultConfig = Config{ Pool2Slots: 1024, Pool3Slots: 1024, - Lifetime: 3 * time.Hour, - ReannounceTime: 10 * 365 * 24 * time.Hour, + Lifetime: 3 * time.Hour, + ReannounceTime: 10 * 365 * 24 * time.Hour, + InterPoolTransferTime: time.Minute, } // sanitize checks the provided user configurations and changes anything that's @@ -308,7 +310,7 @@ func New(config Config, chain BlockChain) *LegacyPool { pool.journal = newTxJournal(config.Journal) } - pool.startPeriodicTransfer() // todo (incomplete) Start the periodic transfer routine + pool.startPeriodicTransfer(config.InterPoolTransferTime) // todo (incomplete) Start the periodic transfer routine return pool } @@ -2174,8 +2176,12 @@ func numSlots(tx *types.Transaction) int { return int((tx.Size() + txSlotSize - 1) / txSlotSize) } -func (pool *LegacyPool) startPeriodicTransfer() { +func (pool *LegacyPool) startPeriodicTransfer(t time.Duration) { ticker := time.NewTicker(time.Minute) // Adjust the interval as needed + if t != 0 { + ticker.Reset(t) + } + go func() { for { select { @@ -2213,6 +2219,8 @@ func (pool *LegacyPool) transferTransactions() { return } + log.Debug("Will attempt to transfer from pool3 to pool2", "transactions", extraTransactions) + tx := pool.localBufferPool.Flush(int(extraTransactions)) if len(tx) == 0 { return @@ -2231,6 +2239,7 @@ func (pool *LegacyPool) transferTransactions() { pool.addToPool12OrPool3(transaction, from, true, false, false, true) continue } + log.Debug("Transferred from pool3 to pool2", "transactions", transaction.Hash().String()) } } From aeec0c71296c00cd0c0339584723c8bd79c488c6 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 3 Sep 2024 13:03:10 +0100 Subject: [PATCH 29/57] pool: remove unused criticalpathpool --- core/txpool/legacypool/legacypool.go | 34 +++++++++++++--------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index bee619e538..2aa38b2051 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -250,8 +250,7 @@ type LegacyPool struct { all *lookup // All transactions to allow lookups priced *pricedList // All transactions sorted by price - criticalPathPool map[common.Address]*list // Critical path transactions (Pool 2) - localBufferPool *LRUBuffer // Local buffer transactions (Pool 3) + localBufferPool *LRUBuffer // Local buffer transactions (Pool 3) reqResetCh chan *txpoolResetRequest reqPromoteCh chan *accountSet @@ -282,22 +281,21 @@ func New(config Config, chain BlockChain) *LegacyPool { // Create the transaction pool with its initial settings pool := &LegacyPool{ - config: config, - chain: chain, - chainconfig: chain.Config(), - signer: types.LatestSigner(chain.Config()), - pending: make(map[common.Address]*list), - queue: make(map[common.Address]*list), - beats: make(map[common.Address]time.Time), - all: newLookup(), - reqResetCh: make(chan *txpoolResetRequest), - reqPromoteCh: make(chan *accountSet), - queueTxEventCh: make(chan QueueTxEventCh), - reorgDoneCh: make(chan chan struct{}), - reorgShutdownCh: make(chan struct{}), - initDoneCh: make(chan struct{}), - criticalPathPool: make(map[common.Address]*list), - localBufferPool: NewLRUBuffer(int(maxPool3Size)), + config: config, + chain: chain, + chainconfig: chain.Config(), + signer: types.LatestSigner(chain.Config()), + pending: make(map[common.Address]*list), + queue: make(map[common.Address]*list), + beats: make(map[common.Address]time.Time), + all: newLookup(), + reqResetCh: make(chan *txpoolResetRequest), + reqPromoteCh: make(chan *accountSet), + queueTxEventCh: make(chan QueueTxEventCh), + reorgDoneCh: make(chan chan struct{}), + reorgShutdownCh: make(chan struct{}), + initDoneCh: make(chan struct{}), + localBufferPool: NewLRUBuffer(int(maxPool3Size)), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { From 53042e15d786551ed17b938dafda9b5a333b3b7f Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 3 Sep 2024 15:08:30 +0100 Subject: [PATCH 30/57] buffer: make private --- core/txpool/legacypool/buffer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/txpool/legacypool/buffer.go b/core/txpool/legacypool/buffer.go index 78a54f98f2..46e56af362 100644 --- a/core/txpool/legacypool/buffer.go +++ b/core/txpool/legacypool/buffer.go @@ -87,8 +87,8 @@ func (lru *LRUBuffer) Size() int { return lru.size } -// New iterator method to iterate over all transactions -func (lru *LRUBuffer) Iterate() <-chan *types.Transaction { +// New iterator method to iterate over all transactions, ONLY used for printing and debugging +func (lru *LRUBuffer) iterate() <-chan *types.Transaction { ch := make(chan *types.Transaction) go func() { lru.mu.Lock() @@ -104,7 +104,7 @@ func (lru *LRUBuffer) Iterate() <-chan *types.Transaction { func (lru *LRUBuffer) PrintTxStats() { // Iterating over the transactions - for tx := range lru.Iterate() { + for tx := range lru.iterate() { // Print transaction details or process them as needed fmt.Println(tx.Hash().String(), tx.GasFeeCap().String(), tx.GasTipCap().String()) } From 8e6833cec41279ea8cab33eb833960b5147d5af7 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 3 Sep 2024 16:39:35 +0100 Subject: [PATCH 31/57] pool: bug fix and test fix --- core/txpool/legacypool/legacypool.go | 17 ++++++--------- core/txpool/legacypool/legacypool_test.go | 26 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 2aa38b2051..e0dcb513b3 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -964,9 +964,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // addToPool12OrPool3 adds a transaction to pool1 or pool2 or pool3 depending on which one is asked for func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Address, isLocal bool, pool1, pool2, pool3 bool) (bool, error) { if pool1 { - // todo (check) logic for pool1 related - pool.all.Add(tx, pool2) - pool.priced.Put(tx, pool2) pool.journalTx(from, tx) pool.queueTxEvent(tx, false) _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true, false) // At this point pool1 can incorporate this. So no need for pool2 or pool3 @@ -981,8 +978,6 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad return true, nil } if pool2 { - pool.all.Add(tx, pool2) - pool.priced.Put(tx, pool2) pool.journalTx(from, tx) pool.queueTxEvent(tx, true) _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true, true) @@ -2197,20 +2192,20 @@ func (pool *LegacyPool) startPeriodicTransfer(t time.Duration) { // transferTransactions mainly moves from pool 3 to pool 2 func (pool *LegacyPool) transferTransactions() { - maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue - maxPool2Size := pool.config.Pool2Slots + maxPool1Size := int(pool.config.GlobalSlots + pool.config.GlobalQueue) + maxPool2Size := int(pool.config.Pool2Slots) maxPool1Pool2CombinedSize := maxPool1Size + maxPool2Size - extraSizePool2Pool1 := uint64(len(pool.pending)) + uint64(len(pool.queue)) - maxPool1Pool2CombinedSize + extraSizePool2Pool1 := maxPool1Pool2CombinedSize - int(uint64(len(pool.pending))+uint64(len(pool.queue))) if extraSizePool2Pool1 <= 0 { return } currentPool1Pool2Size := pool.all.Slots() - canTransferPool3ToPool2 := maxPool1Pool2CombinedSize > uint64(currentPool1Pool2Size) + canTransferPool3ToPool2 := maxPool1Pool2CombinedSize > currentPool1Pool2Size if !canTransferPool3ToPool2 { return } - extraSlots := maxPool1Pool2CombinedSize - uint64(currentPool1Pool2Size) + extraSlots := maxPool1Pool2CombinedSize - currentPool1Pool2Size extraTransactions := extraSlots / 4 // Since maximum slots per transaction is 4 // So now we can take out extraTransactions number of transactions from pool3 and put in pool2 if extraTransactions < 1 { @@ -2225,7 +2220,7 @@ func (pool *LegacyPool) transferTransactions() { } for _, transaction := range tx { - if !(uint64(pool.all.Slots()) < maxPool1Pool2CombinedSize) { + if !(pool.all.Slots() < maxPool1Pool2CombinedSize) { break } from, _ := types.Sender(pool.signer, transaction) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 09d5b7c933..e307ea78bb 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2256,7 +2256,7 @@ func TestReplacement(t *testing.T) { func TestTransferTransactions(t *testing.T) { t.Parallel() - + testTxPoolConfig.InterPoolTransferTime = 5 * time.Second pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() @@ -2265,6 +2265,7 @@ func TestTransferTransactions(t *testing.T) { pool.config.Pool2Slots = 1 pool.config.Pool3Slots = 1 + pool.config.InterPoolTransferTime = 5 * time.Second // Create a number of test accounts and fund them keys := make([]*ecdsa.PrivateKey, 4) @@ -2276,7 +2277,7 @@ func TestTransferTransactions(t *testing.T) { tx := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[0]) from, _ := types.Sender(pool.signer, tx) pool.addToPool12OrPool3(tx, from, true, false, false, true) - time.Sleep(2 * time.Minute) + time.Sleep(10 * time.Second) pending, queue := pool.Stats() if pending != 0 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) @@ -2284,6 +2285,27 @@ func TestTransferTransactions(t *testing.T) { if queue != 1 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } + + tx2 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) + from2, _ := types.Sender(pool.signer, tx2) + pool.addToPool12OrPool3(tx2, from2, true, false, false, true) + time.Sleep(2 * time.Second) + pending, queue = pool.Stats() + if pending != 0 { + t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queue != 1 { + t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) + } + time.Sleep(3 * time.Second) + pending, queue = pool.Stats() + if pending != 0 { + t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queue != 1 { + t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) + } + } // Tests that the pool rejects replacement dynamic fee transactions that don't From 5f398db90ab5006ca1899df3a75695c4d6f8ccd8 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 3 Sep 2024 16:57:21 +0100 Subject: [PATCH 32/57] pool: pool2 can have 0 size --- core/txpool/legacypool/legacypool.go | 2 +- core/txpool/legacypool/legacypool_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index e0dcb513b3..4eaeada726 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -198,7 +198,7 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) conf.GlobalQueue = DefaultConfig.GlobalQueue } - if conf.Pool2Slots < 1 { + if conf.Pool2Slots < 0 { log.Warn("Sanitizing invalid txpool pool 2 slots", "provided", conf.Pool2Slots, "updated", DefaultConfig.Pool2Slots) conf.Pool2Slots = DefaultConfig.Pool2Slots } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index e307ea78bb..2c22706b04 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1740,6 +1740,7 @@ func TestRepricingKeepsLocals(t *testing.T) { func TestUnderpricing(t *testing.T) { t.Parallel() testTxPoolConfig.Pool3Slots = 5 + testTxPoolConfig.Pool2Slots = 0 // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) From 706a24eb2ce7112633b4b648f9313aa7dd7568e3 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 4 Sep 2024 04:46:45 +0100 Subject: [PATCH 33/57] pool: lint fix --- core/txpool/legacypool/legacypool.go | 6 +----- core/txpool/legacypool/legacypool_test.go | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 4eaeada726..b69d77e694 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -198,10 +198,6 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) conf.GlobalQueue = DefaultConfig.GlobalQueue } - if conf.Pool2Slots < 0 { - log.Warn("Sanitizing invalid txpool pool 2 slots", "provided", conf.Pool2Slots, "updated", DefaultConfig.Pool2Slots) - conf.Pool2Slots = DefaultConfig.Pool2Slots - } if conf.Pool3Slots < 1 { log.Warn("Sanitizing invalid txpool pool 3 slots", "provided", conf.Pool3Slots, "updated", DefaultConfig.Pool3Slots) conf.Pool3Slots = DefaultConfig.Pool3Slots @@ -2214,7 +2210,7 @@ func (pool *LegacyPool) transferTransactions() { log.Debug("Will attempt to transfer from pool3 to pool2", "transactions", extraTransactions) - tx := pool.localBufferPool.Flush(int(extraTransactions)) + tx := pool.localBufferPool.Flush(extraTransactions) if len(tx) == 0 { return } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 2c22706b04..8701d32716 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2306,7 +2306,6 @@ func TestTransferTransactions(t *testing.T) { if queue != 1 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } - } // Tests that the pool rejects replacement dynamic fee transactions that don't From 0e6154395496148ff5d63954cbac52d758e928bd Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Fri, 6 Sep 2024 12:20:36 +0100 Subject: [PATCH 34/57] test: requestPromoteExecutables after every enqueue for testing --- core/txpool/legacypool/legacypool.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b69d77e694..25716080fe 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -966,6 +966,9 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad if err != nil { return false, err } + dirty := newAccountSet(pool.signer) + dirty.addTx(tx) + <-pool.requestPromoteExecutables(dirty) log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -980,6 +983,9 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad if err != nil { return false, err } + dirty := newAccountSet(pool.signer) + dirty.addTx(tx) + <-pool.requestPromoteExecutables(dirty) log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat From 0a5dbef9f4fb38307e9169d9384f3767f663852e Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Fri, 6 Sep 2024 17:19:56 +0100 Subject: [PATCH 35/57] pool: queued goes to 0 locally after this change --- core/txpool/legacypool/legacypool.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 25716080fe..c9b3e3474b 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -104,6 +104,8 @@ var ( queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) localGauge = metrics.NewRegisteredGauge("txpool/local", nil) slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) + pool2Gauge = metrics.NewRegisteredGauge("txpool/pool2", nil) + pool3Gauge = metrics.NewRegisteredGauge("txpool/pool3", nil) reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil) ) @@ -968,7 +970,9 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad } dirty := newAccountSet(pool.signer) dirty.addTx(tx) - <-pool.requestPromoteExecutables(dirty) + go func() { + <-pool.requestPromoteExecutables(dirty) + }() log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -985,7 +989,10 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad } dirty := newAccountSet(pool.signer) dirty.addTx(tx) - <-pool.requestPromoteExecutables(dirty) + pool2Gauge.Inc(1) + go func() { + <-pool.requestPromoteExecutables(dirty) + }() log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -994,6 +1001,7 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad } if pool3 { pool.localBufferPool.Add(tx) + pool3Gauge.Inc(1) return true, nil } return false, errors.New("could not add to any pool") From 248bb6b0d67297e84f32c1f182155e2ab8626a50 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 11 Sep 2024 15:57:06 +0100 Subject: [PATCH 36/57] pool: fastcache, interface, metrics modify --- core/txpool/legacypool/buffer.go | 4 + core/txpool/legacypool/fastcache.go | 187 ++++++++++++++++++++++ core/txpool/legacypool/fastcache_test.go | 94 +++++++++++ core/txpool/legacypool/legacypool.go | 16 +- core/txpool/legacypool/legacypool_test.go | 62 +++++-- core/txpool/legacypool/pool3.go | 15 ++ 6 files changed, 362 insertions(+), 16 deletions(-) create mode 100644 core/txpool/legacypool/fastcache.go create mode 100644 core/txpool/legacypool/fastcache_test.go create mode 100644 core/txpool/legacypool/pool3.go diff --git a/core/txpool/legacypool/buffer.go b/core/txpool/legacypool/buffer.go index 46e56af362..d9d88b1219 100644 --- a/core/txpool/legacypool/buffer.go +++ b/core/txpool/legacypool/buffer.go @@ -49,6 +49,8 @@ func (lru *LRUBuffer) Add(tx *types.Transaction) { elem := lru.buffer.PushFront(tx) lru.index[tx.Hash()] = elem lru.size += txSlots // Increase size by the slots of the new transaction + // Update pool3Gauge + pool3Gauge.Inc(1) } func (lru *LRUBuffer) Get(hash common.Hash) (*types.Transaction, bool) { @@ -76,6 +78,8 @@ func (lru *LRUBuffer) Flush(maxTransactions int) []*types.Transaction { delete(lru.index, removedTx.Hash()) lru.size -= numSlots(removedTx) // Decrease size by the slots of the removed transaction count++ + // Update pool3Gauge + pool3Gauge.Dec(1) } return txs } diff --git a/core/txpool/legacypool/fastcache.go b/core/txpool/legacypool/fastcache.go new file mode 100644 index 0000000000..7afbdf509f --- /dev/null +++ b/core/txpool/legacypool/fastcache.go @@ -0,0 +1,187 @@ +package legacypool + +import ( + "fmt" + "sync" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type LRUBufferFastCache struct { + cache *fastcache.Cache + capacity int + mu sync.Mutex + size int // Total number of slots used + order []common.Hash // Tracks the order of added transactions for LRU eviction +} + +// SerializeTransaction converts a transaction to a byte slice using RLP or any other encoding +func SerializeTransaction(tx *types.Transaction) []byte { + data, err := tx.MarshalBinary() // Ethereum uses RLP for transactions, so we can use this function + if err != nil { + return nil + } + return data +} + +// DeserializeTransaction converts a byte slice back to a transaction +func DeserializeTransaction(data []byte) (*types.Transaction, error) { + var tx types.Transaction + err := tx.UnmarshalBinary(data) + if err != nil { + return nil, err + } + return &tx, nil +} + +// LRUBufferFastCache initializes an LRU buffer with a given capacity +func NewLRUBufferFastCache(capacity int) *LRUBufferFastCache { + return &LRUBufferFastCache{ + cache: fastcache.New(capacity * 1024 * 1024), // fastcache size is in bytes + capacity: capacity, + order: make([]common.Hash, 0), + } +} + +func (lru *LRUBufferFastCache) Add(tx *types.Transaction) { + lru.mu.Lock() + defer lru.mu.Unlock() + + txHash := tx.Hash() + + // Check if the transaction already exists + if lru.cache.Has(txHash.Bytes()) { + // Move the transaction to the front in LRU order + lru.moveToFront(txHash) + return + } + + txSlots := numSlots(tx) + + // Evict the oldest transactions if the new one doesn't fit + for lru.size+txSlots > lru.capacity && len(lru.order) > 0 { + lru.evictOldest() + } + + // Add the transaction to the cache + txData := SerializeTransaction(tx) + lru.cache.Set(txHash.Bytes(), txData) + lru.size += txSlots + + // Update pool3Gauge + pool3Gauge.Inc(1) + + // Add to the order tracking + lru.order = append(lru.order, txHash) +} + +// Evict the oldest transaction in the LRU order +func (lru *LRUBufferFastCache) evictOldest() { + oldestHash := lru.order[0] + lru.order = lru.order[1:] + + // Remove from the cache + txData := lru.cache.Get(nil, oldestHash.Bytes()) + if len(txData) > 0 { + tx, err := DeserializeTransaction(txData) + if err == nil { + lru.size -= numSlots(tx) + } + } + + // Remove the oldest entry + lru.cache.Del(oldestHash.Bytes()) + + // Update pool3Gauge + pool3Gauge.Dec(1) +} + +// Move a transaction to the front of the LRU order +func (lru *LRUBufferFastCache) moveToFront(hash common.Hash) { + for i, h := range lru.order { + if h == hash { + // Remove the hash from its current position + lru.order = append(lru.order[:i], lru.order[i+1:]...) + break + } + } + // Add it to the front + lru.order = append(lru.order, hash) +} + +// Get retrieves a transaction from the cache and moves it to the front of the LRU order +func (lru *LRUBufferFastCache) Get(hash common.Hash) (*types.Transaction, bool) { + lru.mu.Lock() + defer lru.mu.Unlock() + + txData := lru.cache.Get(nil, hash.Bytes()) + if len(txData) == 0 { + return nil, false + } + + tx, err := DeserializeTransaction(txData) + if err != nil { + return nil, false + } + + // Move the accessed transaction to the front in LRU order + lru.moveToFront(hash) + + return tx, true +} + +// Flush removes and returns up to `maxTransactions` transactions from the cache +func (lru *LRUBufferFastCache) Flush(maxTransactions int) []*types.Transaction { + lru.mu.Lock() + defer lru.mu.Unlock() + + var txs []*types.Transaction + count := 0 + + // Remove up to maxTransactions transactions from the oldest + for count < maxTransactions && len(lru.order) > 0 { + oldestHash := lru.order[0] + lru.order = lru.order[1:] + + txData := lru.cache.Get(nil, oldestHash.Bytes()) + if len(txData) > 0 { + tx, err := DeserializeTransaction(txData) + if err == nil { + txs = append(txs, tx) + lru.size -= numSlots(tx) + count++ + } + } + + lru.cache.Del(oldestHash.Bytes()) + // Update pool3Gauge + pool3Gauge.Dec(1) + } + + return txs +} + +// Size returns the current size of the buffer in terms of slots +func (lru *LRUBufferFastCache) Size() int { + lru.mu.Lock() + defer lru.mu.Unlock() + return lru.size +} + +// PrintTxStats prints the hash, gas fee cap, and gas tip cap of all transactions +func (lru *LRUBufferFastCache) PrintTxStats() { + lru.mu.Lock() + defer lru.mu.Unlock() + + for _, hash := range lru.order { + txData := lru.cache.Get(nil, hash.Bytes()) + if len(txData) > 0 { + tx, err := DeserializeTransaction(txData) + if err == nil { + fmt.Println(tx.Hash().String(), tx.GasFeeCap().String(), tx.GasTipCap().String()) + } + } + } +} diff --git a/core/txpool/legacypool/fastcache_test.go b/core/txpool/legacypool/fastcache_test.go new file mode 100644 index 0000000000..050dc5d819 --- /dev/null +++ b/core/txpool/legacypool/fastcache_test.go @@ -0,0 +1,94 @@ +package legacypool + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewLRUBufferFastCache(t *testing.T) { + capacity := 10 + lru := NewLRUBufferFastCache(capacity) + + assert.Equal(t, capacity, lru.capacity, "capacity should match the given value") + assert.Equal(t, 0, lru.Size(), "size should be 0 for a new buffer") + assert.Equal(t, 0, len(lru.order), "order should be empty for a new buffer") +} + +func TestAddAndGetFastCache(t *testing.T) { + lru := NewLRUBufferFastCache(10) + + tx1 := createDummyTransaction(500) + tx2 := createDummyTransaction(1500) + + lru.Add(tx1) + lru.Add(tx2) + + assert.Equal(t, 2, lru.Size(), "size should be 2 after adding two transactions") + + retrievedTx, ok := lru.Get(tx1.Hash()) + assert.True(t, ok, "tx1 should be found in the buffer") + assert.Equal(t, tx1.Hash(), retrievedTx.Hash(), "retrieved tx1 hash should match the original hash") + + retrievedTx, ok = lru.Get(tx2.Hash()) + assert.True(t, ok, "tx2 should be found in the buffer") + assert.Equal(t, tx2.Hash(), retrievedTx.Hash(), "retrieved tx2 hash should match the original hash") +} + +func TestBufferCapacityFastCache(t *testing.T) { + lru := NewLRUBufferFastCache(2) // Capacity in slots + + tx1 := createDummyTransaction(500) // 1 slot + tx2 := createDummyTransaction(1500) // 1 slot + tx3 := createDummyTransaction(1000) // 1 slot + + lru.Add(tx1) + lru.Add(tx2) + + assert.Equal(t, 2, lru.Size(), "size should be 2 after adding two transactions") + + lru.Add(tx3) + + assert.Equal(t, 2, lru.Size(), "size should still be 2 after adding the third transaction") + _, ok := lru.Get(tx1.Hash()) + assert.False(t, ok, "tx1 should have been evicted") +} + +func TestFlushFastCache(t *testing.T) { + lru := NewLRUBufferFastCache(10) + + tx1 := createDummyTransaction(500) + tx2 := createDummyTransaction(1500) + tx3 := createDummyTransaction(1000) + + lru.Add(tx1) + lru.Add(tx2) + lru.Add(tx3) + + lru.PrintTxStats() + + flushedTxs := lru.Flush(2) + + assert.Equal(t, 2, len(flushedTxs), "should flush 2 transactions") + assert.Equal(t, tx1.Hash().String(), flushedTxs[0].Hash().String(), "correct flushed transaction") + assert.Equal(t, tx2.Hash().String(), flushedTxs[1].Hash().String(), "correct flushed transaction") + assert.Equal(t, 1, lru.Size(), "size should be 1 after flushing 2 transactions") + + lru.PrintTxStats() +} + +func TestSizeFastCache(t *testing.T) { + lru := NewLRUBufferFastCache(10) + + tx1 := createDummyTransaction(500) // 1 slot + tx2 := createDummyTransaction(1500) // 1 slot + + lru.Add(tx1) + assert.Equal(t, 1, lru.Size(), "size should be 1 after adding tx1") + + lru.Add(tx2) + assert.Equal(t, 2, lru.Size(), "size should be 2 after adding tx2") + + lru.Flush(1) + assert.Equal(t, 1, lru.Size(), "size should be 1 after flushing one transaction") +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index c9b3e3474b..174179ec72 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -248,7 +248,7 @@ type LegacyPool struct { all *lookup // All transactions to allow lookups priced *pricedList // All transactions sorted by price - localBufferPool *LRUBuffer // Local buffer transactions (Pool 3) + localBufferPool Pool3 // Local buffer transactions (Pool 3) reqResetCh chan *txpoolResetRequest reqPromoteCh chan *accountSet @@ -293,7 +293,7 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), - localBufferPool: NewLRUBuffer(int(maxPool3Size)), + localBufferPool: NewLRUBufferFastCache(int(maxPool3Size)), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -1001,7 +1001,6 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad } if pool3 { pool.localBufferPool.Add(tx) - pool3Gauge.Inc(1) return true, nil } return false, errors.New("could not add to any pool") @@ -1686,6 +1685,9 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T } log.Trace("Promoted queued transactions", "count", len(promoted)) queuedGauge.Dec(int64(len(readies))) + if list.txs.staticOnly { + pool2Gauge.Dec(int64(len(readies))) + } // Drop all transactions over the allowed limit var caps types.Transactions @@ -1701,6 +1703,9 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T // Mark all the items dropped as removed pool.priced.Removed(len(forwards) + len(drops) + len(caps)) queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) + if list.txs.staticOnly { + pool2Gauge.Dec(int64(len(forwards) + len(drops) + len(caps))) + } if pool.locals.contains(addr) { localGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) } @@ -2216,7 +2221,7 @@ func (pool *LegacyPool) transferTransactions() { return } extraSlots := maxPool1Pool2CombinedSize - currentPool1Pool2Size - extraTransactions := extraSlots / 4 // Since maximum slots per transaction is 4 + extraTransactions := (extraSlots + 3) / 4 // Since maximum slots per transaction is 4 // So now we can take out extraTransactions number of transactions from pool3 and put in pool2 if extraTransactions < 1 { return @@ -2240,8 +2245,11 @@ func (pool *LegacyPool) transferTransactions() { if err != nil { // if it never gets added to anything then add it back pool.addToPool12OrPool3(transaction, from, true, false, false, true) + //slots := int64(numSlots(transaction)) + pool2Gauge.Dec(1) continue } + pool2Gauge.Inc(1) log.Debug("Transferred from pool3 to pool2", "transactions", transaction.Hash().String()) } } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 8701d32716..74700ea8ba 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1926,6 +1926,7 @@ func TestStableUnderpricing(t *testing.T) { // Note, local transactions are never allowed to be dropped. func TestUnderpricingDynamicFee(t *testing.T) { t.Parallel() + testTxPoolConfig.InterPoolTransferTime = 5 * time.Second pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() @@ -2090,7 +2091,7 @@ func TestDualHeapEviction(t *testing.T) { } pending, queued := pool.Stats() if pending+queued != 5 { - t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, pool3 %d", pending+queued, 5, pending, queued, pool.localBufferPool.size) + t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, pool3 %d", pending+queued, 5, pending, queued, pool.localBufferPool.Size()) } } @@ -2261,7 +2262,7 @@ func TestTransferTransactions(t *testing.T) { pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() - pool.config.GlobalSlots = 2 + pool.config.GlobalSlots = 1 pool.config.GlobalQueue = 1 pool.config.Pool2Slots = 1 @@ -2269,7 +2270,7 @@ func TestTransferTransactions(t *testing.T) { pool.config.InterPoolTransferTime = 5 * time.Second // Create a number of test accounts and fund them - keys := make([]*ecdsa.PrivateKey, 4) + keys := make([]*ecdsa.PrivateKey, 5) for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) @@ -2278,34 +2279,71 @@ func TestTransferTransactions(t *testing.T) { tx := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[0]) from, _ := types.Sender(pool.signer, tx) pool.addToPool12OrPool3(tx, from, true, false, false, true) - time.Sleep(10 * time.Second) + time.Sleep(6 * time.Second) pending, queue := pool.Stats() - if pending != 0 { + if pending != 1 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queue != 1 { + if queue != 0 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } tx2 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) from2, _ := types.Sender(pool.signer, tx2) pool.addToPool12OrPool3(tx2, from2, true, false, false, true) - time.Sleep(2 * time.Second) + time.Sleep(6 * time.Second) pending, queue = pool.Stats() - if pending != 0 { + if pending != 2 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queue != 1 { + if queue != 0 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } - time.Sleep(3 * time.Second) + + tx3 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[2]) + from3, _ := types.Sender(pool.signer, tx3) + pool.addToPool12OrPool3(tx3, from3, true, false, false, true) + time.Sleep(6 * time.Second) pending, queue = pool.Stats() - if pending != 0 { + if pending != 3 { + t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queue != 0 { + t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) + } + + tx4 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[3]) + from4, _ := types.Sender(pool.signer, tx4) + pool.addToPool12OrPool3(tx4, from4, true, false, false, true) + time.Sleep(6 * time.Second) + pending, queue = pool.Stats() + if pending != 3 { + t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queue != 0 { + t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) + } + bufferSize := pool.localBufferPool.Size() + if bufferSize != 1 { + t.Errorf("buffer transactions mismatched: have %d, want %d", bufferSize, 1) + } + + tx5 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[4]) + from5, _ := types.Sender(pool.signer, tx5) + pool.addToPool12OrPool3(tx5, from5, true, false, false, true) + time.Sleep(6 * time.Second) + pending, queue = pool.Stats() + if pending != 3 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queue != 1 { + if queue != 0 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } + bufferSize = pool.localBufferPool.Size() + if bufferSize != 2 { + t.Errorf("buffer transactions mismatched: have %d, want %d", bufferSize, 1) + } + } // Tests that the pool rejects replacement dynamic fee transactions that don't diff --git a/core/txpool/legacypool/pool3.go b/core/txpool/legacypool/pool3.go new file mode 100644 index 0000000000..4b75001727 --- /dev/null +++ b/core/txpool/legacypool/pool3.go @@ -0,0 +1,15 @@ +package legacypool + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Pool3 is an interface representing a transaction buffer +type Pool3 interface { + Add(tx *types.Transaction) // Adds a transaction to the buffer + Get(hash common.Hash) (*types.Transaction, bool) // Retrieves a transaction by hash + Flush(maxTransactions int) []*types.Transaction // Flushes up to maxTransactions transactions + Size() int // Returns the current size of the buffer + PrintTxStats() // Prints the statistics of all transactions in the buffer +} From cf10c5cfe6c04a473dd276938c8b2db1f3d5a33f Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 11 Sep 2024 15:57:29 +0100 Subject: [PATCH 37/57] eth: send to some peers of pool2, not just static --- eth/handler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eth/handler.go b/eth/handler.go index 80bede11ec..3728046bb2 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -864,6 +864,9 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, staticOnly bool) default: numDirect = int(math.Sqrt(float64(len(peers)))) } + if staticOnly { + numDirect = int(math.Cbrt(float64(len(peers)))) + } // Send the tx unconditionally to a subset of our peers for _, peer := range peers[:numDirect] { txset[peer] = append(txset[peer], tx.Hash()) From b818cb78db718866facd2f4bd63b845f3f284f30 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 12 Sep 2024 16:29:57 +0100 Subject: [PATCH 38/57] pool: transfer on block import and simplify it --- core/txpool/errors.go | 2 +- core/txpool/legacypool/legacypool.go | 31 +++++++++------------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/core/txpool/errors.go b/core/txpool/errors.go index 52991a4890..2e5040127c 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -30,7 +30,7 @@ var ( // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") - // ErrUnderpriced is returned if a transaction's gas price is below the minimum + // ErrUnderpricedTransferredtoAnotherPool is returned if a transaction's gas price is below the minimum // configured for the transaction pool. ErrUnderpricedTransferredtoAnotherPool = errors.New("transaction underpriced, so it is either in pool2 or pool3") diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 174179ec72..5d2b2fc6de 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -306,7 +306,7 @@ func New(config Config, chain BlockChain) *LegacyPool { pool.journal = newTxJournal(config.Journal) } - pool.startPeriodicTransfer(config.InterPoolTransferTime) // todo (incomplete) Start the periodic transfer routine + //pool.startPeriodicTransfer(config.InterPoolTransferTime) return pool } @@ -831,7 +831,8 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e if !isLocal && pool.priced.Underpriced(tx) { addedToAnyPool, err := pool.addToPool12OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) if addedToAnyPool { - return false, txpool.ErrUnderpricedTransferredtoAnotherPool // The reserve code expects named error formatting + //return false, txpool.ErrUnderpricedTransferredtoAnotherPool // The reserve code expects named error formatting + return false, nil } if err != nil { log.Error("Error while trying to add to pool2 or pool3", "error", err) @@ -1001,6 +1002,7 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad } if pool3 { pool.localBufferPool.Add(tx) + log.Debug("adding to pool3", "transaction", tx.Hash().String()) return true, nil } return false, errors.New("could not add to any pool") @@ -1225,7 +1227,7 @@ func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, local bool) ([]er for i, tx := range txs { replaced, err := pool.add(tx, local) errs[i] = err - if err == nil && !replaced { + if err == nil && !replaced { // todo ensure err is nil for certain case in add() where there is actually no error dirty.addTx(tx) } } @@ -1483,6 +1485,10 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, promoteAddrs = append(promoteAddrs, addr) } } + + // Transfer transactions from pool3 to pool2 for new block import + pool.transferTransactions() + // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) @@ -2234,24 +2240,7 @@ func (pool *LegacyPool) transferTransactions() { return } - for _, transaction := range tx { - if !(pool.all.Slots() < maxPool1Pool2CombinedSize) { - break - } - from, _ := types.Sender(pool.signer, transaction) - - // use addToPool12OrPool3() function to transfer from pool3 to pool2 - _, err := pool.addToPool12OrPool3(transaction, from, true, false, true, false) // by default all pool3 transactions are considered local - if err != nil { - // if it never gets added to anything then add it back - pool.addToPool12OrPool3(transaction, from, true, false, false, true) - //slots := int64(numSlots(transaction)) - pool2Gauge.Dec(1) - continue - } - pool2Gauge.Inc(1) - log.Debug("Transferred from pool3 to pool2", "transactions", transaction.Hash().String()) - } + pool.Add(tx, true, false) } func (pool *LegacyPool) availableSlotsPool3() int { From 774e314096dac1e39583701c67605969e887d484 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 18 Sep 2024 13:01:22 +0100 Subject: [PATCH 39/57] pool: truly discard underpriced, Transfer after lock is over --- core/txpool/legacypool/buffer_test.go | 66 +++++---------- core/txpool/legacypool/legacypool.go | 97 +++++++++++------------ core/txpool/legacypool/legacypool_test.go | 68 +++++++++------- 3 files changed, 106 insertions(+), 125 deletions(-) diff --git a/core/txpool/legacypool/buffer_test.go b/core/txpool/legacypool/buffer_test.go index 49c1b3603a..8b69385315 100644 --- a/core/txpool/legacypool/buffer_test.go +++ b/core/txpool/legacypool/buffer_test.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" ) // Helper function to create a dummy transaction of specified size @@ -16,18 +17,11 @@ func createDummyTransaction(size int) *types.Transaction { func TestNewLRUBuffer(t *testing.T) { capacity := 10 lru := NewLRUBuffer(capacity) - if lru.capacity != capacity { - t.Errorf("expected capacity %d, got %d", capacity, lru.capacity) - } - if lru.buffer.Len() != 0 { - t.Errorf("expected buffer length 0, got %d", lru.buffer.Len()) - } - if len(lru.index) != 0 { - t.Errorf("expected index length 0, got %d", len(lru.index)) - } - if lru.size != 0 { - t.Errorf("expected size 0, got %d", lru.size) - } + + require.Equal(t, capacity, lru.capacity, "expected capacity to match") + require.Zero(t, lru.buffer.Len(), "expected buffer length to be zero") + require.Zero(t, len(lru.index), "expected index length to be zero") + require.Zero(t, lru.size, "expected size to be zero") } func TestAddAndGet(t *testing.T) { @@ -39,19 +33,15 @@ func TestAddAndGet(t *testing.T) { lru.Add(tx1) lru.Add(tx2) - if lru.Size() != 2 { - t.Errorf("expected size 2, got %d", lru.Size()) - } + require.Equal(t, 2, lru.Size(), "expected size to be 2") retrievedTx, ok := lru.Get(tx1.Hash()) - if !ok || retrievedTx.Hash() != tx1.Hash() { - t.Errorf("failed to retrieve tx1") - } + require.True(t, ok, "expected to retrieve tx1") + require.Equal(t, tx1.Hash(), retrievedTx.Hash(), "retrieved tx1 hash does not match") retrievedTx, ok = lru.Get(tx2.Hash()) - if !ok || retrievedTx.Hash() != tx2.Hash() { - t.Errorf("failed to retrieve tx2") - } + require.True(t, ok, "expected to retrieve tx2") + require.Equal(t, tx2.Hash(), retrievedTx.Hash(), "retrieved tx2 hash does not match") } func TestBufferCapacity(t *testing.T) { @@ -64,19 +54,13 @@ func TestBufferCapacity(t *testing.T) { lru.Add(tx1) lru.Add(tx2) - if lru.Size() != 2 { - t.Errorf("expected size 2, got %d", lru.Size()) - } + require.Equal(t, 2, lru.Size(), "expected size to be 2") lru.Add(tx3) - if lru.Size() != 2 { - t.Errorf("expected size 2 after adding tx3, got %d", lru.Size()) - } - - if _, ok := lru.Get(tx1.Hash()); ok { - t.Errorf("expected tx1 to be evicted") - } + require.Equal(t, 2, lru.Size(), "expected size to remain 2 after adding tx3") + _, ok := lru.Get(tx1.Hash()) + require.False(t, ok, "expected tx1 to be evicted") } func TestFlush(t *testing.T) { @@ -92,15 +76,11 @@ func TestFlush(t *testing.T) { flushedTxs := lru.Flush(2) - if len(flushedTxs) != 2 { - t.Errorf("expected to flush 2 transactions, got %d", len(flushedTxs)) - } + require.Len(t, flushedTxs, 2, "expected to flush 2 transactions") expectedSize := 1 actualSize := lru.Size() - if expectedSize != actualSize { - t.Errorf("expected size after flush %d, got %d", expectedSize, actualSize) - } + require.Equal(t, expectedSize, actualSize, "expected size after flush to match") } func TestSize(t *testing.T) { @@ -110,17 +90,11 @@ func TestSize(t *testing.T) { tx2 := createDummyTransaction(1500) // 2 slots lru.Add(tx1) - if lru.Size() != 1 { - t.Errorf("expected size 1, got %d", lru.Size()) - } + require.Equal(t, 1, lru.Size(), "expected size to be 1") lru.Add(tx2) - if lru.Size() != 2 { - t.Errorf("expected size 2, got %d", lru.Size()) - } + require.Equal(t, 2, lru.Size(), "expected size to be 2") lru.Flush(1) - if lru.Size() != 1 { - t.Errorf("expected size 1 after flush, got %d", lru.Size()) - } + require.Equal(t, 1, lru.Size(), "expected size to be 1 after flush") } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 5d2b2fc6de..677012bff4 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -143,9 +143,8 @@ type Config struct { Pool2Slots uint64 // Maximum number of transaction slots in pool 2 Pool3Slots uint64 // Maximum number of transaction slots in pool 3 - Lifetime time.Duration // Maximum amount of time non-executable transaction are queued - ReannounceTime time.Duration // Duration for announcing local pending transactions again - InterPoolTransferTime time.Duration // Attempt to transfer from pool3 to pool2 every this much time + Lifetime time.Duration // Maximum amount of time non-executable transaction are queued + ReannounceTime time.Duration // Duration for announcing local pending transactions again } // DefaultConfig contains the default configurations for the transaction pool. @@ -163,9 +162,8 @@ var DefaultConfig = Config{ Pool2Slots: 1024, Pool3Slots: 1024, - Lifetime: 3 * time.Hour, - ReannounceTime: 10 * 365 * 24 * time.Hour, - InterPoolTransferTime: time.Minute, + Lifetime: 3 * time.Hour, + ReannounceTime: 10 * 365 * 24 * time.Hour, } // sanitize checks the provided user configurations and changes anything that's @@ -200,10 +198,7 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) conf.GlobalQueue = DefaultConfig.GlobalQueue } - if conf.Pool3Slots < 1 { - log.Warn("Sanitizing invalid txpool pool 3 slots", "provided", conf.Pool3Slots, "updated", DefaultConfig.Pool3Slots) - conf.Pool3Slots = DefaultConfig.Pool3Slots - } + if conf.Lifetime < 1 { log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) conf.Lifetime = DefaultConfig.Lifetime @@ -306,8 +301,6 @@ func New(config Config, chain BlockChain) *LegacyPool { pool.journal = newTxJournal(config.Journal) } - //pool.startPeriodicTransfer(config.InterPoolTransferTime) - return pool } @@ -544,6 +537,17 @@ func (pool *LegacyPool) Stats() (int, int) { return pool.stats() } +func (pool *LegacyPool) StatsPool3() int { + pool.mu.RLock() + defer pool.mu.RUnlock() + + if pool.localBufferPool == nil { + return 0 + } + + return pool.localBufferPool.Size() +} + // stats retrieves the current pool stats, namely the number of pending and the // number of queued (non-executable) transactions. func (pool *LegacyPool) stats() (int, int) { @@ -780,14 +784,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue maxPool2Size := pool.config.Pool2Slots txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) - var includePool1, includePool2, includePool3 bool - if txPoolSizeAfterCurrentTx <= maxPool1Size { - includePool1 = true - } else if (txPoolSizeAfterCurrentTx > maxPool1Size) && (txPoolSizeAfterCurrentTx <= (maxPool1Size + maxPool2Size)) { - includePool2 = true - } else { - includePool3 = true - } // Make the local flag. If it's from local source or it's from the network but // the sender is marked as local previously, treat it as the local transaction. @@ -829,15 +825,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e if txPoolSizeAfterCurrentTx > (maxPool1Size + maxPool2Size) { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { - addedToAnyPool, err := pool.addToPool12OrPool3(tx, from, isLocal, includePool1, includePool2, includePool3) - if addedToAnyPool { - //return false, txpool.ErrUnderpricedTransferredtoAnotherPool // The reserve code expects named error formatting - return false, nil - } - if err != nil { - log.Error("Error while trying to add to pool2 or pool3", "error", err) - } - log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) underpricedTxMeter.Mark(1) return false, txpool.ErrUnderpriced @@ -885,22 +872,8 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return false, txpool.ErrFutureReplacePending // todo 1 maybe in this case the future transaction can be part of pool3? } } - - // calculate total number of slots in drop. Accordingly add them to pool3 (if there is space) - // all members of drop will be dropped from pool1/2 regardless of whether they get added to pool3 or not - availableSlotsPool3 := pool.availableSlotsPool3() - if availableSlotsPool3 > 0 { - // transfer availableSlotsPool3 number of transactions slots from drop to pool3 - currentSlotsUsed := 0 - for _, tx := range drop { - txSlots := numSlots(tx) - if currentSlotsUsed+txSlots <= availableSlotsPool3 { - from, _ := types.Sender(pool.signer, tx) - pool.addToPool12OrPool3(tx, from, isLocal, false, false, true) - currentSlotsUsed += txSlots - } - } - } + + pool.addToPool3(drop, isLocal) // Kick out the underpriced remote transactions. for _, tx := range drop { @@ -917,7 +890,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Try to replace an existing transaction in the pending pool if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { // Nonce already pending, check if required price bump is met - inserted, old := list.Add(tx, pool.config.PriceBump, includePool2) + inserted, old := list.Add(tx, pool.config.PriceBump, false) if !inserted { pendingDiscardMeter.Mark(1) return false, txpool.ErrReplaceUnderpriced @@ -940,7 +913,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.enqueueTx(hash, tx, isLocal, true, includePool2) // At this point pool1 can incorporate this. So no need for pool2 or pool3 + replaced, err = pool.enqueueTx(hash, tx, isLocal, true, true) // At this point pool1 can incorporate this. So no need for pool2 or pool3 if err != nil { return false, err } @@ -960,6 +933,24 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return replaced, nil } +func (pool *LegacyPool) addToPool3(drop types.Transactions, isLocal bool) { + // calculate total number of slots in drop. Accordingly add them to pool3 (if there is space) + // all members of drop will be dropped from pool1/2 regardless of whether they get added to pool3 or not + availableSlotsPool3 := pool.availableSlotsPool3() + if availableSlotsPool3 > 0 { + // transfer availableSlotsPool3 number of transactions slots from drop to pool3 + currentSlotsUsed := 0 + for _, tx := range drop { + txSlots := numSlots(tx) + if currentSlotsUsed+txSlots <= availableSlotsPool3 { + from, _ := types.Sender(pool.signer, tx) + pool.addToPool12OrPool3(tx, from, isLocal, false, false, true) + currentSlotsUsed += txSlots + } + } + } +} + // addToPool12OrPool3 adds a transaction to pool1 or pool2 or pool3 depending on which one is asked for func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Address, isLocal bool, pool1, pool2, pool3 bool) (bool, error) { if pool1 { @@ -1486,8 +1477,8 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } } - // Transfer transactions from pool3 to pool2 for new block import - pool.transferTransactions() + //// Transfer transactions from pool3 to pool2 for new block import + //pool.transferTransactions() // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) @@ -1521,6 +1512,9 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, pool.changesSinceReorg = 0 // Reset change counter pool.mu.Unlock() + // Transfer transactions from pool3 to pool2 for new block import + pool.transferTransactions() + // Notify subsystems for newly added transactions for _, tx := range promoted { addr, _ := types.Sender(pool.signer, tx) @@ -1542,11 +1536,13 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } // Send static transactions if len(staticTxs) > 0 { + fmt.Println("New txevent emitted for static ", staticTxs[0].Hash()) pool.txFeed.Send(core.NewTxsEvent{Txs: staticTxs, Static: true}) } // Send dynamic transactions if len(nonStaticTxs) > 0 { + fmt.Println("New txevent emitted for non static ", nonStaticTxs[0].Hash()) pool.txFeed.Send(core.NewTxsEvent{Txs: nonStaticTxs, Static: false}) } } @@ -2255,7 +2251,8 @@ func (pool *LegacyPool) availableSlotsPool3() int { func (pool *LegacyPool) printTxStats() { for _, l := range pool.pending { for _, transaction := range l.txs.items { - fmt.Println("Pending:", transaction.Hash().String(), transaction.GasFeeCap(), transaction.GasTipCap()) + from, _ := types.Sender(pool.signer, transaction) + fmt.Println("from: ", from, " Pending:", transaction.Hash().String(), transaction.GasFeeCap(), transaction.GasTipCap()) } } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 74700ea8ba..fc07d4b586 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -40,6 +40,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" ) var ( @@ -1793,8 +1794,8 @@ func TestUnderpricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Ensure that adding an underpriced transaction on block limit fails - if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpricedTransferredtoAnotherPool) { - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpricedTransferredtoAnotherPool) + if err := pool.addRemoteSync(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) } // Replace a future transaction with a future transaction if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(2), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1 @@ -1926,7 +1927,6 @@ func TestStableUnderpricing(t *testing.T) { // Note, local transactions are never allowed to be dropped. func TestUnderpricingDynamicFee(t *testing.T) { t.Parallel() - testTxPoolConfig.InterPoolTransferTime = 5 * time.Second pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() @@ -1935,7 +1935,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { pool.config.GlobalQueue = 1 pool.config.Pool2Slots = 1 - pool.config.Pool3Slots = 1 + pool.config.Pool3Slots = 0 // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) @@ -1946,6 +1946,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { keys := make([]*ecdsa.PrivateKey, 4) for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() + fmt.Println("key ", i, "is ", crypto.PubkeyToAddress(keys[i].PublicKey).String()) testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } @@ -1978,8 +1979,8 @@ func TestUnderpricingDynamicFee(t *testing.T) { // Ensure that adding an underpriced transaction fails tx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]) - if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpricedTransferredtoAnotherPool) { // Pend K0:0, K0:1, K2:0; Que K1:1 - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpricedTransferredtoAnotherPool) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { // Pend K0:0, K0:1, K2:0; Que K1:1 + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } // Ensure that adding high priced transactions drops cheap ones, but not own @@ -1996,16 +1997,22 @@ func TestUnderpricingDynamicFee(t *testing.T) { if err := pool.addRemoteSync(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } + fmt.Println("Stats before before validateEvents") + pool.printTxStats() pending, queued = pool.Stats() if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 1 { + if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - if err := validateEvents(events, 2); err != nil { + fmt.Println("Stats before validateEvents") + pool.printTxStats() + if err := validateEvents(events, 4); err != nil { // todo make it 4...After this validateEvents the pending becomes 3?! t.Fatalf("additional event firing failed: %v", err) } + fmt.Println("Stats after validateEvents") + pool.printTxStats() if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2018,11 +2025,12 @@ func TestUnderpricingDynamicFee(t *testing.T) { if err := pool.addLocal(ltx); err != nil { t.Fatalf("failed to add new underpriced local transaction: %v", err) } + pending, queued = pool.Stats() - if pending != 3 { + if pending != 5 { // 3 t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 1 { + if queued != 0 { // 1 t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } if err := validateEvents(events, 2); err != nil { @@ -2257,8 +2265,8 @@ func TestReplacement(t *testing.T) { } func TestTransferTransactions(t *testing.T) { + // todo do runReorg() as only during that time the transfer of transctions occur t.Parallel() - testTxPoolConfig.InterPoolTransferTime = 5 * time.Second pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() @@ -2267,7 +2275,6 @@ func TestTransferTransactions(t *testing.T) { pool.config.Pool2Slots = 1 pool.config.Pool3Slots = 1 - pool.config.InterPoolTransferTime = 5 * time.Second // Create a number of test accounts and fund them keys := make([]*ecdsa.PrivateKey, 5) @@ -2279,68 +2286,71 @@ func TestTransferTransactions(t *testing.T) { tx := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[0]) from, _ := types.Sender(pool.signer, tx) pool.addToPool12OrPool3(tx, from, true, false, false, true) - time.Sleep(6 * time.Second) + //time.Sleep(6 * time.Second) pending, queue := pool.Stats() - if pending != 1 { + p3Size := pool.StatsPool3() + if pending != 0 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) } if queue != 0 { - t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) + t.Errorf("queued transactions mismatched: have %d, want %d", queue, 0) } + assert.Equal(t, 1, p3Size, "pool3 size unexpected") tx2 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) from2, _ := types.Sender(pool.signer, tx2) pool.addToPool12OrPool3(tx2, from2, true, false, false, true) - time.Sleep(6 * time.Second) + <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) + //time.Sleep(6 * time.Second) pending, queue = pool.Stats() - if pending != 2 { + if pending != 0 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queue != 0 { + if queue != 1 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } tx3 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[2]) from3, _ := types.Sender(pool.signer, tx3) pool.addToPool12OrPool3(tx3, from3, true, false, false, true) - time.Sleep(6 * time.Second) + //time.Sleep(6 * time.Second) pending, queue = pool.Stats() - if pending != 3 { + if pending != 0 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queue != 0 { + if queue != 1 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } tx4 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[3]) from4, _ := types.Sender(pool.signer, tx4) pool.addToPool12OrPool3(tx4, from4, true, false, false, true) - time.Sleep(6 * time.Second) + //time.Sleep(6 * time.Second) pending, queue = pool.Stats() - if pending != 3 { + if pending != 0 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queue != 0 { + if queue != 1 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } bufferSize := pool.localBufferPool.Size() - if bufferSize != 1 { + if bufferSize != 2 { t.Errorf("buffer transactions mismatched: have %d, want %d", bufferSize, 1) } tx5 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[4]) from5, _ := types.Sender(pool.signer, tx5) pool.addToPool12OrPool3(tx5, from5, true, false, false, true) - time.Sleep(6 * time.Second) + //time.Sleep(6 * time.Second) pending, queue = pool.Stats() - if pending != 3 { + if pending != 0 { t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queue != 0 { + if queue != 1 { t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) } bufferSize = pool.localBufferPool.Size() - if bufferSize != 2 { + if bufferSize != 3 { t.Errorf("buffer transactions mismatched: have %d, want %d", bufferSize, 1) } From 629af6dab4c8593d37e8b2203242030a696dc50b Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 18 Sep 2024 13:04:29 +0100 Subject: [PATCH 40/57] pool: else ifs instead of ifs --- core/txpool/legacypool/legacypool.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 677012bff4..85c84b7586 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -872,7 +872,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return false, txpool.ErrFutureReplacePending // todo 1 maybe in this case the future transaction can be part of pool3? } } - + pool.addToPool3(drop, isLocal) // Kick out the underpriced remote transactions. @@ -971,8 +971,7 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad pool.beats[from] = time.Now() return true, nil - } - if pool2 { + } else if pool2 { pool.journalTx(from, tx) pool.queueTxEvent(tx, true) _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true, true) @@ -990,8 +989,7 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad // Successful promotion, bump the heartbeat pool.beats[from] = time.Now() return true, nil - } - if pool3 { + } else if pool3 { pool.localBufferPool.Add(tx) log.Debug("adding to pool3", "transaction", tx.Hash().String()) return true, nil From 3e3c56b235b7a9537dc5f66c1e1fdf7865242533 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 19 Sep 2024 04:32:47 +0100 Subject: [PATCH 41/57] pool: address minor issues --- core/txpool/legacypool/legacypool.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 85c84b7586..3b8d2b46e0 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -270,7 +270,6 @@ type txpoolResetRequest struct { func New(config Config, chain BlockChain) *LegacyPool { // Sanitize the input to ensure no vulnerable gas prices are set config = (&config).sanitize() - maxPool3Size := config.Pool3Slots // Create the transaction pool with its initial settings pool := &LegacyPool{ @@ -288,7 +287,7 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), - localBufferPool: NewLRUBufferFastCache(int(maxPool3Size)), + localBufferPool: NewLRUBufferFastCache(int(config.Pool3Slots)), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -993,8 +992,9 @@ func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Ad pool.localBufferPool.Add(tx) log.Debug("adding to pool3", "transaction", tx.Hash().String()) return true, nil + } else { + return false, errors.New("could not add to any pool") } - return false, errors.New("could not add to any pool") } // isGapped reports whether the given transaction is immediately executable. From 09575625dbeb90964a88c301afd1e626249f50be Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 25 Sep 2024 14:06:24 +0100 Subject: [PATCH 42/57] pool: heap map as pool3 --- core/txpool/legacypool/heap.go | 153 ++++++++++++++++ core/txpool/legacypool/heap_test.go | 263 +++++++++++++++++++++++++++ core/txpool/legacypool/legacypool.go | 10 +- 3 files changed, 422 insertions(+), 4 deletions(-) create mode 100644 core/txpool/legacypool/heap.go create mode 100644 core/txpool/legacypool/heap_test.go diff --git a/core/txpool/legacypool/heap.go b/core/txpool/legacypool/heap.go new file mode 100644 index 0000000000..78fb2b2f03 --- /dev/null +++ b/core/txpool/legacypool/heap.go @@ -0,0 +1,153 @@ +package legacypool + +import ( + "container/heap" + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// txHeapItem implements the Interface interface of heap so that it can be heapified +type txHeapItem struct { + tx *types.Transaction + timestamp int64 // Unix timestamp of when the transaction was added + sequence uint64 // Unique, monotonically increasing sequence number + index int +} + +type txHeap []*txHeapItem + +func (h txHeap) Len() int { return len(h) } +func (h txHeap) Less(i, j int) bool { + // Order first by timestamp, then by sequence number if timestamps are equal + if h[i].timestamp == h[j].timestamp { + return h[i].sequence < h[j].sequence + } + return h[i].timestamp < h[j].timestamp +} +func (h txHeap) Swap(i, j int) { + h[i], h[j] = h[j], h[i] + h[i].index = i + h[j].index = j +} + +func (h *txHeap) Push(x interface{}) { + n := len(*h) + item := x.(*txHeapItem) + item.index = n + *h = append(*h, item) +} + +func (h *txHeap) Pop() interface{} { + old := *h + n := len(old) + item := old[n-1] + old[n-1] = nil // avoid memory leak + item.index = -1 // for safety + *h = old[0 : n-1] + return item +} + +type TxPool3Heap struct { + txHeap txHeap + index map[common.Hash]*txHeapItem + mu sync.RWMutex + sequence uint64 // Monotonically increasing sequence number +} + +func NewTxPool3Heap() *TxPool3Heap { + return &TxPool3Heap{ + txHeap: make(txHeap, 0), + index: make(map[common.Hash]*txHeapItem), + sequence: 0, + } +} + +func (tp *TxPool3Heap) Add(tx *types.Transaction) { + tp.mu.Lock() + defer tp.mu.Unlock() + + if _, exists := tp.index[tx.Hash()]; exists { + // Transaction already in pool, ignore + return + } + + tp.sequence++ + item := &txHeapItem{ + tx: tx, + timestamp: time.Now().Unix(), + sequence: tp.sequence, + } + heap.Push(&tp.txHeap, item) + tp.index[tx.Hash()] = item +} + +func (tp *TxPool3Heap) Get(hash common.Hash) (*types.Transaction, bool) { + tp.mu.RLock() + defer tp.mu.RUnlock() + + if item, ok := tp.index[hash]; ok { + return item.tx, true + } + return nil, false +} + +func (tp *TxPool3Heap) Remove(hash common.Hash) { + tp.mu.Lock() + defer tp.mu.Unlock() + + if item, ok := tp.index[hash]; ok { + heap.Remove(&tp.txHeap, item.index) + delete(tp.index, hash) + } +} + +func (tp *TxPool3Heap) Flush(n int) []*types.Transaction { + tp.mu.Lock() + defer tp.mu.Unlock() + + if n > tp.txHeap.Len() { + n = tp.txHeap.Len() + } + + txs := make([]*types.Transaction, n) + for i := 0; i < n; i++ { + item := heap.Pop(&tp.txHeap).(*txHeapItem) + txs[i] = item.tx + delete(tp.index, item.tx.Hash()) + } + + return txs +} + +func (tp *TxPool3Heap) Len() int { + tp.mu.RLock() + defer tp.mu.RUnlock() + return tp.txHeap.Len() +} + +func (tp *TxPool3Heap) Size() int { + tp.mu.RLock() + defer tp.mu.RUnlock() + + totalSize := 0 + for _, item := range tp.txHeap { + totalSize += numSlots(item.tx) + } + + return totalSize +} + +func (tp *TxPool3Heap) PrintTxStats() { + tp.mu.RLock() + defer tp.mu.RUnlock() + + for _, item := range tp.txHeap { + tx := item.tx + fmt.Printf("Hash: %s, Timestamp: %d, Sequence: %d, GasFeeCap: %s, GasTipCap: %s\n", + tx.Hash().String(), item.timestamp, item.sequence, tx.GasFeeCap().String(), tx.GasTipCap().String()) + } +} diff --git a/core/txpool/legacypool/heap_test.go b/core/txpool/legacypool/heap_test.go new file mode 100644 index 0000000000..c001e27d42 --- /dev/null +++ b/core/txpool/legacypool/heap_test.go @@ -0,0 +1,263 @@ +package legacypool + +import ( + "math/big" + rand2 "math/rand" + "testing" + "time" + + "github.com/cometbft/cometbft/libs/rand" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Helper function to create a test transaction +func createTestTx(nonce uint64, gasPrice *big.Int) *types.Transaction { + to := common.HexToAddress("0x1234567890123456789012345678901234567890") + return types.NewTransaction(nonce, to, big.NewInt(1000), 21000, gasPrice, nil) +} + +func TestNewTxPool3Heap(t *testing.T) { + pool := NewTxPool3Heap() + if pool == nil { + t.Fatal("NewTxPool3Heap returned nil") + } + if pool.Len() != 0 { + t.Errorf("New pool should be empty, got length %d", pool.Len()) + } +} + +func TestTxPool3HeapAdd(t *testing.T) { + pool := NewTxPool3Heap() + tx := createTestTx(1, big.NewInt(1000)) + + pool.Add(tx) + if pool.Len() != 1 { + t.Errorf("Pool should have 1 transaction, got %d", pool.Len()) + } + + // Add the same transaction again + pool.Add(tx) + if pool.Len() != 1 { + t.Errorf("Pool should still have 1 transaction after adding duplicate, got %d", pool.Len()) + } +} + +func TestTxPool3HeapGet(t *testing.T) { + pool := NewTxPool3Heap() + tx := createTestTx(1, big.NewInt(1000)) + pool.Add(tx) + + gotTx, exists := pool.Get(tx.Hash()) + if !exists { + t.Fatal("Get returned false for existing transaction") + } + if gotTx.Hash() != tx.Hash() { + t.Errorf("Get returned wrong transaction. Want %v, got %v", tx.Hash(), gotTx.Hash()) + } + + _, exists = pool.Get(common.Hash{}) + if exists { + t.Error("Get returned true for non-existent transaction") + } +} + +func TestTxPool3HeapRemove(t *testing.T) { + pool := NewTxPool3Heap() + tx := createTestTx(1, big.NewInt(1000)) + pool.Add(tx) + + pool.Remove(tx.Hash()) + if pool.Len() != 0 { + t.Errorf("Pool should be empty after removing the only transaction, got length %d", pool.Len()) + } + + // Try to remove non-existent transaction + pool.Remove(common.Hash{}) + if pool.Len() != 0 { + t.Error("Removing non-existent transaction should not affect pool size") + } +} + +func TestTxPool3HeapPopN(t *testing.T) { + pool := NewTxPool3Heap() + tx1 := createTestTx(1, big.NewInt(1000)) + tx2 := createTestTx(2, big.NewInt(2000)) + tx3 := createTestTx(3, big.NewInt(3000)) + + pool.Add(tx1) + time.Sleep(time.Millisecond) // Ensure different timestamps + pool.Add(tx2) + time.Sleep(time.Millisecond) + pool.Add(tx3) + + popped := pool.Flush(2) + if len(popped) != 2 { + t.Fatalf("PopN(2) should return 2 transactions, got %d", len(popped)) + } + if popped[0].Hash() != tx1.Hash() || popped[1].Hash() != tx2.Hash() { + t.Error("PopN returned transactions in wrong order") + } + if pool.Len() != 1 { + t.Errorf("Pool should have 1 transaction left, got %d", pool.Len()) + } + + // Pop more than available + popped = pool.Flush(2) + if len(popped) != 1 { + t.Fatalf("PopN(2) should return 1 transaction when only 1 is left, got %d", len(popped)) + } + if popped[0].Hash() != tx3.Hash() { + t.Error("PopN returned wrong transaction") + } + if pool.Len() != 0 { + t.Errorf("Pool should be empty, got length %d", pool.Len()) + } +} + +func TestTxPool3HeapOrdering(t *testing.T) { + pool := NewTxPool3Heap() + tx1 := createTestTx(1, big.NewInt(1000)) + tx2 := createTestTx(2, big.NewInt(2000)) + tx3 := createTestTx(3, big.NewInt(3000)) + + pool.Add(tx2) + time.Sleep(time.Millisecond) // Ensure different timestamps + pool.Add(tx1) + pool.Add(tx3) // Added immediately after tx1, should have same timestamp but higher sequence + + popped := pool.Flush(3) + if len(popped) != 3 { + t.Fatalf("PopN(3) should return 3 transactions, got %d", len(popped)) + } + if popped[0].Hash() != tx2.Hash() || popped[1].Hash() != tx1.Hash() || popped[2].Hash() != tx3.Hash() { + t.Error("Transactions not popped in correct order (earliest timestamp first, then by sequence)") + } +} + +func TestTxPool3HeapLen(t *testing.T) { + pool := NewTxPool3Heap() + if pool.Len() != 0 { + t.Errorf("New pool should have length 0, got %d", pool.Len()) + } + + pool.Add(createTestTx(1, big.NewInt(1000))) + if pool.Len() != 1 { + t.Errorf("Pool should have length 1 after adding a transaction, got %d", pool.Len()) + } + + pool.Add(createTestTx(2, big.NewInt(2000))) + if pool.Len() != 2 { + t.Errorf("Pool should have length 2 after adding another transaction, got %d", pool.Len()) + } + + pool.Flush(1) + if pool.Len() != 1 { + t.Errorf("Pool should have length 1 after popping a transaction, got %d", pool.Len()) + } +} + +// Helper function to create a random test transaction +func createRandomTestTx() *types.Transaction { + nonce := uint64(rand.Intn(1000000)) + to := common.BytesToAddress(rand.Bytes(20)) + amount := new(big.Int).Rand(rand2.New(rand2.NewSource(rand.Int63())), big.NewInt(1e18)) + gasLimit := uint64(21000) + gasPrice := new(big.Int).Rand(rand2.New(rand2.NewSource(rand.Int63())), big.NewInt(1e9)) + data := rand.Bytes(100) + return types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data) +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/core/txpool/legacypool +// BenchmarkTxPool3HeapAdd-8 45870 24270 ns/op +func BenchmarkTxPool3HeapAdd(b *testing.B) { + pool := NewTxPool3Heap() + b.ResetTimer() + for i := 0; i < b.N; i++ { + tx := createRandomTestTx() + pool.Add(tx) + } +} + +// BenchmarkTxPool3HeapGet-8 34522438 37.05 ns/op +func BenchmarkTxPool3HeapGet(b *testing.B) { + pool := NewTxPool3Heap() + txs := make([]*types.Transaction, 1000) + for i := 0; i < 1000; i++ { + tx := createRandomTestTx() + pool.Add(tx) + txs[i] = tx + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.Get(txs[i%1000].Hash()) + } +} + +// BenchmarkTxPool3HeapRemove-8 2643650 539.2 ns/op +func BenchmarkTxPool3HeapRemove(b *testing.B) { + pool := NewTxPool3Heap() + txs := make([]*types.Transaction, b.N) + for i := 0; i < b.N; i++ { + tx := createRandomTestTx() + pool.Add(tx) + txs[i] = tx + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.Remove(txs[i].Hash()) + } +} + +// BenchmarkTxPool3HeapPopN-8 47899808 23.80 ns/op +func BenchmarkTxPool3HeapPopN(b *testing.B) { + pool := NewTxPool3Heap() + for i := 0; i < 1000; i++ { + tx := createRandomTestTx() + pool.Add(tx) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.Flush(10) + } +} + +// BenchmarkTxPool3HeapLen-8 86149902 13.32 ns/op +func BenchmarkTxPool3HeapLen(b *testing.B) { + pool := NewTxPool3Heap() + for i := 0; i < 1000; i++ { + tx := createRandomTestTx() + pool.Add(tx) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.Len() + } +} + +// BenchmarkTxPool3HeapAddRemove-8 46156 25019 ns/op +func BenchmarkTxPool3HeapAddRemove(b *testing.B) { + pool := NewTxPool3Heap() + b.ResetTimer() + for i := 0; i < b.N; i++ { + tx := createRandomTestTx() + pool.Add(tx) + pool.Remove(tx.Hash()) + } +} + +// BenchmarkTxPool3HeapAddPopN-8 470 2377928 ns/op pool.PopN(100) +// BenchmarkTxPool3HeapAddPopN-8 4694 285026 ns/op pool.PopN(10) +func BenchmarkTxPool3HeapAddPopN(b *testing.B) { + pool := NewTxPool3Heap() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 10; j++ { + tx := createRandomTestTx() + pool.Add(tx) + } + pool.Flush(10) + } +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 3b8d2b46e0..91da2d98c9 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -287,7 +287,7 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), - localBufferPool: NewLRUBufferFastCache(int(config.Pool3Slots)), + localBufferPool: NewTxPool3Heap(), // NewLRUBufferFastCache(int(config.Pool3Slots)), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -1540,7 +1540,8 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, // Send dynamic transactions if len(nonStaticTxs) > 0 { - fmt.Println("New txevent emitted for non static ", nonStaticTxs[0].Hash()) + from, _ := types.Sender(pool.signer, nonStaticTxs[0]) + fmt.Println("New txevent emitted for non static ", nonStaticTxs[0].Hash(), len(nonStaticTxs), from.String()) pool.txFeed.Send(core.NewTxsEvent{Txs: nonStaticTxs, Static: false}) } } @@ -1814,7 +1815,8 @@ func (pool *LegacyPool) truncateQueue() { for _, list := range pool.queue { queued += uint64(list.Len()) } - if queued <= pool.config.GlobalQueue { + queueMax := pool.config.GlobalQueue + pool.config.Pool2Slots + if queued <= queueMax { return } @@ -1828,7 +1830,7 @@ func (pool *LegacyPool) truncateQueue() { sort.Sort(sort.Reverse(addresses)) // Drop transactions until the total is below the limit or only locals remain - for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { + for drop := queued - queueMax; drop > 0 && len(addresses) > 0; { addr := addresses[len(addresses)-1] list := pool.queue[addr.address] From 846e55b9a45ed6170f024cb3f8b43928b5f86008 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 25 Sep 2024 16:05:01 +0100 Subject: [PATCH 43/57] pool: remove pool2 from legacypool --- cmd/geth/main.go | 1 - cmd/utils/flags.go | 10 --- core/txpool/legacypool/legacypool.go | 102 ++++++--------------- core/txpool/legacypool/legacypool_test.go | 103 ++++++---------------- eth/handler.go | 4 +- eth/protocols/eth/peer.go | 3 + 6 files changed, 59 insertions(+), 164 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index cb13c0aeaf..a2b2b759c4 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -91,7 +91,6 @@ var ( utils.TxPoolGlobalSlotsFlag, utils.TxPoolAccountQueueFlag, utils.TxPoolGlobalQueueFlag, - utils.TxPoolPool2SlotsFlag, utils.TxPoolPool3SlotsFlag, utils.TxPoolLifetimeFlag, utils.TxPoolReannounceTimeFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c0beb976cf..cc0f6a6340 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -450,12 +450,6 @@ var ( Value: ethconfig.Defaults.TxPool.GlobalQueue, Category: flags.TxPoolCategory, } - TxPoolPool2SlotsFlag = &cli.Uint64Flag{ - Name: "txpool.pool2slots", - Usage: "Maximum number of transaction slots in pool 2", - Value: ethconfig.Defaults.TxPool.Pool2Slots, - Category: flags.TxPoolCategory, - } TxPoolPool3SlotsFlag = &cli.Uint64Flag{ Name: "txpool.pool3slots", Usage: "Maximum number of transaction slots in pool 3", @@ -1774,9 +1768,6 @@ func setTxPool(ctx *cli.Context, cfg *legacypool.Config) { if ctx.IsSet(TxPoolGlobalQueueFlag.Name) { cfg.GlobalQueue = ctx.Uint64(TxPoolGlobalQueueFlag.Name) } - if ctx.IsSet(TxPoolPool2SlotsFlag.Name) { - cfg.Pool2Slots = ctx.Uint64(TxPoolPool2SlotsFlag.Name) - } if ctx.IsSet(TxPoolPool3SlotsFlag.Name) { cfg.Pool3Slots = ctx.Uint64(TxPoolPool3SlotsFlag.Name) } @@ -2310,7 +2301,6 @@ func EnableNodeInfo(poolConfig *legacypool.Config, nodeInfo *p2p.NodeInfo) Setup "GlobalSlots": poolConfig.GlobalSlots, "AccountQueue": poolConfig.AccountQueue, "GlobalQueue": poolConfig.GlobalQueue, - "Pool2Slots": poolConfig.Pool2Slots, "Pool3Slots": poolConfig.Pool3Slots, "Lifetime": poolConfig.Lifetime, }) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 91da2d98c9..5630c51b8f 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -104,7 +104,6 @@ var ( queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) localGauge = metrics.NewRegisteredGauge("txpool/local", nil) slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) - pool2Gauge = metrics.NewRegisteredGauge("txpool/pool2", nil) pool3Gauge = metrics.NewRegisteredGauge("txpool/pool3", nil) reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil) @@ -140,7 +139,6 @@ type Config struct { GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts - Pool2Slots uint64 // Maximum number of transaction slots in pool 2 Pool3Slots uint64 // Maximum number of transaction slots in pool 3 Lifetime time.Duration // Maximum amount of time non-executable transaction are queued @@ -159,7 +157,6 @@ var DefaultConfig = Config{ GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio AccountQueue: 64, GlobalQueue: 1024, - Pool2Slots: 1024, Pool3Slots: 1024, Lifetime: 3 * time.Hour, @@ -475,6 +472,7 @@ func (pool *LegacyPool) Close() error { // Reset implements txpool.SubPool, allowing the legacy pool's internal state to be // kept in sync with the main transaction pool's internal state. func (pool *LegacyPool) Reset(oldHead, newHead *types.Header) { + fmt.Println("Reset request") wait := pool.requestReset(oldHead, newHead) <-wait } @@ -781,7 +779,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue - maxPool2Size := pool.config.Pool2Slots txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) // Make the local flag. If it's from local source or it's from the network but @@ -821,7 +818,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the transaction pool is full, discard underpriced transactions - if txPoolSizeAfterCurrentTx > (maxPool1Size + maxPool2Size) { + if txPoolSizeAfterCurrentTx > maxPool1Size { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) @@ -841,7 +838,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // New transaction is better than our worse ones, make room for it. // If it's a local transaction, forcibly discard all available transactions. // Otherwise if we can't make enough room for new one, abort the operation. - toBeDiscarded := pool.all.Slots() - int(pool.config.GlobalSlots+pool.config.GlobalQueue+pool.config.Pool2Slots) + numSlots(tx) + toBeDiscarded := pool.all.Slots() - int(pool.config.GlobalSlots+pool.config.GlobalQueue) + numSlots(tx) drop, success := pool.priced.Discard(toBeDiscarded, isLocal) // Special case, we still can't make the room for the new remote one. @@ -903,7 +900,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.all.Add(tx, isLocal) pool.priced.Put(tx, isLocal) pool.journalTx(from, tx) - pool.queueTxEvent(tx, false) // At this point pool1 can incorporate this. So no need for pool2 or pool3 + pool.queueTxEvent(tx, false) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -912,7 +909,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.enqueueTx(hash, tx, isLocal, true, true) // At this point pool1 can incorporate this. So no need for pool2 or pool3 + replaced, err = pool.enqueueTx(hash, tx, isLocal, true, true) if err != nil { return false, err } @@ -943,57 +940,15 @@ func (pool *LegacyPool) addToPool3(drop types.Transactions, isLocal bool) { txSlots := numSlots(tx) if currentSlotsUsed+txSlots <= availableSlotsPool3 { from, _ := types.Sender(pool.signer, tx) - pool.addToPool12OrPool3(tx, from, isLocal, false, false, true) + //pool.addToPool12OrPool3(tx, from, isLocal, false, false, true) + pool.localBufferPool.Add(tx) + log.Debug("adding to pool3", "transaction", tx.Hash().String(), "from", from.String()) currentSlotsUsed += txSlots } } - } -} - -// addToPool12OrPool3 adds a transaction to pool1 or pool2 or pool3 depending on which one is asked for -func (pool *LegacyPool) addToPool12OrPool3(tx *types.Transaction, from common.Address, isLocal bool, pool1, pool2, pool3 bool) (bool, error) { - if pool1 { - pool.journalTx(from, tx) - pool.queueTxEvent(tx, false) - _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true, false) // At this point pool1 can incorporate this. So no need for pool2 or pool3 - if err != nil { - return false, err - } - dirty := newAccountSet(pool.signer) - dirty.addTx(tx) - go func() { - <-pool.requestPromoteExecutables(dirty) - }() - log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) - - // Successful promotion, bump the heartbeat - pool.beats[from] = time.Now() - - return true, nil - } else if pool2 { - pool.journalTx(from, tx) - pool.queueTxEvent(tx, true) - _, err := pool.enqueueTx(tx.Hash(), tx, isLocal, true, true) - if err != nil { - return false, err - } - dirty := newAccountSet(pool.signer) - dirty.addTx(tx) - pool2Gauge.Inc(1) - go func() { - <-pool.requestPromoteExecutables(dirty) - }() - log.Trace("Pooled new executable transaction", "hash", tx.Hash(), "from", from, "to", tx.To()) - - // Successful promotion, bump the heartbeat - pool.beats[from] = time.Now() - return true, nil - } else if pool3 { - pool.localBufferPool.Add(tx) - log.Debug("adding to pool3", "transaction", tx.Hash().String()) - return true, nil } else { - return false, errors.New("could not add to any pool") + log.Debug("adding to pool3 unsuccessful", "availableSlotsPool3", availableSlotsPool3) + fmt.Println("adding to pool3 unsuccessful") } } @@ -1448,7 +1403,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, reorgDurationTimer.Update(time.Since(t0)) }(time.Now()) defer close(done) - + fmt.Println("runReorg called") var promoteAddrs []common.Address if dirtyAccounts != nil && reset == nil { // Only dirty accounts need to be promoted, unless we're resetting. @@ -1475,9 +1430,6 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } } - //// Transfer transactions from pool3 to pool2 for new block import - //pool.transferTransactions() - // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) @@ -1510,7 +1462,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, pool.changesSinceReorg = 0 // Reset change counter pool.mu.Unlock() - // Transfer transactions from pool3 to pool2 for new block import + // Transfer transactions from pool3 to pool1 for new block import pool.transferTransactions() // Notify subsystems for newly added transactions @@ -1650,6 +1602,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { // future queue to the set of pending transactions. During this process, all // invalidated transactions (low nonce, low balance) are deleted. func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { + fmt.Println("promoteExecutables called") // Track the promoted transactions to broadcast them at once var promoted []*types.Transaction @@ -1685,10 +1638,8 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T } } log.Trace("Promoted queued transactions", "count", len(promoted)) + fmt.Println("promoting") queuedGauge.Dec(int64(len(readies))) - if list.txs.staticOnly { - pool2Gauge.Dec(int64(len(readies))) - } // Drop all transactions over the allowed limit var caps types.Transactions @@ -1704,9 +1655,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T // Mark all the items dropped as removed pool.priced.Removed(len(forwards) + len(drops) + len(caps)) queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) - if list.txs.staticOnly { - pool2Gauge.Dec(int64(len(forwards) + len(drops) + len(caps))) - } + if pool.locals.contains(addr) { localGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) } @@ -1815,7 +1764,7 @@ func (pool *LegacyPool) truncateQueue() { for _, list := range pool.queue { queued += uint64(list.Len()) } - queueMax := pool.config.GlobalQueue + pool.config.Pool2Slots + queueMax := pool.config.GlobalQueue if queued <= queueMax { return } @@ -2210,31 +2159,30 @@ func (pool *LegacyPool) startPeriodicTransfer(t time.Duration) { // transferTransactions mainly moves from pool 3 to pool 2 func (pool *LegacyPool) transferTransactions() { maxPool1Size := int(pool.config.GlobalSlots + pool.config.GlobalQueue) - maxPool2Size := int(pool.config.Pool2Slots) - maxPool1Pool2CombinedSize := maxPool1Size + maxPool2Size - extraSizePool2Pool1 := maxPool1Pool2CombinedSize - int(uint64(len(pool.pending))+uint64(len(pool.queue))) - if extraSizePool2Pool1 <= 0 { + extraSizePool1 := maxPool1Size - int(uint64(len(pool.pending))+uint64(len(pool.queue))) + if extraSizePool1 <= 0 { return } - currentPool1Pool2Size := pool.all.Slots() - canTransferPool3ToPool2 := maxPool1Pool2CombinedSize > currentPool1Pool2Size - if !canTransferPool3ToPool2 { + currentPool1Size := pool.all.Slots() + canTransferPool3ToPool1 := maxPool1Size > currentPool1Size + if !canTransferPool3ToPool1 { return } - extraSlots := maxPool1Pool2CombinedSize - currentPool1Pool2Size + extraSlots := maxPool1Size - currentPool1Size extraTransactions := (extraSlots + 3) / 4 // Since maximum slots per transaction is 4 - // So now we can take out extraTransactions number of transactions from pool3 and put in pool2 + // So now we can take out extraTransactions number of transactions from pool3 and put in pool1 if extraTransactions < 1 { return } - log.Debug("Will attempt to transfer from pool3 to pool2", "transactions", extraTransactions) + log.Debug("Will attempt to transfer from pool3 to pool1", "transactions", extraTransactions) tx := pool.localBufferPool.Flush(extraTransactions) if len(tx) == 0 { return } + fmt.Println("transferring tranasction") pool.Add(tx, true, false) } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index fc07d4b586..a717166c5a 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1741,7 +1741,6 @@ func TestRepricingKeepsLocals(t *testing.T) { func TestUnderpricing(t *testing.T) { t.Parallel() testTxPoolConfig.Pool3Slots = 5 - testTxPoolConfig.Pool2Slots = 0 // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) @@ -1932,9 +1931,8 @@ func TestUnderpricingDynamicFee(t *testing.T) { defer pool.Close() pool.config.GlobalSlots = 2 - pool.config.GlobalQueue = 1 + pool.config.GlobalQueue = 2 - pool.config.Pool2Slots = 1 pool.config.Pool3Slots = 0 // Keep track of transaction events to ensure all executables get announced @@ -1962,7 +1960,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { // Import the batch and that both pending and queued transactions match up pool.addRemotes(txs) // Pend K0:0, K0:1; Que K1:1 pool.addLocal(ltx) // +K2:0 => Pend K0:0, K0:1, K2:0; Que K1:1 - + fmt.Println("pool.addLocal(ltx) done") pending, queued := pool.Stats() if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) @@ -1970,9 +1968,11 @@ func TestUnderpricingDynamicFee(t *testing.T) { if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } + fmt.Println("before validateEvents") if err := validateEvents(events, 3); err != nil { t.Fatalf("original event firing failed: %v", err) } + fmt.Println("after validateEvents") if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2008,7 +2008,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { } fmt.Println("Stats before validateEvents") pool.printTxStats() - if err := validateEvents(events, 4); err != nil { // todo make it 4...After this validateEvents the pending becomes 3?! + if err := validateEvents(events, 2); err != nil { // todo make it 4...After this validateEvents the pending becomes 3?! t.Fatalf("additional event firing failed: %v", err) } fmt.Println("Stats after validateEvents") @@ -2027,10 +2027,10 @@ func TestUnderpricingDynamicFee(t *testing.T) { } pending, queued = pool.Stats() - if pending != 5 { // 3 + if pending != 3 { // 3 t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 0 { // 1 + if queued != 1 { // 1 t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } if err := validateEvents(events, 2); err != nil { @@ -2046,14 +2046,13 @@ func TestUnderpricingDynamicFee(t *testing.T) { func TestDualHeapEviction(t *testing.T) { t.Parallel() - testTxPoolConfig.Pool3Slots = 5 + testTxPoolConfig.Pool3Slots = 1 pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() pool.config.GlobalSlots = 2 pool.config.GlobalQueue = 2 - pool.config.Pool2Slots = 1 - pool.config.Pool3Slots = 5 + pool.config.Pool3Slots = 1 var ( highTip, highCap *types.Transaction @@ -2071,7 +2070,7 @@ func TestDualHeapEviction(t *testing.T) { } add := func(urgent bool) { - for i := 0; i < 5; i++ { + for i := 0; i < 4; i++ { var tx *types.Transaction // Create a test accounts and fund it key, _ := crypto.GenerateKey() @@ -2098,7 +2097,7 @@ func TestDualHeapEviction(t *testing.T) { } } pending, queued := pool.Stats() - if pending+queued != 5 { + if pending+queued != 4 { t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, pool3 %d", pending+queued, 5, pending, queued, pool.localBufferPool.Size()) } } @@ -2271,89 +2270,43 @@ func TestTransferTransactions(t *testing.T) { defer pool.Close() pool.config.GlobalSlots = 1 - pool.config.GlobalQueue = 1 - - pool.config.Pool2Slots = 1 + pool.config.GlobalQueue = 2 pool.config.Pool3Slots = 1 // Create a number of test accounts and fund them keys := make([]*ecdsa.PrivateKey, 5) for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() + fmt.Println(crypto.PubkeyToAddress(keys[i].PublicKey)) testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } tx := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[0]) from, _ := types.Sender(pool.signer, tx) - pool.addToPool12OrPool3(tx, from, true, false, false, true) - //time.Sleep(6 * time.Second) + pool.addToPool3([]*types.Transaction{tx}, true) pending, queue := pool.Stats() - p3Size := pool.StatsPool3() - if pending != 0 { - t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) - } - if queue != 0 { - t.Errorf("queued transactions mismatched: have %d, want %d", queue, 0) - } - assert.Equal(t, 1, p3Size, "pool3 size unexpected") + + assert.Equal(t, 0, pending, "pending transactions mismatched") + assert.Equal(t, 0, queue, "queued transactions mismatched") + assert.Equal(t, 1, pool.StatsPool3(), "pool3 size unexpected") tx2 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) - from2, _ := types.Sender(pool.signer, tx2) - pool.addToPool12OrPool3(tx2, from2, true, false, false, true) + pool.addToPool3([]*types.Transaction{tx2}, true) + assert.Equal(t, 1, pool.StatsPool3(), "pool3 size unexpected") <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) - //time.Sleep(6 * time.Second) pending, queue = pool.Stats() - if pending != 0 { - t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) - } - if queue != 1 { - t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) - } - tx3 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[2]) - from3, _ := types.Sender(pool.signer, tx3) - pool.addToPool12OrPool3(tx3, from3, true, false, false, true) - //time.Sleep(6 * time.Second) - pending, queue = pool.Stats() - if pending != 0 { - t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) - } - if queue != 1 { - t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) - } - - tx4 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[3]) - from4, _ := types.Sender(pool.signer, tx4) - pool.addToPool12OrPool3(tx4, from4, true, false, false, true) - //time.Sleep(6 * time.Second) - pending, queue = pool.Stats() - if pending != 0 { - t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) - } - if queue != 1 { - t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) - } - bufferSize := pool.localBufferPool.Size() - if bufferSize != 2 { - t.Errorf("buffer transactions mismatched: have %d, want %d", bufferSize, 1) - } + assert.Equal(t, 0, pending, "pending transactions mismatched") + assert.Equal(t, 1, queue, "queued transactions mismatched") + assert.Equal(t, 0, pool.StatsPool3(), "pool3 size unexpected") - tx5 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[4]) - from5, _ := types.Sender(pool.signer, tx5) - pool.addToPool12OrPool3(tx5, from5, true, false, false, true) - //time.Sleep(6 * time.Second) + tx3 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[2]) + pool.addToPool3([]*types.Transaction{tx3}, true) pending, queue = pool.Stats() - if pending != 0 { - t.Errorf("pending transactions mismatched: have %d, want %d", pending, 0) - } - if queue != 1 { - t.Errorf("queued transactions mismatched: have %d, want %d", queue, 1) - } - bufferSize = pool.localBufferPool.Size() - if bufferSize != 3 { - t.Errorf("buffer transactions mismatched: have %d, want %d", bufferSize, 1) - } + assert.Equal(t, 1, pending, "pending transactions mismatched") + assert.Equal(t, 0, queue, "queued transactions mismatched") + assert.Equal(t, 1, pool.StatsPool3(), "pool3 size unexpected") } // Tests that the pool rejects replacement dynamic fee transactions that don't diff --git a/eth/handler.go b/eth/handler.go index 3728046bb2..8d12254811 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -851,6 +851,8 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, staticOnly bool) txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce ) + // for pool2 it is p = static+sqrt(peers) is total available. + // Send tx to sqrt(p) and announce to (p - sqrt(p)) peers // Broadcast transactions to a batch of peers not knowing about it for _, tx := range txs { peers := h.peers.peersWithoutTransaction(tx.Hash()) @@ -865,7 +867,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, staticOnly bool) numDirect = int(math.Sqrt(float64(len(peers)))) } if staticOnly { - numDirect = int(math.Cbrt(float64(len(peers)))) + numDirect = int(math.Sqrt(math.Sqrt(float64(len(peers))))) } // Send the tx unconditionally to a subset of our peers for _, peer := range peers[:numDirect] { diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 732da20a77..cec1453b4f 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -235,6 +235,9 @@ func (p *Peer) AsyncSendTransactions(hashes []common.Hash, staticOnly bool) { case p.txBroadcast <- hashes: // Mark all the transactions as known, but ensure we don't overflow our limits p.knownTxs.Add(hashes...) + if staticOnly && p.Peer.Info().Network.Static { + p.Log().Debug("Sent pool-2 transaction", "count", len(hashes)) + } default: // Handle the case when the channel is full or not ready p.Log().Debug("Unable to send transactions, channel full or not ready", "count", len(hashes)) From 6a6e09c84967c659847d0ccda8867533fb5df88c Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 26 Sep 2024 14:13:13 +0100 Subject: [PATCH 44/57] pool: remove pool2 related code --- core/events.go | 8 +-- core/txpool/errors.go | 4 -- core/txpool/legacypool/legacypool.go | 74 ++++++++++------------------ core/txpool/legacypool/list.go | 34 ++++++------- core/txpool/subpool.go | 2 - eth/handler.go | 14 +++--- eth/protocols/eth/peer.go | 37 ++------------ eth/sync.go | 20 ++------ 8 files changed, 58 insertions(+), 135 deletions(-) diff --git a/core/events.go b/core/events.go index 8daf531152..ff69cfb4a9 100644 --- a/core/events.go +++ b/core/events.go @@ -22,13 +22,7 @@ import ( ) // NewTxsEvent is posted when a batch of transactions enters the transaction pool. -type NewTxsEvent struct { - Txs []*types.Transaction - // Static bool is Whether to send to only Static peer or not. - // This is because at high traffic we still want to broadcast transactions to at least some peers so that we - // minimize the transaction lost. - Static bool -} +type NewTxsEvent struct{ Txs []*types.Transaction } // ReannoTxsEvent is posted when a batch of local pending transactions exceed a specified duration. type ReannoTxsEvent struct{ Txs []*types.Transaction } diff --git a/core/txpool/errors.go b/core/txpool/errors.go index 2e5040127c..8298a15993 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -30,10 +30,6 @@ var ( // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") - // ErrUnderpricedTransferredtoAnotherPool is returned if a transaction's gas price is below the minimum - // configured for the transaction pool. - ErrUnderpricedTransferredtoAnotherPool = errors.New("transaction underpriced, so it is either in pool2 or pool3") - // 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") diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 5630c51b8f..d16c9a0318 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -244,7 +244,7 @@ type LegacyPool struct { reqResetCh chan *txpoolResetRequest reqPromoteCh chan *accountSet - queueTxEventCh chan QueueTxEventCh + queueTxEventCh chan *types.Transaction reorgDoneCh chan chan struct{} reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop wg sync.WaitGroup // tracks loop, scheduleReorgLoop @@ -280,7 +280,7 @@ func New(config Config, chain BlockChain) *LegacyPool { all: newLookup(), reqResetCh: make(chan *txpoolResetRequest), reqPromoteCh: make(chan *accountSet), - queueTxEventCh: make(chan QueueTxEventCh), + queueTxEventCh: make(chan *types.Transaction), reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), @@ -405,7 +405,7 @@ func (pool *LegacyPool) loop() { } // Any non-locals old enough should be removed if time.Since(pool.beats[addr]) > pool.config.Lifetime { - list, _ := pool.queue[addr].Flatten() + list := pool.queue[addr].Flatten() for _, tx := range list { pool.removeTx(tx.Hash(), true, true) } @@ -416,27 +416,25 @@ func (pool *LegacyPool) loop() { case <-reannounce.C: pool.mu.RLock() - reannoTxs, _ := func() ([]*types.Transaction, []bool) { + reannoTxs := func() []*types.Transaction { txs := make([]*types.Transaction, 0) - statics := make([]bool, 0) for addr, list := range pool.pending { if !pool.locals.contains(addr) { continue } - transactions, static := list.Flatten() + transactions := list.Flatten() for _, tx := range transactions { // Default ReannounceTime is 10 years, won't announce by default. if time.Since(tx.Time()) < pool.config.ReannounceTime { break } txs = append(txs, tx) - statics = append(statics, static) if len(txs) >= txReannoMaxNum { - return txs, statics + return txs } } } - return txs, statics + return txs }() pool.mu.RUnlock() if len(reannoTxs) > 0 { @@ -567,11 +565,11 @@ func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[ pending := make(map[common.Address][]*types.Transaction, len(pool.pending)) for addr, list := range pool.pending { - pending[addr], _ = list.Flatten() + pending[addr] = list.Flatten() } queued := make(map[common.Address][]*types.Transaction, len(pool.queue)) for addr, list := range pool.queue { - queued[addr], _ = list.Flatten() + queued[addr] = list.Flatten() } return pending, queued } @@ -584,11 +582,11 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction, var pending []*types.Transaction if list, ok := pool.pending[addr]; ok { - pending, _ = list.Flatten() + pending = list.Flatten() } var queued []*types.Transaction if list, ok := pool.queue[addr]; ok { - queued, _ = list.Flatten() + queued = list.Flatten() } return pending, queued } @@ -620,7 +618,7 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address] } pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) for addr, list := range pool.pending { - txs, static := list.Flatten() + txs := list.Flatten() // If the miner requests tip enforcement, cap the lists now if minTipBig != nil && !pool.locals.contains(addr) { @@ -643,7 +641,6 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address] GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()), Gas: txs[i].Gas(), BlobGas: txs[i].BlobGas(), - Static: static, } } pending[addr] = lazies @@ -667,11 +664,11 @@ func (pool *LegacyPool) local() map[common.Address]types.Transactions { txs := make(map[common.Address]types.Transactions) for addr := range pool.locals.accounts { if pending := pool.pending[addr]; pending != nil { - transactions, _ := pending.Flatten() + transactions := pending.Flatten() txs[addr] = append(txs[addr], transactions...) } if queued := pool.queue[addr]; queued != nil { - transactions, _ := queued.Flatten() + transactions := queued.Flatten() txs[addr] = append(txs[addr], transactions...) } } @@ -1315,12 +1312,8 @@ func (pool *LegacyPool) requestPromoteExecutables(set *accountSet) chan struct{} // queueTxEvent enqueues a transaction event to be sent in the next reorg run. func (pool *LegacyPool) queueTxEvent(tx *types.Transaction, static bool) { - event := QueueTxEventCh{ - tx: tx, - static: static, - } select { - case pool.queueTxEventCh <- event: + case pool.queueTxEventCh <- tx: case <-pool.reorgShutdownCh: } } @@ -1374,14 +1367,14 @@ func (pool *LegacyPool) scheduleReorgLoop() { launchNextRun = true pool.reorgDoneCh <- nextDone - case queue := <-pool.queueTxEventCh: + case tx := <-pool.queueTxEventCh: // Queue up the event, but don't schedule a reorg. It's up to the caller to // request one later if they want the events sent. - addr, _ := types.Sender(pool.signer, queue.tx) + addr, _ := types.Sender(pool.signer, tx) if _, ok := queuedEvents[addr]; !ok { queuedEvents[addr] = newSortedMap() } - queuedEvents[addr].Put(queue.tx, queue.static) + queuedEvents[addr].Put(tx) case <-curDone: curDone = nil @@ -1449,7 +1442,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, // Update all accounts to the latest known pending nonce nonces := make(map[common.Address]uint64, len(pool.pending)) for addr, list := range pool.pending { - highestPending, _ := list.LastElement() + highestPending := list.LastElement() nonces[addr] = highestPending.Nonce() + 1 } pool.pendingNonces.setAll(nonces) @@ -1471,31 +1464,14 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, if _, ok := events[addr]; !ok { events[addr] = newSortedMap() } - events[addr].Put(tx, false) // todo putting false as placeholder for now + events[addr].Put(tx) } if len(events) > 0 { - staticTxs := make([]*types.Transaction, 0) - nonStaticTxs := make([]*types.Transaction, 0) + var txs []*types.Transaction for _, set := range events { - flattenedTxs, _ := set.Flatten() - if set.staticOnly { - staticTxs = append(staticTxs, flattenedTxs...) - } else { - nonStaticTxs = append(nonStaticTxs, flattenedTxs...) - } - } - // Send static transactions - if len(staticTxs) > 0 { - fmt.Println("New txevent emitted for static ", staticTxs[0].Hash()) - pool.txFeed.Send(core.NewTxsEvent{Txs: staticTxs, Static: true}) - } - - // Send dynamic transactions - if len(nonStaticTxs) > 0 { - from, _ := types.Sender(pool.signer, nonStaticTxs[0]) - fmt.Println("New txevent emitted for non static ", nonStaticTxs[0].Hash(), len(nonStaticTxs), from.String()) - pool.txFeed.Send(core.NewTxsEvent{Txs: nonStaticTxs, Static: false}) + txs = append(txs, set.Flatten()...) } + pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) } } @@ -1787,7 +1763,7 @@ func (pool *LegacyPool) truncateQueue() { // Drop all transactions if they are less than the overflow if size := uint64(list.Len()); size <= drop { - transactions, _ := list.Flatten() + transactions := list.Flatten() for _, tx := range transactions { pool.removeTx(tx.Hash(), true, true) } @@ -1796,7 +1772,7 @@ func (pool *LegacyPool) truncateQueue() { continue } // Otherwise drop only last few transactions - txs, _ := list.Flatten() + txs := list.Flatten() for i := len(txs) - 1; i >= 0 && drop > 0; i-- { pool.removeTx(txs[i].Hash(), true, true) drop-- diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index c14d613b07..62628b5ce3 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -63,11 +63,10 @@ func (h *nonceHeap) Pop() interface{} { // sortedMap is a nonce->transaction hash map with a heap based index to allow // iterating over the contents in a nonce-incrementing way. type sortedMap struct { - items map[uint64]*types.Transaction // Hash map storing the transaction data - index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) - cache types.Transactions // Cache of the transactions already sorted - cacheMu sync.Mutex // Mutex covering the cache - staticOnly bool // only send this transaction to static peers + items map[uint64]*types.Transaction // Hash map storing the transaction data + index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) + cache types.Transactions // Cache of the transactions already sorted + cacheMu sync.Mutex // Mutex covering the cache } // newSortedMap creates a new nonce-sorted transaction map. @@ -85,7 +84,7 @@ func (m *sortedMap) Get(nonce uint64) *types.Transaction { // Put inserts a new transaction into the map, also updating the map's nonce // index. If a transaction already exists with the same nonce, it's overwritten. -func (m *sortedMap) Put(tx *types.Transaction, static bool) { +func (m *sortedMap) Put(tx *types.Transaction) { nonce := tx.Nonce() if m.items[nonce] == nil { heap.Push(m.index, nonce) @@ -95,7 +94,6 @@ func (m *sortedMap) Put(tx *types.Transaction, static bool) { txSortedMapPool.Put(m.cache) } m.items[nonce], m.cache = tx, nil - m.staticOnly = static m.cacheMu.Unlock() } @@ -255,7 +253,7 @@ func (m *sortedMap) Len() int { return len(m.items) } -func (m *sortedMap) flatten() (types.Transactions, bool) { +func (m *sortedMap) flatten() types.Transactions { m.cacheMu.Lock() defer m.cacheMu.Unlock() // If the sorting was not cached yet, create and cache it @@ -272,25 +270,25 @@ func (m *sortedMap) flatten() (types.Transactions, bool) { } sort.Sort(types.TxByNonce(m.cache)) } - return m.cache, m.staticOnly + return m.cache } // Flatten creates a nonce-sorted slice of transactions based on the loosely // sorted internal representation. The result of the sorting is cached in case // it's requested again before any modifications are made to the contents. -func (m *sortedMap) Flatten() (types.Transactions, bool) { - cache, static := m.flatten() +func (m *sortedMap) Flatten() types.Transactions { + cache := m.flatten() // Copy the cache to prevent accidental modification txs := make(types.Transactions, len(cache)) copy(txs, cache) - return txs, static + return txs } // LastElement returns the last element of a flattened list, thus, the // transaction with the highest nonce -func (m *sortedMap) LastElement() (*types.Transaction, bool) { - cache, static := m.flatten() - return cache[len(cache)-1], static +func (m *sortedMap) LastElement() *types.Transaction { + cache := m.flatten() + return cache[len(cache)-1] } // list is a "list" of transactions belonging to an account, sorted by account @@ -362,7 +360,7 @@ func (l *list) Add(tx *types.Transaction, priceBump uint64, static bool) (bool, l.totalcost.Add(l.totalcost, cost) // Otherwise overwrite the old transaction with the current one - l.txs.Put(tx, static) + l.txs.Put(tx) if l.costcap.Cmp(cost) < 0 { l.costcap = cost } @@ -477,13 +475,13 @@ func (l *list) Empty() bool { // Flatten creates a nonce-sorted slice of transactions based on the loosely // sorted internal representation. The result of the sorting is cached in case // it's requested again before any modifications are made to the contents. -func (l *list) Flatten() (types.Transactions, bool) { +func (l *list) Flatten() types.Transactions { return l.txs.Flatten() } // LastElement returns the last element of a flattened list, thus, the // transaction with the highest nonce -func (l *list) LastElement() (*types.Transaction, bool) { +func (l *list) LastElement() *types.Transaction { return l.txs.LastElement() } diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 0947150c97..be5f6840d3 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -41,8 +41,6 @@ type LazyTransaction struct { Gas uint64 // Amount of gas required by the transaction BlobGas uint64 // Amount of blob gas required by the transaction - - Static bool // To specify whether to broadcast it to static peers or not } // Resolve retrieves the full transaction belonging to a lazy handle if it is still diff --git a/eth/handler.go b/eth/handler.go index 8d12254811..949add9a5b 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -838,7 +838,7 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { // - To a square root of all peers for non-blob transactions // - And, separately, as announcements to all peers which are not known to // already have the given transaction. -func (h *handler) BroadcastTransactions(txs types.Transactions, staticOnly bool) { +func (h *handler) BroadcastTransactions(txs types.Transactions) { var ( blobTxs int // Number of blob transactions to announce only largeTxs int // Number of large transactions to announce only @@ -866,9 +866,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, staticOnly bool) default: numDirect = int(math.Sqrt(float64(len(peers)))) } - if staticOnly { - numDirect = int(math.Sqrt(math.Sqrt(float64(len(peers))))) - } + // Send the tx unconditionally to a subset of our peers for _, peer := range peers[:numDirect] { txset[peer] = append(txset[peer], tx.Hash()) @@ -881,12 +879,12 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, staticOnly bool) for peer, hashes := range txset { directPeers++ directCount += len(hashes) - peer.AsyncSendTransactions(hashes, staticOnly) + peer.AsyncSendTransactions(hashes) } for peer, hashes := range annos { annPeers++ annCount += len(hashes) - peer.AsyncSendPooledTransactionHashes(hashes, staticOnly) + peer.AsyncSendPooledTransactionHashes(hashes) } log.Debug("Distributed transactions", "plaintxs", len(txs)-blobTxs-largeTxs, "blobtxs", blobTxs, "largetxs", largeTxs, "bcastpeers", directPeers, "bcastcount", directCount, "annpeers", annPeers, "anncount", annCount) @@ -904,7 +902,7 @@ func (h *handler) ReannounceTransactions(txs types.Transactions) { peersCount := uint(math.Sqrt(float64(h.peers.len()))) peers := h.peers.headPeers(peersCount) for _, peer := range peers { - peer.AsyncSendPooledTransactionHashes(hashes, false) // todo keeping it false for now. Reannounce never really happens + peer.AsyncSendPooledTransactionHashes(hashes) } log.Debug("Transaction reannounce", "txs", len(txs), "announce packs", peersCount, "announced hashes", peersCount*uint(len(hashes))) @@ -967,7 +965,7 @@ func (h *handler) txBroadcastLoop() { for { select { case event := <-h.txsCh: - h.BroadcastTransactions(event.Txs, event.Static) + h.BroadcastTransactions(event.Txs) case <-h.txsSub.Err(): return case <-h.stopCh: diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index cec1453b4f..fa2a311d6c 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -222,29 +222,12 @@ func (p *Peer) SendTransactions(txs types.Transactions) error { // AsyncSendTransactions queues a list of transactions (by hash) to eventually // propagate to a remote peer. The number of pending sends are capped (new ones // will force old sends to be dropped) -func (p *Peer) AsyncSendTransactions(hashes []common.Hash, staticOnly bool) { - // p.Peer.Info().Network.Static bool decides if pool2 transaction will be broadcasted to that peer or not +func (p *Peer) AsyncSendTransactions(hashes []common.Hash) { select { case <-p.txTerm: p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) case <-p.term: p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) - default: - if (staticOnly && p.Peer.Info().Network.Static) || !staticOnly { - select { - case p.txBroadcast <- hashes: - // Mark all the transactions as known, but ensure we don't overflow our limits - p.knownTxs.Add(hashes...) - if staticOnly && p.Peer.Info().Network.Static { - p.Log().Debug("Sent pool-2 transaction", "count", len(hashes)) - } - default: - // Handle the case when the channel is full or not ready - p.Log().Debug("Unable to send transactions, channel full or not ready", "count", len(hashes)) - } - } else { - p.Log().Debug("Not sending transactions as peer not static", "count", len(hashes)) - } } } @@ -264,25 +247,15 @@ func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash, types []byte, s // AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually // announce to a remote peer. The number of pending sends are capped (new ones // will force old sends to be dropped) -func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash, staticOnly bool) { +func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { select { + case p.txAnnounce <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + p.knownTxs.Add(hashes...) case <-p.txTerm: p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) case <-p.term: p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) - default: - if (staticOnly && p.Peer.Info().Network.Static) || !staticOnly { - select { - case p.txAnnounce <- hashes: - // Mark all the transactions as known, but ensure we don't overflow our limits - p.knownTxs.Add(hashes...) - default: - // Handle the case when the channel is full or not ready - p.Log().Debug("Unable to send transactions, channel full or not ready", "count", len(hashes)) - } - } else { - p.Log().Debug("Not sending transactions as peer not static", "count", len(hashes)) - } } } diff --git a/eth/sync.go b/eth/sync.go index 7dd8f1a8f3..3b04d09920 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -35,26 +35,16 @@ const ( // syncTransactions starts sending all currently pending transactions to the given peer. func (h *handler) syncTransactions(p *eth.Peer) { - var hashesFalse []common.Hash - var hashesTrue []common.Hash - + var hashes []common.Hash for _, batch := range h.txpool.Pending(txpool.PendingFilter{OnlyPlainTxs: true}) { for _, tx := range batch { - if tx.Static { - hashesTrue = append(hashesTrue, tx.Hash) - } else { - hashesFalse = append(hashesFalse, tx.Hash) - } + hashes = append(hashes, tx.Hash) } } - - if len(hashesFalse) > 0 { - p.AsyncSendPooledTransactionHashes(hashesFalse, false) - } - - if len(hashesTrue) > 0 { - p.AsyncSendPooledTransactionHashes(hashesTrue, true) + if len(hashes) == 0 { + return } + p.AsyncSendPooledTransactionHashes(hashes) } // syncVotes starts sending all currently pending votes to the given peer. From 355dee90008fa851a90ae10fa0e6ecfe89821316 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 26 Sep 2024 14:21:36 +0100 Subject: [PATCH 45/57] pool: edit tests and remove remaining pool2 logic --- core/txpool/legacypool/legacypool.go | 21 ++++++++------------- core/txpool/legacypool/legacypool_test.go | 18 +++++++++--------- core/txpool/legacypool/list.go | 2 +- core/txpool/legacypool/list_test.go | 8 ++++---- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index d16c9a0318..a1c2e96317 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -253,11 +253,6 @@ type LegacyPool struct { changesSinceReorg int // A counter for how many drops we've performed in-between reorg. } -type QueueTxEventCh struct { - tx *types.Transaction - static bool -} - type txpoolResetRequest struct { oldHead, newHead *types.Header } @@ -883,7 +878,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Try to replace an existing transaction in the pending pool if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { // Nonce already pending, check if required price bump is met - inserted, old := list.Add(tx, pool.config.PriceBump, false) + inserted, old := list.Add(tx, pool.config.PriceBump) if !inserted { pendingDiscardMeter.Mark(1) return false, txpool.ErrReplaceUnderpriced @@ -906,7 +901,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.enqueueTx(hash, tx, isLocal, true, true) + replaced, err = pool.enqueueTx(hash, tx, isLocal, true) if err != nil { return false, err } @@ -976,13 +971,13 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo // enqueueTx inserts a new transaction into the non-executable transaction queue. // // Note, this method assumes the pool lock is held! -func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool, static bool) (bool, error) { +func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { // Try to insert the transaction into the future queue from, _ := types.Sender(pool.signer, tx) // already validated if pool.queue[from] == nil { pool.queue[from] = newList(false) } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, static) + inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) if !inserted { // An older transaction was better, discard this queuedDiscardMeter.Mark(1) @@ -1036,7 +1031,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ } list := pool.pending[addr] - inserted, old := list.Add(tx, pool.config.PriceBump, false) // todo check and confirm + inserted, old := list.Add(tx, pool.config.PriceBump) if !inserted { // An older transaction was better, discard this pool.all.Remove(hash) @@ -1265,7 +1260,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo // Postpone any invalidated transactions for _, tx := range invalids { // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(tx.Hash(), tx, false, false, false) + pool.enqueueTx(tx.Hash(), tx, false, false) } // Update the account nonce if needed pool.pendingNonces.setIfLower(addr, tx.Nonce()) @@ -1815,7 +1810,7 @@ func (pool *LegacyPool) demoteUnexecutables() { log.Trace("Demoting pending transaction", "hash", hash) // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(hash, tx, false, false, false) + pool.enqueueTx(hash, tx, false, false) } pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) if pool.locals.contains(addr) { @@ -1829,7 +1824,7 @@ func (pool *LegacyPool) demoteUnexecutables() { log.Error("Demoting invalidated transaction", "hash", hash) // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(hash, tx, false, false, false) + pool.enqueueTx(hash, tx, false, false) } pendingGauge.Dec(int64(len(gapped))) } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index a717166c5a..747d52c57c 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -367,7 +367,7 @@ func TestQueue(t *testing.T) { testAddBalance(pool, from, big.NewInt(1000)) <-pool.requestReset(nil, nil) - pool.enqueueTx(tx.Hash(), tx, false, true, false) + pool.enqueueTx(tx.Hash(), tx, false, true) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) if len(pool.pending) != 1 { t.Error("expected valid txs to be 1 is", len(pool.pending)) @@ -376,7 +376,7 @@ func TestQueue(t *testing.T) { tx = transaction(1, 100, key) from, _ = deriveSender(tx) testSetNonce(pool, from, 2) - pool.enqueueTx(tx.Hash(), tx, false, true, false) + pool.enqueueTx(tx.Hash(), tx, false, true) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { @@ -400,9 +400,9 @@ func TestQueue2(t *testing.T) { testAddBalance(pool, from, big.NewInt(1000)) pool.reset(nil, nil) - pool.enqueueTx(tx1.Hash(), tx1, false, true, false) - pool.enqueueTx(tx2.Hash(), tx2, false, true, false) - pool.enqueueTx(tx3.Hash(), tx3, false, true, false) + pool.enqueueTx(tx1.Hash(), tx1, false, true) + pool.enqueueTx(tx2.Hash(), tx2, false, true) + pool.enqueueTx(tx3.Hash(), tx3, false, true) pool.promoteExecutables([]common.Address{from}) if len(pool.pending) != 1 { @@ -620,9 +620,9 @@ func TestDropping(t *testing.T) { pool.priced.Put(tx2, false) pool.promoteTx(account, tx2.Hash(), tx2) - pool.enqueueTx(tx10.Hash(), tx10, false, true, false) - pool.enqueueTx(tx11.Hash(), tx11, false, true, false) - pool.enqueueTx(tx12.Hash(), tx12, false, true, false) + pool.enqueueTx(tx10.Hash(), tx10, false, true) + pool.enqueueTx(tx11.Hash(), tx11, false, true) + pool.enqueueTx(tx12.Hash(), tx12, false, true) // Check that pre and post validations leave the pool as is if pool.pending[account].Len() != 3 { @@ -2691,7 +2691,7 @@ func benchmarkFuturePromotion(b *testing.B, size int) { for i := 0; i < size; i++ { tx := transaction(uint64(1+i), 100000, key) - pool.enqueueTx(tx.Hash(), tx, false, true, false) + pool.enqueueTx(tx.Hash(), tx, false, true) } // Benchmark the speed of pool validation b.ResetTimer() diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index 62628b5ce3..582ef85d07 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -326,7 +326,7 @@ func (l *list) Contains(nonce uint64) bool { // // If the new transaction is accepted into the list, the lists' cost and gas // thresholds are also potentially updated. -func (l *list) Add(tx *types.Transaction, priceBump uint64, static bool) (bool, *types.Transaction) { +func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { // If there's an older better transaction, abort old := l.txs.Get(tx.Nonce()) if old != nil { diff --git a/core/txpool/legacypool/list_test.go b/core/txpool/legacypool/list_test.go index 4411d788f2..8587c66f7d 100644 --- a/core/txpool/legacypool/list_test.go +++ b/core/txpool/legacypool/list_test.go @@ -40,7 +40,7 @@ func TestStrictListAdd(t *testing.T) { // Insert the transactions in a random order list := newList(true) for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultConfig.PriceBump, false) + list.Add(txs[v], DefaultConfig.PriceBump) } // Verify internal state if len(list.txs.items) != len(txs) { @@ -64,7 +64,7 @@ func TestListAddVeryExpensive(t *testing.T) { gaslimit := uint64(i) tx, _ := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, value, gaslimit, gasprice, nil), types.HomesteadSigner{}, key) t.Logf("cost: %x bitlen: %d\n", tx.Cost(), tx.Cost().BitLen()) - list.Add(tx, DefaultConfig.PriceBump, false) + list.Add(tx, DefaultConfig.PriceBump) } } @@ -82,7 +82,7 @@ func BenchmarkListAdd(b *testing.B) { for i := 0; i < b.N; i++ { list := newList(true) for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultConfig.PriceBump, false) + list.Add(txs[v], DefaultConfig.PriceBump) list.Filter(priceLimit, DefaultConfig.PriceBump) } } @@ -102,7 +102,7 @@ func BenchmarkListCapOneTx(b *testing.B) { list := newList(true) // Insert the transactions in a random order for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultConfig.PriceBump, false) + list.Add(txs[v], DefaultConfig.PriceBump) } b.StartTimer() list.Cap(list.Len() - 1) From 1ad40cda3f9e293ce9eb74b5ff7f28a947999e8c Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 26 Sep 2024 14:24:13 +0100 Subject: [PATCH 46/57] pool: add back removed logic --- eth/handler.go | 4 +--- eth/protocols/eth/peer.go | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 949add9a5b..89b2bb6abd 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -851,8 +851,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce ) - // for pool2 it is p = static+sqrt(peers) is total available. - // Send tx to sqrt(p) and announce to (p - sqrt(p)) peers + // Broadcast transactions to a batch of peers not knowing about it for _, tx := range txs { peers := h.peers.peersWithoutTransaction(tx.Hash()) @@ -866,7 +865,6 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { default: numDirect = int(math.Sqrt(float64(len(peers)))) } - // Send the tx unconditionally to a subset of our peers for _, peer := range peers[:numDirect] { txset[peer] = append(txset[peer], tx.Hash()) diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index fa2a311d6c..7720d9bd44 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -224,6 +224,9 @@ func (p *Peer) SendTransactions(txs types.Transactions) error { // will force old sends to be dropped) func (p *Peer) AsyncSendTransactions(hashes []common.Hash) { select { + case p.txBroadcast <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + p.knownTxs.Add(hashes...) case <-p.txTerm: p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) case <-p.term: From d5b10e09a7db14ed5804b49d1106ea3855589eb7 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 26 Sep 2024 14:24:37 +0100 Subject: [PATCH 47/57] pool: remove fastcache which is no longer required --- core/txpool/legacypool/fastcache.go | 187 ----------------------- core/txpool/legacypool/fastcache_test.go | 94 ------------ 2 files changed, 281 deletions(-) delete mode 100644 core/txpool/legacypool/fastcache.go delete mode 100644 core/txpool/legacypool/fastcache_test.go diff --git a/core/txpool/legacypool/fastcache.go b/core/txpool/legacypool/fastcache.go deleted file mode 100644 index 7afbdf509f..0000000000 --- a/core/txpool/legacypool/fastcache.go +++ /dev/null @@ -1,187 +0,0 @@ -package legacypool - -import ( - "fmt" - "sync" - - "github.com/VictoriaMetrics/fastcache" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -type LRUBufferFastCache struct { - cache *fastcache.Cache - capacity int - mu sync.Mutex - size int // Total number of slots used - order []common.Hash // Tracks the order of added transactions for LRU eviction -} - -// SerializeTransaction converts a transaction to a byte slice using RLP or any other encoding -func SerializeTransaction(tx *types.Transaction) []byte { - data, err := tx.MarshalBinary() // Ethereum uses RLP for transactions, so we can use this function - if err != nil { - return nil - } - return data -} - -// DeserializeTransaction converts a byte slice back to a transaction -func DeserializeTransaction(data []byte) (*types.Transaction, error) { - var tx types.Transaction - err := tx.UnmarshalBinary(data) - if err != nil { - return nil, err - } - return &tx, nil -} - -// LRUBufferFastCache initializes an LRU buffer with a given capacity -func NewLRUBufferFastCache(capacity int) *LRUBufferFastCache { - return &LRUBufferFastCache{ - cache: fastcache.New(capacity * 1024 * 1024), // fastcache size is in bytes - capacity: capacity, - order: make([]common.Hash, 0), - } -} - -func (lru *LRUBufferFastCache) Add(tx *types.Transaction) { - lru.mu.Lock() - defer lru.mu.Unlock() - - txHash := tx.Hash() - - // Check if the transaction already exists - if lru.cache.Has(txHash.Bytes()) { - // Move the transaction to the front in LRU order - lru.moveToFront(txHash) - return - } - - txSlots := numSlots(tx) - - // Evict the oldest transactions if the new one doesn't fit - for lru.size+txSlots > lru.capacity && len(lru.order) > 0 { - lru.evictOldest() - } - - // Add the transaction to the cache - txData := SerializeTransaction(tx) - lru.cache.Set(txHash.Bytes(), txData) - lru.size += txSlots - - // Update pool3Gauge - pool3Gauge.Inc(1) - - // Add to the order tracking - lru.order = append(lru.order, txHash) -} - -// Evict the oldest transaction in the LRU order -func (lru *LRUBufferFastCache) evictOldest() { - oldestHash := lru.order[0] - lru.order = lru.order[1:] - - // Remove from the cache - txData := lru.cache.Get(nil, oldestHash.Bytes()) - if len(txData) > 0 { - tx, err := DeserializeTransaction(txData) - if err == nil { - lru.size -= numSlots(tx) - } - } - - // Remove the oldest entry - lru.cache.Del(oldestHash.Bytes()) - - // Update pool3Gauge - pool3Gauge.Dec(1) -} - -// Move a transaction to the front of the LRU order -func (lru *LRUBufferFastCache) moveToFront(hash common.Hash) { - for i, h := range lru.order { - if h == hash { - // Remove the hash from its current position - lru.order = append(lru.order[:i], lru.order[i+1:]...) - break - } - } - // Add it to the front - lru.order = append(lru.order, hash) -} - -// Get retrieves a transaction from the cache and moves it to the front of the LRU order -func (lru *LRUBufferFastCache) Get(hash common.Hash) (*types.Transaction, bool) { - lru.mu.Lock() - defer lru.mu.Unlock() - - txData := lru.cache.Get(nil, hash.Bytes()) - if len(txData) == 0 { - return nil, false - } - - tx, err := DeserializeTransaction(txData) - if err != nil { - return nil, false - } - - // Move the accessed transaction to the front in LRU order - lru.moveToFront(hash) - - return tx, true -} - -// Flush removes and returns up to `maxTransactions` transactions from the cache -func (lru *LRUBufferFastCache) Flush(maxTransactions int) []*types.Transaction { - lru.mu.Lock() - defer lru.mu.Unlock() - - var txs []*types.Transaction - count := 0 - - // Remove up to maxTransactions transactions from the oldest - for count < maxTransactions && len(lru.order) > 0 { - oldestHash := lru.order[0] - lru.order = lru.order[1:] - - txData := lru.cache.Get(nil, oldestHash.Bytes()) - if len(txData) > 0 { - tx, err := DeserializeTransaction(txData) - if err == nil { - txs = append(txs, tx) - lru.size -= numSlots(tx) - count++ - } - } - - lru.cache.Del(oldestHash.Bytes()) - // Update pool3Gauge - pool3Gauge.Dec(1) - } - - return txs -} - -// Size returns the current size of the buffer in terms of slots -func (lru *LRUBufferFastCache) Size() int { - lru.mu.Lock() - defer lru.mu.Unlock() - return lru.size -} - -// PrintTxStats prints the hash, gas fee cap, and gas tip cap of all transactions -func (lru *LRUBufferFastCache) PrintTxStats() { - lru.mu.Lock() - defer lru.mu.Unlock() - - for _, hash := range lru.order { - txData := lru.cache.Get(nil, hash.Bytes()) - if len(txData) > 0 { - tx, err := DeserializeTransaction(txData) - if err == nil { - fmt.Println(tx.Hash().String(), tx.GasFeeCap().String(), tx.GasTipCap().String()) - } - } - } -} diff --git a/core/txpool/legacypool/fastcache_test.go b/core/txpool/legacypool/fastcache_test.go deleted file mode 100644 index 050dc5d819..0000000000 --- a/core/txpool/legacypool/fastcache_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package legacypool - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewLRUBufferFastCache(t *testing.T) { - capacity := 10 - lru := NewLRUBufferFastCache(capacity) - - assert.Equal(t, capacity, lru.capacity, "capacity should match the given value") - assert.Equal(t, 0, lru.Size(), "size should be 0 for a new buffer") - assert.Equal(t, 0, len(lru.order), "order should be empty for a new buffer") -} - -func TestAddAndGetFastCache(t *testing.T) { - lru := NewLRUBufferFastCache(10) - - tx1 := createDummyTransaction(500) - tx2 := createDummyTransaction(1500) - - lru.Add(tx1) - lru.Add(tx2) - - assert.Equal(t, 2, lru.Size(), "size should be 2 after adding two transactions") - - retrievedTx, ok := lru.Get(tx1.Hash()) - assert.True(t, ok, "tx1 should be found in the buffer") - assert.Equal(t, tx1.Hash(), retrievedTx.Hash(), "retrieved tx1 hash should match the original hash") - - retrievedTx, ok = lru.Get(tx2.Hash()) - assert.True(t, ok, "tx2 should be found in the buffer") - assert.Equal(t, tx2.Hash(), retrievedTx.Hash(), "retrieved tx2 hash should match the original hash") -} - -func TestBufferCapacityFastCache(t *testing.T) { - lru := NewLRUBufferFastCache(2) // Capacity in slots - - tx1 := createDummyTransaction(500) // 1 slot - tx2 := createDummyTransaction(1500) // 1 slot - tx3 := createDummyTransaction(1000) // 1 slot - - lru.Add(tx1) - lru.Add(tx2) - - assert.Equal(t, 2, lru.Size(), "size should be 2 after adding two transactions") - - lru.Add(tx3) - - assert.Equal(t, 2, lru.Size(), "size should still be 2 after adding the third transaction") - _, ok := lru.Get(tx1.Hash()) - assert.False(t, ok, "tx1 should have been evicted") -} - -func TestFlushFastCache(t *testing.T) { - lru := NewLRUBufferFastCache(10) - - tx1 := createDummyTransaction(500) - tx2 := createDummyTransaction(1500) - tx3 := createDummyTransaction(1000) - - lru.Add(tx1) - lru.Add(tx2) - lru.Add(tx3) - - lru.PrintTxStats() - - flushedTxs := lru.Flush(2) - - assert.Equal(t, 2, len(flushedTxs), "should flush 2 transactions") - assert.Equal(t, tx1.Hash().String(), flushedTxs[0].Hash().String(), "correct flushed transaction") - assert.Equal(t, tx2.Hash().String(), flushedTxs[1].Hash().String(), "correct flushed transaction") - assert.Equal(t, 1, lru.Size(), "size should be 1 after flushing 2 transactions") - - lru.PrintTxStats() -} - -func TestSizeFastCache(t *testing.T) { - lru := NewLRUBufferFastCache(10) - - tx1 := createDummyTransaction(500) // 1 slot - tx2 := createDummyTransaction(1500) // 1 slot - - lru.Add(tx1) - assert.Equal(t, 1, lru.Size(), "size should be 1 after adding tx1") - - lru.Add(tx2) - assert.Equal(t, 2, lru.Size(), "size should be 2 after adding tx2") - - lru.Flush(1) - assert.Equal(t, 1, lru.Size(), "size should be 1 after flushing one transaction") -} From 4e69ac4a0f746f7149c89f4f33be75a234551c50 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 26 Sep 2024 14:55:59 +0100 Subject: [PATCH 48/57] pool: remove event and debug logs --- core/txpool/legacypool/legacypool.go | 24 +++++++++--------------- eth/handler.go | 1 - 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index a1c2e96317..c193ca7719 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -465,7 +465,6 @@ func (pool *LegacyPool) Close() error { // Reset implements txpool.SubPool, allowing the legacy pool's internal state to be // kept in sync with the main transaction pool's internal state. func (pool *LegacyPool) Reset(oldHead, newHead *types.Header) { - fmt.Println("Reset request") wait := pool.requestReset(oldHead, newHead) <-wait } @@ -841,7 +840,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the new transaction is a future transaction it should never churn pending transactions - // todo WHY THIS CHECK HAPPENS AFTER CALLING DISCARD()?? if !isLocal && pool.isGapped(from, tx) { var replacesPending bool for _, dropTx := range drop { @@ -857,7 +855,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.priced.Put(dropTx, false) } log.Trace("Discarding future transaction replacing pending tx", "hash", hash) - return false, txpool.ErrFutureReplacePending // todo 1 maybe in this case the future transaction can be part of pool3? + return false, txpool.ErrFutureReplacePending } } @@ -892,7 +890,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.all.Add(tx, isLocal) pool.priced.Put(tx, isLocal) pool.journalTx(from, tx) - pool.queueTxEvent(tx, false) + pool.queueTxEvent(tx) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat @@ -923,24 +921,24 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e func (pool *LegacyPool) addToPool3(drop types.Transactions, isLocal bool) { // calculate total number of slots in drop. Accordingly add them to pool3 (if there is space) - // all members of drop will be dropped from pool1/2 regardless of whether they get added to pool3 or not availableSlotsPool3 := pool.availableSlotsPool3() if availableSlotsPool3 > 0 { // transfer availableSlotsPool3 number of transactions slots from drop to pool3 currentSlotsUsed := 0 - for _, tx := range drop { + for i, tx := range drop { txSlots := numSlots(tx) if currentSlotsUsed+txSlots <= availableSlotsPool3 { from, _ := types.Sender(pool.signer, tx) - //pool.addToPool12OrPool3(tx, from, isLocal, false, false, true) pool.localBufferPool.Add(tx) log.Debug("adding to pool3", "transaction", tx.Hash().String(), "from", from.String()) currentSlotsUsed += txSlots + } else { + log.Debug("not all got added to pool3", "totalAdded", i+1) + return } } } else { log.Debug("adding to pool3 unsuccessful", "availableSlotsPool3", availableSlotsPool3) - fmt.Println("adding to pool3 unsuccessful") } } @@ -1163,7 +1161,7 @@ func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, local bool) ([]er for i, tx := range txs { replaced, err := pool.add(tx, local) errs[i] = err - if err == nil && !replaced { // todo ensure err is nil for certain case in add() where there is actually no error + if err == nil && !replaced { dirty.addTx(tx) } } @@ -1306,7 +1304,7 @@ func (pool *LegacyPool) requestPromoteExecutables(set *accountSet) chan struct{} } // queueTxEvent enqueues a transaction event to be sent in the next reorg run. -func (pool *LegacyPool) queueTxEvent(tx *types.Transaction, static bool) { +func (pool *LegacyPool) queueTxEvent(tx *types.Transaction) { select { case pool.queueTxEventCh <- tx: case <-pool.reorgShutdownCh: @@ -1391,7 +1389,6 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, reorgDurationTimer.Update(time.Since(t0)) }(time.Now()) defer close(done) - fmt.Println("runReorg called") var promoteAddrs []common.Address if dirtyAccounts != nil && reset == nil { // Only dirty accounts need to be promoted, unless we're resetting. @@ -1573,7 +1570,6 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { // future queue to the set of pending transactions. During this process, all // invalidated transactions (low nonce, low balance) are deleted. func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { - fmt.Println("promoteExecutables called") // Track the promoted transactions to broadcast them at once var promoted []*types.Transaction @@ -1609,7 +1605,6 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T } } log.Trace("Promoted queued transactions", "count", len(promoted)) - fmt.Println("promoting") queuedGauge.Dec(int64(len(readies))) // Drop all transactions over the allowed limit @@ -2127,7 +2122,7 @@ func (pool *LegacyPool) startPeriodicTransfer(t time.Duration) { }() } -// transferTransactions mainly moves from pool 3 to pool 2 +// transferTransactions mainly moves from pool3 to pool1 func (pool *LegacyPool) transferTransactions() { maxPool1Size := int(pool.config.GlobalSlots + pool.config.GlobalQueue) extraSizePool1 := maxPool1Size - int(uint64(len(pool.pending))+uint64(len(pool.queue))) @@ -2153,7 +2148,6 @@ func (pool *LegacyPool) transferTransactions() { if len(tx) == 0 { return } - fmt.Println("transferring tranasction") pool.Add(tx, true, false) } diff --git a/eth/handler.go b/eth/handler.go index 89b2bb6abd..23dba9e14d 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -851,7 +851,6 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce ) - // Broadcast transactions to a batch of peers not knowing about it for _, tx := range txs { peers := h.peers.peersWithoutTransaction(tx.Hash()) From 31c9465eace86a08607b118921e8f8bbc0cd823a Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 26 Sep 2024 14:58:31 +0100 Subject: [PATCH 49/57] pool: remove old buffer for pool3 --- core/txpool/legacypool/buffer.go | 115 -------------------------- core/txpool/legacypool/buffer_test.go | 100 ---------------------- 2 files changed, 215 deletions(-) delete mode 100644 core/txpool/legacypool/buffer.go delete mode 100644 core/txpool/legacypool/buffer_test.go diff --git a/core/txpool/legacypool/buffer.go b/core/txpool/legacypool/buffer.go deleted file mode 100644 index d9d88b1219..0000000000 --- a/core/txpool/legacypool/buffer.go +++ /dev/null @@ -1,115 +0,0 @@ -package legacypool - -import ( - containerList "container/list" - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -type LRUBuffer struct { - capacity int - buffer *containerList.List - index map[common.Hash]*containerList.Element - mu sync.Mutex - size int // Total number of slots used -} - -func NewLRUBuffer(capacity int) *LRUBuffer { - return &LRUBuffer{ - capacity: capacity, - buffer: containerList.New(), - index: make(map[common.Hash]*containerList.Element), - size: 0, // Initialize size to 0 - } -} - -func (lru *LRUBuffer) Add(tx *types.Transaction) { - lru.mu.Lock() - defer lru.mu.Unlock() - - if elem, ok := lru.index[tx.Hash()]; ok { - lru.buffer.MoveToFront(elem) - return - } - - txSlots := numSlots(tx) - - // Remove elements until there is enough capacity - for lru.size+txSlots > lru.capacity && lru.buffer.Len() > 0 { - back := lru.buffer.Back() - removedTx := back.Value.(*types.Transaction) - lru.buffer.Remove(back) - delete(lru.index, removedTx.Hash()) - lru.size -= numSlots(removedTx) // Decrease size by the slots of the removed transaction - } - - elem := lru.buffer.PushFront(tx) - lru.index[tx.Hash()] = elem - lru.size += txSlots // Increase size by the slots of the new transaction - // Update pool3Gauge - pool3Gauge.Inc(1) -} - -func (lru *LRUBuffer) Get(hash common.Hash) (*types.Transaction, bool) { - lru.mu.Lock() - defer lru.mu.Unlock() - - if elem, ok := lru.index[hash]; ok { - lru.buffer.MoveToFront(elem) - return elem.Value.(*types.Transaction), true - } - return nil, false -} - -func (lru *LRUBuffer) Flush(maxTransactions int) []*types.Transaction { - lru.mu.Lock() - defer lru.mu.Unlock() - - txs := make([]*types.Transaction, 0, maxTransactions) - count := 0 - for count < maxTransactions && lru.buffer.Len() > 0 { - back := lru.buffer.Back() - removedTx := back.Value.(*types.Transaction) - txs = append(txs, removedTx) - lru.buffer.Remove(back) - delete(lru.index, removedTx.Hash()) - lru.size -= numSlots(removedTx) // Decrease size by the slots of the removed transaction - count++ - // Update pool3Gauge - pool3Gauge.Dec(1) - } - return txs -} - -// New method to get the current size of the buffer in terms of slots -func (lru *LRUBuffer) Size() int { - lru.mu.Lock() - defer lru.mu.Unlock() - return lru.size -} - -// New iterator method to iterate over all transactions, ONLY used for printing and debugging -func (lru *LRUBuffer) iterate() <-chan *types.Transaction { - ch := make(chan *types.Transaction) - go func() { - lru.mu.Lock() - defer lru.mu.Unlock() - defer close(ch) - - for e := lru.buffer.Front(); e != nil; e = e.Next() { - ch <- e.Value.(*types.Transaction) - } - }() - return ch -} - -func (lru *LRUBuffer) PrintTxStats() { - // Iterating over the transactions - for tx := range lru.iterate() { - // Print transaction details or process them as needed - fmt.Println(tx.Hash().String(), tx.GasFeeCap().String(), tx.GasTipCap().String()) - } -} diff --git a/core/txpool/legacypool/buffer_test.go b/core/txpool/legacypool/buffer_test.go deleted file mode 100644 index 8b69385315..0000000000 --- a/core/txpool/legacypool/buffer_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package legacypool - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/require" -) - -// Helper function to create a dummy transaction of specified size -func createDummyTransaction(size int) *types.Transaction { - data := make([]byte, size) - return types.NewTransaction(0, common.Address{}, nil, 0, nil, data) -} - -func TestNewLRUBuffer(t *testing.T) { - capacity := 10 - lru := NewLRUBuffer(capacity) - - require.Equal(t, capacity, lru.capacity, "expected capacity to match") - require.Zero(t, lru.buffer.Len(), "expected buffer length to be zero") - require.Zero(t, len(lru.index), "expected index length to be zero") - require.Zero(t, lru.size, "expected size to be zero") -} - -func TestAddAndGet(t *testing.T) { - lru := NewLRUBuffer(10) - - tx1 := createDummyTransaction(500) - tx2 := createDummyTransaction(1500) - - lru.Add(tx1) - lru.Add(tx2) - - require.Equal(t, 2, lru.Size(), "expected size to be 2") - - retrievedTx, ok := lru.Get(tx1.Hash()) - require.True(t, ok, "expected to retrieve tx1") - require.Equal(t, tx1.Hash(), retrievedTx.Hash(), "retrieved tx1 hash does not match") - - retrievedTx, ok = lru.Get(tx2.Hash()) - require.True(t, ok, "expected to retrieve tx2") - require.Equal(t, tx2.Hash(), retrievedTx.Hash(), "retrieved tx2 hash does not match") -} - -func TestBufferCapacity(t *testing.T) { - lru := NewLRUBuffer(2) // Capacity in slots - - tx1 := createDummyTransaction(500) // 1 slot - tx2 := createDummyTransaction(1500) // 1 slot - tx3 := createDummyTransaction(1000) // 1 slot - - lru.Add(tx1) - lru.Add(tx2) - - require.Equal(t, 2, lru.Size(), "expected size to be 2") - - lru.Add(tx3) - - require.Equal(t, 2, lru.Size(), "expected size to remain 2 after adding tx3") - _, ok := lru.Get(tx1.Hash()) - require.False(t, ok, "expected tx1 to be evicted") -} - -func TestFlush(t *testing.T) { - lru := NewLRUBuffer(10) - - tx1 := createDummyTransaction(500) - tx2 := createDummyTransaction(1500) - tx3 := createDummyTransaction(1000) - - lru.Add(tx1) - lru.Add(tx2) - lru.Add(tx3) - - flushedTxs := lru.Flush(2) - - require.Len(t, flushedTxs, 2, "expected to flush 2 transactions") - - expectedSize := 1 - actualSize := lru.Size() - require.Equal(t, expectedSize, actualSize, "expected size after flush to match") -} - -func TestSize(t *testing.T) { - lru := NewLRUBuffer(10) - - tx1 := createDummyTransaction(500) // 1 slot - tx2 := createDummyTransaction(1500) // 2 slots - - lru.Add(tx1) - require.Equal(t, 1, lru.Size(), "expected size to be 1") - - lru.Add(tx2) - require.Equal(t, 2, lru.Size(), "expected size to be 2") - - lru.Flush(1) - require.Equal(t, 1, lru.Size(), "expected size to be 1 after flush") -} From a8959fe60e368fcd8cdb779373bd9253c8fbefbe Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 26 Sep 2024 15:18:06 +0100 Subject: [PATCH 50/57] pool: minor refactors --- core/txpool/legacypool/legacypool.go | 16 ++++++---------- core/txpool/legacypool/legacypool_test.go | 8 ++++---- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index c193ca7719..32c556b46c 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -279,7 +279,7 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), - localBufferPool: NewTxPool3Heap(), // NewLRUBufferFastCache(int(config.Pool3Slots)), + localBufferPool: NewTxPool3Heap(), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -417,8 +417,7 @@ func (pool *LegacyPool) loop() { if !pool.locals.contains(addr) { continue } - transactions := list.Flatten() - for _, tx := range transactions { + for _, tx := range list.Flatten() { // Default ReannounceTime is 10 years, won't announce by default. if time.Since(tx.Time()) < pool.config.ReannounceTime { break @@ -526,7 +525,7 @@ func (pool *LegacyPool) Stats() (int, int) { return pool.stats() } -func (pool *LegacyPool) StatsPool3() int { +func (pool *LegacyPool) statsPool3() int { pool.mu.RLock() defer pool.mu.RUnlock() @@ -658,12 +657,10 @@ func (pool *LegacyPool) local() map[common.Address]types.Transactions { txs := make(map[common.Address]types.Transactions) for addr := range pool.locals.accounts { if pending := pool.pending[addr]; pending != nil { - transactions := pending.Flatten() - txs[addr] = append(txs[addr], transactions...) + txs[addr] = append(txs[addr], pending.Flatten()...) } if queued := pool.queue[addr]; queued != nil { - transactions := queued.Flatten() - txs[addr] = append(txs[addr], transactions...) + txs[addr] = append(txs[addr], queued.Flatten()...) } } return txs @@ -1753,8 +1750,7 @@ func (pool *LegacyPool) truncateQueue() { // Drop all transactions if they are less than the overflow if size := uint64(list.Len()); size <= drop { - transactions := list.Flatten() - for _, tx := range transactions { + for _, tx := range list.Flatten() { pool.removeTx(tx.Hash(), true, true) } drop -= size diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 747d52c57c..6b017c7f09 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2288,17 +2288,17 @@ func TestTransferTransactions(t *testing.T) { assert.Equal(t, 0, pending, "pending transactions mismatched") assert.Equal(t, 0, queue, "queued transactions mismatched") - assert.Equal(t, 1, pool.StatsPool3(), "pool3 size unexpected") + assert.Equal(t, 1, pool.statsPool3(), "pool3 size unexpected") tx2 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) pool.addToPool3([]*types.Transaction{tx2}, true) - assert.Equal(t, 1, pool.StatsPool3(), "pool3 size unexpected") + assert.Equal(t, 1, pool.statsPool3(), "pool3 size unexpected") <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) pending, queue = pool.Stats() assert.Equal(t, 0, pending, "pending transactions mismatched") assert.Equal(t, 1, queue, "queued transactions mismatched") - assert.Equal(t, 0, pool.StatsPool3(), "pool3 size unexpected") + assert.Equal(t, 0, pool.statsPool3(), "pool3 size unexpected") tx3 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[2]) pool.addToPool3([]*types.Transaction{tx3}, true) @@ -2306,7 +2306,7 @@ func TestTransferTransactions(t *testing.T) { assert.Equal(t, 1, pending, "pending transactions mismatched") assert.Equal(t, 0, queue, "queued transactions mismatched") - assert.Equal(t, 1, pool.StatsPool3(), "pool3 size unexpected") + assert.Equal(t, 1, pool.statsPool3(), "pool3 size unexpected") } // Tests that the pool rejects replacement dynamic fee transactions that don't From 9c72c02502878c37073466eb27f75245a7b181b0 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Thu, 26 Sep 2024 16:03:23 +0100 Subject: [PATCH 51/57] pool: preallocate pool3 --- core/txpool/legacypool/heap.go | 4 ++-- core/txpool/legacypool/heap_test.go | 28 ++++++++++++++-------------- core/txpool/legacypool/legacypool.go | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/core/txpool/legacypool/heap.go b/core/txpool/legacypool/heap.go index 78fb2b2f03..ff76241cc9 100644 --- a/core/txpool/legacypool/heap.go +++ b/core/txpool/legacypool/heap.go @@ -58,9 +58,9 @@ type TxPool3Heap struct { sequence uint64 // Monotonically increasing sequence number } -func NewTxPool3Heap() *TxPool3Heap { +func NewTxPool3Heap(estimatedMaxSize uint64) *TxPool3Heap { return &TxPool3Heap{ - txHeap: make(txHeap, 0), + txHeap: make(txHeap, 0, estimatedMaxSize), index: make(map[common.Hash]*txHeapItem), sequence: 0, } diff --git a/core/txpool/legacypool/heap_test.go b/core/txpool/legacypool/heap_test.go index c001e27d42..7cc06b00f6 100644 --- a/core/txpool/legacypool/heap_test.go +++ b/core/txpool/legacypool/heap_test.go @@ -18,7 +18,7 @@ func createTestTx(nonce uint64, gasPrice *big.Int) *types.Transaction { } func TestNewTxPool3Heap(t *testing.T) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) if pool == nil { t.Fatal("NewTxPool3Heap returned nil") } @@ -28,7 +28,7 @@ func TestNewTxPool3Heap(t *testing.T) { } func TestTxPool3HeapAdd(t *testing.T) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -44,7 +44,7 @@ func TestTxPool3HeapAdd(t *testing.T) { } func TestTxPool3HeapGet(t *testing.T) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -63,7 +63,7 @@ func TestTxPool3HeapGet(t *testing.T) { } func TestTxPool3HeapRemove(t *testing.T) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -80,7 +80,7 @@ func TestTxPool3HeapRemove(t *testing.T) { } func TestTxPool3HeapPopN(t *testing.T) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) tx1 := createTestTx(1, big.NewInt(1000)) tx2 := createTestTx(2, big.NewInt(2000)) tx3 := createTestTx(3, big.NewInt(3000)) @@ -116,7 +116,7 @@ func TestTxPool3HeapPopN(t *testing.T) { } func TestTxPool3HeapOrdering(t *testing.T) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) tx1 := createTestTx(1, big.NewInt(1000)) tx2 := createTestTx(2, big.NewInt(2000)) tx3 := createTestTx(3, big.NewInt(3000)) @@ -136,7 +136,7 @@ func TestTxPool3HeapOrdering(t *testing.T) { } func TestTxPool3HeapLen(t *testing.T) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) if pool.Len() != 0 { t.Errorf("New pool should have length 0, got %d", pool.Len()) } @@ -173,7 +173,7 @@ func createRandomTestTx() *types.Transaction { // pkg: github.com/ethereum/go-ethereum/core/txpool/legacypool // BenchmarkTxPool3HeapAdd-8 45870 24270 ns/op func BenchmarkTxPool3HeapAdd(b *testing.B) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) b.ResetTimer() for i := 0; i < b.N; i++ { tx := createRandomTestTx() @@ -183,7 +183,7 @@ func BenchmarkTxPool3HeapAdd(b *testing.B) { // BenchmarkTxPool3HeapGet-8 34522438 37.05 ns/op func BenchmarkTxPool3HeapGet(b *testing.B) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) txs := make([]*types.Transaction, 1000) for i := 0; i < 1000; i++ { tx := createRandomTestTx() @@ -198,7 +198,7 @@ func BenchmarkTxPool3HeapGet(b *testing.B) { // BenchmarkTxPool3HeapRemove-8 2643650 539.2 ns/op func BenchmarkTxPool3HeapRemove(b *testing.B) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) txs := make([]*types.Transaction, b.N) for i := 0; i < b.N; i++ { tx := createRandomTestTx() @@ -213,7 +213,7 @@ func BenchmarkTxPool3HeapRemove(b *testing.B) { // BenchmarkTxPool3HeapPopN-8 47899808 23.80 ns/op func BenchmarkTxPool3HeapPopN(b *testing.B) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) for i := 0; i < 1000; i++ { tx := createRandomTestTx() pool.Add(tx) @@ -226,7 +226,7 @@ func BenchmarkTxPool3HeapPopN(b *testing.B) { // BenchmarkTxPool3HeapLen-8 86149902 13.32 ns/op func BenchmarkTxPool3HeapLen(b *testing.B) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) for i := 0; i < 1000; i++ { tx := createRandomTestTx() pool.Add(tx) @@ -239,7 +239,7 @@ func BenchmarkTxPool3HeapLen(b *testing.B) { // BenchmarkTxPool3HeapAddRemove-8 46156 25019 ns/op func BenchmarkTxPool3HeapAddRemove(b *testing.B) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) b.ResetTimer() for i := 0; i < b.N; i++ { tx := createRandomTestTx() @@ -251,7 +251,7 @@ func BenchmarkTxPool3HeapAddRemove(b *testing.B) { // BenchmarkTxPool3HeapAddPopN-8 470 2377928 ns/op pool.PopN(100) // BenchmarkTxPool3HeapAddPopN-8 4694 285026 ns/op pool.PopN(10) func BenchmarkTxPool3HeapAddPopN(b *testing.B) { - pool := NewTxPool3Heap() + pool := NewTxPool3Heap(0) b.ResetTimer() for i := 0; i < b.N; i++ { for j := 0; j < 10; j++ { diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 32c556b46c..56788480b3 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -279,7 +279,7 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), - localBufferPool: NewTxPool3Heap(), + localBufferPool: NewTxPool3Heap(config.Pool3Slots), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { From 5d44ba9c6a6c9662b9df22de36dc7ca115c463dc Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 1 Oct 2024 11:48:56 +0100 Subject: [PATCH 52/57] pool: remove sequence, more sturdy, maxsize --- core/txpool/legacypool/heap.go | 87 +++++++++++++++++------------ core/txpool/legacypool/heap_test.go | 87 +++++++++++++++-------------- 2 files changed, 95 insertions(+), 79 deletions(-) diff --git a/core/txpool/legacypool/heap.go b/core/txpool/legacypool/heap.go index ff76241cc9..716f76ac19 100644 --- a/core/txpool/legacypool/heap.go +++ b/core/txpool/legacypool/heap.go @@ -13,8 +13,7 @@ import ( // txHeapItem implements the Interface interface of heap so that it can be heapified type txHeapItem struct { tx *types.Transaction - timestamp int64 // Unix timestamp of when the transaction was added - sequence uint64 // Unique, monotonically increasing sequence number + timestamp int64 // Unix timestamp (nanoseconds) of when the transaction was added index int } @@ -22,21 +21,27 @@ type txHeap []*txHeapItem func (h txHeap) Len() int { return len(h) } func (h txHeap) Less(i, j int) bool { - // Order first by timestamp, then by sequence number if timestamps are equal - if h[i].timestamp == h[j].timestamp { - return h[i].sequence < h[j].sequence - } return h[i].timestamp < h[j].timestamp } func (h txHeap) Swap(i, j int) { + if i < 0 || j < 0 || i >= len(h) || j >= len(h) { + return // Silently fail if indices are out of bounds + } h[i], h[j] = h[j], h[i] - h[i].index = i - h[j].index = j + if h[i] != nil { + h[i].index = i + } + if h[j] != nil { + h[j].index = j + } } func (h *txHeap) Push(x interface{}) { + item, ok := x.(*txHeapItem) + if !ok { + return + } n := len(*h) - item := x.(*txHeapItem) item.index = n *h = append(*h, item) } @@ -44,25 +49,31 @@ func (h *txHeap) Push(x interface{}) { func (h *txHeap) Pop() interface{} { old := *h n := len(old) + if n == 0 { + return nil // Return nil if the heap is empty + } item := old[n-1] - old[n-1] = nil // avoid memory leak - item.index = -1 // for safety + old[n-1] = nil // avoid memory leak *h = old[0 : n-1] + if item != nil { + item.index = -1 // for safety + } return item } type TxPool3Heap struct { - txHeap txHeap - index map[common.Hash]*txHeapItem - mu sync.RWMutex - sequence uint64 // Monotonically increasing sequence number + txHeap txHeap + index map[common.Hash]*txHeapItem + mu sync.RWMutex + maxSize uint64 + totalSize int } func NewTxPool3Heap(estimatedMaxSize uint64) *TxPool3Heap { return &TxPool3Heap{ - txHeap: make(txHeap, 0, estimatedMaxSize), - index: make(map[common.Hash]*txHeapItem), - sequence: 0, + txHeap: make(txHeap, 0, estimatedMaxSize), + index: make(map[common.Hash]*txHeapItem, estimatedMaxSize), + maxSize: estimatedMaxSize, } } @@ -75,20 +86,28 @@ func (tp *TxPool3Heap) Add(tx *types.Transaction) { return } - tp.sequence++ + if uint64(len(tp.txHeap)) >= tp.maxSize { + // Remove the oldest transaction to make space + oldestItem, ok := heap.Pop(&tp.txHeap).(*txHeapItem) + if !ok || oldestItem == nil { + return + } + delete(tp.index, oldestItem.tx.Hash()) + tp.totalSize -= numSlots(oldestItem.tx) + } + item := &txHeapItem{ tx: tx, - timestamp: time.Now().Unix(), - sequence: tp.sequence, + timestamp: time.Now().UnixNano(), } heap.Push(&tp.txHeap, item) tp.index[tx.Hash()] = item + tp.totalSize += numSlots(tx) } func (tp *TxPool3Heap) Get(hash common.Hash) (*types.Transaction, bool) { tp.mu.RLock() defer tp.mu.RUnlock() - if item, ok := tp.index[hash]; ok { return item.tx, true } @@ -98,28 +117,29 @@ func (tp *TxPool3Heap) Get(hash common.Hash) (*types.Transaction, bool) { func (tp *TxPool3Heap) Remove(hash common.Hash) { tp.mu.Lock() defer tp.mu.Unlock() - if item, ok := tp.index[hash]; ok { heap.Remove(&tp.txHeap, item.index) delete(tp.index, hash) + tp.totalSize -= numSlots(item.tx) } } func (tp *TxPool3Heap) Flush(n int) []*types.Transaction { tp.mu.Lock() defer tp.mu.Unlock() - if n > tp.txHeap.Len() { n = tp.txHeap.Len() } - txs := make([]*types.Transaction, n) for i := 0; i < n; i++ { - item := heap.Pop(&tp.txHeap).(*txHeapItem) + item, ok := heap.Pop(&tp.txHeap).(*txHeapItem) + if !ok || item == nil { + continue + } txs[i] = item.tx delete(tp.index, item.tx.Hash()) + tp.totalSize -= numSlots(item.tx) } - return txs } @@ -132,22 +152,15 @@ func (tp *TxPool3Heap) Len() int { func (tp *TxPool3Heap) Size() int { tp.mu.RLock() defer tp.mu.RUnlock() - - totalSize := 0 - for _, item := range tp.txHeap { - totalSize += numSlots(item.tx) - } - - return totalSize + return tp.totalSize } func (tp *TxPool3Heap) PrintTxStats() { tp.mu.RLock() defer tp.mu.RUnlock() - for _, item := range tp.txHeap { tx := item.tx - fmt.Printf("Hash: %s, Timestamp: %d, Sequence: %d, GasFeeCap: %s, GasTipCap: %s\n", - tx.Hash().String(), item.timestamp, item.sequence, tx.GasFeeCap().String(), tx.GasTipCap().String()) + fmt.Printf("Hash: %s, Timestamp: %d, GasFeeCap: %s, GasTipCap: %s\n", + tx.Hash().String(), item.timestamp, tx.GasFeeCap().String(), tx.GasTipCap().String()) } } diff --git a/core/txpool/legacypool/heap_test.go b/core/txpool/legacypool/heap_test.go index 7cc06b00f6..6453bbd99f 100644 --- a/core/txpool/legacypool/heap_test.go +++ b/core/txpool/legacypool/heap_test.go @@ -28,7 +28,7 @@ func TestNewTxPool3Heap(t *testing.T) { } func TestTxPool3HeapAdd(t *testing.T) { - pool := NewTxPool3Heap(0) + pool := NewTxPool3Heap(1) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -44,7 +44,7 @@ func TestTxPool3HeapAdd(t *testing.T) { } func TestTxPool3HeapGet(t *testing.T) { - pool := NewTxPool3Heap(0) + pool := NewTxPool3Heap(1) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -63,7 +63,7 @@ func TestTxPool3HeapGet(t *testing.T) { } func TestTxPool3HeapRemove(t *testing.T) { - pool := NewTxPool3Heap(0) + pool := NewTxPool3Heap(1) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -80,7 +80,7 @@ func TestTxPool3HeapRemove(t *testing.T) { } func TestTxPool3HeapPopN(t *testing.T) { - pool := NewTxPool3Heap(0) + pool := NewTxPool3Heap(3) tx1 := createTestTx(1, big.NewInt(1000)) tx2 := createTestTx(2, big.NewInt(2000)) tx3 := createTestTx(3, big.NewInt(3000)) @@ -116,7 +116,7 @@ func TestTxPool3HeapPopN(t *testing.T) { } func TestTxPool3HeapOrdering(t *testing.T) { - pool := NewTxPool3Heap(0) + pool := NewTxPool3Heap(3) tx1 := createTestTx(1, big.NewInt(1000)) tx2 := createTestTx(2, big.NewInt(2000)) tx3 := createTestTx(3, big.NewInt(3000)) @@ -136,7 +136,7 @@ func TestTxPool3HeapOrdering(t *testing.T) { } func TestTxPool3HeapLen(t *testing.T) { - pool := NewTxPool3Heap(0) + pool := NewTxPool3Heap(2) if pool.Len() != 0 { t.Errorf("New pool should have length 0, got %d", pool.Len()) } @@ -168,27 +168,33 @@ func createRandomTestTx() *types.Transaction { return types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data) } +func createRandomTestTxs(n int) []*types.Transaction { + txs := make([]*types.Transaction, n) + for i := 0; i < n; i++ { + txs[i] = createRandomTestTx() + } + return txs +} + // goos: darwin // goarch: arm64 // pkg: github.com/ethereum/go-ethereum/core/txpool/legacypool -// BenchmarkTxPool3HeapAdd-8 45870 24270 ns/op +// BenchmarkTxPool3HeapAdd-8 813326 2858 ns/op func BenchmarkTxPool3HeapAdd(b *testing.B) { - pool := NewTxPool3Heap(0) + pool := NewTxPool3Heap(uint64(b.N)) + txs := createRandomTestTxs(b.N) b.ResetTimer() for i := 0; i < b.N; i++ { - tx := createRandomTestTx() - pool.Add(tx) + pool.Add(txs[i]) } } -// BenchmarkTxPool3HeapGet-8 34522438 37.05 ns/op +// BenchmarkTxPool3HeapGet-8 32613938 35.63 ns/op func BenchmarkTxPool3HeapGet(b *testing.B) { - pool := NewTxPool3Heap(0) - txs := make([]*types.Transaction, 1000) - for i := 0; i < 1000; i++ { - tx := createRandomTestTx() + pool := NewTxPool3Heap(1000) + txs := createRandomTestTxs(1000) + for _, tx := range txs { pool.Add(tx) - txs[i] = tx } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -196,14 +202,12 @@ func BenchmarkTxPool3HeapGet(b *testing.B) { } } -// BenchmarkTxPool3HeapRemove-8 2643650 539.2 ns/op +// BenchmarkTxPool3HeapRemove-8 3020841 417.8 ns/op func BenchmarkTxPool3HeapRemove(b *testing.B) { - pool := NewTxPool3Heap(0) - txs := make([]*types.Transaction, b.N) - for i := 0; i < b.N; i++ { - tx := createRandomTestTx() + pool := NewTxPool3Heap(uint64(b.N)) + txs := createRandomTestTxs(b.N) + for _, tx := range txs { pool.Add(tx) - txs[i] = tx } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -211,11 +215,11 @@ func BenchmarkTxPool3HeapRemove(b *testing.B) { } } -// BenchmarkTxPool3HeapPopN-8 47899808 23.80 ns/op -func BenchmarkTxPool3HeapPopN(b *testing.B) { - pool := NewTxPool3Heap(0) - for i := 0; i < 1000; i++ { - tx := createRandomTestTx() +// BenchmarkTxPool3HeapFlush-8 42963656 29.90 ns/op +func BenchmarkTxPool3HeapFlush(b *testing.B) { + pool := NewTxPool3Heap(1000) + txs := createRandomTestTxs(1000) + for _, tx := range txs { pool.Add(tx) } b.ResetTimer() @@ -224,11 +228,11 @@ func BenchmarkTxPool3HeapPopN(b *testing.B) { } } -// BenchmarkTxPool3HeapLen-8 86149902 13.32 ns/op +// BenchmarkTxPool3HeapLen-8 79147188 20.07 ns/op func BenchmarkTxPool3HeapLen(b *testing.B) { - pool := NewTxPool3Heap(0) - for i := 0; i < 1000; i++ { - tx := createRandomTestTx() + pool := NewTxPool3Heap(1000) + txs := createRandomTestTxs(1000) + for _, tx := range txs { pool.Add(tx) } b.ResetTimer() @@ -237,26 +241,25 @@ func BenchmarkTxPool3HeapLen(b *testing.B) { } } -// BenchmarkTxPool3HeapAddRemove-8 46156 25019 ns/op +// BenchmarkTxPool3HeapAddRemove-8 902896 1546 ns/op func BenchmarkTxPool3HeapAddRemove(b *testing.B) { - pool := NewTxPool3Heap(0) + pool := NewTxPool3Heap(uint64(b.N)) + txs := createRandomTestTxs(b.N) b.ResetTimer() for i := 0; i < b.N; i++ { - tx := createRandomTestTx() - pool.Add(tx) - pool.Remove(tx.Hash()) + pool.Add(txs[i]) + pool.Remove(txs[i].Hash()) } } -// BenchmarkTxPool3HeapAddPopN-8 470 2377928 ns/op pool.PopN(100) -// BenchmarkTxPool3HeapAddPopN-8 4694 285026 ns/op pool.PopN(10) -func BenchmarkTxPool3HeapAddPopN(b *testing.B) { - pool := NewTxPool3Heap(0) +// BenchmarkTxPool3HeapAddFlush-8 84417 14899 ns/op +func BenchmarkTxPool3HeapAddFlush(b *testing.B) { + pool := NewTxPool3Heap(uint64(b.N * 10)) + txs := createRandomTestTxs(b.N * 10) b.ResetTimer() for i := 0; i < b.N; i++ { for j := 0; j < 10; j++ { - tx := createRandomTestTx() - pool.Add(tx) + pool.Add(txs[i*10+j]) } pool.Flush(10) } From 7a929d6324e1d909648019d8b10872fac37231de Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Mon, 14 Oct 2024 11:05:49 +0100 Subject: [PATCH 53/57] pool: refactor to overflow pool --- cmd/geth/main.go | 2 +- cmd/utils/flags.go | 34 +++---- core/txpool/legacypool/heap.go | 25 +++--- core/txpool/legacypool/heap_test.go | 72 +++++++-------- core/txpool/legacypool/legacypool.go | 88 +++++++++---------- core/txpool/legacypool/legacypool_test.go | 27 +++--- .../legacypool/{pool3.go => overflowpool.go} | 4 +- 7 files changed, 128 insertions(+), 124 deletions(-) rename core/txpool/legacypool/{pool3.go => overflowpool.go} (86%) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a2b2b759c4..94143c7d88 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -91,7 +91,7 @@ var ( utils.TxPoolGlobalSlotsFlag, utils.TxPoolAccountQueueFlag, utils.TxPoolGlobalQueueFlag, - utils.TxPoolPool3SlotsFlag, + utils.TxPoolOverflowPoolSlotsFlag, utils.TxPoolLifetimeFlag, utils.TxPoolReannounceTimeFlag, utils.BlobPoolDataDirFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cc0f6a6340..6221be7d61 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -450,10 +450,10 @@ var ( Value: ethconfig.Defaults.TxPool.GlobalQueue, Category: flags.TxPoolCategory, } - TxPoolPool3SlotsFlag = &cli.Uint64Flag{ - Name: "txpool.pool3slots", - Usage: "Maximum number of transaction slots in pool 3", - Value: ethconfig.Defaults.TxPool.Pool3Slots, + TxPoolOverflowPoolSlotsFlag = &cli.Uint64Flag{ + Name: "txpool.overflowpoolslots", + Usage: "Maximum number of transaction slots in overflow pool", + Value: ethconfig.Defaults.TxPool.OverflowPoolSlots, Category: flags.TxPoolCategory, } TxPoolLifetimeFlag = &cli.DurationFlag{ @@ -1768,8 +1768,8 @@ func setTxPool(ctx *cli.Context, cfg *legacypool.Config) { if ctx.IsSet(TxPoolGlobalQueueFlag.Name) { cfg.GlobalQueue = ctx.Uint64(TxPoolGlobalQueueFlag.Name) } - if ctx.IsSet(TxPoolPool3SlotsFlag.Name) { - cfg.Pool3Slots = ctx.Uint64(TxPoolPool3SlotsFlag.Name) + if ctx.IsSet(TxPoolOverflowPoolSlotsFlag.Name) { + cfg.OverflowPoolSlots = ctx.Uint64(TxPoolOverflowPoolSlotsFlag.Name) } if ctx.IsSet(TxPoolLifetimeFlag.Name) { cfg.Lifetime = ctx.Duration(TxPoolLifetimeFlag.Name) @@ -2292,17 +2292,17 @@ func EnableNodeInfo(poolConfig *legacypool.Config, nodeInfo *p2p.NodeInfo) Setup return func() { // register node info into metrics metrics.NewRegisteredLabel("node-info", nil).Mark(map[string]interface{}{ - "Enode": nodeInfo.Enode, - "ENR": nodeInfo.ENR, - "ID": nodeInfo.ID, - "PriceLimit": poolConfig.PriceLimit, - "PriceBump": poolConfig.PriceBump, - "AccountSlots": poolConfig.AccountSlots, - "GlobalSlots": poolConfig.GlobalSlots, - "AccountQueue": poolConfig.AccountQueue, - "GlobalQueue": poolConfig.GlobalQueue, - "Pool3Slots": poolConfig.Pool3Slots, - "Lifetime": poolConfig.Lifetime, + "Enode": nodeInfo.Enode, + "ENR": nodeInfo.ENR, + "ID": nodeInfo.ID, + "PriceLimit": poolConfig.PriceLimit, + "PriceBump": poolConfig.PriceBump, + "AccountSlots": poolConfig.AccountSlots, + "GlobalSlots": poolConfig.GlobalSlots, + "AccountQueue": poolConfig.AccountQueue, + "GlobalQueue": poolConfig.GlobalQueue, + "OverflowPoolSlots": poolConfig.OverflowPoolSlots, + "Lifetime": poolConfig.Lifetime, }) } } diff --git a/core/txpool/legacypool/heap.go b/core/txpool/legacypool/heap.go index 716f76ac19..f78d61c3d1 100644 --- a/core/txpool/legacypool/heap.go +++ b/core/txpool/legacypool/heap.go @@ -61,7 +61,7 @@ func (h *txHeap) Pop() interface{} { return item } -type TxPool3Heap struct { +type TxOverflowPoolHeap struct { txHeap txHeap index map[common.Hash]*txHeapItem mu sync.RWMutex @@ -69,15 +69,15 @@ type TxPool3Heap struct { totalSize int } -func NewTxPool3Heap(estimatedMaxSize uint64) *TxPool3Heap { - return &TxPool3Heap{ +func NewTxOverflowPoolHeap(estimatedMaxSize uint64) *TxOverflowPoolHeap { + return &TxOverflowPoolHeap{ txHeap: make(txHeap, 0, estimatedMaxSize), index: make(map[common.Hash]*txHeapItem, estimatedMaxSize), maxSize: estimatedMaxSize, } } -func (tp *TxPool3Heap) Add(tx *types.Transaction) { +func (tp *TxOverflowPoolHeap) Add(tx *types.Transaction) { tp.mu.Lock() defer tp.mu.Unlock() @@ -94,6 +94,7 @@ func (tp *TxPool3Heap) Add(tx *types.Transaction) { } delete(tp.index, oldestItem.tx.Hash()) tp.totalSize -= numSlots(oldestItem.tx) + OverflowPoolGauge.Dec(1) } item := &txHeapItem{ @@ -103,9 +104,10 @@ func (tp *TxPool3Heap) Add(tx *types.Transaction) { heap.Push(&tp.txHeap, item) tp.index[tx.Hash()] = item tp.totalSize += numSlots(tx) + OverflowPoolGauge.Inc(1) } -func (tp *TxPool3Heap) Get(hash common.Hash) (*types.Transaction, bool) { +func (tp *TxOverflowPoolHeap) Get(hash common.Hash) (*types.Transaction, bool) { tp.mu.RLock() defer tp.mu.RUnlock() if item, ok := tp.index[hash]; ok { @@ -114,17 +116,18 @@ func (tp *TxPool3Heap) Get(hash common.Hash) (*types.Transaction, bool) { return nil, false } -func (tp *TxPool3Heap) Remove(hash common.Hash) { +func (tp *TxOverflowPoolHeap) Remove(hash common.Hash) { tp.mu.Lock() defer tp.mu.Unlock() if item, ok := tp.index[hash]; ok { heap.Remove(&tp.txHeap, item.index) delete(tp.index, hash) tp.totalSize -= numSlots(item.tx) + OverflowPoolGauge.Dec(1) } } -func (tp *TxPool3Heap) Flush(n int) []*types.Transaction { +func (tp *TxOverflowPoolHeap) Flush(n int) []*types.Transaction { tp.mu.Lock() defer tp.mu.Unlock() if n > tp.txHeap.Len() { @@ -140,22 +143,24 @@ func (tp *TxPool3Heap) Flush(n int) []*types.Transaction { delete(tp.index, item.tx.Hash()) tp.totalSize -= numSlots(item.tx) } + + OverflowPoolGauge.Dec(int64(n)) return txs } -func (tp *TxPool3Heap) Len() int { +func (tp *TxOverflowPoolHeap) Len() int { tp.mu.RLock() defer tp.mu.RUnlock() return tp.txHeap.Len() } -func (tp *TxPool3Heap) Size() int { +func (tp *TxOverflowPoolHeap) Size() int { tp.mu.RLock() defer tp.mu.RUnlock() return tp.totalSize } -func (tp *TxPool3Heap) PrintTxStats() { +func (tp *TxOverflowPoolHeap) PrintTxStats() { tp.mu.RLock() defer tp.mu.RUnlock() for _, item := range tp.txHeap { diff --git a/core/txpool/legacypool/heap_test.go b/core/txpool/legacypool/heap_test.go index 6453bbd99f..9a4aee5008 100644 --- a/core/txpool/legacypool/heap_test.go +++ b/core/txpool/legacypool/heap_test.go @@ -17,18 +17,18 @@ func createTestTx(nonce uint64, gasPrice *big.Int) *types.Transaction { return types.NewTransaction(nonce, to, big.NewInt(1000), 21000, gasPrice, nil) } -func TestNewTxPool3Heap(t *testing.T) { - pool := NewTxPool3Heap(0) +func TestNewTxOverflowPoolHeap(t *testing.T) { + pool := NewTxOverflowPoolHeap(0) if pool == nil { - t.Fatal("NewTxPool3Heap returned nil") + t.Fatal("NewTxOverflowPoolHeap returned nil") } if pool.Len() != 0 { t.Errorf("New pool should be empty, got length %d", pool.Len()) } } -func TestTxPool3HeapAdd(t *testing.T) { - pool := NewTxPool3Heap(1) +func TestTxOverflowPoolHeapAdd(t *testing.T) { + pool := NewTxOverflowPoolHeap(1) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -43,8 +43,8 @@ func TestTxPool3HeapAdd(t *testing.T) { } } -func TestTxPool3HeapGet(t *testing.T) { - pool := NewTxPool3Heap(1) +func TestTxOverflowPoolHeapGet(t *testing.T) { + pool := NewTxOverflowPoolHeap(1) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -62,8 +62,8 @@ func TestTxPool3HeapGet(t *testing.T) { } } -func TestTxPool3HeapRemove(t *testing.T) { - pool := NewTxPool3Heap(1) +func TestTxOverflowPoolHeapRemove(t *testing.T) { + pool := NewTxOverflowPoolHeap(1) tx := createTestTx(1, big.NewInt(1000)) pool.Add(tx) @@ -79,8 +79,8 @@ func TestTxPool3HeapRemove(t *testing.T) { } } -func TestTxPool3HeapPopN(t *testing.T) { - pool := NewTxPool3Heap(3) +func TestTxOverflowPoolHeapPopN(t *testing.T) { + pool := NewTxOverflowPoolHeap(3) tx1 := createTestTx(1, big.NewInt(1000)) tx2 := createTestTx(2, big.NewInt(2000)) tx3 := createTestTx(3, big.NewInt(3000)) @@ -115,8 +115,8 @@ func TestTxPool3HeapPopN(t *testing.T) { } } -func TestTxPool3HeapOrdering(t *testing.T) { - pool := NewTxPool3Heap(3) +func TestTxOverflowPoolHeapOrdering(t *testing.T) { + pool := NewTxOverflowPoolHeap(3) tx1 := createTestTx(1, big.NewInt(1000)) tx2 := createTestTx(2, big.NewInt(2000)) tx3 := createTestTx(3, big.NewInt(3000)) @@ -135,8 +135,8 @@ func TestTxPool3HeapOrdering(t *testing.T) { } } -func TestTxPool3HeapLen(t *testing.T) { - pool := NewTxPool3Heap(2) +func TestTxOverflowPoolHeapLen(t *testing.T) { + pool := NewTxOverflowPoolHeap(2) if pool.Len() != 0 { t.Errorf("New pool should have length 0, got %d", pool.Len()) } @@ -179,9 +179,9 @@ func createRandomTestTxs(n int) []*types.Transaction { // goos: darwin // goarch: arm64 // pkg: github.com/ethereum/go-ethereum/core/txpool/legacypool -// BenchmarkTxPool3HeapAdd-8 813326 2858 ns/op -func BenchmarkTxPool3HeapAdd(b *testing.B) { - pool := NewTxPool3Heap(uint64(b.N)) +// BenchmarkTxOverflowPoolHeapAdd-8 813326 2858 ns/op +func BenchmarkTxOverflowPoolHeapAdd(b *testing.B) { + pool := NewTxOverflowPoolHeap(uint64(b.N)) txs := createRandomTestTxs(b.N) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -189,9 +189,9 @@ func BenchmarkTxPool3HeapAdd(b *testing.B) { } } -// BenchmarkTxPool3HeapGet-8 32613938 35.63 ns/op -func BenchmarkTxPool3HeapGet(b *testing.B) { - pool := NewTxPool3Heap(1000) +// BenchmarkTxOverflowPoolHeapGet-8 32613938 35.63 ns/op +func BenchmarkTxOverflowPoolHeapGet(b *testing.B) { + pool := NewTxOverflowPoolHeap(1000) txs := createRandomTestTxs(1000) for _, tx := range txs { pool.Add(tx) @@ -202,9 +202,9 @@ func BenchmarkTxPool3HeapGet(b *testing.B) { } } -// BenchmarkTxPool3HeapRemove-8 3020841 417.8 ns/op -func BenchmarkTxPool3HeapRemove(b *testing.B) { - pool := NewTxPool3Heap(uint64(b.N)) +// BenchmarkTxOverflowPoolHeapRemove-8 3020841 417.8 ns/op +func BenchmarkTxOverflowPoolHeapRemove(b *testing.B) { + pool := NewTxOverflowPoolHeap(uint64(b.N)) txs := createRandomTestTxs(b.N) for _, tx := range txs { pool.Add(tx) @@ -215,9 +215,9 @@ func BenchmarkTxPool3HeapRemove(b *testing.B) { } } -// BenchmarkTxPool3HeapFlush-8 42963656 29.90 ns/op -func BenchmarkTxPool3HeapFlush(b *testing.B) { - pool := NewTxPool3Heap(1000) +// BenchmarkTxOverflowPoolHeapFlush-8 42963656 29.90 ns/op +func BenchmarkTxOverflowPoolHeapFlush(b *testing.B) { + pool := NewTxOverflowPoolHeap(1000) txs := createRandomTestTxs(1000) for _, tx := range txs { pool.Add(tx) @@ -228,9 +228,9 @@ func BenchmarkTxPool3HeapFlush(b *testing.B) { } } -// BenchmarkTxPool3HeapLen-8 79147188 20.07 ns/op -func BenchmarkTxPool3HeapLen(b *testing.B) { - pool := NewTxPool3Heap(1000) +// BenchmarkTxOverflowPoolHeapLen-8 79147188 20.07 ns/op +func BenchmarkTxOverflowPoolHeapLen(b *testing.B) { + pool := NewTxOverflowPoolHeap(1000) txs := createRandomTestTxs(1000) for _, tx := range txs { pool.Add(tx) @@ -241,9 +241,9 @@ func BenchmarkTxPool3HeapLen(b *testing.B) { } } -// BenchmarkTxPool3HeapAddRemove-8 902896 1546 ns/op -func BenchmarkTxPool3HeapAddRemove(b *testing.B) { - pool := NewTxPool3Heap(uint64(b.N)) +// BenchmarkTxOverflowPoolHeapAddRemove-8 902896 1546 ns/op +func BenchmarkTxOverflowPoolHeapAddRemove(b *testing.B) { + pool := NewTxOverflowPoolHeap(uint64(b.N)) txs := createRandomTestTxs(b.N) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -252,9 +252,9 @@ func BenchmarkTxPool3HeapAddRemove(b *testing.B) { } } -// BenchmarkTxPool3HeapAddFlush-8 84417 14899 ns/op -func BenchmarkTxPool3HeapAddFlush(b *testing.B) { - pool := NewTxPool3Heap(uint64(b.N * 10)) +// BenchmarkTxOverflowPoolHeapAddFlush-8 84417 14899 ns/op +func BenchmarkTxOverflowPoolHeapAddFlush(b *testing.B) { + pool := NewTxOverflowPoolHeap(uint64(b.N * 10)) txs := createRandomTestTxs(b.N * 10) b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 56788480b3..be6defb70e 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -100,11 +100,11 @@ var ( // that this number is pretty low, since txpool reorgs happen very frequently. dropBetweenReorgHistogram = metrics.NewRegisteredHistogram("txpool/dropbetweenreorg", nil, metrics.NewExpDecaySample(1028, 0.015)) - pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) - queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) - localGauge = metrics.NewRegisteredGauge("txpool/local", nil) - slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) - pool3Gauge = metrics.NewRegisteredGauge("txpool/pool3", nil) + pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) + queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) + localGauge = metrics.NewRegisteredGauge("txpool/local", nil) + slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) + OverflowPoolGauge = metrics.NewRegisteredGauge("txpool/overflowpool", nil) reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil) ) @@ -135,11 +135,11 @@ type Config struct { PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce) - AccountSlots uint64 // Number of executable transaction slots guaranteed per account - GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts - AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account - GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts - Pool3Slots uint64 // Maximum number of transaction slots in pool 3 + AccountSlots uint64 // Number of executable transaction slots guaranteed per account + GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts + AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account + GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts + OverflowPoolSlots uint64 // Maximum number of transaction slots in overflow pool Lifetime time.Duration // Maximum amount of time non-executable transaction are queued ReannounceTime time.Duration // Duration for announcing local pending transactions again @@ -153,11 +153,11 @@ var DefaultConfig = Config{ PriceLimit: 1, PriceBump: 10, - AccountSlots: 16, - GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio - AccountQueue: 64, - GlobalQueue: 1024, - Pool3Slots: 1024, + AccountSlots: 16, + GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio + AccountQueue: 64, + GlobalQueue: 1024, + OverflowPoolSlots: 1024, Lifetime: 3 * time.Hour, ReannounceTime: 10 * 365 * 24 * time.Hour, @@ -240,7 +240,7 @@ type LegacyPool struct { all *lookup // All transactions to allow lookups priced *pricedList // All transactions sorted by price - localBufferPool Pool3 // Local buffer transactions (Pool 3) + localBufferPool OverflowPool // Local buffer transactions reqResetCh chan *txpoolResetRequest reqPromoteCh chan *accountSet @@ -279,7 +279,7 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), - localBufferPool: NewTxPool3Heap(config.Pool3Slots), + localBufferPool: NewTxOverflowPoolHeap(config.OverflowPoolSlots), } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -525,7 +525,7 @@ func (pool *LegacyPool) Stats() (int, int) { return pool.stats() } -func (pool *LegacyPool) statsPool3() int { +func (pool *LegacyPool) statsOverflowPool() int { pool.mu.RLock() defer pool.mu.RUnlock() @@ -766,7 +766,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return false, txpool.ErrAlreadyKnown } - maxPool1Size := pool.config.GlobalSlots + pool.config.GlobalQueue + maxMainPoolSize := pool.config.GlobalSlots + pool.config.GlobalQueue txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) // Make the local flag. If it's from local source or it's from the network but @@ -806,7 +806,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the transaction pool is full, discard underpriced transactions - if txPoolSizeAfterCurrentTx > maxPool1Size { + if txPoolSizeAfterCurrentTx > maxMainPoolSize { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) @@ -856,7 +856,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } } - pool.addToPool3(drop, isLocal) + pool.addToOverflowPool(drop, isLocal) // Kick out the underpriced remote transactions. for _, tx := range drop { @@ -916,26 +916,26 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return replaced, nil } -func (pool *LegacyPool) addToPool3(drop types.Transactions, isLocal bool) { - // calculate total number of slots in drop. Accordingly add them to pool3 (if there is space) - availableSlotsPool3 := pool.availableSlotsPool3() - if availableSlotsPool3 > 0 { - // transfer availableSlotsPool3 number of transactions slots from drop to pool3 +func (pool *LegacyPool) addToOverflowPool(drop types.Transactions, isLocal bool) { + // calculate total number of slots in drop. Accordingly add them to OverflowPool (if there is space) + availableSlotsOverflowPool := pool.availableSlotsOverflowPool() + if availableSlotsOverflowPool > 0 { + // transfer availableSlotsOverflowPool number of transactions slots from drop to OverflowPool currentSlotsUsed := 0 for i, tx := range drop { txSlots := numSlots(tx) - if currentSlotsUsed+txSlots <= availableSlotsPool3 { + if currentSlotsUsed+txSlots <= availableSlotsOverflowPool { from, _ := types.Sender(pool.signer, tx) pool.localBufferPool.Add(tx) - log.Debug("adding to pool3", "transaction", tx.Hash().String(), "from", from.String()) + log.Debug("adding to OverflowPool", "transaction", tx.Hash().String(), "from", from.String()) currentSlotsUsed += txSlots } else { - log.Debug("not all got added to pool3", "totalAdded", i+1) + log.Debug("not all got added to OverflowPool", "totalAdded", i+1) return } } } else { - log.Debug("adding to pool3 unsuccessful", "availableSlotsPool3", availableSlotsPool3) + log.Debug("adding to OverflowPool unsuccessful", "availableSlotsOverflowPool", availableSlotsOverflowPool) } } @@ -1444,7 +1444,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, pool.changesSinceReorg = 0 // Reset change counter pool.mu.Unlock() - // Transfer transactions from pool3 to pool1 for new block import + // Transfer transactions from OverflowPool to MainPool for new block import pool.transferTransactions() // Notify subsystems for newly added transactions @@ -2118,27 +2118,27 @@ func (pool *LegacyPool) startPeriodicTransfer(t time.Duration) { }() } -// transferTransactions mainly moves from pool3 to pool1 +// transferTransactions mainly moves from OverflowPool to MainPool func (pool *LegacyPool) transferTransactions() { - maxPool1Size := int(pool.config.GlobalSlots + pool.config.GlobalQueue) - extraSizePool1 := maxPool1Size - int(uint64(len(pool.pending))+uint64(len(pool.queue))) - if extraSizePool1 <= 0 { + maxMainPoolSize := int(pool.config.GlobalSlots + pool.config.GlobalQueue) + extraSizeMainPool := maxMainPoolSize - int(uint64(len(pool.pending))+uint64(len(pool.queue))) + if extraSizeMainPool <= 0 { return } - currentPool1Size := pool.all.Slots() - canTransferPool3ToPool1 := maxPool1Size > currentPool1Size - if !canTransferPool3ToPool1 { + currentMainPoolSize := pool.all.Slots() + canTransferOverflowPoolToMainPool := maxMainPoolSize > currentMainPoolSize + if !canTransferOverflowPoolToMainPool { return } - extraSlots := maxPool1Size - currentPool1Size + extraSlots := maxMainPoolSize - currentMainPoolSize extraTransactions := (extraSlots + 3) / 4 // Since maximum slots per transaction is 4 - // So now we can take out extraTransactions number of transactions from pool3 and put in pool1 + // So now we can take out extraTransactions number of transactions from OverflowPool and put in MainPool if extraTransactions < 1 { return } - log.Debug("Will attempt to transfer from pool3 to pool1", "transactions", extraTransactions) + log.Debug("Will attempt to transfer from OverflowPool to MainPool", "transactions", extraTransactions) tx := pool.localBufferPool.Flush(extraTransactions) if len(tx) == 0 { @@ -2148,9 +2148,9 @@ func (pool *LegacyPool) transferTransactions() { pool.Add(tx, true, false) } -func (pool *LegacyPool) availableSlotsPool3() int { - maxPool3Size := int(pool.config.Pool3Slots) - availableSlots := maxPool3Size - pool.localBufferPool.Size() +func (pool *LegacyPool) availableSlotsOverflowPool() int { + maxOverflowPoolSize := int(pool.config.OverflowPoolSlots) + availableSlots := maxOverflowPoolSize - pool.localBufferPool.Size() if availableSlots > 0 { return availableSlots } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 6b017c7f09..33700bab1c 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1740,7 +1740,7 @@ func TestRepricingKeepsLocals(t *testing.T) { // Note, local transactions are never allowed to be dropped. func TestUnderpricing(t *testing.T) { t.Parallel() - testTxPoolConfig.Pool3Slots = 5 + testTxPoolConfig.OverflowPoolSlots = 5 // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) @@ -1933,7 +1933,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { pool.config.GlobalSlots = 2 pool.config.GlobalQueue = 2 - pool.config.Pool3Slots = 0 + pool.config.OverflowPoolSlots = 0 // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) @@ -2046,13 +2046,13 @@ func TestUnderpricingDynamicFee(t *testing.T) { func TestDualHeapEviction(t *testing.T) { t.Parallel() - testTxPoolConfig.Pool3Slots = 1 + testTxPoolConfig.OverflowPoolSlots = 1 pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() pool.config.GlobalSlots = 2 pool.config.GlobalQueue = 2 - pool.config.Pool3Slots = 1 + pool.config.OverflowPoolSlots = 1 var ( highTip, highCap *types.Transaction @@ -2098,7 +2098,7 @@ func TestDualHeapEviction(t *testing.T) { } pending, queued := pool.Stats() if pending+queued != 4 { - t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, pool3 %d", pending+queued, 5, pending, queued, pool.localBufferPool.Size()) + t.Fatalf("transaction count mismatch: have %d, want %d, pending %d, queued %d, OverflowPool %d", pending+queued, 5, pending, queued, pool.localBufferPool.Size()) } } @@ -2264,14 +2264,13 @@ func TestReplacement(t *testing.T) { } func TestTransferTransactions(t *testing.T) { - // todo do runReorg() as only during that time the transfer of transctions occur t.Parallel() pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() pool.config.GlobalSlots = 1 pool.config.GlobalQueue = 2 - pool.config.Pool3Slots = 1 + pool.config.OverflowPoolSlots = 1 // Create a number of test accounts and fund them keys := make([]*ecdsa.PrivateKey, 5) @@ -2283,30 +2282,30 @@ func TestTransferTransactions(t *testing.T) { tx := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[0]) from, _ := types.Sender(pool.signer, tx) - pool.addToPool3([]*types.Transaction{tx}, true) + pool.addToOverflowPool([]*types.Transaction{tx}, true) pending, queue := pool.Stats() assert.Equal(t, 0, pending, "pending transactions mismatched") assert.Equal(t, 0, queue, "queued transactions mismatched") - assert.Equal(t, 1, pool.statsPool3(), "pool3 size unexpected") + assert.Equal(t, 1, pool.statsOverflowPool(), "OverflowPool size unexpected") tx2 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) - pool.addToPool3([]*types.Transaction{tx2}, true) - assert.Equal(t, 1, pool.statsPool3(), "pool3 size unexpected") + pool.addToOverflowPool([]*types.Transaction{tx2}, true) + assert.Equal(t, 1, pool.statsOverflowPool(), "OverflowPool size unexpected") <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) pending, queue = pool.Stats() assert.Equal(t, 0, pending, "pending transactions mismatched") assert.Equal(t, 1, queue, "queued transactions mismatched") - assert.Equal(t, 0, pool.statsPool3(), "pool3 size unexpected") + assert.Equal(t, 0, pool.statsOverflowPool(), "OverflowPool size unexpected") tx3 := dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[2]) - pool.addToPool3([]*types.Transaction{tx3}, true) + pool.addToOverflowPool([]*types.Transaction{tx3}, true) pending, queue = pool.Stats() assert.Equal(t, 1, pending, "pending transactions mismatched") assert.Equal(t, 0, queue, "queued transactions mismatched") - assert.Equal(t, 1, pool.statsPool3(), "pool3 size unexpected") + assert.Equal(t, 1, pool.statsOverflowPool(), "OverflowPool size unexpected") } // Tests that the pool rejects replacement dynamic fee transactions that don't diff --git a/core/txpool/legacypool/pool3.go b/core/txpool/legacypool/overflowpool.go similarity index 86% rename from core/txpool/legacypool/pool3.go rename to core/txpool/legacypool/overflowpool.go index 4b75001727..95681fa5c1 100644 --- a/core/txpool/legacypool/pool3.go +++ b/core/txpool/legacypool/overflowpool.go @@ -5,8 +5,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// Pool3 is an interface representing a transaction buffer -type Pool3 interface { +// OverflowPool is an interface representing a transaction buffer +type OverflowPool interface { Add(tx *types.Transaction) // Adds a transaction to the buffer Get(hash common.Hash) (*types.Transaction, bool) // Retrieves a transaction by hash Flush(maxTransactions int) []*types.Transaction // Flushes up to maxTransactions transactions From 0e67514ee7645a487aa80cc02ce8b8c9aa55ce6f Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Mon, 14 Oct 2024 14:55:44 +0100 Subject: [PATCH 54/57] pool: remove debug logs --- core/txpool/legacypool/legacypool_test.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 33700bab1c..cf865e1335 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1944,7 +1944,6 @@ func TestUnderpricingDynamicFee(t *testing.T) { keys := make([]*ecdsa.PrivateKey, 4) for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() - fmt.Println("key ", i, "is ", crypto.PubkeyToAddress(keys[i].PublicKey).String()) testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } @@ -1960,7 +1959,6 @@ func TestUnderpricingDynamicFee(t *testing.T) { // Import the batch and that both pending and queued transactions match up pool.addRemotes(txs) // Pend K0:0, K0:1; Que K1:1 pool.addLocal(ltx) // +K2:0 => Pend K0:0, K0:1, K2:0; Que K1:1 - fmt.Println("pool.addLocal(ltx) done") pending, queued := pool.Stats() if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) @@ -1968,11 +1966,9 @@ func TestUnderpricingDynamicFee(t *testing.T) { if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - fmt.Println("before validateEvents") if err := validateEvents(events, 3); err != nil { t.Fatalf("original event firing failed: %v", err) } - fmt.Println("after validateEvents") if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1997,8 +1993,6 @@ func TestUnderpricingDynamicFee(t *testing.T) { if err := pool.addRemoteSync(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } - fmt.Println("Stats before before validateEvents") - pool.printTxStats() pending, queued = pool.Stats() if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) @@ -2006,13 +2000,9 @@ func TestUnderpricingDynamicFee(t *testing.T) { if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - fmt.Println("Stats before validateEvents") - pool.printTxStats() if err := validateEvents(events, 2); err != nil { // todo make it 4...After this validateEvents the pending becomes 3?! t.Fatalf("additional event firing failed: %v", err) } - fmt.Println("Stats after validateEvents") - pool.printTxStats() if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2063,8 +2053,6 @@ func TestDualHeapEviction(t *testing.T) { check := func(tx *types.Transaction, name string) { if pool.all.GetRemote(tx.Hash()) == nil { - //fmt.Println(highCap.GasFeeCap().String(), highCap.GasTipCap().String(), highTip.GasFeeCap().String(), highTip.GasTipCap().String(), len(pool.pending), len(pool.queue), pool.localBufferPool.size) - pool.printTxStats() t.Fatalf("highest %s transaction evicted from the pool, gasTip: %s, gasFeeCap: %s, hash: %s", name, highTip.GasTipCap().String(), highCap.GasFeeCap().String(), tx.Hash().String()) } } @@ -2078,23 +2066,17 @@ func TestDualHeapEviction(t *testing.T) { if urgent { tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+1+i)), big.NewInt(int64(1+i)), key) if int64(1+i) > highTipValue || (int64(1+i) == highTipValue && int64(baseFee+1+i) > highTip.GasFeeCap().Int64()) { - //fmt.Println("highTip updated. tip=", int64(1+i), highTipValue) highTipValue = int64(1 + i) highTip = tx } } else { tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) if int64(baseFee+200+i) > highCapValue { - //fmt.Println("highCap updated. gasFee=", int64(baseFee+200+i), highCapValue) highCapValue = int64(baseFee + 200 + i) highCap = tx } } pool.addRemotesSync([]*types.Transaction{tx}) - //pool.printTxStats() - if len(pool.pending) > 5 { - fmt.Println("pending has more than 5 elements, i: ", i) - } } pending, queued := pool.Stats() if pending+queued != 4 { @@ -2276,7 +2258,6 @@ func TestTransferTransactions(t *testing.T) { keys := make([]*ecdsa.PrivateKey, 5) for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() - fmt.Println(crypto.PubkeyToAddress(keys[i].PublicKey)) testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } From f41bb131602722bc522e2da6bf31204c0c94d9ea Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 15 Oct 2024 12:50:58 +0100 Subject: [PATCH 55/57] pool: refactoring, addressing comments --- core/txpool/legacypool/heap.go | 2 +- core/txpool/legacypool/legacypool.go | 47 +++++----------------------- 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/core/txpool/legacypool/heap.go b/core/txpool/legacypool/heap.go index f78d61c3d1..dde892f9f1 100644 --- a/core/txpool/legacypool/heap.go +++ b/core/txpool/legacypool/heap.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// txHeapItem implements the Interface interface of heap so that it can be heapified +// txHeapItem implements the Interface interface (https://pkg.go.dev/container/heap#Interface) of heap so that it can be heapified type txHeapItem struct { tx *types.Transaction timestamp int64 // Unix timestamp (nanoseconds) of when the transaction was added diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index be6defb70e..3ba58ce7b6 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -766,9 +766,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e return false, txpool.ErrAlreadyKnown } - maxMainPoolSize := pool.config.GlobalSlots + pool.config.GlobalQueue - txPoolSizeAfterCurrentTx := uint64(pool.all.Slots() + numSlots(tx)) - // Make the local flag. If it's from local source or it's from the network but // the sender is marked as local previously, treat it as the local transaction. isLocal := local || pool.locals.containsTx(tx) @@ -806,7 +803,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } // If the transaction pool is full, discard underpriced transactions - if txPoolSizeAfterCurrentTx > maxMainPoolSize { + if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) @@ -826,8 +823,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // New transaction is better than our worse ones, make room for it. // If it's a local transaction, forcibly discard all available transactions. // Otherwise if we can't make enough room for new one, abort the operation. - toBeDiscarded := pool.all.Slots() - int(pool.config.GlobalSlots+pool.config.GlobalQueue) + numSlots(tx) - drop, success := pool.priced.Discard(toBeDiscarded, isLocal) + drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) // Special case, we still can't make the room for the new remote one. if !isLocal && !success { @@ -1727,8 +1723,7 @@ func (pool *LegacyPool) truncateQueue() { for _, list := range pool.queue { queued += uint64(list.Len()) } - queueMax := pool.config.GlobalQueue - if queued <= queueMax { + if queued <= pool.config.GlobalQueue { return } @@ -1742,7 +1737,7 @@ func (pool *LegacyPool) truncateQueue() { sort.Sort(sort.Reverse(addresses)) // Drop transactions until the total is below the limit or only locals remain - for drop := queued - queueMax; drop > 0 && len(addresses) > 0; { + for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { addr := addresses[len(addresses)-1] list := pool.queue[addr.address] @@ -2097,27 +2092,6 @@ func numSlots(tx *types.Transaction) int { return int((tx.Size() + txSlotSize - 1) / txSlotSize) } -func (pool *LegacyPool) startPeriodicTransfer(t time.Duration) { - ticker := time.NewTicker(time.Minute) // Adjust the interval as needed - if t != 0 { - ticker.Reset(t) - } - - go func() { - for { - select { - case <-ticker.C: - pool.mu.Lock() - pool.transferTransactions() - pool.mu.Unlock() - case <-pool.reorgShutdownCh: - ticker.Stop() - return - } - } - }() -} - // transferTransactions mainly moves from OverflowPool to MainPool func (pool *LegacyPool) transferTransactions() { maxMainPoolSize := int(pool.config.GlobalSlots + pool.config.GlobalQueue) @@ -2133,19 +2107,14 @@ func (pool *LegacyPool) transferTransactions() { } extraSlots := maxMainPoolSize - currentMainPoolSize extraTransactions := (extraSlots + 3) / 4 // Since maximum slots per transaction is 4 - // So now we can take out extraTransactions number of transactions from OverflowPool and put in MainPool - if extraTransactions < 1 { - return - } - log.Debug("Will attempt to transfer from OverflowPool to MainPool", "transactions", extraTransactions) - tx := pool.localBufferPool.Flush(extraTransactions) - if len(tx) == 0 { + txs := pool.localBufferPool.Flush(extraTransactions) + if len(txs) == 0 { return } - pool.Add(tx, true, false) + pool.Add(txs, true, false) } func (pool *LegacyPool) availableSlotsOverflowPool() int { @@ -2157,7 +2126,7 @@ func (pool *LegacyPool) availableSlotsOverflowPool() int { return 0 } -func (pool *LegacyPool) printTxStats() { +func (pool *LegacyPool) PrintTxStats() { for _, l := range pool.pending { for _, transaction := range l.txs.items { from, _ := types.Sender(pool.signer, transaction) From 0dd0bd7b3806ef1d1df336ee75a2e92828edf97d Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Tue, 15 Oct 2024 18:25:12 +0100 Subject: [PATCH 56/57] pool: remove extra new lines --- core/txpool/legacypool/legacypool.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 3ba58ce7b6..d1c1618b15 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -195,7 +195,6 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) conf.GlobalQueue = DefaultConfig.GlobalQueue } - if conf.Lifetime < 1 { log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) conf.Lifetime = DefaultConfig.Lifetime @@ -291,7 +290,6 @@ func New(config Config, chain BlockChain) *LegacyPool { if !config.NoLocals && config.Journal != "" { pool.journal = newTxJournal(config.Journal) } - return pool } @@ -765,7 +763,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e knownTxMeter.Mark(1) return false, txpool.ErrAlreadyKnown } - // Make the local flag. If it's from local source or it's from the network but // the sender is marked as local previously, treat it as the local transaction. isLocal := local || pool.locals.containsTx(tx) @@ -801,7 +798,6 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e } }() } - // If the transaction pool is full, discard underpriced transactions if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it @@ -890,13 +886,11 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e pool.beats[from] = time.Now() return old != nil, nil } - // New transaction isn't replacing a pending one, push into queue replaced, err = pool.enqueueTx(hash, tx, isLocal, true) if err != nil { return false, err } - // Mark local addresses and journal local transactions if local && !pool.locals.contains(from) { log.Info("Setting new local account", "address", from) @@ -1150,7 +1144,6 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) { dirty := newAccountSet(pool.signer) errs := make([]error, len(txs)) - for i, tx := range txs { replaced, err := pool.add(tx, local) errs[i] = err @@ -1407,7 +1400,6 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, promoteAddrs = append(promoteAddrs, addr) } } - // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) @@ -1614,7 +1606,6 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T // Mark all the items dropped as removed pool.priced.Removed(len(forwards) + len(drops) + len(caps)) queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) - if pool.locals.contains(addr) { localGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) } From 60bdc2539e656c4e7dcbfabb10c0f16fd56186d5 Mon Sep 17 00:00:00 2001 From: emailtovamos Date: Wed, 16 Oct 2024 13:56:48 +0100 Subject: [PATCH 57/57] pool: fail fast, disable by default, no interface --- core/txpool/legacypool/legacypool.go | 20 +++++++++---------- core/txpool/legacypool/legacypool_test.go | 2 +- core/txpool/legacypool/overflowpool.go | 15 -------------- .../{heap.go => tx_overflowpool.go} | 20 +++++++++---------- .../{heap_test.go => tx_overflowpool_test.go} | 0 5 files changed, 21 insertions(+), 36 deletions(-) delete mode 100644 core/txpool/legacypool/overflowpool.go rename core/txpool/legacypool/{heap.go => tx_overflowpool.go} (87%) rename core/txpool/legacypool/{heap_test.go => tx_overflowpool_test.go} (100%) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index d1c1618b15..0d5a1fb183 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -157,7 +157,7 @@ var DefaultConfig = Config{ GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio AccountQueue: 64, GlobalQueue: 1024, - OverflowPoolSlots: 1024, + OverflowPoolSlots: 0, Lifetime: 3 * time.Hour, ReannounceTime: 10 * 365 * 24 * time.Hour, @@ -239,7 +239,7 @@ type LegacyPool struct { all *lookup // All transactions to allow lookups priced *pricedList // All transactions sorted by price - localBufferPool OverflowPool // Local buffer transactions + localBufferPool *TxOverflowPool // Local buffer transactions reqResetCh chan *txpoolResetRequest reqPromoteCh chan *accountSet @@ -2083,23 +2083,23 @@ func numSlots(tx *types.Transaction) int { return int((tx.Size() + txSlotSize - 1) / txSlotSize) } -// transferTransactions mainly moves from OverflowPool to MainPool +// transferTransactions moves transactions from OverflowPool to MainPool func (pool *LegacyPool) transferTransactions() { - maxMainPoolSize := int(pool.config.GlobalSlots + pool.config.GlobalQueue) - extraSizeMainPool := maxMainPoolSize - int(uint64(len(pool.pending))+uint64(len(pool.queue))) - if extraSizeMainPool <= 0 { + // Fail fast if the overflow pool is empty + if pool.localBufferPool.Size() == 0 { return } + maxMainPoolSize := int(pool.config.GlobalSlots + pool.config.GlobalQueue) + // Use pool.all.Slots() to get the total slots used by all transactions currentMainPoolSize := pool.all.Slots() - canTransferOverflowPoolToMainPool := maxMainPoolSize > currentMainPoolSize - if !canTransferOverflowPoolToMainPool { + if currentMainPoolSize >= maxMainPoolSize { return } + extraSlots := maxMainPoolSize - currentMainPoolSize - extraTransactions := (extraSlots + 3) / 4 // Since maximum slots per transaction is 4 + extraTransactions := (extraSlots + 3) / 4 // Since a transaction can take up to 4 slots log.Debug("Will attempt to transfer from OverflowPool to MainPool", "transactions", extraTransactions) - txs := pool.localBufferPool.Flush(extraTransactions) if len(txs) == 0 { return diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index cf865e1335..53c62b9bd3 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2247,12 +2247,12 @@ func TestReplacement(t *testing.T) { func TestTransferTransactions(t *testing.T) { t.Parallel() + testTxPoolConfig.OverflowPoolSlots = 1 pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Close() pool.config.GlobalSlots = 1 pool.config.GlobalQueue = 2 - pool.config.OverflowPoolSlots = 1 // Create a number of test accounts and fund them keys := make([]*ecdsa.PrivateKey, 5) diff --git a/core/txpool/legacypool/overflowpool.go b/core/txpool/legacypool/overflowpool.go deleted file mode 100644 index 95681fa5c1..0000000000 --- a/core/txpool/legacypool/overflowpool.go +++ /dev/null @@ -1,15 +0,0 @@ -package legacypool - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -// OverflowPool is an interface representing a transaction buffer -type OverflowPool interface { - Add(tx *types.Transaction) // Adds a transaction to the buffer - Get(hash common.Hash) (*types.Transaction, bool) // Retrieves a transaction by hash - Flush(maxTransactions int) []*types.Transaction // Flushes up to maxTransactions transactions - Size() int // Returns the current size of the buffer - PrintTxStats() // Prints the statistics of all transactions in the buffer -} diff --git a/core/txpool/legacypool/heap.go b/core/txpool/legacypool/tx_overflowpool.go similarity index 87% rename from core/txpool/legacypool/heap.go rename to core/txpool/legacypool/tx_overflowpool.go index dde892f9f1..4bfd4b6f5a 100644 --- a/core/txpool/legacypool/heap.go +++ b/core/txpool/legacypool/tx_overflowpool.go @@ -61,7 +61,7 @@ func (h *txHeap) Pop() interface{} { return item } -type TxOverflowPoolHeap struct { +type TxOverflowPool struct { txHeap txHeap index map[common.Hash]*txHeapItem mu sync.RWMutex @@ -69,15 +69,15 @@ type TxOverflowPoolHeap struct { totalSize int } -func NewTxOverflowPoolHeap(estimatedMaxSize uint64) *TxOverflowPoolHeap { - return &TxOverflowPoolHeap{ +func NewTxOverflowPoolHeap(estimatedMaxSize uint64) *TxOverflowPool { + return &TxOverflowPool{ txHeap: make(txHeap, 0, estimatedMaxSize), index: make(map[common.Hash]*txHeapItem, estimatedMaxSize), maxSize: estimatedMaxSize, } } -func (tp *TxOverflowPoolHeap) Add(tx *types.Transaction) { +func (tp *TxOverflowPool) Add(tx *types.Transaction) { tp.mu.Lock() defer tp.mu.Unlock() @@ -107,7 +107,7 @@ func (tp *TxOverflowPoolHeap) Add(tx *types.Transaction) { OverflowPoolGauge.Inc(1) } -func (tp *TxOverflowPoolHeap) Get(hash common.Hash) (*types.Transaction, bool) { +func (tp *TxOverflowPool) Get(hash common.Hash) (*types.Transaction, bool) { tp.mu.RLock() defer tp.mu.RUnlock() if item, ok := tp.index[hash]; ok { @@ -116,7 +116,7 @@ func (tp *TxOverflowPoolHeap) Get(hash common.Hash) (*types.Transaction, bool) { return nil, false } -func (tp *TxOverflowPoolHeap) Remove(hash common.Hash) { +func (tp *TxOverflowPool) Remove(hash common.Hash) { tp.mu.Lock() defer tp.mu.Unlock() if item, ok := tp.index[hash]; ok { @@ -127,7 +127,7 @@ func (tp *TxOverflowPoolHeap) Remove(hash common.Hash) { } } -func (tp *TxOverflowPoolHeap) Flush(n int) []*types.Transaction { +func (tp *TxOverflowPool) Flush(n int) []*types.Transaction { tp.mu.Lock() defer tp.mu.Unlock() if n > tp.txHeap.Len() { @@ -148,19 +148,19 @@ func (tp *TxOverflowPoolHeap) Flush(n int) []*types.Transaction { return txs } -func (tp *TxOverflowPoolHeap) Len() int { +func (tp *TxOverflowPool) Len() int { tp.mu.RLock() defer tp.mu.RUnlock() return tp.txHeap.Len() } -func (tp *TxOverflowPoolHeap) Size() int { +func (tp *TxOverflowPool) Size() int { tp.mu.RLock() defer tp.mu.RUnlock() return tp.totalSize } -func (tp *TxOverflowPoolHeap) PrintTxStats() { +func (tp *TxOverflowPool) PrintTxStats() { tp.mu.RLock() defer tp.mu.RUnlock() for _, item := range tp.txHeap { diff --git a/core/txpool/legacypool/heap_test.go b/core/txpool/legacypool/tx_overflowpool_test.go similarity index 100% rename from core/txpool/legacypool/heap_test.go rename to core/txpool/legacypool/tx_overflowpool_test.go