eth/txtracker: use lru.BasicLRU

This commit is contained in:
healthykim 2026-07-02 16:48:10 +02:00
parent 2d7ebc5e4f
commit f9a0352853
2 changed files with 20 additions and 40 deletions

View file

@ -31,6 +31,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
@ -90,9 +91,8 @@ type TxInfo struct {
// when their transactions appear on chain. // when their transactions appear on chain.
type Tracker struct { type Tracker struct {
mu sync.Mutex mu sync.Mutex
txs map[common.Hash]*TxInfo // hash → per-tx state txs lru.BasicLRU[common.Hash, *TxInfo] // tx hash -> tx info with lru eviction
peers map[string]*peerStats peers map[string]*peerStats
order []common.Hash // insertion order for LRU eviction
chain Chain chain Chain
lastFinalNum uint64 // last finalized block number processed lastFinalNum uint64 // last finalized block number processed
@ -109,7 +109,7 @@ type Tracker struct {
// New creates a new tracker. // New creates a new tracker.
func New() *Tracker { func New() *Tracker {
return &Tracker{ return &Tracker{
txs: make(map[common.Hash]*TxInfo), txs: lru.NewBasicLRU[common.Hash, *TxInfo](maxTracked),
peers: make(map[string]*peerStats), peers: make(map[string]*peerStats),
quit: make(chan struct{}), quit: make(chan struct{}),
step: make(chan struct{}, 1), step: make(chan struct{}, 1),
@ -159,27 +159,15 @@ func (t *Tracker) NotifyAccepted(peer string, hashes []common.Hash) {
addedAt := t.now() addedAt := t.now()
for _, hash := range hashes { for _, hash := range hashes {
if _, ok := t.txs[hash]; ok { if t.txs.Contains(hash) {
continue // already tracked, keep first deliverer continue // already tracked, keep first deliverer
} }
t.txs[hash] = &TxInfo{Deliverer: peer, AddedAt: addedAt} t.txs.Add(hash, &TxInfo{Deliverer: peer, AddedAt: addedAt})
t.order = append(t.order, hash)
} }
// Ensure the delivering peer has a stats entry. // Ensure the delivering peer has a stats entry.
if len(hashes) > 0 && t.peers[peer] == nil { if len(hashes) > 0 && t.peers[peer] == nil {
t.peers[peer] = &peerStats{} t.peers[peer] = &peerStats{}
} }
// Evict oldest entries if over capacity.
for len(t.txs) > maxTracked {
oldest := t.order[0]
t.order = t.order[1:]
delete(t.txs, oldest)
}
// Compact the backing array when it grows too large. Reslicing
// with order[1:] doesn't free earlier slots in the array.
if cap(t.order) > 2*maxTracked {
t.order = append([]common.Hash(nil), t.order...)
}
} }
// GetAllPeerStats returns a snapshot of per-peer inclusion statistics. // GetAllPeerStats returns a snapshot of per-peer inclusion statistics.
@ -238,7 +226,7 @@ func (t *Tracker) handleChainHead(ev core.ChainHeadEvent) {
blockHash := block.Hash() blockHash := block.Hash()
blockIncl := make(map[string]int) blockIncl := make(map[string]int)
for _, tx := range block.Transactions() { for _, tx := range block.Transactions() {
ti, ok := t.txs[tx.Hash()] ti, ok := t.txs.Peek(tx.Hash())
if !ok || ti.AddedAt >= blockTime { if !ok || ti.AddedAt >= blockTime {
continue continue
} }
@ -294,13 +282,15 @@ func (t *Tracker) collectFinalizationCredits() map[string]int {
// "already credited in a prior pass" (BlockNum <= lastFinalNum); no // "already credited in a prior pass" (BlockNum <= lastFinalNum); no
// separate status bookkeeping is needed. // separate status bookkeeping is needed.
buckets := make(map[uint64][]*TxInfo) buckets := make(map[uint64][]*TxInfo)
for _, ti := range t.txs { for _, hash := range t.txs.Keys() {
if ti.BlockNum <= t.lastFinalNum || ti.BlockNum > finalNum { ti, ok := t.txs.Peek(hash)
if !ok || ti.BlockNum <= t.lastFinalNum || ti.BlockNum > finalNum {
continue continue
} }
buckets[ti.BlockNum] = append(buckets[ti.BlockNum], ti) buckets[ti.BlockNum] = append(buckets[ti.BlockNum], ti)
} }
total := 0
for num, tis := range buckets { for num, tis := range buckets {
canonHash := t.chain.GetCanonicalHash(num) canonHash := t.chain.GetCanonicalHash(num)
if canonHash == (common.Hash{}) { if canonHash == (common.Hash{}) {
@ -316,22 +306,15 @@ func (t *Tracker) collectFinalizationCredits() map[string]int {
} }
if ti.Deliverer != "" { if ti.Deliverer != "" {
credits[ti.Deliverer]++ credits[ti.Deliverer]++
total++
} }
} }
} }
if total := sumCounts(credits); total > 0 { if total > 0 {
log.Trace("Accumulated finalization credits", log.Trace("Accumulated finalization credits",
"from", t.lastFinalNum+1, "to", finalNum, "txs", total) "from", t.lastFinalNum+1, "to", finalNum, "txs", total)
} }
t.lastFinalNum = finalNum t.lastFinalNum = finalNum
return credits return credits
} }
func sumCounts(m map[string]int) int {
var sum int
for _, v := range m {
sum += v
}
return sum
}

View file

@ -182,22 +182,19 @@ func TestNotifyReceived(t *testing.T) {
t.Fatalf("expected zero stats before chain events, got %+v", ps) t.Fatalf("expected zero stats before chain events, got %+v", ps)
} }
// Internal state: all tx→deliverer mappings recorded, insertion order // Internal state: all tx→deliverer mappings recorded.
// preserved in the FIFO slice.
tr.mu.Lock() tr.mu.Lock()
defer tr.mu.Unlock() defer tr.mu.Unlock()
if len(tr.txs) != 3 { if tr.txs.Len() != 3 {
t.Fatalf("expected 3 tracked txs, got %d", len(tr.txs)) t.Fatalf("expected 3 tracked txs, got %d", tr.txs.Len())
}
if len(tr.order) != 3 {
t.Fatalf("expected order length 3, got %d", len(tr.order))
} }
for i, h := range hashes { for i, h := range hashes {
if got := tr.txs[h]; got.Deliverer != "peerA" { got, ok := tr.txs.Peek(h)
t.Fatalf("tx %d: expected deliverer=peerA, got %q", i, got.Deliverer) if !ok {
t.Fatalf("tx %d: not tracked", i)
} }
if tr.order[i] != h { if got.Deliverer != "peerA" {
t.Fatalf("order[%d] mismatch", i) t.Fatalf("tx %d: expected deliverer=peerA, got %q", i, got.Deliverer)
} }
} }
} }