Skip to content

perf: anchor is mostly None#631

Merged
KtorZ merged 2 commits into
mainfrom
eh2406/anchor
Jan 9, 2026
Merged

perf: anchor is mostly None#631
KtorZ merged 2 commits into
mainfrom
eh2406/anchor

Conversation

@Eh2406

@Eh2406 Eh2406 commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

On preprod the anchor of a Ballot is <1% of the time Some on mainnet it is more like 42%. In this PR we change from pub anchor: Option<Anchor> to pub anchor: Option<Box<Anchor>>. Witch makes Ballot go from 64 bytes to 16, but at the cost of adding an additional allocation for the Box when the anchor is Some. For <1% Some that is a clear win. For the mainnet numbers we have approximately (1497*64)=95808 vs (1497*16 + 636 * 56)=59568.

This is a less drastic alternative to #601

Summary by CodeRabbit

  • Refactor
    • Internal ballot representation changed to reduce memory footprint and use an explicit constructor and accessors.
    • Encoding/validation/bootstrapping updated to use the new constructor and accessors.
    • Tests updated to construct ballots via the new API.
    • No user-visible behavior or workflow changes; functionality and encoding remain unchanged.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Ballot fields were made private and anchor changed from Option<Anchor> to Option<Box<Anchor>>. A public constructor Ballot::new(vote, anchor) plus accessors vote() and anchor() were added. Call sites updated to use the constructor and accessors; CBOR encode now uses accessors.

Changes

Cohort / File(s) Summary
Ballot core
crates/amaru-kernel/src/ballot.rs
vote and anchor made private; anchor: Option<Anchor>anchor: Option<Box<Anchor>>. Added pub fn new(vote, anchor), pub fn vote(&self), pub fn anchor(&self). CBOR encode updated to call accessors.
Ledger — construction sites
crates/amaru-ledger/src/bootstrap.rs, crates/amaru-ledger/src/context/default/validation.rs
Replaced struct-literal Ballot { vote, anchor } / anchor: None with Ballot::new(vote, anchor) / Ballot::new(vote, None); anchors boxed where present.
Ledger — vote consumption
crates/amaru-ledger/src/governance/ratification.rs
Replaced ballot.vote field access with ballot.vote(); maps previously storing &Vote now store Vote (ownership moved).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • perf: vec over btree #594 — modifies the same ratification/governance codepaths and vote handling, likely touching the same ownership/access patterns.

Poem

Anchors boxed, votes kept neat,
Fields tucked in, all future-proofed and sweet.
Constructor calls strut down the lane,
Accessors hum like an 8-bit train. 🚂🎮

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main optimization: boxing the anchor field to reduce struct size when anchor is mostly None, which is the core architectural change across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

@codecov

codecov Bot commented Jan 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 60.86957% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/amaru-ledger/src/governance/ratification.rs 0.00% 5 Missing ⚠️
crates/amaru-ledger/src/bootstrap.rs 0.00% 3 Missing ⚠️
...tes/amaru-ledger/src/context/default/validation.rs 0.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
crates/amaru-kernel/src/ballot.rs 100.00% <100.00%> (ø)
...tes/amaru-ledger/src/context/default/validation.rs 0.00% <0.00%> (ø)
crates/amaru-ledger/src/bootstrap.rs 76.37% <0.00%> (ø)
crates/amaru-ledger/src/governance/ratification.rs 6.04% <0.00%> (+0.02%) ⬆️

... and 8 files with indirect coverage changes

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

Signed-off-by: KtorZ <matthias.benkort@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/amaru-kernel/src/ballot.rs (1)

66-76: Minor: Consider using the constructor in decode for consistency.

The decode implementation directly constructs the struct bypassing Ballot::new(). While this works fine (the decoder returns Option<Box<Anchor>> directly), using the constructor would be more consistent with the encapsulation pattern.

♻️ Optional refactor to use constructor
 impl<'d, C> cbor::decode::Decode<'d, C> for Ballot {
     fn decode(d: &mut cbor::Decoder<'d>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
         heterogeneous_array(d, |d, assert_len| {
             assert_len(2)?;
-            Ok(Self {
-                vote: d.decode_with(ctx)?,
-                anchor: d.decode_with(ctx)?,
-            })
+            Ok(Ballot::new(
+                d.decode_with(ctx)?,
+                d.decode_with(ctx)?,
+            ))
         })
     }
 }

This way, if you ever change the internal representation again, the decode logic benefits from the constructor's transformation automatically. That said, the current approach might be more efficient since the decoder already produces Option<Box<Anchor>>, so no worries either way!

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a717fa2 and 4484ea9.

📒 Files selected for processing (4)
  • crates/amaru-kernel/src/ballot.rs
  • crates/amaru-ledger/src/bootstrap.rs
  • crates/amaru-ledger/src/context/default/validation.rs
  • crates/amaru-ledger/src/governance/ratification.rs
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-04-22T09:18:19.893Z
Learnt from: abailly
Repo: pragma-org/amaru PR: 195
File: simulation/amaru-sim/src/simulator/mod.rs:167-182
Timestamp: 2025-04-22T09:18:19.893Z
Learning: In the Amaru consensus pipeline refactor, ValidateHeader::handle_roll_forward returns a Result<PullEvent, ConsensusError>, not ValidateHeaderEvent as might be expected from the older code structure.

Applied to files:

  • crates/amaru-ledger/src/context/default/validation.rs
📚 Learning: 2025-12-16T21:32:37.668Z
Learnt from: rkuhn
Repo: pragma-org/amaru PR: 584
File: crates/amaru-network/src/handshake/tests.rs:40-47
Timestamp: 2025-12-16T21:32:37.668Z
Learning: In Rust, shadowing a binding with a new let does not drop the previous binding until the end of the scope. All shadowed bindings in a scope are dropped in reverse-declaration order when the scope ends. Therefore, multiple let _guard = register_*() calls will keep all guards alive until the end of the function (or the surrounding scope). When reviewing code, be mindful that resources tied to shadowed bindings persist longer than the most recent binding; to release early, constrain the lifetime in an inner block or explicitly drop guards when appropriate.

Applied to files:

  • crates/amaru-ledger/src/context/default/validation.rs
  • crates/amaru-ledger/src/governance/ratification.rs
  • crates/amaru-kernel/src/ballot.rs
  • crates/amaru-ledger/src/bootstrap.rs
📚 Learning: 2025-08-19T09:54:04.412Z
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 374
File: crates/amaru-ledger/src/summary/stake_distribution.rs:219-225
Timestamp: 2025-08-19T09:54:04.412Z
Learning: In stake distribution calculations for governance voting, pool deposit refunds are intentionally handled asymmetrically: DRep voting stake includes the refund (`dreps_voting_stake += account.lovelace + drep_deposits + refund`) while Pool voting stake does not include the refund (`let delta = account.lovelace + pool_deposits`). This behavior matches the Haskell Cardano ledger reference implementation and is by design, not a bug.

Applied to files:

  • crates/amaru-ledger/src/governance/ratification.rs
🧬 Code graph analysis (3)
crates/amaru-ledger/src/context/default/validation.rs (1)
crates/amaru-kernel/src/ballot.rs (3)
  • new (37-42)
  • vote (44-46)
  • anchor (48-50)
crates/amaru-kernel/src/ballot.rs (1)
crates/amaru-ledger/src/context/default/validation.rs (2)
  • new (47-58)
  • vote (228-236)
crates/amaru-ledger/src/bootstrap.rs (3)
crates/amaru-kernel/src/ballot.rs (2)
  • new (37-42)
  • vote (44-46)
crates/amaru-ledger/src/context/assert.rs (1)
  • vote (266-267)
crates/amaru-ledger/src/context.rs (1)
  • vote (251-251)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build x86_64/windows
  • GitHub Check: Analyze (rust)
🔇 Additional comments (7)
crates/amaru-ledger/src/governance/ratification.rs (1)

532-546: Crikey, mate! Clean accessor migration across all voter types.

The update from direct field access to ballot.vote() is bang-on consistent across all five voter variants. The returned &Vote references are properly borrowed from the input map, so no lifetime dramas here. Nice one! 🎯

crates/amaru-ledger/src/bootstrap.rs (1)

1056-1078: G'day! Solid constructor usage throughout the import path.

All three ballot instantiations in import_votes consistently use Ballot::new(vote, None) instead of the struct literal. These are all constructing ballots without anchors (the common case), which is exactly where this optimization shines — no heap allocation, just that sweet 16-byte footprint. Beaut! 🦘

crates/amaru-ledger/src/context/default/validation.rs (1)

228-236: Strewth! Constructor pattern applied perfectly in the validation context.

The ProposalsSlice::vote implementation now uses Ballot::new(vote, anchor), letting the constructor handle the boxing logic internally when the anchor is Some. Dead simple migration, no worries here! 🎬

crates/amaru-kernel/src/ballot.rs (4)

18-34: Bloody brilliant memory optimization, mate!

The field privatization and boxing of anchor is spot-on for this use case. Your documentation nails why this works:

  • Anchor is None ~60% of the time on mainnet (way more on preprod)
  • Struct shrinks from 64B to 16B when anchor is None
  • The heap indirection cost for Some cases is negligible since anchors are rarely accessed

The Box keeps the on-chain data available for users even though the ledger doesn't touch it. Classic space-time trade-off done right! 🎮


36-51: Constructor and accessors looking mint!

The API design is clean as:

  • new() handles the boxing via .map(Box::new) — smooth operator
  • vote() returns &Vote — straightforward
  • anchor() uses .as_deref() to unwrap the Box back to Option<&Anchor> — proper Rust wizardry

This encapsulation lets you change the internal representation without breaking the public API. Choice! 🍿


53-64: CBOR encode adapted perfectly to the new accessors.

Using self.vote() and self.anchor() keeps the encode implementation clean and consistent with the encapsulation. The wire format stays the same, so no breaking changes for serialization. Noice! 🎯


87-94: Test updated to match the new constructor API — champion!

The proptest generator now uses Ballot::new(vote, anchor) instead of the struct literal. Keeps the tests aligned with the public API, which is exactly how it should be. Top shelf! 🏆

@KtorZ KtorZ merged commit 8f9190c into main Jan 9, 2026
22 checks passed
@KtorZ KtorZ deleted the eh2406/anchor branch January 9, 2026 09:29
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