Skip to content

Commit 44f896a

Browse files
authored
Merge pull request #432 from ch4r10t33r/pr-logging
Add frequent node logging: attestations, blocks, justified/finalized, peers
2 parents bbf25bd + 3bbbfb3 commit 44f896a

3 files changed

Lines changed: 105 additions & 9 deletions

File tree

src/lean_spec/subspecs/networking/service/service.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,17 +144,17 @@ async def _handle_event(self, event: NetworkEvent) -> None:
144144
)
145145
await self.sync_service.on_gossip_block(block, peer_id)
146146

147-
case GossipAttestationEvent(attestation=attestation):
148-
#
149-
# SyncService will validate signature and update forkchoice.
150-
await self.sync_service.on_gossip_attestation(attestation)
147+
case GossipAttestationEvent(attestation=attestation, peer_id=peer_id):
148+
# SyncService validates signature and updates forkchoice.
149+
# Logs attestation source and validation result.
150+
await self.sync_service.on_gossip_attestation(attestation, peer_id)
151151

152-
case GossipAggregatedAttestationEvent(signed_attestation=att):
152+
case GossipAggregatedAttestationEvent(signed_attestation=att, peer_id=peer_id):
153153
# Route aggregated attestations to sync service.
154154
#
155155
# Aggregates contain multiple validator votes and are used
156156
# to advance justification and finalization.
157-
await self.sync_service.on_gossip_aggregated_attestation(att)
157+
await self.sync_service.on_gossip_aggregated_attestation(att, peer_id)
158158

159159
case PeerStatusEvent(peer_id=peer_id, status=status):
160160
# Route peer status updates to sync service.

src/lean_spec/subspecs/node/node.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from __future__ import annotations
1212

1313
import asyncio
14+
import logging
1415
import signal
1516
import time
1617
from collections.abc import Callable
@@ -42,6 +43,11 @@
4243
from lean_spec.subspecs.validator import ValidatorRegistry, ValidatorService
4344
from lean_spec.types import Bytes32, Uint64
4445

46+
logger = logging.getLogger(__name__)
47+
48+
# Interval in seconds for periodic justified/finalized slot logging.
49+
_JUSTIFIED_FINALIZED_LOG_INTERVAL_SEC: Final = 10.0
50+
4551
_ZERO_TIME: Final = Uint64(0)
4652
"""Default genesis time for database loading when no genesis time is available."""
4753

@@ -424,6 +430,7 @@ async def run(self, *, install_signal_handlers: bool = True) -> None:
424430
tg.create_task(self.api_server.run())
425431
if self.validator_service is not None:
426432
tg.create_task(self.validator_service.run())
433+
tg.create_task(self._log_justified_finalized_periodically())
427434
tg.create_task(self._wait_shutdown())
428435
finally:
429436
if self.database is not None:
@@ -446,6 +453,36 @@ def _install_signal_handlers(self) -> None:
446453
# Cannot add handlers outside main thread.
447454
pass
448455

