Skip to content

fix: make sure that accounts don't automatically redelegate to unregistered and re-registered pools#1030

Open
etorreborre wants to merge 2 commits into
mainfrom
etorreborre/feat/unregistered-pools
Open

fix: make sure that accounts don't automatically redelegate to unregistered and re-registered pools#1030
etorreborre wants to merge 2 commits into
mainfrom
etorreborre/feat/unregistered-pools

Conversation

@etorreborre

@etorreborre etorreborre commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

This PR implements the FIXME section described in #878

Summary by CodeRabbit

  • Bug Fixes

    • Accounts delegated to retired pools are now automatically unbound at the epoch boundary.
    • Account deposits and rewards remain unchanged when delegations are cleared.
    • Delegations to active pools continue to be preserved.
  • Observability

    • Added an accounts_unbound metric to epoch transition traces, showing how many accounts were unbound.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change introduces explicit account create/update values, unbinds accounts delegated to retiring pools during epoch transitions, records the unbound count in tracing schemas, and adds store and RocksDB integration coverage.

Changes

Retired-pool account unbinding

Layer / File(s) Summary
Explicit account value shapes
crates/amaru-ledger/src/store/columns/accounts.rs, crates/amaru-ledger/src/bootstrap.rs, crates/amaru-ledger/src/state/volatile/fragment.rs, crates/amaru-stores/src/lib.rs
Account writes now use AccountsValue::Create or AccountsValue::Update with named fields.
RocksDB account persistence
crates/amaru-stores/src/rocksdb/ledger/columns/accounts.rs
RocksDB matches account writes by variant, preserving existing rewards during registration updates.
Epoch retirement and observability
crates/amaru-ledger/src/store/epoch_transition.rs, crates/amaru-observability/src/schemas.rs, docs/TRACES.md, docs/traces-schema.json
Delegations to retiring pools are cleared and the accounts_unbound count is recorded in trace schemas.
Integration coverage and release notes
crates/amaru-stores/src/lib.rs, crates/amaru-stores/src/rocksdb/mod.rs, CHANGELOG.md
Tests verify retired delegations are cleared while balances and live delegations remain unchanged; the changelog references the fix.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EpochTransition
  participant AccountsStore
  participant TraceSpan
  EpochTransition->>AccountsStore: inspect accounts during pool retirement
  AccountsStore-->>EpochTransition: unset delegations to retired pools
  EpochTransition->>TraceSpan: record accounts_unbound
  EpochTransition->>AccountsStore: update retired and remaining pool rows
Loading

Possibly related PRs

  • pragma-org/amaru#128: Earlier changes to the accounts column’s DRep representation relate to the new AccountsValue account shapes.

Suggested reviewers: jeluard

Poem

Retired pools ride off into the night,
Delegations loosen, balances stay right.
RocksDB keeps the ledger’s beat,
Trace counts make the tale complete—
A tidy fix, pixel-perfect neat.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: preventing accounts from redelegating to pools that were unregistered and later re-registered.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 etorreborre/feat/unregistered-pools

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.

@etorreborre etorreborre force-pushed the etorreborre/feat/unregistered-pools branch from 1398a3a to e77a4c9 Compare July 13, 2026 10:32
@etorreborre etorreborre changed the title feat: make sure that accounts don't automatically redelegate to unregistered and re-registered pools fix: make sure that accounts don't automatically redelegate to unregistered and re-registered pools Jul 13, 2026
@etorreborre etorreborre self-assigned this Jul 13, 2026
Signed-off-by: Eric Torreborre <etorreborre@yahoo.com>
Signed-off-by: Eric Torreborre <etorreborre@yahoo.com>
@etorreborre etorreborre force-pushed the etorreborre/feat/unregistered-pools branch from e77a4c9 to 13edd8e Compare July 13, 2026 10:50
@etorreborre etorreborre marked this pull request as ready for review July 13, 2026 12:05

@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: 1

🧹 Nitpick comments (1)
crates/amaru-ledger/src/state/volatile/fragment.rs (1)

410-424: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Ditch the inline commentary, mate — let the match speak for itself.

The logic's grand (Create-vs-Update split reads true to the new AccountsValue contract), but that freshly-bolted-on two-liner explaining the deposit semantics runs against the repo's own house rules.

As per coding guidelines: "Never include comments in code unless explicitly asked; use descriptive names instead."

♻️ Suggested tidy-up
     iterator.map(|(credential, Bind { left: pool, right: drep, value: deposit })| {
-        // A bound deposit denotes a (re-)registration within the window (see DiffBind::register);
-        // without one, only delegations changed and the account is known to exist already.
-        let value = match deposit {
+        let value = match deposit {
             Some(deposit) => AccountsValue::Create { pool, drep, deposit, rewards: 0 },
             None => AccountsValue::Update { pool, drep },
         };
         (credential, value)
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/amaru-ledger/src/state/volatile/fragment.rs` around lines 410 - 424,
Remove the inline two-line comment inside add_accounts while leaving the
existing deposit match and AccountsValue::Create/Update behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/amaru-ledger/src/store/epoch_transition.rs`:
- Around line 179-197: The pool-retirement handling currently scans every
account through with_accounts; replace this full-table traversal with a
pool-to-delegator lookup or equivalent delegation index. Update the retirement
branch to fetch only accounts delegated to pools in retirements, clear their
pool assignments, and preserve the accounts_unbound metric and existing error
propagation.

---

Nitpick comments:
In `@crates/amaru-ledger/src/state/volatile/fragment.rs`:
- Around line 410-424: Remove the inline two-line comment inside add_accounts
while leaving the existing deposit match and AccountsValue::Create/Update
behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cbf6379f-71ea-4639-8dd2-d908aef21c4d

📥 Commits

Reviewing files that changed from the base of the PR and between 5c34172 and 13edd8e.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • crates/amaru-ledger/src/bootstrap.rs
  • crates/amaru-ledger/src/epoch_transition.rs
  • crates/amaru-ledger/src/state/volatile/fragment.rs
  • crates/amaru-ledger/src/store/columns/accounts.rs
  • crates/amaru-ledger/src/store/epoch_transition.rs
  • crates/amaru-observability/src/schemas.rs
  • crates/amaru-stores/src/lib.rs
  • crates/amaru-stores/src/rocksdb/ledger/columns/accounts.rs
  • crates/amaru-stores/src/rocksdb/mod.rs
  • docs/TRACES.md
  • docs/traces-schema.json
💤 Files with no reviewable changes (1)
  • crates/amaru-ledger/src/epoch_transition.rs

Comment on lines +179 to +197
if !retirements.is_empty() {
db.with_accounts(|iterator| {
let mut accounts_unbound: u64 = 0;
for (_, mut row) in iterator {
let delegated_to_retired = row
.borrow()
.as_ref()
.and_then(|account| account.pool.as_ref())
.is_some_and(|(pool, _)| retirements.contains(pool));

if delegated_to_retired && let Some(account) = row.borrow_mut() {
account.pool = None;
accounts_unbound += 1;
}
}

Span::current().record("accounts_unbound", accounts_unbound);
})?;
}

@coderabbitai coderabbitai Bot Jul 13, 2026

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.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Gauge how large the accounts column can get vs pools, and whether any pool->accounts index exists
rg -nP -C2 'with_accounts|iter_accounts' crates/amaru-ledger/src/store
rg -nP -C2 'fn account\b|by_pool|delegators' crates/amaru-stores crates/amaru-ledger

Repository: pragma-org/amaru

Length of output: 8936


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant store APIs and the epoch transition context.
ast-grep outline crates/amaru-ledger/src/store.rs --view expanded
ast-grep outline crates/amaru-ledger/src/store/epoch_transition.rs --view expanded

printf '\n--- account-related store methods ---\n'
rg -n -C 2 '\bwith_accounts\b|\baccounts\b|\baccount\(' crates/amaru-ledger/src/store.rs crates/amaru-stores/src/lib.rs crates/amaru-stores/src/rocksdb/mod.rs

printf '\n--- retirement / epoch transition context ---\n'
sed -n '1,260p' crates/amaru-ledger/src/store/epoch_transition.rs

printf '\n--- store trait around account iteration ---\n'
sed -n '200,280p' crates/amaru-ledger/src/store.rs

Repository: pragma-org/amaru

Length of output: 39147


Full account scan on pool retirement is a scalability hotspot

with_accounts is the only lookup path here, so every retirement walks the whole accounts table just to clear a handful of delegations. Fine for correctness, but at mainnet scale it turns epoch boundary work into a chunky boss fight. A pool→delegator index, or some equivalent delegation lookup, would let this touch only affected accounts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/amaru-ledger/src/store/epoch_transition.rs` around lines 179 - 197,
The pool-retirement handling currently scans every account through
with_accounts; replace this full-table traversal with a pool-to-delegator lookup
or equivalent delegation index. Update the retirement branch to fetch only
accounts delegated to pools in retirements, clear their pool assignments, and
preserve the accounts_unbound metric and existing error propagation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@KtorZ that would require a larger change, only effective when there are retirements. Should we do it? If so, should we do it in the same PR?

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.81250% with 22 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...maru-stores/src/rocksdb/ledger/columns/accounts.rs 52.63% 9 Missing ⚠️
crates/amaru-ledger/src/state/volatile/fragment.rs 0.00% 6 Missing ⚠️
crates/amaru-ledger/src/bootstrap.rs 0.00% 5 Missing ⚠️
crates/amaru-ledger/src/store/epoch_transition.rs 93.75% 1 Missing ⚠️
crates/amaru-stores/src/lib.rs 98.70% 1 Missing ⚠️
Files with missing lines Coverage Δ
crates/amaru-ledger/src/epoch_transition.rs 0.00% <ø> (ø)
crates/amaru-ledger/src/store/columns/accounts.rs 100.00% <ø> (ø)
crates/amaru-observability/src/schemas.rs 100.00% <ø> (ø)
crates/amaru-stores/src/rocksdb/mod.rs 74.68% <100.00%> (+1.58%) ⬆️
crates/amaru-ledger/src/store/epoch_transition.rs 18.75% <93.75%> (+18.75%) ⬆️
crates/amaru-stores/src/lib.rs 98.48% <98.70%> (+0.03%) ⬆️
crates/amaru-ledger/src/bootstrap.rs 12.59% <0.00%> (-0.02%) ⬇️
crates/amaru-ledger/src/state/volatile/fragment.rs 37.22% <0.00%> (-0.66%) ⬇️
...maru-stores/src/rocksdb/ledger/columns/accounts.rs 79.77% <52.63%> (-1.18%) ⬇️

... and 5 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.

@etorreborre etorreborre requested a review from KtorZ July 13, 2026 14:32
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.

1 participant