eth/txtracker: rename txEntry to TxInfo, align field names with richer model

No functional change. Prepares the minimal model to mirror the field
naming of the richer txtracker variants on adjacent branches so that
upcoming cherry-picks (lastFinalNum seed, IncludedDeliverer freeze,
collectFinalization pivot to iterate t.txs) can apply with smaller
deltas.

Changes:
  - txEntry         -> TxInfo (exported, pointer in t.txs map)
  - txEntry.peer    -> TxInfo.Deliverer
  - txEntry.addedAt -> TxInfo.AddedAt
  - add TxStatus enum (StatusUnknown / StatusIncluded / StatusFinalized);
    not yet assigned on any code path here
  - add TxInfo.BlockNum and TxInfo.BlockHash; not yet assigned
This commit is contained in:
Csaba Kiraly 2026-06-22 21:04:37 +02:00
parent 905bc9d102
commit 1f77413959
2 changed files with 29 additions and 20 deletions

View file

@ -68,20 +68,29 @@ type peerStats struct {
recentIncluded float64 recentIncluded float64
} }
// txEntry tracks the deliverer of a transaction and when delivery was // TxInfo records the per-transaction state the tracker maintains.
// recorded. addedAt is unix seconds; it is compared against block.Time() //
// to suppress credit for txs delivered at or after the slot of their // Deliverer is the peer that first handed us this tx via NotifyAccepted.
// inclusion block (e.g., re-broadcasts of just-mined txs). // AddedAt is the unix-seconds wall-clock at that moment; it is compared
type txEntry struct { // against block.Time() to suppress credit for txs delivered at or after
peer string // the slot of their inclusion block (re-broadcasts of just-mined txs).
addedAt uint64 //
// BlockNum / BlockHash are populated when the tracker first sees the tx
// in a head block (BlockNum == 0 means not yet seen on chain). BlockHash
// is re-checked against canonical-at-height at finalization time so
// reorgs do not yield credit.
type TxInfo struct {
Deliverer string
AddedAt uint64
BlockNum uint64
BlockHash common.Hash
} }
// Tracker records which peer delivered each transaction and credits peers // Tracker records which peer delivered each transaction and credits peers
// 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]txEntry // hash → deliverer + arrival time txs map[common.Hash]*TxInfo // hash → per-tx state
peers map[string]*peerStats peers map[string]*peerStats
order []common.Hash // insertion order for LRU eviction order []common.Hash // insertion order for LRU eviction
@ -100,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]txEntry), txs: make(map[common.Hash]*TxInfo),
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),
@ -153,7 +162,7 @@ func (t *Tracker) NotifyAccepted(peer string, hashes []common.Hash) {
if _, ok := t.txs[hash]; ok { if _, ok := t.txs[hash]; ok {
continue // already tracked, keep first deliverer continue // already tracked, keep first deliverer
} }
t.txs[hash] = txEntry{peer: peer, addedAt: addedAt} t.txs[hash] = &TxInfo{Deliverer: peer, AddedAt: addedAt}
t.order = append(t.order, hash) t.order = append(t.order, hash)
} }
// Ensure the delivering peer has a stats entry. // Ensure the delivering peer has a stats entry.
@ -225,11 +234,11 @@ func (t *Tracker) handleChainHead(ev core.ChainHeadEvent) {
blockTime := block.Time() blockTime := block.Time()
blockIncl := make(map[string]int) blockIncl := make(map[string]int)
for _, tx := range block.Transactions() { for _, tx := range block.Transactions() {
entry, ok := t.txs[tx.Hash()] ti, ok := t.txs[tx.Hash()]
if !ok || entry.addedAt >= blockTime { if !ok || ti.AddedAt >= blockTime {
continue continue
} }
blockIncl[entry.peer]++ blockIncl[ti.Deliverer]++
} }
// Accumulate per-peer finalization credits over the newly-finalized // Accumulate per-peer finalization credits over the newly-finalized
// range (possibly zero blocks). Only counts peers still tracked. // range (possibly zero blocks). Only counts peers still tracked.
@ -266,14 +275,14 @@ func (t *Tracker) collectFinalizationCredits() map[string]int {
} }
blockTime := block.Time() blockTime := block.Time()
for _, tx := range block.Transactions() { for _, tx := range block.Transactions() {
entry, ok := t.txs[tx.Hash()] ti, ok := t.txs[tx.Hash()]
if !ok || entry.addedAt >= blockTime { if !ok || ti.AddedAt >= blockTime {
continue // unknown, or post-slot re-broadcast continue // unknown, or post-slot re-broadcast
} }
if _, ok := t.peers[entry.peer]; !ok { if _, ok := t.peers[ti.Deliverer]; !ok {
continue // peer disconnected, skip credit continue // peer disconnected, skip credit
} }
credits[entry.peer]++ credits[ti.Deliverer]++
} }
} }
if total := sumCounts(credits); total > 0 { if total := sumCounts(credits); total > 0 {

View file

@ -197,8 +197,8 @@ func TestNotifyReceived(t *testing.T) {
t.Fatalf("expected order length 3, got %d", len(tr.order)) 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.peer != "peerA" { if got := tr.txs[h]; got.Deliverer != "peerA" {
t.Fatalf("tx %d: expected deliverer=peerA, got %q", i, got.peer) t.Fatalf("tx %d: expected deliverer=peerA, got %q", i, got.Deliverer)
} }
if tr.order[i] != h { if tr.order[i] != h {
t.Fatalf("order[%d] mismatch", i) t.Fatalf("order[%d] mismatch", i)
@ -468,7 +468,7 @@ func TestRecentFinalizedDecays(t *testing.T) {
// post-block-propagation re-broadcast attribution attack: a peer that // post-block-propagation re-broadcast attribution attack: a peer that
// learns a tx from the just-mined block and re-broadcasts it to our pool // learns a tx from the just-mined block and re-broadcasts it to our pool
// should not gain credit when that block is processed. The finalization // should not gain credit when that block is processed. The finalization
// path applies the same gate (entry.addedAt >= blockTime) and is exercised // path applies the same gate (ti.AddedAt >= blockTime) and is exercised
// by the existing TestFinalization with the new clock semantics. // by the existing TestFinalization with the new clock semantics.
func TestPreSlotGate(t *testing.T) { func TestPreSlotGate(t *testing.T) {
tr := New() tr := New()