456+
async def _log_justified_finalized_periodically(self) -> None:
457+
"""
458+
Log latest justified and finalized slot periodically.
459+
460+
Runs every _JUSTIFIED_FINALIZED_LOG_INTERVAL_SEC seconds to aid
461+
monitoring and debugging of consensus progress.
462+
"""
463+
while not self._shutdown.is_set():
464+
await asyncio.sleep(_JUSTIFIED_FINALIZED_LOG_INTERVAL_SEC)
465+
if self._shutdown.is_set():
466+
break
467+
store = self.sync_service.store
468+
peers_connected = sum(
469+
1 for p in self.sync_service.peer_manager.get_all_peers() if p.is_connected()
470+
)
471+
j = store.latest_justified
472+
f = store.latest_finalized
473+
j_root = j.root.hex() if hasattr(j.root, "hex") else str(j.root)
474+
f_root = f.root.hex() if hasattr(f.root, "hex") else str(f.root)
475+
logger.info("=" * 64)
476+
logger.info(
477+
"Peers=%s | Justified slot=%s root=%s | Finalized slot=%s root=%s",
478+
peers_connected,
479+
j.slot,
480+
j_root,
481+
f.slot,
482+
f_root,
483+
)
484+
logger.info("=" * 64)
485+
449486
async def _wait_shutdown(self) -> None:
450487
"""
451488
Wait for shutdown signal then stop services.

src/lean_spec/subspecs/sync/service.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,12 @@ async def on_gossip_block(
420420
)
421421
return
422422

423-
logger.debug("Processing gossip block from %s in state %s", peer_id, self._state.name)
423+
logger.info(
424+
"Block received from peer %s slot=%s (state=%s)",
425+
peer_id,
426+
block.message.block.slot,
427+
self._state.name,
428+
)
424429

425430
if self._head_sync is None:
426431
raise RuntimeError("HeadSync not initialized")
@@ -440,6 +445,14 @@ async def on_gossip_block(
440445
#
441446
# A block may be cached instead of processed if its parent is unknown.
442447
if result.processed:
448+
slot = block.message.block.slot
449+
block_root = hash_tree_root(block.message.block)
450+
logger.info(
451+
"Block processed slot=%s root=%s from peer %s",
452+
slot,
453+
block_root.hex(),
454+
peer_id,
455+
)
443456
self.store = new_store
444457
self._replay_pending_attestations()
445458

@@ -452,6 +465,7 @@ async def on_gossip_block(
452465
async def on_gossip_attestation(
453466
self,
454467
attestation: SignedAttestation,
468+
peer_id: PeerId | None = None,
455469
) -> None:
456470
"""
457471
Handle attestation received via gossip.
@@ -465,6 +479,7 @@ async def on_gossip_attestation(
465479
466480
Args:
467481
attestation: The signed attestation received.
482+
peer_id: Peer that propagated the attestation (None if produced locally).
468483
"""
469484
# Guard: Only process gossip in states that accept it.
470485
#
@@ -473,6 +488,16 @@ async def on_gossip_attestation(
473488
if not self._state.accepts_gossip:
474489
return
475490

491+
slot = attestation.data.slot
492+
validator_id = attestation.validator_id
493+
peer_str = str(peer_id) if peer_id is not None else "local"
494+
logger.info(
495+
"Attestation received from peer %s slot=%s validator=%s",
496+
peer_str,
497+
slot,
498+
validator_id,
499+
)
500+
476501
# Check if we are an aggregator.
477502
#
478503
# A validator acts as an aggregator when it is active (has an ID)
@@ -489,7 +514,20 @@ async def on_gossip_attestation(
489514
signed_attestation=attestation,
490515
is_aggregator=is_aggregator_role,
491516
)
492-
except (AssertionError, KeyError):
517+
logger.info(
518+
"Attestation from peer %s slot=%s validator=%s: validation and signature ok",
519+
peer_str,
520+
slot,
521+
validator_id,
522+
)
523+
except (AssertionError, KeyError) as e:
524+
logger.warning(
525+
"Attestation from peer %s slot=%s validator=%s: validation or signature failed: %s",
526+
peer_str,
527+
slot,
528+
validator_id,
529+
e,
530+
)
493531
# Attestation references a block not yet in our store.
494532
#
495533
# Buffer it for replay after the next block is processed.
@@ -502,6 +540,7 @@ async def on_gossip_attestation(
502540
async def on_gossip_aggregated_attestation(
503541
self,
504542
signed_attestation: SignedAggregatedAttestation,
543+
peer_id: PeerId | None = None,
505544
) -> None:
506545
"""
507546
Handle aggregated attestation received via gossip.
@@ -512,13 +551,33 @@ async def on_gossip_aggregated_attestation(
512551
513552
Args:
514553
signed_attestation: The signed aggregated attestation received.
554+
peer_id: Peer that propagated the attestation (None if produced locally).
515555
"""
516556
if not self._state.accepts_gossip:
517557
return
518558

559+
slot = signed_attestation.data.slot
560+
peer_str = str(peer_id) if peer_id is not None else "local"
561+
logger.info(
562+
"Aggregated attestation received from peer %s slot=%s",
563+
peer_str,
564+
slot,
565+
)
566+
519567
try:
520568
self.store = self.store.on_gossip_aggregated_attestation(signed_attestation)
521-
except (AssertionError, KeyError):
569+
logger.info(
570+
"Aggregated attestation from peer %s slot=%s: validation and signature ok",
571+
peer_str,
572+
slot,
573+
)
574+
except (AssertionError, KeyError) as e:
575+
logger.warning(
576+
"Aggregated attestation from peer %s slot=%s: validation or signature failed: %s",
577+
peer_str,
578+
slot,
579+
e,
580+
)
522581
# Target block not yet processed. Buffer for replay.
523582
self._pending_aggregated_attestations.append(signed_attestation)
524583
if len(self._pending_aggregated_attestations) > MAX_PENDING_ATTESTATIONS:

0 commit comments

Comments
 (0)