Add timing logs for block processing and XMSS signature verification#225
Add timing logs for block processing and XMSS signature verification#225pablodeymo wants to merge 2 commits intomainfrom
Conversation
…rocessing latency Instruments verify_signatures() with std::time::Instant to log per-phase breakdown: aggregated attestation signatures, proposer signature, and total elapsed time in milliseconds. This helps diagnose issue #197 where lean_fork_choice_block_processing_time_seconds shows constant ~1s on devnet.
🤖 Kimi Code ReviewReview of PR #225Issues Found
Minor Issues
Recommendations// Instead of manual timing, consider:
#[cfg(feature = "detailed-timing")]
{
let _span = tracing::span!(
tracing::Level::DEBUG,
"signature_verification",
slot = block.slot,
attestation_count = attestations.len()
);
let _enter = _span.enter();
}This PR introduces non-trivial overhead in a critical consensus function. The timing data might be useful for debugging, but should not be enabled by default in production builds. Automated review by Kimi (Moonshot AI) · custom prompt |
🤖 Codex Code Review
No correctness bugs or consensus-logic regressions are apparent in this PR (fork choice, attestation validity, justification/finalization, and state transition behavior are unchanged by this diff). I’d keep the change if logging is moved to Automated review by OpenAI Codex · custom prompt |
🤖 Claude Code ReviewHere is my review of PR 225: PR 225: Add timing logs to XMSS signature verificationOverall: The change is minimal, non-behavioral, and achieves its stated diagnostic goal. The implementation is straightforward and correct. A few minor points worth addressing: Correctness
PerformanceThe Logging style (project convention)Per the codebase's logging conventions, the field order should follow temporal → identity → identifiers → context → metadata. The current order ( One minor convention note: the project uses shorthand Diagnostic coverage gapThe timing currently covers:
But it does not time the intermediate steps between SummaryThe PR is safe to merge as-is. The main actionable suggestion is to drop the redundant Automated review by Claude (Anthropic) · custom prompt |
Greptile SummaryThis PR adds structured timing instrumentation to Key observations:
Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/store.rs | Adds structured tracing::info! timing instrumentation to verify_signatures() with Instant timers for the aggregated-attestation loop, proposer signature check, and total wall-clock duration. The only issue is that proposer_start is placed after ValidatorSignature::from_bytes and get_pubkey(), leaving those deserialisation calls unaccounted in the logged proposer_sig_ms field while still contributing to total_ms. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[verify_signatures called] --> B[total_start = Instant::now]
B --> C{attestations.len == sigs.len?}
C -- No --> ERR1[return Err AttestationSignatureMismatch]
C -- Yes --> D[aggregated_start = Instant::now]
D --> E[for each attestation + proof]
E --> F{participants match?}
F -- No --> ERR2[return Err ParticipantsMismatch]
F -- Yes --> G[verify_aggregated_signature via leanVM]
G --> H{Ok?}
H -- No --> ERR3[return Err AggregateVerificationFailed]
H -- Yes --> E
E -- loop done --> I[aggregated_elapsed = aggregated_start.elapsed]
I --> J[ValidatorSignature::from_bytes\nproposer.get_pubkey\ntree_hash_root\n⚠️ NOT timed in proposer_sig_ms]
J --> K[proposer_start = Instant::now]
K --> L[proposer_signature.is_valid]
L --> M{valid?}
M -- No --> ERR4[return Err ProposerSignatureVerificationFailed]
M -- Yes --> N[proposer_elapsed = proposer_start.elapsed]
N --> O[total_elapsed = total_start.elapsed]
O --> P[info! slot aggregated_sigs_ms proposer_sig_ms total_ms]
P --> Q[return Ok]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: crates/blockchain/src/store.rs
Line: 1237
Comment:
**Proposer timer starts after deserialization, creating an unaccounted gap**
`proposer_start` is created after the proposer signature bytes have been deserialized and the public key has been decoded. Both of those calls process cryptographic material and their cost is included in `total_ms` but not in `proposer_sig_ms`. This means `total_ms - (aggregated_sigs_ms + proposer_sig_ms)` will be non-zero and could be confusing when reading the logs to diagnose the timing bottleneck.
Since the goal of this PR is to account for all time inside `verify_signatures()`, consider moving `proposer_start` to just before the `ValidatorSignature::from_bytes` call on line 1218. That way the logged fields are fully self-consistent: `aggregated_sigs_ms + proposer_sig_ms` would account for nearly all of `total_ms`.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: cd25c30
| .expect("slot exceeds u32"); | ||
| let message = proposer_attestation.data.tree_hash_root(); | ||
|
|
||
| let proposer_start = std::time::Instant::now(); |
There was a problem hiding this comment.
Proposer timer starts after deserialization, creating an unaccounted gap
proposer_start is created after the proposer signature bytes have been deserialized and the public key has been decoded. Both of those calls process cryptographic material and their cost is included in total_ms but not in proposer_sig_ms. This means total_ms - (aggregated_sigs_ms + proposer_sig_ms) will be non-zero and could be confusing when reading the logs to diagnose the timing bottleneck.
Since the goal of this PR is to account for all time inside verify_signatures(), consider moving proposer_start to just before the ValidatorSignature::from_bytes call on line 1218. That way the logged fields are fully self-consistent: aggregated_sigs_ms + proposer_sig_ms would account for nearly all of total_ms.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/blockchain/src/store.rs
Line: 1237
Comment:
**Proposer timer starts after deserialization, creating an unaccounted gap**
`proposer_start` is created after the proposer signature bytes have been deserialized and the public key has been decoded. Both of those calls process cryptographic material and their cost is included in `total_ms` but not in `proposer_sig_ms`. This means `total_ms - (aggregated_sigs_ms + proposer_sig_ms)` will be non-zero and could be confusing when reading the logs to diagnose the timing bottleneck.
Since the goal of this PR is to account for all time inside `verify_signatures()`, consider moving `proposer_start` to just before the `ValidatorSignature::from_bytes` call on line 1218. That way the logged fields are fully self-consistent: `aggregated_sigs_ms + proposer_sig_ms` would account for nearly all of `total_ms`.
How can I resolve this? If you propose a fix, please make it concise.
MegaRedHand
left a comment
There was a problem hiding this comment.
Looks good. Let's add for block processing too
Adds sig_verification_ms, state_transition_ms, and block_total_ms fields to the existing "Processed new block" log line. This allows comparing the overall block processing time against the signature verification breakdown from the previous commit.
| sig_verification_ms, | ||
| state_transition_ms, | ||
| block_total_ms, |
There was a problem hiding this comment.
We should use the Debug impl of Duration for prettier logs
Summary
on_block_core()with per-phase timing breakdown of full block processingverify_signatures()with per-phase timing breakdown of XMSS signature verificationlean_fork_choice_block_processing_time_secondsmetric #197 wherelean_fork_choice_block_processing_time_secondsshows constant ~1s on devnet dashboardsContext
Issue #197 reports that block processing time on the devnet Grafana dashboard is always approximately 1 second. Investigation revealed that:
lean_fork_choice_block_processing_time_secondshistogram has a bucket gap between0.1sand1.0s, which could cause Grafana'shistogram_quantileto interpolate misleadinglyThis PR adds structured timing logs at two levels to pinpoint exactly where time is spent, without changing any behavior.
Example output
Each block produces two log lines — first the signature detail, then the block summary:
In this example you can see:
sig_verification_ms=572out ofblock_total_ms=590aggregated_sigs_ms=450vsproposer_sig_ms=120state_transition_ms=15590 - 572 - 15 = 3msfor storage, fork choice, etc.Logged fields
Block processing breakdown (
on_block_core)sig_verification_msverify_signatures()(0 when verification is skipped)state_transition_msstate_transition()(process_slots + process_block)block_total_mson_block_core()functionSignature verification breakdown (
verify_signatures)attestation_countaggregated_sigs_msproposer_sig_mstotal_msverify_signatures()functionHow to use
Test plan
cargo check -p ethlambda-blockchainpassescargo fmt --all -- --checkpassescargo clippy -p ethlambda-blockchain -- -D warningspassesCloses #197 (diagnostic step — may need follow-up based on findings)