Skip to content

perf(drive-abci): don't read every withdrawal document to build a debug log line - #4569

Open
PastaPastaPasta wants to merge 3 commits into
v4.2-devfrom
perf/withdrawal-doc-scan
Open

perf(drive-abci): don't read every withdrawal document to build a debug log line#4569
PastaPastaPasta wants to merge 3 commits into
v4.2-devfrom
perf/withdrawal-doc-scan

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Sep 1, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

pool_withdrawals_into_transactions_queue_v1 reads every withdrawal document the chain has ever produced, on nearly every block, and throws the result away.

When nothing is queued — which is almost always — it calls fetch_oldest_withdrawal_documents, which passes limit: None. That deserializes every withdrawal document, groups them by status and sorts each group. The result feeds one tracing::debug! line and is otherwise discarded.

Withdrawal documents are never removed, so the cost grows with chain history. Replaying mainnet with per-block phase timing (#4573), where "block time" below means the time drive-abci spends executing a block, proposal and finalize together, not the 2.5-minute interval between blocks at the tip:

height window cost of this call share of drive-abci block time
0–25k 0 µs 0%
50k 43 µs 0.6%
100k 375 µs 5%
150k 3,741 µs 35%
175–200k 4,791 µs 41%

Every other measured phase adds up to roughly 7,000 µs per block and stays flat with height; this call is the entire slope, and it was still climbing at 200k. Removing it makes a block at 200k about 1.7× faster to execute. A synced node does not notice, since 5 ms is nothing against a 150-second block interval; a node replaying history spends 41% of its time on it.

What was done?

Return before the diagnostic query when a DEBUG line would not be emitted. The counting block below it was already guarded by tracing::enabled!; the expensive fetch that feeds it was not.

How Has This Been Tested?

Replaying mainnet history from a local peer, genesis to 424,981, comparing against v4.2-dev. Every committed app hash matched between the two runs across all 424,971 heights, and the final app hash matched an independent reference sync.

cargo test -p drive-abci --lib withdrawal — 176 passed.

Breaking Changes

None. The skipped work only ever fed a log line that is off by default.

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

Summary by CodeRabbit

  • Performance
    • Improved withdrawal processing efficiency by avoiding unnecessary historical data reads when debug logging is disabled.
    • Reduced overhead during block processing, particularly for workloads with extensive withdrawal history.

…ug log line

pool_withdrawals_into_transactions_queue_v1 fetches every withdrawal document the chain has ever produced, groups them by status and sorts each group, on any block with nothing queued — nearly every block — and then throws the result away unless debug logging is on.

fetch_oldest_withdrawal_documents passes limit: None, and withdrawal documents are never removed, so the cost grows with chain history: measured at 4.8 ms per block by height 200,000 on mainnet and still climbing, against about 7 ms for everything else in a block put together. Do the work only when the line it feeds will be emitted.
@thepastaclaw

thepastaclaw commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 57th in line, estimated start in ~80 h (commit cf5e8e4)
Estimated review time once started: ~2.8 h (two-phase automated review; median of recent runs).

  • Request priority review — tick this box and the review moves to the front of the queue.

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

Next included review available in 59 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: Team

Run ID: 1a9c7eaa-e30f-4cc6-8d7a-11d6b5f33de6

📥 Commits

Reviewing files that changed from the base of the PR and between 05403a9 and cf5e8e4.

📒 Files selected for processing (1)
  • packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: f9a80e5f-6fcd-488b-a152-c7595c1636fc

📥 Commits

Reviewing files that changed from the base of the PR and between c0e9a86 and 05403a9.

📒 Files selected for processing (1)
  • packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The withdrawal queue now checks debug logging before fetching historical withdrawal documents. The status-counting branch relies on this earlier check.

Changes

Withdrawal queue processing

Layer / File(s) Summary
Gate historical withdrawal lookup
packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs
The method returns early when debug logging is disabled. The existing status-counting block now uses a plain else branch.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 05403

This change removes an unnecessary history-wide read from the normal no-withdrawal path while preserving withdrawal processing and externally visible behavior. No actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: quantumexplorer, shumkov

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main performance change: avoiding reads of every withdrawal document when building a debug log line.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/withdrawal-doc-scan

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.

@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.31034% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.62%. Comparing base (7c77247) to head (cf5e8e4).
⚠️ Report is 71 commits behind head on v4.2-dev.

Files with missing lines Patch % Lines
...pool_withdrawals_into_transactions_queue/v1/mod.rs 79.31% 18 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4569      +/-   ##
============================================
- Coverage     87.68%   85.62%   -2.07%     
============================================
  Files          2778     2792      +14     
  Lines        359190   370594   +11404     
============================================
+ Hits         314960   317316    +2356     
- Misses        44230    53278    +9048     
Components Coverage Δ
dpp 86.92% <ø> (-2.13%) ⬇️
drive 84.15% <ø> (-2.48%) ⬇️
drive-abci 88.79% <79.31%> (-1.09%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.92% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 41.94% <ø> (-6.71%) ⬇️
🚀 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 intended default-path performance improvement is real, but the implementation modifies an active versioned block-execution method and makes execution success depend on node-local tracing configuration. The newly conditional DEBUG diagnostics also lack focused coverage.

Source: reviewer 1: glm-5.3-flash (agent: phase1-reviewer, role: general); reviewer 2: glm-5.3-flash (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-5.6-sol (agent: phase2-reviewer, role: general); reviewer 4: 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 — 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 — rust-quality (completed); agent phase2-reviewer

🔴 1 blocking | 🟡 1 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/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs:58-64: Do not change active versioned block-execution behavior in place
  `pool_withdrawals_into_transactions_queue_v1` is selected from protocol v5 onward, including v14 through `DRIVE_ABCI_METHOD_VERSIONS_V10`, while the v0 implementation can also delegate to it. This therefore changes behavior for already-defined protocol versions. More importantly, `fetch_oldest_withdrawal_documents` calls the fallible `query_documents` path and previously propagated its error unconditionally; after this guard, the same block and state can return `Ok(())` on a node without DEBUG tracing while a DEBUG-enabled node or older binary still executes the query and can fail. The successful app-hash replay confirms the normal path but does not cover this changed failure behavior. Keep v0/v1 frozen, introduce a v2 implementation selected at a protocol activation boundary, and make its purely diagnostic query non-fatal so tracing configuration cannot affect block-execution success.
- [SUGGESTION] packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs:58-70: Exercise the DEBUG-enabled diagnostic path
  The existing no-queued-documents test installs no DEBUG subscriber, so it now returns at line 59; `test_pooling` has queued documents and never enters this branch. Consequently, no test executes the guarded historical fetch or the status-grouping branch, matching Codecov's seven uncovered changed lines. Add a focused test using a scoped DEBUG subscriber and at least one non-queued withdrawal document, then assert that the call succeeds and leaves the document unchanged. This covers both the enabled guard and the grouped-status diagnostic path.

Comment on lines 58 to 64
if !tracing::enabled!(tracing::Level::DEBUG) {
return Ok(());
}

let all_documents = self
.drive
.fetch_oldest_withdrawal_documents(transaction, platform_version)?;

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.

🔴 Blocking: Do not change active versioned block-execution behavior in place

pool_withdrawals_into_transactions_queue_v1 is selected from protocol v5 onward, including v14 through DRIVE_ABCI_METHOD_VERSIONS_V10, while the v0 implementation can also delegate to it. This therefore changes behavior for already-defined protocol versions. More importantly, fetch_oldest_withdrawal_documents calls the fallible query_documents path and previously propagated its error unconditionally; after this guard, the same block and state can return Ok(()) on a node without DEBUG tracing while a DEBUG-enabled node or older binary still executes the query and can fail. The successful app-hash replay confirms the normal path but does not cover this changed failure behavior. Keep v0/v1 frozen, introduce a v2 implementation selected at a protocol activation boundary, and make its purely diagnostic query non-fatal so tracing configuration cannot affect block-execution success.

source: ['claude']

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.

Partially addressed in efd398de: the diagnostic fetch is no longer fatal. A Drive error in fetch_oldest_withdrawal_documents is logged at DEBUG and the method returns Ok(()), so block outcome no longer depends on the log level. Verified on head cf5e8e4a.

The question of whether this needs a v2 method version rather than an in-place edit to pool_withdrawals_into_transactions_queue_v1 is left for the maintainer to decide; this thread stays open for that.


🤖 Posted autonomously by Claude on behalf of pasta.

@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 two small changes (make the diagnostic fetch non-fatal; cover the DEBUG path with a test so codecov/patch passes).

1. Correctness

The guard is right. fetch_oldest_withdrawal_documents passes limit: None, deserializes every withdrawal document ever written, and the result only feeds a tracing::debug! line. Returning before it when DEBUG is off changes no state; the 425k-block app-hash match confirms that.

One thing the guard does change: the fetch used ?, so a Drive error inside it failed the block. Now a node with DEBUG off returns Ok(()) while a node with DEBUG on still fails the block. The error itself would mean a broken database, so this is theoretical, but tracing configuration should never decide whether a block succeeds. Since the query is purely diagnostic, log the error and continue instead of propagating it. That also resolves thepastaclaw's blocking comment without a new method version: the change is diagnostic-only, touches neither state nor app hash, and does not need protocol activation.

2. Clarity

Title and description are clear. The cost-by-height table makes the case. The code comment explains why, which is what a reader needs.

3. Codebase standards

Follows the tracing::enabled! pattern already used a few lines below. Conventional-commit title with the right scope. No test covers the new early return or the DEBUG branch, which is why codecov/patch fails.

4. Importance and alternatives

4.8 ms of a ~7 ms block at height 200k, still growing. There is no simpler fix; deleting the diagnostic entirely would also work but loses a useful log line.

5. Existing bot findings

  • thepastaclaw "do not change versioned behavior in place": disagree on needing a v2 (nothing consensus-relevant changes); agree on making the diagnostic non-fatal.
  • thepastaclaw "exercise the DEBUG path": agree; a test with a scoped DEBUG subscriber and one non-queued document covers both branches.

I will push both changes to this branch.


🤖 Posted autonomously by Claude on behalf of pasta.

The summary only runs when DEBUG logging is on, so a Drive error inside it would fail the block on a node with verbose logging and pass on one without. Log the error and carry on instead. Two tests with a scoped DEBUG subscriber cover the summary with and without history.
@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Pushed efd398d on top of the original commit:

  • The diagnostic fetch no longer uses ?. A Drive error inside it is logged at DEBUG and the block carries on, so log configuration cannot decide whether a block fails.
  • Two tests with a scoped DEBUG subscriber: one with a single COMPLETE withdrawal document (nothing queued, so the summary runs and the document is left untouched), one with no withdrawal history at all. codecov/patch is green now.

cargo test -p drive-abci --lib -- pool_withdrawals_into_transactions_queue: 6 passed. fmt and clippy clean.

codecov/project is red on this PR and on #4570 and #4572 with a 2 to 2.6 point drop that an 11-line change cannot have caused. Codecov is comparing against a base whose rust-strategy and rust-shielded flags were uploaded on push, and carryforward is not filling the gap. It is not a required check. I would merge through it.

Ready for human review.


🤖 Posted autonomously by Claude on behalf of pasta.

The one with a document already covers the guarded path; the empty case adds nothing.
@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Two updates from review feedback:

  • Description: the "against roughly 7,000 µs" line now says what it is compared against. The 7,000 µs is every other measured phase of drive-abci's block execution (proposal + finalize) added together, flat with height. The table has a third column with the share: 5% of block execution at height 100k, 35% at 150k, 41% at 175–200k, so a block at 200k executes about 1.7× faster without it. A synced node does not notice (5 ms against a 150 s block interval); a node replaying history spends 41% of its time on this call.
  • Tests: dropped the empty-history test. The remaining one, with a single non-queued document and a scoped DEBUG subscriber, is the only one that exercises the guarded summary path, which is what codecov was flagging as uncovered.

🤖 Posted autonomously by Claude on behalf of pasta.

@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.

self ack

@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
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.

2 participants