Skip to content

perf(drive-abci): stop rewriting the whole platform state every block - #4554

Closed
PastaPastaPasta wants to merge 2 commits into
dashpay:v4.2-devfrom
PastaPastaPasta:perf/platform-state-writes
Closed

perf(drive-abci): stop rewriting the whole platform state every block#4554
PastaPastaPasta wants to merge 2 commits into
dashpay:v4.2-devfrom
PastaPastaPasta:perf/platform-state-writes

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

The saved platform state is written to GroveDB aux storage on every block, and on mainnet it is 1.28 MB — almost all of it masternode lists, validator sets and the chain-lock and instant-lock quorum sets.

PlatformSerializable::serialize_to_bytes also did self.clone() before converting to the saving form, so a block paid two full deep copies of some 4,000 masternodes plus the validator sets, then serialized 1.28 MB, then wrote it.

Measured replaying mainnet, per block: 1.08 ms serializing, 0.23 ms for the extra clone, and 1.28 MB into the block's transaction — 545 GB of writes over a full sync.

Stacked on #4553, which introduces the utils::is_historical_block predicate this uses.

What was done?

Three things.

Serialize from a borrowed state. A TryFrom<&PlatformState> for the saving form clones each field once instead of cloning the whole state first. A test asserts the bytes are byte-identical to the owned path.

Rewrite the full record only when it changed. The heavy fields carry a dirty flag, set by the accessors that can change them. While replaying history the full record is written only when the flag is set; a small companion record under a second aux key carries the per-block fields — block info, quorum hashes, protocol versions — every block. Both are written in the block's transaction, so a reader never sees them disagree, and a database without the companion record reads exactly as before.

Once the node is at the tip the full record is written every block again, so an up-to-date node always has a complete record on disk and an older drive-abci can still read it. Skipping is confined to a node that is catching up, where the remedy for any format trouble is the resync it is already doing.

Don't take a mutable borrow when nothing moved. Core reports the same quorums on most blocks and an empty masternode diff often, but update_quorum_info and update_state_masternode_list reached for _mut() accessors regardless — and the borrow alone marks the state dirty. Both now decide read-only whether anything actually changed first.

How Has This Been Tested?

Full mainnet replay, genesis to 424,981. Every committed app hash matched a reference sync across all 424,971 heights, and the final app hash matched exactly.

Crash recovery specifically: two mid-sync restarts, at heights 40,200 and 80,188. Both resumed at the right height from the companion record and continued with no app-hash mismatch — which requires the heavy fields to be correct too, since they feed the app hash through masternode identity updates and rewards.

cargo test -p drive-abci --lib — 2,776 passed, including the new byte-identity test.

Breaking Changes

A node interrupted mid-initial-sync writes a full record that lags the database, and an older drive-abci reading that database panics in the Info handler, which compares the saved state's app hash against GroveDB's root hash. This is why the skip is confined to historical blocks: a node that has caught up is always downgrade-safe. A node interrupted mid-sync is not, and the remedy there is the resync it was already doing.

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

…story

Protocol version 11 turns checkpoints on, with a policy of one every 600 seconds of chain time keeping the last 3. Mainnet blocks are about 2.5 minutes apart, so that is a checkpoint every four blocks — which at replay speed is roughly 37 a second, each one a RocksDB checkpoint over the whole database plus a copy of the platform state, and all but the last three deleted again immediately.

Measured at 15.1 ms a block from height 318,704, against about 7 ms for everything else in a block put together. A finished mainnet sync had four checkpoint directories left on disk out of some 26,000 created.

Checkpoints are restore points for a running node, so skip them for blocks more than ten minutes old and write the first real one on reaching the tip. They live outside the tree, so no app hash changes.
The saved platform state is 1.28 MB on mainnet, almost all of it masternode lists, validator sets and quorum sets, and it was serialized and written to GroveDB aux storage on every block. Serialization also cloned the entire state first, so a block paid two full copies of some 4,000 masternodes.

Serialization now builds the saving form from a borrowed state, and a test asserts the bytes are identical to the owned path. The heavy fields carry a dirty flag set by the accessors that can change them, and while replaying history the full record is rewritten only when it is set, with a small companion record holding the per-block fields written every block; both land in the block's transaction, and a database without the companion record reads exactly as before. Once at the tip the full record is written every block again, so an up-to-date node always has a complete record on disk. The two Core-driven update paths now decide read-only whether anything actually moved before taking a mutable borrow, because Core reports the same masternodes and quorums on most blocks and the borrow alone would force the rewrite.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 28 minutes.

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: Pro Plus

Run ID: 8924ae1b-7b23-412f-b604-de1e3ec6b69b

📥 Commits

Reviewing files that changed from the base of the PR and between 17a2962 and a319483.

📒 Files selected for processing (14)
  • packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/block_end/update_state_cache/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs
  • packages/rs-drive-abci/src/execution/storage/fetch_platform_state/v0/mod.rs
  • packages/rs-drive-abci/src/execution/storage/store_platform_state/v0/mod.rs
  • packages/rs-drive-abci/src/platform_types/platform_state/accessors.rs
  • packages/rs-drive-abci/src/platform_types/platform_state/mod.rs
  • packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/mod.rs
  • packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v1/mod.rs
  • packages/rs-drive-abci/src/platform_types/platform_state/recent.rs
  • packages/rs-drive-abci/src/utils/mod.rs
  • packages/rs-drive-abci/src/utils/replay.rs
  • packages/rs-drive/src/drive/platform_state/mod.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 Aug 31, 2026

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — 16 ahead in queue (commit a319483)
Queue position: 17/33 · 2 reviews active
ETA: start ~09:09 UTC · complete ~10:13 UTC (median 1h 4m across 30 recent reviews; 2 slots)
Queued 8h 50m ago · Last checked: 2026-09-01 00:30 UTC

@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Superseded by #4571, which is the same change from a branch in dashpay/platform rather than my fork.


🤖 Posted autonomously by Claude on behalf of pasta.

@PastaPastaPasta
PastaPastaPasta deleted the perf/platform-state-writes branch September 1, 2026 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants