Skip to content

feat(drive-abci): debug-only per-block phase timing - #4573

Merged
QuantumExplorer merged 6 commits into
v4.2-devfrom
bench/block-phase-timing
Sep 16, 2026
Merged

QuantumExplorer merged 6 commits into
v4.2-devfrom
bench/block-phase-timing

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Sep 1, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

There was no way to see where a block's time goes inside drive-abci. ProcessProposal logged one elapsed_time_ms — truncated to whole milliseconds — and FinalizeBlock logged nothing at all, so more than half the per-block cost was unattributed.

That gap hid two costs that scale with chain history and together accounted for most of a mainnet sync:

Neither is visible without per-phase numbers. Both were found with this.

What was done?

In debug builds, a PhaseTimer times the successive phases of one function during block execution and merges them into process-wide totals on drop. Each timer has a scope (the function it lives in) and each phase is named after the call it times, so a report term like run_block_proposal.process_raw_state_transitions greps straight to the code. perf::end_block reports the means every DRIVE_BLOCK_PERF_EVERY blocks (default 500) as a single log line.

Two design points worth noting:

  • Compiled out of standard release builds. The perf module and every timing call site are guarded by #[cfg(debug_assertions)]. Release builds contain no phase timers, environment reads, totals mutex, or report logging from this feature, even when DRIVE_BLOCK_PERF=1 is set. In builds with debug assertions enabled, that environment switch still defaults to off and is read once through OnceLock<bool>; when off, Laps::new allocates nothing and every lap returns immediately.
  • Accumulated, not logged per block. An earlier version emitted a line per block and the JSON formatting landed inside the spans being measured, inflating exactly the phases under investigation. Means are reported periodically instead.

The mean is over blocks rather than over samples, so a phase that only runs on some blocks shows its share of the per-block cost rather than its cost when it fires. Sample counts are reported alongside, which is how the fire rate of a phase becomes visible. Phases whose work is conditional (upgrade_protocol_version_on_epoch_change, verify_chain_lock, update_core_info, update_broadcasted_withdrawal_statuses, verify_commit_signature, append_signatures_and_broadcast_withdrawal_transactions, create_grovedb_checkpoint) record a sample only on the blocks the work ran, using end_phase_if. Only real work is timed: struct construction, field assignments and plain comparisons between two calls are not given a phase of their own.

finalize_block.finalize_block_proposal in the finalize handler wraps the whole of finalize_block_proposal, so it is the sum of the finalize_block_proposal.* phases; add up phases from one level only.

Phases covered: the block-proposal path (epoch info, drive cache preparation, platform state clone, protocol upgrade, chain lock, core info, proposed app version, withdrawals, DAO events, state transitions, address balances, shielded anchors, fees, credit history, root hash, validator set) and the finalize path (block id hash, vote extensions, commit signature, withdrawal broadcast, drive cache, state cache, commit, data-contract cache promotion, prefix-drop flush, checkpoint).

Example output:

block perf  height=195000 blocks=500
  run_block_proposal.update_core_info=1220/331 finalize_block_proposal.verify_commit_signature=567/500
  run_block_proposal.verify_chain_lock=456/331 finalize_block.commit_transaction=354/500
  run_block_proposal.update_broadcasted_withdrawal_statuses=335/331 run_block_proposal.run_dao_platform_events=187/500
  run_block_proposal.clone_platform_state=154/500 ...

Each term is scope.phase=mean_µs_per_block/blocks_it_ran_on. update_core_info, verify_chain_lock and update_broadcasted_withdrawal_statuses only run on blocks that advance the core height, which is why their sample counts are below 500; phases that run on every block show /500.

How Has This Been Tested?

Used throughout a full mainnet replay, genesis to 424,981, and for every A/B measurement behind #4569, #4570, #4571 and #4572.

cargo test -p drive-abci --lib — 2,770 passed. Unit tests in perf.rs cover the accumulation (first-seen order, scopes kept apart), mean-over-blocks, interval reset, the skipped-phase boundary and the empty report.

Release-exclusion validation:

  • cargo build -p drive-abci --release --locked passed.
  • cargo test -p drive-abci --lib --locked -- perf:: passed: 5 tests.
  • The release executable has no DRIVE_BLOCK_PERF strings, phase/report markers, or drive_abci::perf symbols; the debug test executable retains the instrumentation as a positive control.
  • A compile probe referencing drive_abci::perf::Laps against the release library fails because the module is configured out.
  • cargo clippy -p drive-abci --lib --locked -- -D warnings, formatting, and git diff --check passed.

Breaking Changes

None for standard release builds: all phase-timing instrumentation is excluded at compile time. Debug builds require DRIVE_BLOCK_PERF=1 to enable it.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Times each phase of ProcessProposal and FinalizeBlock and reports the means every DRIVE_BLOCK_PERF_EVERY blocks (default 500). Off unless DRIVE_BLOCK_PERF=1, and accumulated in memory rather than logged per block, so the measurement does not pay for a log line inside the spans it measures.

This is what located the two per-block costs that scale with chain history: an unbounded withdrawal-document query and GroveDB checkpoint creation during replay.
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 1, 2026
@coderabbitai

coderabbitai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 10 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 88ab7732-46cb-4731-9f3e-5fd92f4f9b8c

📥 Commits

Reviewing files that changed from the base of the PR and between 8535291 and e39d1a5.

📒 Files selected for processing (6)
  • packages/rs-drive-abci/src/abci/handler/finalize_block.rs
  • packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs
  • packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs
  • packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs
  • packages/rs-drive-abci/src/lib.rs
  • packages/rs-drive-abci/src/perf.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Sep 1, 2026 •

Copy link
Copy Markdown
Collaborator

⚡ Priority review — 9th in line, starts as soon as a slot frees (commit e39d1a5)
Estimated review time once started: ~5 min (two-phase automated review; median of recent runs).

@codecov

codecov Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.08696% with 66 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.23%. Comparing base (8535291) to head (e39d1a5).
⚠️ Report is 1 commits behind head on v4.2-dev.

Files with missing lines Patch % Lines
packages/rs-drive-abci/src/perf.rs 83.33% 25 Missing ⚠️
.../src/execution/engine/run_block_proposal/v0/mod.rs 70.66% 22 Missing ⚠️
...execution/engine/finalize_block_proposal/v0/mod.rs 62.50% 9 Missing ⚠️
...s/rs-drive-abci/src/abci/handler/finalize_block.rs 66.66% 6 Missing ⚠️
...bci/src/execution/engine/run_block_proposal/mod.rs 55.55% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4573      +/-   ##
============================================
+ Coverage     78.79%   82.23%   +3.44%     
============================================
  Files          2845     2845              
  Lines        406819   402104    -4715     
============================================
+ Hits         320549   330680   +10131     
+ Misses        86270    71424   -14846     
Components Coverage Δ
dpp 80.17% <ø> (+3.41%) ⬆️
drive 83.14% <ø> (+3.58%) ⬆️
drive-abci 84.09% <76.08%> (+3.57%) ⬆️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 88.51% <ø> (+1.90%) ⬆️
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 34.26% <ø> (+3.39%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — GLM Flash + Sol

The opt-in timing instrumentation is consensus-inert and appropriately keeps logging outside measured spans, but several accounting details reduce the trustworthiness of its output: conditional phases report misleading sample counts, and protocol-change work remains unattributed. The aggregation lifecycle also lacks focused tests, and best-effort telemetry should not be able to panic block processing after mutex poisoning.

Source: reviewer 1: glm-5.3-flash (agent: phase1-reviewer, role: general); reviewer 2: glm-5.3-flash (agent: phase1-reviewer, role: security-auditor); reviewer 3: glm-5.3-flash (agent: phase1-reviewer, role: rust-quality); reviewer 4: gpt-5.6-sol (agent: phase2-reviewer, role: general); reviewer 5: gpt-5.6-sol (agent: phase2-reviewer, role: security-auditor); reviewer 6: gpt-5.6-sol (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-5.6-sol (agent: sol-verifier, role: final-verifier)

Review provenance

  • Phase 1 reviewers (GLM Flash): glm-5.3-flash — general (completed); agent phase1-reviewer, glm-5.3-flash — security-auditor (completed); agent phase1-reviewer, glm-5.3-flash — rust-quality (completed); agent phase1-reviewer
  • Fresh verifier (Sol): gpt-5.6-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers (Sol): gpt-5.6-sol — general (completed); agent phase2-reviewer, gpt-5.6-sol — security-auditor (completed); agent phase2-reviewer, gpt-5.6-sol — rust-quality (completed); agent phase2-reviewer

🟡 4 suggestion(s)

1 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-drive-abci/src/abci/handler/finalize_block.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/abci/handler/finalize_block.rs:108-113: Conditional phases report one sample even when skipped
  `fb_checkpoint` is sampled on every finalized block even though checkpoint creation only runs when `checkpoint_needed` is true. A window with one checkpoint and 499 ordinary blocks therefore reports `/500`, not `/1`, so the sample count cannot provide the advertised fire rate. The same issue affects `chainlock`, `wd_status`, and `fbp_wd_broadcast`, whose lap calls also sit outside their conditional branches. Add a timer operation that always advances the boundary but records a sample only when the corresponding work executed, and use it consistently for conditional phases.

In `packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs:86-149: Protocol-change migrations are omitted from the phase totals
  After recording `state_clone`, this function resolves the block platform version and may execute `perform_events_on_first_block_of_protocol_change`, then enters the versioned implementation without another outer lap. `Laps::drop` only merges recorded entries and does not record the elapsed tail, while the inner `Laps` starts after the migration. Protocol-activation work can therefore be substantial yet entirely absent from a replay report. Record this segment after the protocol-change event, preferably only when the migration executes so its sample count also reflects the actual fire rate.

In `packages/rs-drive-abci/src/perf.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/perf.rs:26-160: No unit tests cover the new aggregation and reporting logic
  The module has no focused coverage for `Totals::add`, first-seen phase ordering, sum/sample accumulation, mean-over-block arithmetic, or clearing both phases and the block counter at an interval boundary. These calculations directly determine the performance measurements and can regress while still producing plausible output. The process-global `OnceLock` configuration makes environment-based tests order-dependent, but `Totals` can be tested directly and report formatting/reset can be extracted into a pure helper for deterministic unit tests.
- [SUGGESTION] packages/rs-drive-abci/src/perf.rs:96-127: Poisoned perf mutex can abort block processing
  All three mutex acquisitions call `expect("block perf totals poisoned")`, including the acquisition in `Drop for Laps`. If the mutex is ever poisoned, this diagnostics-only feature will panic during block processing; if the drop occurs during unwinding, the second panic aborts the process. The `expect` message also does not document an invariant proving poisoning impossible. Since these counters are best-effort telemetry and the inner value remains usable, recover consistently with `PoisonError::into_inner` at every lock site.

Comment thread packages/rs-drive-abci/src/perf.rs Resolved
Comment thread packages/rs-drive-abci/src/perf.rs Resolved

@PastaPastaPasta PastaPastaPasta left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Verdict: merge after changes. Inert unless the environment variable is set, so the risk is low; the changes are CI hygiene and two small correctness points in the instrument itself.

1. Correctness

The timing logic is right: Instant deltas accumulated in memory, merged into process totals on drop, reported as a mean over blocks. Two issues:

  • Sample counts do not mean fire rate for conditional phases. fb_checkpoint, chainlock, wd_status and fbp_wd_broadcast call lap outside their if, so they record a near-zero sample on every block. The description says the sample count "is how the fire rate of a phase becomes visible"; for these four it is not. Add a lap_if(condition, name) that always advances the boundary and records only when the work ran.
  • expect on a poisoned mutex inside Drop. If a block panics while the totals lock is held, the next Laps drop during unwinding panics again and aborts the process. This is telemetry; recover with unwrap_or_else(PoisonError::into_inner) at all three lock sites.

Also: fb_proposal wraps the whole of finalize_block_proposal, so it overlaps the fbp_* phases. That is fine as a nested total, but the module doc should say so, or a reader will add them up.

2. Clarity

The description explains the design choices well (accumulate instead of log per block, mean over blocks). The example output lists sps_serialize, which no lap in this PR produces; it came from a branch that also instrumented store_platform_state. Please make the example match the code.

3. Codebase standards

  • CI is red on the title: bench is not an allowed type in .github/workflows/pr.yml. This is instrumentation, so feat(drive-abci): ... fits. Squash merges use the PR title, so the commit type follows.
  • The codebase configures operators through PlatformConfig (envy), not raw std::env::var inside modules. For a diagnostic switch that must stay zero-cost and zero-touch I think the OnceLock read is defensible, but it is a deviation and the module doc should say why.
  • Laps::on() and perf::value() have no callers. Remove them; whoever needs them can add them back with a use.
  • No unit tests, which is why codecov/patch fails. Totals::add and the report formatting are pure and easy to test once the formatting is pulled out of end_block.

4. Importance and alternatives

This found #4569 and #4570, so it has paid for itself. The idiomatic alternative in this codebase is Prometheus histograms via crate::metrics, which would give per-phase distributions to any scraper. For a replay benchmark a single periodic log line is easier to read, and the two are not exclusive. I would merge this and consider histograms later.

5. Existing bot findings

All three thepastaclaw comments (conditional sample counts, poisoned mutex, tests) are valid and addressed above.

I will push: title to feat, lap_if, poison recovery, removal of dead API, tests, and a corrected example in the description.


🤖 Posted autonomously by Claude on behalf of pasta.

@PastaPastaPasta PastaPastaPasta changed the title bench(drive-abci): per-block phase timing behind DRIVE_BLOCK_PERF feat(drive-abci): per-block phase timing behind DRIVE_BLOCK_PERF Sep 7, 2026
fb_checkpoint, chainlock, wd_status and fbp_wd_broadcast recorded a near-zero sample on every block, so their sample counts said nothing about how often the work ran. A lap_if records a sample only when it did. Recover a poisoned totals lock instead of panicking in Drop, remove the unused on() and value(), pull the report formatting into Totals so it can be unit tested, and note the env-var switch and the nested fb_proposal lap in the module doc.
…rified

The verification inside the chainlock block runs only for a lock this node did not propose itself, so the sample condition needs known_from_us as well. Also note the zero-interval behaviour of end_block and cover lap_if's skipped path.
@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Pushed 19f82f4 and 9826308 on top of the original commit, and retitled the PR to feat(drive-abci): … since bench is not an allowed type; the title check is green.

  • Laps::lap_if(ran, name) always moves the lap boundary and records a sample only when the work ran. fb_checkpoint, chainlock, wd_status and fbp_wd_broadcast use it, so their sample counts are now the number of blocks the work ran on. For chainlock the condition is core_chain_lock_update.is_some() && !known_from_us, because the verification only runs for a lock this node did not propose (my first version missed the second half; caught in review).
  • A poisoned totals lock is recovered with PoisonError::into_inner at every site, so telemetry can never turn a panic into an abort during unwinding.
  • Unused Laps::on() and perf::value() removed.
  • Report formatting moved into Totals::report_line / Totals::end_block, with five unit tests (first-seen order, mean over blocks not samples, interval reset, empty report, lap_if skipped path). codecov/patch is green.
  • Module doc says why this reads the environment instead of PlatformConfig, and that fb_proposal nests the fbp_* laps. end_block documents that an interval of zero reports every block.
  • PR description: the example output now matches what the code prints (name=mean/samples) and no longer lists a phase this PR does not have.

cargo test -p drive-abci --lib -- perf::: 5 passed. fmt and clippy clean.

Ready for human review.


🤖 Posted autonomously by Claude on behalf of pasta.

@PastaPastaPasta PastaPastaPasta added the ready for final review Ready for the final review. If AI was involved in producing this PR, it has already had a reviewer. label Sep 8, 2026
@PastaPastaPasta PastaPastaPasta changed the title feat(drive-abci): per-block phase timing behind DRIVE_BLOCK_PERF feat(drive-abci): debug-only per-block phase timing Sep 8, 2026

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — Phase 2 only (queue backlog)

Verified all three supplied findings against head 2e7fb4d and retained them as suggestions: two conditional phases overcount executions, and the skipped-lap test does not enforce boundary advancement. The instrumentation is consistently debug-gated, and no blocking issue was confirmed. Validation used source and diff inspection plus git diff --check; reviewer-reported test runs were not independently rerun.

Source: reviewer 1: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 2: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)

Review provenance

  • Triage: critical by gpt-6-astra (effort low) — Although compiled out of standard release builds, the instrumentation runs directly inside consensus-sensitive proposal execution and finalization paths, including signature verification, withdrawals, and storage commits, so timing, synchronization, or failure side effects warrant heightened review.
  • Phase 1 reviewers: not run (skipped for throughput: 36 PRs queued, above the 10 limit)
  • Fresh verifier: gpt-6-astra — final-verifier; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — security-auditor (completed, effort xhigh); agent phase2-reviewer

🟡 3 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs:269-270: Exclude no-op core-info updates from the sample count
  `update_core_info` dispatches to `update_core_info_v0`, which returns immediately when `block_platform_state.last_committed_core_height()` equals the proposed Core height and `is_init_chain` is false. This call passes false, but the unconditional lap records a sample even on those skipped updates. Consequently, a 500-block replay window with 331 Core-height advances reports `core_info=.../500` instead of the documented `/331`, obscuring the update's execution frequency. Capture the height-change condition before calling `update_core_info` and pass it to `lap_if`, so skipped updates advance the timing boundary without recording a sample.

In `packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs:204: Do not sample commit verification when testing disables it
  With `testing-config` enabled, `PlatformTestConfig::default_minimal_verifications()` sets `block_commit_signature_verification` to false, so the verification branch above does not execute. This unconditional lap still records a sample for every finalized block, making the timing report indicate that commit-signature verification ran even when the simulation disabled it. Use the existing `verify_commit_signature` condition with `lap_if` to preserve the boundary without counting skipped verification.

In `packages/rs-drive-abci/src/perf.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/perf.rs:232-235: Make the skipped-lap test reject an unchanged boundary
  `before` is the old boundary, and `assert!(laps.last >= before)` permits that boundary to remain unchanged. If `lap_if` were changed to update `self.last` only when `ran` is true, both this assertion and the remaining buffer assertions would still pass, although the next recorded phase would incorrectly include the skipped interval. Set the old boundary deliberately in the past and compare against an instant captured immediately before the call, so the test rejects that regression without sleeps.

Comment thread packages/rs-drive-abci/src/perf.rs Resolved
{
let _timer = crate::metrics::abci_request_duration("finalize_block");
#[cfg(debug_assertions)]
let mut laps = crate::perf::Laps::new();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why you not using tracing spans? as a side effect it would make logging more descriptive.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered spans and kept the stopwatch, for three reasons:

  1. Aggregation. A span gives you its own duration when it closes; turning that into a per-phase mean over 500 blocks needs a custom Layer that keys on span name and accumulates, which is the same amount of code as perf.rs but harder to keep zero-cost.
  2. Where the cost lands. The out-of-the-box way to get span timings is the fmt layer with FmtSpan::CLOSE, which formats a log line per span per block inside the very region being measured. That is what the first version of this PR did with events and it visibly inflated the phases under investigation, which is why it moved to accumulated means.
  3. Shape. These phases are consecutive marks between statements in one function, not nested scopes. Wrapping each of ~25 statements in let _span = span!(...).entered(); ...; drop(_span) would make the block execution code much harder to read than one #[cfg(debug_assertions)] line after each call.

The descriptive-logging side effect is real but I think it is a separate change: adding tracing::instrument to the phase functions would benefit release logs, which this debug-only instrumentation deliberately stays out of. Happy to do that as a follow-up if you want it.

Comment thread packages/rs-drive-abci/src/perf.rs Resolved
QuantumExplorer and others added 2 commits September 16, 2026 11:43
…-timing

# Conflicts:
#	packages/rs-drive-abci/src/abci/handler/finalize_block.rs
#	packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs
Address review on the phase timing instrumentation:

- `Laps` becomes `PhaseTimer`, constructed with a scope (the function it
  lives in); every phase is reported as `scope.phase`, so the
  `run_block_proposal`, `finalize_block_proposal` and `finalize_block`
  phases are prefixed consistently.
- Phases are named after the call they time
  (`process_raw_state_transitions`, `update_core_info`, ...) instead of
  abbreviations like `val_app_ver` or `wd_status`.
- Phases that timed nothing but struct construction or plain comparisons
  (`fb_commit_check`, `fbp_basic_checks`, `fbp_block_info`,
  `fbp_ext_block_info`, `fbp_should_checkpoint`, `exec_ctx`) are dropped.
- `upgrade_protocol_version_on_epoch_change`, `update_core_info` and
  `verify_commit_signature` record a sample only on the blocks the work
  ran, like the other conditional phases.
- The post-commit work the base branch added since the PR forked gets
  its own phases: `promote_data_contract_cache` and
  `flush_pending_prefix_drops`.
- The skipped-phase test now rejects a boundary that does not move.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@QuantumExplorer

Copy link
Copy Markdown
Member

Merged v4.2-dev (bb93dbb) and pushed e39d1a5 addressing @shumkov's review and the three open bot findings:

  • Laps is now PhaseTimer::new(scope); every phase reports as scope.phase (run_block_proposal.*, finalize_block_proposal.*, finalize_block.*), replacing the fb_/fbp_ prefixes and the unprefixed proposal phases.
  • Phases are named after the call they time (process_raw_state_transitions, update_core_info, commit_transaction, ...). No more val_app_ver or wd_status.
  • Six phases that only wrapped struct construction or comparisons are gone (fb_commit_check, fbp_basic_checks, fbp_block_info, fbp_ext_block_info, fbp_should_checkpoint, exec_ctx). clone_platform_state stays: the clone was ~150 µs/block on the mainnet replay.
  • upgrade_protocol_version_on_epoch_change, update_core_info and verify_commit_signature now count a sample only on blocks the work ran, via end_phase_if.
  • Post-commit work the base added since the fork gets its own phases: promote_data_contract_cache and flush_pending_prefix_drops.
  • The skipped-phase unit test rejects a boundary that does not move.

The tracing-spans thread is left open with my reasoning; the rest are resolved. PR description updated to match.

cargo test -p drive-abci --lib -- perf:: 5 passed; cargo clippy -p drive-abci --lib -- -D warnings and cargo fmt --check clean.

🤖 Generated with Claude Code

@QuantumExplorer
QuantumExplorer merged commit e82800b into v4.2-dev Sep 16, 2026
9 of 19 checks passed
@QuantumExplorer
QuantumExplorer deleted the bench/block-phase-timing branch September 16, 2026 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for final review Ready for the final review. If AI was involved in producing this PR, it has already had a reviewer.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants