mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
txtracker tests (7 tests): - NotifyReceived: stats empty before chain events - InclusionEMA: EMA increases on inclusion, decays on empty blocks - Finalization: Finalized counter credited after finalization - MultiplePeers: each peer credited for own txs only - FirstDelivererWins: duplicate delivery ignored - NoFinalizationCredit: no credit without finalization - EMADecay: EMA approaches zero after 30 empty blocks dropper tests (6 tests): - FilterProtectedNoStats: nil stats → all droppable - FilterProtectedEmptyStats: empty map → all droppable - FilterProtectedTopPeer: top-scored peers removed from droppable - FilterProtectedZeroScore: zero scores → no protection - FilterProtectedOverlap: peer top in both categories → counted once - FilterProtectedAllProtected: all droppable protected → empty list Also fix: create peer entries during EMA update for peers with inclusions in the current block (previously only created during finalization, so EMA was not tracked before first finalization).
262 lines
6.2 KiB
Go
262 lines
6.2 KiB
Go
package txtracker
|
|
|
|
import (
|
|
"math/big"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/core"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/event"
|
|
"github.com/ethereum/go-ethereum/trie"
|
|
)
|
|
|
|
// mockChain implements the Chain interface for testing.
|
|
type mockChain struct {
|
|
mu sync.Mutex
|
|
headFeed event.Feed
|
|
blocks map[uint64]*types.Block
|
|
finalNum uint64
|
|
}
|
|
|
|
func newMockChain() *mockChain {
|
|
return &mockChain{blocks: make(map[uint64]*types.Block)}
|
|
}
|
|
|
|
func (c *mockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
|
return c.headFeed.Subscribe(ch)
|
|
}
|
|
|
|
func (c *mockChain) GetBlockByNumber(number uint64) *types.Block {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.blocks[number]
|
|
}
|
|
|
|
func (c *mockChain) CurrentFinalBlock() *types.Header {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.finalNum == 0 {
|
|
return nil
|
|
}
|
|
return &types.Header{Number: new(big.Int).SetUint64(c.finalNum)}
|
|
}
|
|
|
|
func (c *mockChain) addBlock(num uint64, txs []*types.Transaction) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
header := &types.Header{Number: new(big.Int).SetUint64(num)}
|
|
c.blocks[num] = types.NewBlock(header, &types.Body{Transactions: txs}, nil, trie.NewListHasher())
|
|
}
|
|
|
|
func (c *mockChain) setFinalBlock(num uint64) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.finalNum = num
|
|
}
|
|
|
|
func (c *mockChain) sendHead(num uint64) {
|
|
c.headFeed.Send(core.ChainHeadEvent{
|
|
Header: &types.Header{Number: new(big.Int).SetUint64(num)},
|
|
})
|
|
}
|
|
|
|
func makeTx(nonce uint64) *types.Transaction {
|
|
return types.NewTx(&types.LegacyTx{Nonce: nonce, GasPrice: big.NewInt(1), Gas: 21000})
|
|
}
|
|
|
|
// waitForHead gives the tracker time to process a chain head event.
|
|
func waitForHead() {
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
|
|
func TestNotifyReceived(t *testing.T) {
|
|
tr := New()
|
|
chain := newMockChain()
|
|
tr.Start(chain)
|
|
defer tr.Stop()
|
|
|
|
txs := []*types.Transaction{makeTx(1), makeTx(2), makeTx(3)}
|
|
tr.NotifyReceived("peerA", txs)
|
|
|
|
// No chain events yet — stats should be empty.
|
|
stats := tr.GetAllPeerStats()
|
|
if len(stats) != 0 {
|
|
t.Fatalf("expected empty stats before any chain events, got %d peers", len(stats))
|
|
}
|
|
}
|
|
|
|
func TestInclusionEMA(t *testing.T) {
|
|
tr := New()
|
|
chain := newMockChain()
|
|
tr.Start(chain)
|
|
defer tr.Stop()
|
|
|
|
tx := makeTx(1)
|
|
tr.NotifyReceived("peerA", []*types.Transaction{tx})
|
|
|
|
// Block 1 includes peerA's tx.
|
|
chain.addBlock(1, []*types.Transaction{tx})
|
|
chain.sendHead(1)
|
|
waitForHead()
|
|
|
|
stats := tr.GetAllPeerStats()
|
|
if stats["peerA"].RecentIncluded <= 0 {
|
|
t.Fatalf("expected RecentIncluded > 0 after inclusion, got %f", stats["peerA"].RecentIncluded)
|
|
}
|
|
ema1 := stats["peerA"].RecentIncluded
|
|
|
|
// Block 2 has no txs from peerA — EMA should decay.
|
|
chain.addBlock(2, nil)
|
|
chain.sendHead(2)
|
|
waitForHead()
|
|
|
|
stats = tr.GetAllPeerStats()
|
|
if stats["peerA"].RecentIncluded >= ema1 {
|
|
t.Fatalf("expected EMA to decay, got %f >= %f", stats["peerA"].RecentIncluded, ema1)
|
|
}
|
|
}
|
|
|
|
func TestFinalization(t *testing.T) {
|
|
tr := New()
|
|
chain := newMockChain()
|
|
tr.Start(chain)
|
|
defer tr.Stop()
|
|
|
|
tx := makeTx(1)
|
|
tr.NotifyReceived("peerA", []*types.Transaction{tx})
|
|
|
|
// Include in block 1.
|
|
chain.addBlock(1, []*types.Transaction{tx})
|
|
chain.sendHead(1)
|
|
waitForHead()
|
|
|
|
// Not finalized yet.
|
|
stats := tr.GetAllPeerStats()
|
|
if stats["peerA"].Finalized != 0 {
|
|
t.Fatalf("expected Finalized=0 before finalization, got %d", stats["peerA"].Finalized)
|
|
}
|
|
|
|
// Finalize block 1, then send head 2 to trigger checkFinalization.
|
|
chain.setFinalBlock(1)
|
|
chain.addBlock(2, nil)
|
|
chain.sendHead(2)
|
|
waitForHead()
|
|
|
|
stats = tr.GetAllPeerStats()
|
|
if stats["peerA"].Finalized != 1 {
|
|
t.Fatalf("expected Finalized=1 after finalization, got %d", stats["peerA"].Finalized)
|
|
}
|
|
}
|
|
|
|
func TestMultiplePeers(t *testing.T) {
|
|
tr := New()
|
|
chain := newMockChain()
|
|
tr.Start(chain)
|
|
defer tr.Stop()
|
|
|
|
tx1 := makeTx(1)
|
|
tx2 := makeTx(2)
|
|
tr.NotifyReceived("peerA", []*types.Transaction{tx1})
|
|
tr.NotifyReceived("peerB", []*types.Transaction{tx2})
|
|
|
|
// Both included in block 1.
|
|
chain.addBlock(1, []*types.Transaction{tx1, tx2})
|
|
chain.sendHead(1)
|
|
waitForHead()
|
|
|
|
// Finalize.
|
|
chain.setFinalBlock(1)
|
|
chain.addBlock(2, nil)
|
|
chain.sendHead(2)
|
|
waitForHead()
|
|
|
|
stats := tr.GetAllPeerStats()
|
|
if stats["peerA"].Finalized != 1 {
|
|
t.Fatalf("peerA: expected Finalized=1, got %d", stats["peerA"].Finalized)
|
|
}
|
|
if stats["peerB"].Finalized != 1 {
|
|
t.Fatalf("peerB: expected Finalized=1, got %d", stats["peerB"].Finalized)
|
|
}
|
|
}
|
|
|
|
func TestFirstDelivererWins(t *testing.T) {
|
|
tr := New()
|
|
chain := newMockChain()
|
|
tr.Start(chain)
|
|
defer tr.Stop()
|
|
|
|
tx := makeTx(1)
|
|
tr.NotifyReceived("peerA", []*types.Transaction{tx})
|
|
tr.NotifyReceived("peerB", []*types.Transaction{tx}) // duplicate, should be ignored
|
|
|
|
chain.addBlock(1, []*types.Transaction{tx})
|
|
chain.sendHead(1)
|
|
waitForHead()
|
|
|
|
chain.setFinalBlock(1)
|
|
chain.addBlock(2, nil)
|
|
chain.sendHead(2)
|
|
waitForHead()
|
|
|
|
stats := tr.GetAllPeerStats()
|
|
if stats["peerA"].Finalized != 1 {
|
|
t.Fatalf("peerA should be credited, got Finalized=%d", stats["peerA"].Finalized)
|
|
}
|
|
if stats["peerB"].Finalized != 0 {
|
|
t.Fatalf("peerB should NOT be credited, got Finalized=%d", stats["peerB"].Finalized)
|
|
}
|
|
}
|
|
|
|
func TestNoFinalizationCredit(t *testing.T) {
|
|
tr := New()
|
|
chain := newMockChain()
|
|
tr.Start(chain)
|
|
defer tr.Stop()
|
|
|
|
tx := makeTx(1)
|
|
tr.NotifyReceived("peerA", []*types.Transaction{tx})
|
|
|
|
// Include but don't finalize.
|
|
chain.addBlock(1, []*types.Transaction{tx})
|
|
chain.sendHead(1)
|
|
waitForHead()
|
|
|
|
// Send more heads without finalization.
|
|
chain.addBlock(2, nil)
|
|
chain.sendHead(2)
|
|
waitForHead()
|
|
|
|
stats := tr.GetAllPeerStats()
|
|
if stats["peerA"].Finalized != 0 {
|
|
t.Fatalf("expected Finalized=0 without finalization, got %d", stats["peerA"].Finalized)
|
|
}
|
|
}
|
|
|
|
func TestEMADecay(t *testing.T) {
|
|
tr := New()
|
|
chain := newMockChain()
|
|
tr.Start(chain)
|
|
defer tr.Stop()
|
|
|
|
tx := makeTx(1)
|
|
tr.NotifyReceived("peerA", []*types.Transaction{tx})
|
|
|
|
// Include in block 1.
|
|
chain.addBlock(1, []*types.Transaction{tx})
|
|
chain.sendHead(1)
|
|
waitForHead()
|
|
|
|
// Send 30 empty blocks — EMA should decay close to zero.
|
|
for i := uint64(2); i <= 31; i++ {
|
|
chain.addBlock(i, nil)
|
|
chain.sendHead(i)
|
|
waitForHead()
|
|
}
|
|
|
|
stats := tr.GetAllPeerStats()
|
|
if stats["peerA"].RecentIncluded > 0.02 {
|
|
t.Fatalf("expected RecentIncluded near zero after 30 empty blocks, got %f", stats["peerA"].RecentIncluded)
|
|
}
|
|
}
|