Skip to content

Commit df2faf6

Browse files
committed
fix(eth): log live sync status every cycle
The sync status logger only fired while the downloader was inside a bulk historical sync. Once the node is caught up, new blocks are followed through the block fetcher / BFT announcements, so Synchronising() stays false and the warn log appeared only once or never over hours of normal operation instead of every 10 minutes. The logger now emits a status line on every 10-minute cycle with a neutral message (current / highest / behind / peers). While the downloader is actively synchronising it reports the downloader's discovered target and the fast-sync snap block; otherwise it uses the local head merged with a per-peer live high-water mark fed by block announcements (NewBlockMsg / NewBlockHashesMsg). The announced numbers are only trusted after passing the fetcher's plausibility window, exposed as IsPlausibleAnnouncement, so a peer cannot inflate the reported gap.
1 parent 436a229 commit df2faf6

6 files changed

Lines changed: 494 additions & 19 deletions

File tree

eth/fetcher/block_fetcher.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ const (
4545
blockLimit = 64 // Maximum number of unique blocks a peer may have delivered
4646
)
4747

48+
// IsPlausibleAnnouncement reports whether a block announcement at the given
49+
// number is within the fetcher's plausibility window of the current chain
50+
// height. Untrusted announced numbers must be gated on this check before being
51+
// recorded (e.g. as a peer's live tip), so a peer cannot inflate the recorded
52+
// number beyond the window.
53+
func IsPlausibleAnnouncement(number, height uint64) bool {
54+
if number == 0 {
55+
return true
56+
}
57+
if number < height {
58+
return height <= maxUncleDist+number
59+
}
60+
return number <= maxQueueDist+height
61+
}
62+
4863
var (
4964
blockAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/in", nil)
5065
blockAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/announces/out", nil)
@@ -365,12 +380,11 @@ func (f *BlockFetcher) loop() {
365380
break
366381
}
367382
// If we have a valid block number, check that it's potentially useful
368-
if notification.number > 0 {
369-
if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
370-
log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist)
371-
blockAnnounceDropMeter.Mark(1)
372-
break
373-
}
383+
height := f.chainHeight()
384+
if !IsPlausibleAnnouncement(notification.number, height) {
385+
log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "height", height)
386+
blockAnnounceDropMeter.Mark(1)
387+
break
374388
}
375389
// All is well, schedule the announce if block's not yet downloading
376390
if _, ok := f.fetching[notification.hash]; ok {

eth/fetcher/block_fetcher_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,28 @@ func TestDistantPropagationDiscarding(t *testing.T) {
629629
}
630630
}
631631

632+
// Tests that IsPlausibleAnnouncement accepts announcements within the fetcher's
633+
// plausibility window and rejects those too far from the local chain head.
634+
func TestIsPlausibleAnnouncement(t *testing.T) {
635+
height := uint64(1000)
636+
tests := []struct {
637+
number uint64
638+
want bool
639+
}{
640+
{number: 0, want: true}, // Unknown height, always plausible
641+
{number: height - maxUncleDist, want: true}, // Backward window edge
642+
{number: height - maxUncleDist - 1, want: false}, // Too far behind
643+
{number: height + maxQueueDist, want: true}, // Forward window edge
644+
{number: height + maxQueueDist + 1, want: false}, // Too far ahead
645+
{number: ^uint64(0), want: false}, // Far future
646+
}
647+
for i, tt := range tests {
648+
if got := IsPlausibleAnnouncement(tt.number, height); got != tt.want {
649+
t.Fatalf("case %d: number=%d got %v, want %v", i, tt.number, got, tt.want)
650+
}
651+
}
652+
}
653+
632654
// Tests that announcements with numbers much lower or higher than out current
633655
// head get discarded to prevent wasting resources on useless blocks from faulty
634656
// peers.

eth/handler.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,9 +733,14 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
733733
if err := msg.Decode(&announces); err != nil {
734734
return errResp(ErrDecode, "%v: %v", msg, err)
735735
}
736-
// Mark the hashes as present at the remote node
736+
// Mark the hashes as present at the remote node and track the highest
737+
// plausible announced block as the peer's live tip.
738+
height := pm.blockchain.CurrentBlock().Number.Uint64()
737739
for _, block := range announces {
738740
p.MarkBlock(block.Hash)
741+
if fetcher.IsPlausibleAnnouncement(block.Number, height) {
742+
p.SetTipNumber(block.Number)
743+
}
739744
}
740745
// Schedule all the unknown hashes for retrieval
741746
unknown := make(newBlockHashesData, 0, len(announces))
@@ -768,8 +773,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
768773
request.Block.ReceivedAt = msg.ReceivedAt
769774
request.Block.ReceivedFrom = p
770775

771-
// Mark the peer as owning the block and schedule it for import
776+
// Mark the peer as owning the block, track its tip (only when the block
777+
// number is within the fetcher's plausibility window), and schedule it
778+
// for import
772779
p.MarkBlock(request.Block.Hash())
780+
if fetcher.IsPlausibleAnnouncement(request.Block.NumberU64(), pm.blockchain.CurrentBlock().Number.Uint64()) {
781+
p.SetTipNumber(request.Block.NumberU64())
782+
}
773783
pm.blockFetcher.Enqueue(p.id, request.Block)
774784

775785
// Assuming the block is importable by the peer, but possibly not yet done so,

eth/peer.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ type peer struct {
9595
td *big.Int
9696
lock sync.RWMutex
9797

98+
tipNumber uint64 // Highest block number announced by the peer (live network high-water mark)
99+
98100
knownBlocks mapset.Set[common.Hash] // Set of block hashes known to be known by this peer
99101
queuedBlocks chan *propEvent // Queue of blocks to broadcast to the peer
100102
queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer
@@ -343,6 +345,25 @@ func (p *peer) SetHead(hash common.Hash, td *big.Int) {
343345
p.td.Set(td)
344346
}
345347

348+
// TipNumber retrieves the highest block number announced by the peer.
349+
func (p *peer) TipNumber() uint64 {
350+
p.lock.RLock()
351+
defer p.lock.RUnlock()
352+
return p.tipNumber
353+
}
354+
355+
// SetTipNumber records the highest block number announced by the peer. The tip
356+
// only ever moves forward; unlike SetHead (which conservatively tracks the
357+
// parent of an announced block), this reflects the actual tip the peer has
358+
// advertised, so it can serve as a live network high-water mark.
359+
func (p *peer) SetTipNumber(number uint64) {
360+
p.lock.Lock()
361+
defer p.lock.Unlock()
362+
if number > p.tipNumber {
363+
p.tipNumber = number
364+
}
365+
}
366+
346367
// MarkBlock marks a block as known for the peer, ensuring that the block will
347368
// never be propagated to this particular peer.
348369
func (p *peer) MarkBlock(hash common.Hash) {
@@ -1041,6 +1062,22 @@ func (ps *peerSet) BestPeer() *peer {
10411062
return bestPeer
10421063
}
10431064

1065+
// HighestTipNumber returns the highest block number announced by any known
1066+
// peer, providing a live network high-water mark that stays current even when
1067+
// the downloader is idle.
1068+
func (ps *peerSet) HighestTipNumber() uint64 {
1069+
ps.lock.RLock()
1070+
defer ps.lock.RUnlock()
1071+
1072+
var highest uint64
1073+
for _, p := range ps.peers {
1074+
if tip := p.TipNumber(); tip > highest {
1075+
highest = tip
1076+
}
1077+
}
1078+
return highest
1079+
}
1080+
10441081
// Close disconnects all peers.
10451082
// No new peers can be registered after Close has returned.
10461083
func (ps *peerSet) Close() {

eth/sync.go

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -205,24 +205,65 @@ func (pm *ProtocolManager) syncStatusLogger() {
205205
for {
206206
select {
207207
case <-ticker.C:
208-
if pm.downloader.Synchronising() {
209-
progress := pm.downloader.Progress()
210-
log.Warn("Block synchronisation in progress",
211-
"starting", progress.StartingBlock,
212-
"current", progress.CurrentBlock,
213-
"highest", progress.HighestBlock,
214-
"pulledStates", progress.PulledStates,
215-
"knownStates", progress.KnownStates,
216-
"peers", pm.peers.Len(),
217-
)
218-
}
208+
pm.reportSyncStatus()
219209

220210
case <-pm.quitSync:
221211
return
222212
}
223213
}
224214
}
225215

216+
// reportSyncStatus emits a warn-level periodic sync status line, so that the
217+
// current sync state is always visible in the logs every cycle regardless of
218+
// whether the node is catching up or already in sync.
219+
func (pm *ProtocolManager) reportSyncStatus() {
220+
var (
221+
current uint64
222+
highest uint64
223+
)
224+
// While the downloader is actively synchronising, prefer its progress: it
225+
// knows the discovered sync target (peers may not send block announcements
226+
// during a bulk sync) and, in fast sync, reports the snap block as the
227+
// current height.
228+
if pm.downloader.Synchronising() {
229+
progress := pm.downloader.Progress()
230+
current, highest = progress.CurrentBlock, progress.HighestBlock
231+
} else {
232+
current = pm.blockchain.CurrentBlock().Number.Uint64()
233+
}
234+
status := computeSyncStatus(current, highest, pm.peers.HighestTipNumber())
235+
log.Warn("Block synchronisation status",
236+
"current", status.current,
237+
"highest", status.highest,
238+
"behind", status.behind,
239+
"peers", pm.peers.Len(),
240+
)
241+
}
242+
243+
// syncStatus holds the values reported by the periodic sync status heartbeat.
244+
type syncStatus struct {
245+
current uint64 // Local head, or the fast-sync snap block while bulk syncing
246+
highest uint64 // Highest known network block (downloader target + announced tips)
247+
behind uint64 // Number of blocks behind the highest known network block
248+
}
249+
250+
// computeSyncStatus derives the heartbeat values, merging the downloader's
251+
// discovered target with the live network high-water mark from announced tips
252+
// so the reported highest stays current even outside downloader bulk syncs.
253+
func computeSyncStatus(current, highest, announcedTip uint64) syncStatus {
254+
if announcedTip > highest {
255+
highest = announcedTip
256+
}
257+
if current > highest {
258+
highest = current
259+
}
260+
behind := uint64(0)
261+
if highest > current {
262+
behind = highest - current
263+
}
264+
return syncStatus{current: current, highest: highest, behind: behind}
265+
}
266+
226267
// synchronise tries to sync up our local block chain with a remote peer.
227268
func (pm *ProtocolManager) synchronise(peer *peer) {
228269
// Short circuit if no peers are available

0 commit comments

Comments
 (0)