Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: keep track of DReps #128

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open

feat: keep track of DReps #128

wants to merge 24 commits into from

Conversation

jeluard
Copy link
Contributor

@jeluard jeluard commented Feb 26, 2025

This is a first step towards full governance support. This PR goes straight to the point without introducing significant refactoring that are left for when the global picture will be better understood.

What's done:

  • simplify certificate logic handling so that we only deal with atomic certificate (complex ones are flattened)
  • improve DiffBind error handling
  • simplify some abstractions
  • keep track of DReps registration/unregistration/update
  • keep track of delegations registration/unregistration/update
  • keep track of last DRep interaction epoch (to prepare for DRep expiry handling)
  • persist relevant changes

Note that voting_dreps is kept separate as it is expected that following PRs will significantly alter votes handling.

Note that generalized error handling is for now left out of this PR as long as there is no global strategy to tackle this.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced support for delegation and voting procedures, streamlining transaction processing and state updates.
    • Improved ledger operations with refined management of delegation representatives, advanced import procedures, and updated logging events with adjusted severity levels for enhanced monitoring.
    • Introduced new types and methods to facilitate the handling of DRep (Delegated Representatives) within the ledger and RocksDB.
    • Added new trace events for DRep actions, enhancing monitoring capabilities.
    • Added a new DRepsSummary structure for managing delegations in governance.
    • Introduced new functionality for managing DRep entries in the RocksDB database.
  • Bug Fixes

    • Improved error handling for registration and binding processes, ensuring more robust interactions with the ledger.
  • Tests

    • Introduced additional tests to verify improved error handling and processing logic.
  • Documentation

    • Updated severity levels for various trace events to provide better granularity in monitoring.
    • Corrected typographical errors in documentation comments for clarity.

Copy link

coderabbitai bot commented Feb 26, 2025

Walkthrough

This pull request makes extensive modifications across several crates by updating the public API for re-exports, transaction processing, ledger state management, and store handling. Changes include adding new types (such as Anchor, GovActionId, Voter, etc.), replacing a trait-based transaction I/O with dedicated functions for success and failure, introducing enhanced error handling in diff binding, and expanding support for delegation (DRep) and voting. Additional updates refine logging levels and improve RocksDB integration for managing delegation data.

Changes

File(s) Change Summary
crates/amaru-kernel/src/lib.rs Re-export list updated: removed MintedTransactionBody, VrfKeyhash, WitnessSet; added Anchor, GovActionId, Voter, VotingProcedure, VotingProcedures.
crates/amaru-ledger/src/state.rs
crates/amaru-ledger/src/state/transaction.rs
Transaction processing refactored: replaced InputsOutputs trait with apply_io and apply_io_failed, added select_stake_credentials, and enhanced certificate processing for DRep registrations and delegations.
crates/amaru-ledger/src/state/diff_bind.rs Introduced custom Error enum and updated register, bind_left, and bind_right method signatures to return Result<(), Error>.
crates/amaru-ledger/src/state/volatile_db.rs Updated VolatileState and StoreUpdate structures to support optional delegation (DRep) and new voting_dreps field.
crates/amaru-ledger/src/store.rs Modified Store trait and Columns struct to accept additional parameters (dreps and voting_dreps) during state updates.
crates/amaru-ledger/src/store/columns/dreps.rs
crates/amaru-ledger/src/store/columns/mod.rs
Added new module and types for handling dreps, including CBOR encoding/decoding for ledger rows.
crates/amaru-stores/src/rocksdb/columns/dreps.rs
crates/amaru-stores/src/rocksdb/columns/mod.rs
crates/amaru-stores/src/rocksdb/mod.rs
Enhanced RocksDB integration to manage DRep registrations, delegations, and voting_dreps in the save method, with updated method signatures and new parameters.
examples/shared/src/store.rs MemoryStore implementation updated to handle dreps and delegations with extra iterator parameters and a new _voting_dreps parameter.
monitoring/README.md Logging modifications: adjusted severity levels (e.g. from debug to trace), renamed trace events, and added new events (drep.registration, drep.unregistration, drep.update).
crates/amaru/src/bin/amaru/cmd/import_ledger_state.rs Ledger import functions updated: added BTreeSet and empty iterators for dreps in database save calls; introduced import of BTreeSet.
crates/amaru-ledger/src/store/columns/accounts.rs
crates/amaru-stores/src/rocksdb/columns/accounts.rs
Accounts handling extended with new DRep type; updated row encoding/decoding to incorporate an optional delegation representation.
crates/amaru-ledger/Cargo.toml Updated proptest dependency to include specific features in the development environment.

Suggested reviewers

  • abailly

Poem

Code now sparkles with a brand new twist,
DReps and votes join the dance on the list.
Like a proper brew on an English morn,
Our ledger’s reborn, crisp and adorned.
With errors caught and logs in line,
Here’s to our code—simply divine!
Cheers, mates!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Free

📥 Commits

Reviewing files that changed from the base of the PR and between d7a6a3e and 418a559.

📒 Files selected for processing (2)
  • .github/workflows/CI.yml (3 hunks)
  • Makefile (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/CI.yml

Note

🎁 Summarized by CodeRabbit Free

Your organization has reached its limit of developer seats under the Pro Plan. For new users, CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please add seats to your subscription by visiting https://app.coderabbit.ai/login.If you believe this is a mistake and have available seats, please assign one to the pull request author through the subscription management page using the link above.

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

diff_bind.register(1, "a", None);
assert_eq!(true, diff_bind.unregistered.is_empty());
assert_eq!(true, diff_bind.registered.contains_key(&1));
assert_eq!(Some(&(None, Some("a"))), diff_bind.registered.get(&1));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@KtorZ I would assume that this behavior of register value overriding bind implies we always expect register to come first?
If that's true, how about we make sure bind fails in case it's called first?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes and no. We do expect register to come first, but it's also possible to bind without registering (because the registration might have happened in an earlier block!)

What is actually an error is calling register after bind. That is simply not allowed.

The difficulty here is that we are in-between state management and validation rules. Plus, we operate on a partial view of the system here since there might exist previous registrations or delegations in either of the volatile or stable dbs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(because the registration might have happened in an earlier block!)

Of course!
It makes me think that we should have a consistent error strategy here. It looks to me that some failing scenario might generate errors at different time depending on if happening in a single or multiple blocks (i.e. during initial block processing or delayed at the storage layer). e.g. registering the same pool multiple times.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would definitely treat any oddities like that in the storage layer as errors. Anything that the store receives should have been validated and the storage present our last safety net to check for consistency.

Yet, yes, you are right. Some errors are just "local to the transaction", while others depend on the ongoing state. In principle, those errors shall be what the ledger rules enforce and check. So, as we process the state, we might also yield validation errors unless we manage to treat the state changes as a non faillible builder API (driven by the ledger rules).

In which case, the validation rules become in fact the only place where we return validation failures whereas other places (state management or storage) are plain panics (wrapped in value errors, yes, but with a different semantic than the validation failures).

@jeluard jeluard force-pushed the jeluard/dreps branch 2 times, most recently from 4feced6 to f8594ea Compare February 26, 2025 12:46
}

pub fn bind(&mut self, k: K, j: Option<J>) {
assert!(!self.unregistered.contains(&k));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could use clippy to prevent assert usage vi this lint unfortunately it can't be configured to be usable in tests too. See rust-lang/rust-clippy#13790

Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose it's the eternal discussion between user errors and exceptions. Here, the asset was to signify an API misuse. It's a developer mistake to run into this problem; and it indicates that it is your job to check that you cannot bind on an unregistered element.

In this particular case, it's arguable. As we discussed, the line between state management and validation rules is really blurry. In a way, returning an error now makes this instrumental for implementation validation rules; whereas the assertion assumes that the invocations are valid.

db.put(key, as_value(row))
.map_err(|err| StoreError::Internal(err.into()))?;
} else {
error!(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@KtorZ Replicating the behavior from accounts now it appears to me that both always have a deposit? This logic derives from DiffBind storing values as Option.

Copy link
Contributor

Choose a reason for hiding this comment

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

So technically:

  • DReps always have an explicit deposits
  • Accounts may have an explicit deposits; historically, they had not.

Although if I recall correctly how this was implemented in Amaru, we are defaulting to the protocol parameters' deposit value when the deposit is unspecified. So from a storage perspective, they should indeed always have a deposit.

That's a simplification we could do on the DiffBind structure; I am honestly not quite sure why it's still an option. Maybe trying to remove it will re-surface the reason why ^^"

for (credential, (anchor, deposit)) in rows {
let key = as_key(&PREFIX, &credential);

// In case where a registration already exists, then we must only update the underlying
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@KtorZ Looks to me that there would be some values in having a separated update methods in stores.

@@ -304,12 +306,14 @@ impl Store for RocksDB {
utxo::add(&batch, add.utxo)?;
pools::add(&batch, add.pools)?;
accounts::add(&batch, add.accounts)?;
dreps::add(&batch, add.dreps)?;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All those separate changes to add new entities looks a bit brittle. There's tradeoff to be made if there's a need to introduce some more genericity, considering that the number of new entities is capped.

Copy link
Contributor

@KtorZ KtorZ Feb 27, 2025

Choose a reason for hiding this comment

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

It's an awkward trade-off indeed. On the one hand, it's straightforward to read, write and modify. There's nothing inherently complicated with this code and it doesn't really change that often. On the other hand, it's easy to forget to extend or change when we add new columns.

But the effect of forgetting anything here is immediately visible; you just won't get anything out of the app. So the risk of "shipping it wrong" is rather low.

Which is why I am not so interested in making this generic or more abstract. There are enough small subtleties between those methods to make a generic abstraction over it potentially way more complex than spelling them out.

I think two areas we could look into making generic / more abstract to avoid silly mistakes and catastrophic accident are:

  1. The conversion from key objects to their prefixed rocksdb bytes identifier. Right now, you must call as_key very religiously, otherwise you may end up storing things with a wrong key without actually noticing the damage until it's perhaps too late. This screams for a AsKey trait that we require on each key type.

  2. Most of those function calls, as well as few others on epoch boundary MUST happen in a db transaction. Currently, they happen in multiple db transactions so the system is "easily" corruptible. So rather than hiding away the construction and commit of the db transaction, the API should make the transaction management explicit to the caller. We can still abstract away the RocksDB internal but we most likely need to reverse this responsibility here.

@jeluard jeluard force-pushed the jeluard/dreps branch 2 times, most recently from fc9d79f to 3088609 Compare February 26, 2025 16:41
>,
remove: Columns<
impl Iterator<Item = utxo::Key>,
impl Iterator<Item = (pools::Key, Epoch)>,
impl Iterator<Item = accounts::Key>,
impl Iterator<Item = dreps::Key>,
impl Iterator<Item = delegations::Key>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unused but required by type

@jeluard jeluard force-pushed the jeluard/dreps branch 2 times, most recently from 820890f to 91989a4 Compare February 28, 2025 15:21
let row = Row {
anchor,
deposit,
last_interaction: epoch.unwrap(), // Cannot fail; this is a new registration.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is unfortunate. Will be probably improved in further PR working on governance.

@jeluard jeluard marked this pull request as ready for review February 28, 2025 15:44
@jeluard jeluard requested a review from KtorZ February 28, 2025 15:44
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (7)
crates/amaru-stores/src/rocksdb/columns/delegations.rs (1)

24-38: Function implementation looks good but could use more specific naming

The function logic is sound, but the name "add" is a tad generic for what it's doing. Since the comment says "Register a new DRep", might be worth renaming the function to something more descriptive like register_drep or add_delegation to make it crystal clear what's happening.

Also, I see you're overwriting existing mappings without a check. If that's the intended behavior (which seems reasonable for updates), perhaps add a brief comment explaining the design decision.

- /// Register a new DRep.
- pub fn add<DB>(
+ /// Register or update a delegation to a DRep.
+ /// Overwrites any existing delegation for the given credential.
+ pub fn add_delegation<DB>(
crates/amaru-stores/src/rocksdb/columns/dreps.rs (1)

26-69: Implemented DRep registration function.

Added the add function to register or update DRep entries in the database. The function handles several cases:

  1. Updating an existing registration
  2. Creating a new registration with deposit
  3. Logging an error when attempting to register without a deposit

The implementation looks solid but has one potential issue: in line 55, there's an unwrap() call with a comment stating it cannot fail. While this might be true based on current logic, it's generally better to use something like unwrap_or(default_epoch) for future-proofing.

- last_interaction: epoch.unwrap(), // Cannot fail; this is a new registration.
+ last_interaction: epoch.unwrap_or_else(|| panic!("Missing epoch for new DRep registration")), // More explicit about failure conditions

Or even better:

- last_interaction: epoch.unwrap(), // Cannot fail; this is a new registration.
+ last_interaction: epoch.expect("Missing epoch for new DRep registration"), // More descriptive error

This would make the code a bit more robust to future changes, like a bloke with both belt and braces!

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

88-96: parse_voting_procedures function
Neat approach to filter out DRepKey voters into a HashSet. Might be worth adding a quick test to ensure coverage.

crates/amaru-ledger/src/store/columns/delegations.rs (1)

1-64: Good implementation of delegations storage

This new file looks well-structured and follows the established pattern for store columns. The Row structure with CBOR encoding/decoding looks solid.

However, there's a small copy-paste error in the error message:

- "unable to decode account from CBOR ({}): {e:?}",
+ "unable to decode delegation from CBOR ({}): {e:?}",

The error message refers to "account" when it should be referencing "delegation" since this is in the delegations module.

monitoring/README.md (1)

186-188: New DRep trace events added

The new trace events for DRep registration, unregistration, and updates align perfectly with the PR objectives. Consider adding detail sections for these new events, similar to what exists for the certificate events.

For consistency with other events, you might want to add detail sections like:

<details><summary>trace: `drep.registration`</summary>

| field        | description                       |
| ---          | ---                               |
| `credential` | Stake credential being registered |
</details>
crates/amaru-ledger/src/state/volatile_db.rs (1)

232-233: Interesting approach to delegation management

I notice that while dreps have both registered and unregistered collections, delegations are only added (line 233) but never removed (line 240 uses an empty iterator). This matches your PR objectives which mention "delegations are treated as separate entities rather than attributes of DReps".

Is there a specific reason why delegations don't need to be explicitly removed? For future readers of this code, it might be worth adding a brief comment explaining this design decision.

  delegations: self.state.delegations.into_iter(),
- delegations: iter::empty(),
+ // Delegations are treated as separate entities and don't need to be explicitly removed.
+ // They are overwritten when updated or implicitly removed when the associated 
+ // stake credential is deregistered.
+ delegations: iter::empty(),

Also applies to: 239-240

crates/amaru-ledger/src/state/transaction.rs (1)

307-309: Don't forget the other certificate types!
The code quips “Ignore complex type certificates,” but the FIXME suggests incomplete logic. While flattening ensures simpler certificates, you’d still want to confirm no advanced certificate is left high and dry. A quick follow-up to handle additional certificate variants might be in order, so no stowaways remain unprocessed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf28513 and 91989a4.

📒 Files selected for processing (16)
  • crates/amaru-kernel/src/lib.rs (1 hunks)
  • crates/amaru-ledger/src/state.rs (5 hunks)
  • crates/amaru-ledger/src/state/diff_bind.rs (2 hunks)
  • crates/amaru-ledger/src/state/transaction.rs (4 hunks)
  • crates/amaru-ledger/src/state/volatile_db.rs (6 hunks)
  • crates/amaru-ledger/src/store.rs (3 hunks)
  • crates/amaru-ledger/src/store/columns/delegations.rs (1 hunks)
  • crates/amaru-ledger/src/store/columns/dreps.rs (1 hunks)
  • crates/amaru-ledger/src/store/columns/mod.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/columns/delegations.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/columns/dreps.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/columns/mod.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/mod.rs (4 hunks)
  • crates/amaru/src/bin/amaru/cmd/import.rs (5 hunks)
  • examples/shared/src/store.rs (2 hunks)
  • monitoring/README.md (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build on windows-latest with target x86_64-pc-windows-msvc
🔇 Additional comments (50)
crates/amaru-stores/src/rocksdb/columns/mod.rs (1)

16-17: Excellent addition of new modules for governance support!

The modules for delegations and DReps align perfectly with the PR's objective of implementing tracking mechanisms for DRep registration and delegation management. This modular approach will make future governance enhancements a piece of cake!

crates/amaru-ledger/src/store/columns/mod.rs (1)

16-17: Spot on with the ledger store modules!

Brilliant to see the same structure mirrored here in the ledger crate. Keeping the organization consistent across crates is a proper stroke of genius - makes the codebase as easy to navigate as a well-marked London tube map!

crates/amaru-kernel/src/lib.rs (1)

38-42: Good updates to exported types for governance support

The changes to the re-exported types from pallas_primitives::conway look ace! Adding Anchor, GovActionId, Voter, VotingProcedure, and VotingProcedures while simplifying other exports sets the stage nicely for the governance features mentioned in the PR objectives.

I notice that VrfKeyhash appears to have been relocated rather than fully removed (it's still used in the PoolParams struct at line 177). Cheeky move, but all seems to be in working order!

crates/amaru-stores/src/rocksdb/columns/delegations.rs (1)

21-22: Good prefix definition for delegation entries

Using the UTF-8 encoding for "delg" makes for a clear and consistent prefix convention. As obvious as tea and crumpets!

crates/amaru/src/bin/amaru/cmd/import.rs (5)

29-32: Updated imports to support new DRep functionality.

The imports have been updated to include BTreeSet and iter module, which will be used for the new DRep and delegation tracking functionality.


126-127: Added BTreeSet for voting DReps.

A new parameter has been added to handle the collection of voting DReps in the save method. This aligns with the PR objective of tracking DReps.


355-361: Added support for DReps and delegations in UTxO import.

Empty iterators for dreps and delegations have been added to the save method call, along with an empty BTreeSet for voting DReps. This ensures compatibility with the updated database schema.

Good to see a consistent approach across all the import functions. Like adding milk to tea - necessary for everything to work properly!


418-430: Added support for DReps and delegations in stake pools import.

Empty iterators for dreps and delegations have been added to both the add and remove sections of the save method call in the import_stake_pools function, along with an empty BTreeSet for voting DReps.


508-514: Added support for DReps and delegations in accounts import.

Empty iterators for dreps and delegations have been added to the save method call in the import_accounts function, along with an empty BTreeSet for voting DReps.

The pattern of adding these empty collections is consistent across all import functions. Well done on maintaining a uniform approach throughout the codebase!

examples/shared/src/store.rs (4)

1-2: Added imports for DRep functionality.

Added necessary imports for BTreeSet and StakeCredential to support the new DRep tracking features.


119-130: Extended Store trait to support DReps and delegations.

Added new iterator types for handling dreps and delegations in the save method. This enhancement allows the MemoryStore to track and process DRep registrations and delegations.

The structure follows the established pattern for other entity types like accounts and pools, making it a clean and consistent addition to the codebase.


136-138: Added remove support for DReps and delegations.

Extended the remove parameter to support iterators for DRep and delegation keys that need to be removed from the store.


140-141: Added voting_dreps parameter.

Added a new parameter to track a set of voting delegation credentials. This is a crucial addition for the governance functionality being implemented in this PR.

This parameter will be useful for monitoring which DReps are actively participating in voting, like keeping tabs on which MPs showed up to Parliament!

crates/amaru-ledger/src/state/diff_bind.rs (2)

53-66: Updated bind method to return Result.

Modified the bind method to return Result<(), Error> and added proper error handling for the case when a key is already unregistered.

The handling of already unregistered keys is now explicit and clear, reducing the possibility of subtle bugs. Proper Aussie-rules error handling, mate!


74-113: Added comprehensive test coverage for DiffBind.

Added a new test module with various test cases to validate the behavior of the register and bind methods under different conditions.

The tests cover different scenarios including:

  • Register then bind
  • Register with None then bind
  • Bind then register (expecting failure)
  • Bind only

This is brilliant test coverage! Like a good cricket match, you've covered all the bases (or wickets, rather).

crates/amaru-stores/src/rocksdb/columns/dreps.rs (2)

15-25: Defined DRep storage components.

Added the necessary imports and defined the prefix used for storing DRep entries in the database.

The prefix is clearly defined with a comment explaining its purpose, and all the necessary imports are included. Good job on the documentation!


71-82: Implemented DRep removal function.

Added the remove function to clear DRep registrations from the database. The function iterates through the provided keys and deletes the corresponding entries.

Simple and straightforward implementation that follows the pattern established for other entity types. Handles errors appropriately by wrapping them in the StoreError::Internal variant.

crates/amaru-ledger/src/store/columns/dreps.rs (8)

1-14: All shipshape with the licence header
No big drama here, everything is in top form.


15-19: Imports are splendid
No cause for alarm, looks as breezy as a stroll by the Thames.


20-22: Clear doc comment
The doc comment is quite lucid, no suggestion from my side.


23-24: Value type definition
This typed tuple is straightforward and flexible. No issues.


25-26: Key type alias
A direct mapping from StakeCredential is grand. Keep calm and carry on.


27-32: Struct layout
The struct fields are well-chosen and make sense for storing deposit, anchor, and last interaction. Nicely done.


46-58: Encoding logic
Top-notch usage of CBOR encoding. No complaints from this corner of the pub.


60-69: Symmetrical decoding
Decoding matches the encoding structure perfectly, which is spot on.

crates/amaru-ledger/src/state.rs (3)

27-30: Extended kernel imports
Lovely set of imports, capturing all required items. All is well in this corner.


35-35: New container import
BTreeSet and HashSet expansions look good for handling distinct sets of voting or DReps.


368-377: Inserting voting dReps
The logic for extracting voting_dreps from the transaction body is straightforward. Double-check edge cases (like empty or malformed voting_procedures) in an integrated test to avoid runtime surprises.

crates/amaru-stores/src/rocksdb/mod.rs (8)

16-18: Adding epoch & stake-based imports
This seems dandy, bridging the kernel definitions for use in RocksDB.


31-31: Importing BTreeSet
This sets the stage for storing or iterating over sorted credentials. Good show.


267-268: Extra columns for DReps
Adding DReps into the Columns iteration is a tidy approach. No fuss, no muss.


274-275: Removing DReps
Likewise, ensuring we can remove DReps is consistent. Looking shipshape.


278-278: New voting_dreps parameter
Brilliant: capturing the set of DReps used for voting ensures we've all relevant data on hand.


316-327: DReps epoch association
Neat technique to link newly added DReps with an epoch. Might be worth verifying that epoch values align with the rest of the system if there's ever an epoch boundary edge case.


328-329: Persisting DReps & delegations
Storing them in one batch is a time-saver. Everything looks grand.


336-336: DReps removal call
Ensures data remains consistent. No further ado required.

crates/amaru-ledger/src/store.rs (4)

106-107: Support for DReps and delegations added to the save method

Good job adding the new parameters to support DReps and delegations in the save method. This implementation aligns well with the PR objectives of initiating governance support and tracking DReps.

Also applies to: 113-114, 117-117


166-172: New generic parameters for Columns structure

Nice addition of the new generic parameters D and DL to the Columns struct to represent dreps and delegations. The structure maintains clean separation of concerns while extending functionality.


174-184: Updated Default implementation for Columns

The Default implementation has been properly updated to include empty iterators for the new dreps and delegations fields. This ensures backward compatibility with existing code that relies on the Default trait.


114-114: Unused but required by type

I see the previous comment about this line being "unused but required by type". This makes sense as it's needed for the proper type definition even if not directly used.

crates/amaru-ledger/src/store/columns/delegations.rs (1)

23-25: Clean type aliases for delegation storage

Using type aliases for Value and Key makes the code much more maintainable. If you ever need to change the underlying types, you'll only need to update it in one place. Crisp as a packet of Tayto crisps!

monitoring/README.md (4)

181-185: Reclassified severity levels to trace

Good housekeeping - changing the severity levels from debug to trace for certificate events provides better granularity in monitoring. This will keep the logs cleaner while still preserving the ability to debug when needed.


239-240: Event severity and name updates

Changing save.point_already_known to trace level and renaming new.found_snapshot to new.known_snapshots with info level makes logical sense. These adjustments improve the signal-to-noise ratio in the logs.


288-289: Pool events severity updated

Moving these pool events from debug to trace is consistent with the other severity changes. The documentation remains clear and helpful.


181-181: Documentation generation reminder

I see the previous discussion about investigating ways to generate this documentation automatically to prevent it from going out of sync. This remains a valid concern with the addition of new events.

crates/amaru-ledger/src/state/volatile_db.rs (3)

142-145: New fields added to VolatileState

The addition of dreps, delegations, and voting_dreps fields to VolatileState is well-structured and aligns with the PR objectives. This implementation provides a clean way to track DRep registrations and delegations.


175-175: Added voting_dreps to StoreUpdate

Good addition of the voting_dreps field to the StoreUpdate struct, ensuring that the DRep voting state is properly persisted.


207-207: Proper passing of voting_dreps

Good job ensuring that voting_dreps from VolatileState is properly passed to the StoreUpdate. This is important for the correct handling of the DRep expiry tracking mentioned in the PR objectives.

crates/amaru-ledger/src/state/transaction.rs (2)

60-61: No worries! The logic for successful transaction IO looks grand.
You're replacing the transaction inputs and neatly enumerating outputs. This straightforward approach is quite readable, and it's a pleasant stroll in the code garden. Keep it up, mate!


162-164: Beware of potential negative fees!
If collateral_return happens to exceed the total collateral, fees will end up negative, which might be as welcome as a kangaroo in a crowded backpack. Consider adding a sanity check or a graceful handling mechanism to avoid calamities.

Comment on lines 44 to 96
pub fn register(&mut self, k: K, v: V, j: Option<J>) -> Result<(), Error> {
if self.registered.contains_key(&k) {
return Err(Error::KeyAlreadyUnregistered);
}
self.unregistered.remove(&k);
self.registered.insert(k, (j, Some(v)));
Ok(())
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Updated register method to return Result.

Modified the register method to return Result<(), Error> and added proper error handling for the case when a key is already registered.

One small issue though - there seems to be a mismatch in the error message:

- return Err(Error::KeyAlreadyUnregistered);
+ return Err(Error::KeyAlreadyRegistered);

The error variant should be KeyAlreadyRegistered since we're checking if the key is already in the registered map.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn register(&mut self, k: K, v: V, j: Option<J>) -> Result<(), Error> {
if self.registered.contains_key(&k) {
return Err(Error::KeyAlreadyUnregistered);
}
self.unregistered.remove(&k);
self.registered.insert(k, (j, Some(v)));
Ok(())
}
pub fn register(&mut self, k: K, v: V, j: Option<J>) -> Result<(), Error> {
if self.registered.contains_key(&k) {
- return Err(Error::KeyAlreadyUnregistered);
+ return Err(Error::KeyAlreadyRegistered);
}
self.unregistered.remove(&k);
self.registered.insert(k, (j, Some(v)));
Ok(())
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason why this isn't stored next to pool delegations inside the accounts column? I believe they fulfill the same role and relate to the same entities. So it would make more sense to extend the accounts column?

Copy link
Contributor Author

@jeluard jeluard Mar 3, 2025

Choose a reason for hiding this comment

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

Edit: This was based on a misunderstanding of the comment

delegations are a map that can be updated frequently irregardless of the account lifecycle. Storing them as part of an account would imply having to read/deserialize, update the map, serialize and finally update the whole key per delegation change.

I described a bit how I think the workflow could be achieved with this proposed layout (tradeoff being that there are some extra computations needed at startup).

Overall it looks like we could benefit some more structure for storage depending on overall entities lifecycle (what is updated when, what has to be computed when).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
crates/amaru-ledger/src/state/diff_bind.rs (1)

44-51: ⚠️ Potential issue

Wrong error variant used for already-registered keys.
At line 46, we throw KeyAlreadyUnregistered despite the code block logically requiring KeyAlreadyRegistered. This is akin to mixing up your teabags and throwing in a coffee capsule instead—best to fix it lest we brew some chaos.

Proposed fix:

-            return Err(Error::KeyAlreadyUnregistered);
+            return Err(Error::KeyAlreadyRegistered);
crates/amaru-ledger/src/state/transaction.rs (1)

302-305: ⚠️ Potential issue

Unwrap isn't necessary for HashMap.insert.

HashMap's insert method returns the previous value if there was one, or None if there wasn't. Using .unwrap() here is unnecessary and could panic if we expect a previous entry that doesn't exist.

-                delegations.insert(credential, drep).unwrap();
+                delegations.insert(credential, drep);

This is like checking if your coffee cup explodes after putting coffee in it - when all you really need to do is just pour the coffee!

🧹 Nitpick comments (8)
crates/amaru-stores/src/rocksdb/columns/delegations.rs (2)

24-28: Documentation could use a bit more meat on the bones.

The current docstring is a bit sparse like a Dublin pub at 9 am. Consider expanding it to include:

  • What the function returns on success
  • Any side effects to be aware of
  • What happens if rows is empty
  • Parameter descriptions
- /// Register a new DRep.
+ /// Register new Delegated Representatives (DReps) in the database.
+ ///
+ /// # Parameters
+ /// * `db` - The RocksDB transaction to use for the operation
+ /// * `rows` - Iterator of (credential, DRep) pairs to be registered
+ ///
+ /// # Returns
+ /// * `Ok(())` - If all entries were successfully added
+ /// * `Err(StoreError)` - If any database operation fails
+ ///
+ /// # Note
+ /// This function will overwrite any existing mappings for the same credential.
+ /// If `rows` is empty, no changes will be made to the database.

33-34: Error handling looks good, but could be more specific.

The current error handling wraps everything in a generic StoreError::Internal. For a system tracking delegations in a governance context, it might be worth having more specific error variants. But it's a fair approach to start with!

If you expand your error types in the future, consider something like:

- .map_err(|err| StoreError::Internal(err.into()))?;
+ .map_err(|err| StoreError::DelegationRegistrationFailed { 
+    credential: credential.to_string(), 
+    source: err.into() 
+ })?;

This would require adding a new variant to StoreError, but would provide more context in error scenarios.

crates/amaru-ledger/src/state/diff_bind.rs (3)

35-42: Ensure meaningful error messages.
Introducing a custom error type is grand; however, frankly, “KeyAlreadyRegistered” and “KeyAlreadyUnregistered” are so similar it might leave future devs doing a double-take. A wee doc comment or clarifying text in each variant could stave off confusion for the next poor soul rummaging through this code.


53-66: Clarify binding logic for unregistered keys.
bind(...) supports binding newly unregistered keys (pops them in Entry::Vacant). If that’s your intention, all good, mate. But do add a brief comment for future developers, lest we all scratch our heads in a fortnight.


74-113: Spot-check test coverage for edge cases.
The test suite’s looking spiffy, especially verifying that you can bind first then register. Yet, it might pay to test some corner cases like re-binding an unregistered key, or double-registering the same key and expecting an error.

If you fancy, I can propose additional test stubs to cover these suspicious scenarios!

crates/amaru-ledger/src/state.rs (2)

88-97: Selective extraction of DRep keys.
parse_voting_procedures elegantly plops out StakeCredential::AddrKeyhash for each DRepKey. Ensure no other voter variants are left by the wayside if you plan to handle them in future.


368-375: Extend or store complete voting data?
At lines 368–375, only DRep credentials make it into the state. If you’ve a mind to track or process the entire set of VotingProcedure details, this might be the place. Otherwise, no worries, but do comment if further expansions loom on the horizon.

crates/amaru-stores/src/rocksdb/mod.rs (1)

263-279: Augmented save() signature.
The newly introduced parameters for DReps and delegations in save(...) do make it a real mouthful. That said, it’s deliberate and clarifies usage. Bravo for the explicit design.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91989a4 and fd5b3d3.

📒 Files selected for processing (16)
  • crates/amaru-kernel/src/lib.rs (1 hunks)
  • crates/amaru-ledger/src/state.rs (5 hunks)
  • crates/amaru-ledger/src/state/diff_bind.rs (2 hunks)
  • crates/amaru-ledger/src/state/transaction.rs (4 hunks)
  • crates/amaru-ledger/src/state/volatile_db.rs (6 hunks)
  • crates/amaru-ledger/src/store.rs (3 hunks)
  • crates/amaru-ledger/src/store/columns/delegations.rs (1 hunks)
  • crates/amaru-ledger/src/store/columns/dreps.rs (1 hunks)
  • crates/amaru-ledger/src/store/columns/mod.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/columns/delegations.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/columns/dreps.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/columns/mod.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/mod.rs (4 hunks)
  • crates/amaru/src/bin/amaru/cmd/import_ledger_state.rs (5 hunks)
  • examples/shared/src/store.rs (2 hunks)
  • monitoring/README.md (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • crates/amaru-ledger/src/store/columns/mod.rs
  • crates/amaru-stores/src/rocksdb/columns/mod.rs
  • crates/amaru-kernel/src/lib.rs
  • crates/amaru-stores/src/rocksdb/columns/dreps.rs
  • examples/shared/src/store.rs
  • crates/amaru-ledger/src/store/columns/dreps.rs
  • monitoring/README.md
  • crates/amaru-ledger/src/store/columns/delegations.rs
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build on windows-latest with target x86_64-pc-windows-msvc
🔇 Additional comments (31)
crates/amaru-stores/src/rocksdb/columns/delegations.rs (2)

21-22: PREFIX constant looks spot on!

The constant is correctly defined as a byte array representing "delg" in UTF-8, which provides a clear namespace for delegation entries in the database. Good choice of prefix that's both meaningful and space-efficient.


31-32:

✅ Verification successful

Design decision noted about overriding existing mappings.

The comment explicitly states that existing mappings will be overridden. This is a valid approach, just want to make sure it's the intended behavior for your delegation system. If you need to track history or prevent accidental overwrites, you might want a different strategy.


🏁 Script executed:

#!/bin/bash
# Check if there are any similar override patterns or conflicting checks in other RocksDB operations

echo "Searching for similar override patterns..."
rg -A 5 -B 5 "Always override|override if|existing mapping" --type rust crates/amaru-stores/

echo "Checking if there are any get-before-put patterns that might indicate different approaches..."
rg -A 5 -B 5 "db\.get.*db\.put" --type rust crates/amaru-stores/

Length of output: 1442


Heads-up: The intentional override is spot-on!

The explicit comment and corresponding code confirm that existing mappings are meant to be overridden—just as planned. Our search didn’t reveal any conflicting implementations (like get-before-put strategies) elsewhere in the RocksDB operations, so it seems this approach is deliberate. If you ever need to keep a history of changes or prevent accidental overwrites, you might want to reconsider this strategy down the road. For now, carry on, and cheers to a clear design!

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

27-35: Neat import expansions and BTreeSet usage.
Bringing in BTreeSet, HashSet, and VecDeque is a sensible call for the newly minted features. Keep a watchful eye on memory usage, though in many cases it’s as harmless as a tea break with biscuits.

crates/amaru/src/bin/amaru/cmd/import_ledger_state.rs (5)

29-32: New imports for DReps and delegates.
Bringing in BTreeSet and HashMap is a sensible choice here. No issues spotted—just keep these imports pruned if usage changes.


126-127: Explicit placeholders for new store parameters.
Passing BTreeSet::new() to db.save(...) is a fine placeholder for delegations or DReps. Bodes well for future expansions—just remember to pipe actual sets in when ready.
[approve]


355-361: Recurrent usage of empty iterators.
In import_utxo, referencing iter::empty() for all new fields keeps the code uniform across different store columns. No immediate concerns, though if you foresee real data soon, keep an eye out for missed usage.


418-430: Maintaining consistency for stake pool imports.
Again, dreps: iter::empty() and delegations: iter::empty() adhere to the updated store API. No drama there, cobber.


508-514: Accounts import hooking into new store fields.
This is consistent with the rest of the usage in this file. Good on you for your thoroughness— everything’s neat as a pin.

crates/amaru-stores/src/rocksdb/mod.rs (3)

16-18: Imports for epoch and DReps.
Adding epoch_from_slot, Epoch, and BTreeSet paves the way for DRep tracking in the store. All shipshape, chap.

Also applies to: 31-31


316-330: Combining existing DReps with newly voted ones.
This block merges add.dreps with voting_dreps. If a DRep’s in both, do we handle collisions or merges intentionally? Might be worth a smidge of documentation just in case.


336-337: DRep removal.
dreps::remove does the job. No further complications spotted, mate.

crates/amaru-ledger/src/state/volatile_db.rs (6)

142-146: Great additions to track DReps and delegations! 👍

These changes effectively implement the main objective of tracking DReps and delegations as separate entities. The structure aligns nicely with the existing pattern in the codebase.


175-175: Clear addition for tracking voting DReps.

The voting_dreps set is a sensible addition to the StoreUpdate struct. This will help keep track of DReps that are actively participating in voting.


190-199: Types look spot on for state persistence.

These iterator type updates properly extend the store update mechanism to handle the new DRep and delegation data. The signature maintains consistency with the existing pattern.


207-207: Proper transfer of voting_dreps state.

Successfully passing the voting_dreps from the volatile state to the store update.


232-234: Good implementation of DRep state persistence.

The code properly adds the registered DReps and delegations to the store update. This matches the PR's objective of tracking and persisting DRep information.


239-241:

❓ Verification inconclusive

Consider handling delegation removals.

I notice that you're using an empty iterator for delegation removals. Is this intentional? It appears delegations can be updated but never removed from the store. If this is by design, brilliant! If not, you might want to track and handle delegation removals.


🏁 Script executed:

#!/bin/bash
# Check if there's any code that removes delegations

# Look for delegation removal in the codebase
echo "Looking for code that handles delegation removal..."
rg -A 3 "delegation.*remove|remove.*delegation" --type rust

# Check if there are functions that might unregister delegations
echo "Looking for potential unregister functions for delegations..."
rg -A 3 "unregister.*delegation|delegation.*unregister" --type rust

# Look for VoteDeleg certificate handling for removal
echo "Looking for VoteDeleg certificate unregistration..."
rg -A 10 "VoteDeleg.*unregister|unregister.*VoteDeleg" --type rust

Length of output: 559


Delegation Removals: Intentional or Omission?

At present in crates/amaru-ledger/src/state/volatile_db.rs (lines 239–241) we see delegation removals simply returning an empty iterator. I’ve run some queries—albeit with a dodgy output—to ascertain if the codebase handles removals elsewhere and found no evidence of it. This might be by design, but if delegation removal is required, you'll need to track and handle these similarly to how dreps are managed. It could also be helpful to leave a clarifying comment in the code if removals are intentionally unsupported.

  • File: crates/amaru-ledger/src/state/volatile_db.rs
  • Snippet in question:
                    dreps: self.state.dreps.unregistered.into_iter(),
                    delegations: iter::empty(),
                },

Could you please double-check whether the omission of delegation removals is deliberate or an oversight? This will help prevent any unintended side effects later on.

crates/amaru-ledger/src/store.rs (5)

106-108: Nicely structured additions for DRep and delegation data.

These iterator parameters for adding DReps and delegations are well-specified and follow the established pattern in the codebase.


113-115: Good structure, but mind the unused parameter.

The parameter for delegation removal keys appears to be intentionally unused based on the past review comment. This aligns with what we saw in volatile_db.rs where an empty iterator is used. If this is by design, no worries mate! But worth confirming if delegations should never be removed or if this is for a future implementation.


117-117: Good addition for tracking voting DReps.

This parameter aligns with the changes in volatile_db.rs and allows for the persistence of the voting DReps set.


166-172: Clean extension of the Columns struct.

Adding the generic type parameters and fields for DReps and delegations follows the established pattern and maintains consistency.


174-186: Default implementation properly updated.

The Default implementation has been correctly updated to initialize the new fields with empty iterators.

crates/amaru-ledger/src/state/transaction.rs (9)

77-80: Good updates to support DRep tracking.

The updated parameters properly pass the DRep and delegation collections to the certificate application function.


98-134: Nice refactoring of transaction IO handling!

Breaking out the successful transaction handling into a standalone function improves code clarity. The implementation correctly processes inputs and outputs while maintaining the same functionality.


140-192: Clean implementation of failed transaction handling.

The failed transaction logic is neatly extracted and handles the collateral inputs/outputs correctly. The fee calculation is properly implemented as the difference between total collateral and return value.


194-225: Brilliant certificate flattening approach!

This is a cracking solution to simplify certificate processing. Breaking down complex certificates into their atomic parts makes the rest of the certificate handling code more straightforward. Nice one!


232-233: Good additions for DRep handling.

The updated parameter list for apply_certificates properly includes the DRep and delegation collections.


290-293: Proper handling of DRep registration.

The implementation correctly processes DRep registration certificates, storing both the credential and the anchor.


294-297: Proper handling of DRep unregistration.

The implementation correctly processes DRep unregistration certificates.


298-301: Proper handling of DRep updates.

The implementation correctly processes DRep update certificates, updating the anchor for the credential.


306-308: Good handling of flattened certificates.

The code correctly ignores complex certificates since they've been flattened into simpler ones, and includes a FIXME comment for other certificate types. This aligns with the PR objectives of focusing on specific enhancements without extensive refactoring.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/amaru-stores/src/rocksdb/columns/dreps.rs (1)

61-66: ⚠️ Potential issue

We're just logging when there's no deposit? That's a bit dodgy, mate!

When a DRep has no deposit, the code simply logs an error but doesn't throw an exception. This could lead to silent failures where a DRep isn't registered but the caller believes it was. Consider either:

  1. Returning an error to indicate the failure to register
  2. Using a default deposit value as mentioned by @KtorZ in a previous comment

This aligns with @KtorZ's point that "DReps always have an explicit deposits" and would make the behavior more predictable.

        } else {
            error!(
                target: EVENT_TARGET,
                ?credential,
                "add.register_no_deposit",
            )
+           return Err(StoreError::Internal("Cannot register DRep without deposit".into()));
        };
🧹 Nitpick comments (3)
crates/amaru-stores/src/rocksdb/columns/dreps.rs (3)

25-26: Crikey, a tiny typo in the doc comment!

The comment says "Name prefixed used for..." which is a bit awkward. It should be "Name prefix used for..." instead.

-/// Name prefixed used for storing DReps entries. UTF-8 encoding for "drep"
+/// Name prefix used for storing DReps entries. UTF-8 encoding for "drep"

72-73: Missing documentation - don't leave us in the dark!

The tick function is missing a doc comment explaining its purpose. Given that it's updating the "last_interaction" epoch for DReps, a good comment would help understand why and when this function should be called.

+/// Updates the last interaction epoch for existing DRep registrations.
+/// This helps track when DReps were last active, which is useful for expiry mechanisms.
 pub fn tick<DB>(

72-100: Good error logging, but consider making it more actionable.

The error message "tick.unknown_drep" is a bit cryptic. Adding more context about what action the caller should take would make debugging easier. Also, similar to the add function, you might want to return an error rather than just logging it.

        } else {
            error!(
                target: EVENT_TARGET,
                ?credential,
-               "tick.unknown_drep",
+               "tick.unknown_drep - Cannot update last interaction for a non-existent DRep. Register the DRep first.",
            )
+           // Consider whether to return an error here, or continue with other credentials
        };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd5b3d3 and ff7c6d9.

📒 Files selected for processing (2)
  • crates/amaru-stores/src/rocksdb/columns/dreps.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/mod.rs (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/amaru-stores/src/rocksdb/mod.rs
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build on ubuntu-latest with target riscv32im-risc0-zkvm-elf
  • GitHub Check: Build on windows-latest with target x86_64-pc-windows-msvc
🔇 Additional comments (2)
crates/amaru-stores/src/rocksdb/columns/dreps.rs (2)

30-70: Bob's your uncle, but this function's doing two jobs!

The add function is handling both adding new entries and updating existing ones. While it works a treat, it might be clearer to split this into separate functions:

  1. add for new registrations
  2. update for modifying existing ones

This would make the code more maintainable and self-documenting. As our mate @jeluard mentioned in a previous comment, there would be "some values in having a separated update methods in stores."

/// Register a new DRep.
#[allow(clippy::unwrap_used)]
pub fn add<DB>(
    db: &Transaction<'_, DB>,
    rows: impl Iterator<Item = (Key, Value)>,
    epoch: Epoch,
) -> Result<(), StoreError> {
    for (credential, (anchor, deposit)) in rows {
        let key = as_key(&PREFIX, &credential);

-        // In case where a registration already exists, then we must only update the underlying
-        // entry.
-        if let Some(mut row) = db
-            .get(&key)
-            .map_err(|err| StoreError::Internal(err.into()))?
-            .map(Row::unsafe_decode)
-        {
-            row.anchor = anchor;
-            if let Some(deposit) = deposit {
-                row.deposit = deposit;
-            }
-            // Do not update the last interaction epoch as this is an existing DRep.
-            db.put(key, as_value(row))
-                .map_err(|err| StoreError::Internal(err.into()))?;
-        } else if let Some(deposit) = deposit {
+        if db.get(&key).map_err(|err| StoreError::Internal(err.into()))?.is_some() {
+            update_one(db, credential, anchor, deposit)?;
+        } else if let Some(deposit) = deposit {
            let row = Row {
                anchor,
                deposit,
                last_interaction: epoch,
            };
            db.put(key, as_value(row))
                .map_err(|err| StoreError::Internal(err.into()))?;
        } else {
            error!(
                target: EVENT_TARGET,
                ?credential,
                "add.register_no_deposit",
            )
        };
    }

    Ok(())
}

/// Update an existing DRep.
pub fn update_one<DB>(
    db: &Transaction<'_, DB>,
    credential: Key,
    anchor: String,
    deposit: Option<u64>,
) -> Result<(), StoreError> {
    let key = as_key(&PREFIX, &credential);
    
    if let Some(mut row) = db
        .get(&key)
        .map_err(|err| StoreError::Internal(err.into()))?
        .map(Row::unsafe_decode)
    {
        row.anchor = anchor;
        if let Some(deposit_value) = deposit {
            row.deposit = deposit_value;
        }
        // Do not update the last interaction epoch as this is an existing DRep.
        db.put(key, as_value(row))
            .map_err(|err| StoreError::Internal(err.into()))?;
        Ok(())
    } else {
        error!(
            target: EVENT_TARGET,
            ?credential,
            "update.unknown_drep",
        );
        Err(StoreError::Internal("DRep not found for update".into()))
    }
}

102-113: The remove function is simple and sweet - I like it!

The delete operation is straightforward and handles errors properly. Top job!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
crates/amaru-ledger/src/state/diff_bind.rs (1)

44-47: ⚠️ Potential issue

Fix error variant in the register method!

There's a mismatch between the condition and the error variant. You're checking if the key exists in the registered map but returning KeyAlreadyUnregistered. This is like saying the pub's closed when actually it's too full!

-            return Err(Error::KeyAlreadyUnregistered);
+            return Err(Error::KeyAlreadyRegistered);
🧹 Nitpick comments (4)
crates/amaru-ledger/src/store/columns/accounts.rs (1)

61-65: Properly updated CBOR encoding - brilliant!

You've correctly updated the array size to 4 and added encoding for the drep field. The clone on drep might be unnecessary though - worth checking if that's needed or just a habit.

-        e.encode_with(self.drep.clone(), ctx)?;
+        e.encode_with(&self.drep, ctx)?;
crates/amaru-stores/src/rocksdb/mod.rs (1)

317-317: Consider adding a comment about DRep ticking functionality

The dreps::tick function is called with voting_dreps and epoch, but its purpose isn't immediately obvious from the name. A brief comment explaining that this updates the last interaction epoch for DReps would be helpful. If it's as clear as mud now, a comment would make it crystal!

+                // Update the last interaction epoch for DReps that have voted in this epoch
                 dreps::tick(&batch, voting_dreps, epoch)?;
crates/amaru-ledger/src/state/volatile_db.rs (1)

138-139: Enhanced account structure.
You've updated the accounts to store (Option<PoolId>, Option<DRep>) besides Lovelace. That’s a neat approach to represent multiple delegation targets—good on ya! Meanwhile, adding dreps and voting_dreps separately is quite straightforward. Do watch out for potential duplication of logic. If you find yourself repeating the same insertion or merging patterns, it might be worth whittling that down in a future refactor.

Also applies to: 141-141

crates/amaru-ledger/src/state/transaction.rs (1)

226-226: Prolonged reliance on unwrap().
Your new apply_certificates logic looks grand. Mind, though, that repeated unwrap() calls do carry risk. A wee bit more robust error handling might come in handy if, in future, you don’t want your ledger to keel over due to a single unexpected scenario.

Also applies to: 230-316

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff7c6d9 and 2ad03d3.

📒 Files selected for processing (10)
  • crates/amaru-ledger/src/state/diff_bind.rs (1 hunks)
  • crates/amaru-ledger/src/state/transaction.rs (4 hunks)
  • crates/amaru-ledger/src/state/volatile_db.rs (6 hunks)
  • crates/amaru-ledger/src/store.rs (3 hunks)
  • crates/amaru-ledger/src/store/columns/accounts.rs (3 hunks)
  • crates/amaru-ledger/src/store/columns/mod.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/columns/accounts.rs (3 hunks)
  • crates/amaru-stores/src/rocksdb/columns/mod.rs (1 hunks)
  • crates/amaru-stores/src/rocksdb/mod.rs (4 hunks)
  • crates/amaru/src/bin/amaru/cmd/import_ledger_state.rs (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/amaru-ledger/src/store/columns/mod.rs
  • crates/amaru-stores/src/rocksdb/columns/mod.rs
  • crates/amaru/src/bin/amaru/cmd/import_ledger_state.rs
  • crates/amaru-ledger/src/store.rs
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build on windows-latest with target x86_64-pc-windows-msvc
  • GitHub Check: Build on ubuntu-latest with target riscv32im-risc0-zkvm-elf
🔇 Additional comments (23)
crates/amaru-stores/src/rocksdb/columns/accounts.rs (3)

30-33: Smooth addition of DRep handling - nice work!

You've neatly updated the parameter unpacking to handle the extended value tuple, cleanly extracting both the delegatee and the new drep value. Good job on maintaining backward compatibility while extending functionality.


41-43: Well-implemented Row update logic!

The drep field is properly assigned during row updates, ensuring consistent state management. This change properly integrates with the extended Row structure defined in the ledger crate.


49-54: Correctly extended Row initialization - spot on!

The new drep field is properly included when creating a new Row, maintaining consistency with the updated Row structure. The implementation maintains the same pattern as the existing code, which is smashing!

crates/amaru-ledger/src/store/columns/accounts.rs (4)

15-15: Good import organization - clean as a whistle!

The DRep type is properly imported from the kernel module, making the type available for use in the Row struct and Value type.


23-27: Well-structured Value type update!

The Value type has been nicely extended to include both PoolId and DRep in a nested option tuple. This structure allows for flexible representation of delegation data.


35-35: Clean Row struct extension!

Adding the drep field to the Row struct ensures delegation representation data can be properly stored and retrieved. Good job keeping it optional with Option.


76-76: Decoding properly handles the new field - ace work!

The decode implementation has been updated to read the drep field from the encoded data, ensuring proper reconstruction of Row instances.

crates/amaru-ledger/src/state/diff_bind.rs (3)

35-41: Good use of thiserror for custom error handling!

The Error enum with thiserror derive provides clear, well-structured error handling. This is a proper improvement over the previous implicit behavior.


53-66: Nice bind method enhancement with proper error handling!

Good job updating the bind method to use a functional approach for updating the existing value and returning a proper Result. The error check for keys in the unregistered set is a solid improvement.


74-113: Comprehensive test coverage - top notch!

Great job adding thorough tests that verify all the key scenarios: successful registration and binding, failure cases, and binding-only cases. These tests will help ensure the stability of this critical component.

crates/amaru-stores/src/rocksdb/mod.rs (4)

17-18: Good import additions - keeping it tidy!

Adding StakeCredential and epoch_from_slot imports to support the new DRep functionality. Properly organized imports make the code more maintainable.


267-268: Well-structured parameter expansion!

The save method signature has been neatly expanded to include iterators for DReps and a voting_dreps BTreeSet. This cleanly extends the functionality while maintaining the existing pattern.

Also applies to: 273-274, 276-277


314-318: Great addition of DRep epoch tracking!

Deriving the epoch from the slot and using it for DRep tracking is exactly what the PR describes. This will enable proper expiry handling in the future. Nice one!


324-324: DRep removal properly handled!

You've correctly added the DRep removal to match the addition logic, ensuring that DReps can be properly cleaned up when needed.

crates/amaru-ledger/src/state/volatile_db.rs (3)

18-19: Mighty imports, indeed!
Adding Anchor and DRep to the import list is a grand idea, especially with the new delegation bits. Just keep in mind that once you invite them to the party, you’ll want to ensure they’re actually used in a meaningful way across the codebase.


171-171: New fields in StoreUpdate.
Including voting_dreps in the StoreUpdate structure, as well as the sets for newly introduced dreps, is well-aligned with your delegation-tracking objective. This addition is consistent and tidy, so no quibbles from me.

Also applies to: 186-186, 192-192


201-201: Store update expansions.
Kudos for weaving voting_dreps and dreps into the into_store_update flow. This ensures all new data is properly persisted (or removed) from the store. Nicely done, mate! Just be sure to watch for consistency of updates in case of complicated rollback scenarios.

Also applies to: 226-226, 232-232

crates/amaru-ledger/src/state/transaction.rs (6)

20-20: New imports, new horizons.
Bringing in Anchor, DRep, BTreeMap, and BTreeSet sets the stage for your new delegation and anchor logic. This is a nice step forward for more advanced governance features.

Also applies to: 25-26


53-53: Introduction of success or failure handling.
Swapping between apply_io_failed and apply_io for handling transactions is a clean approach—clear as day and keeps things well compartmentalised. Just be mindful about future expansions if error-handling logic grows.

Also applies to: 60-60


73-79: Certificate application call.
Passing new fields (pools, accounts, and dreps) to apply_certificates ensures your DRep logic flows nicely. Bravo for hooking everything up in one place. Top marks!


101-133: Solid function for successful transactions.
The apply_io function is straightforward and efficient. Emitting new transaction outputs with an index referencing the transaction ID is spot on. Make sure your UTxO consumers all handle these indexes consistently.


139-191: Handling the rogues’ gallery of failed transactions.
Clever that you collect collateral in the fees if no collateral return was set. This should deter the uninitiated from forgetting them. Just keep an eye on potential underflows if refunds might exceed the total collateral in some hypothetical scenario.


193-223: Flatten certificate wizardry.
Splitting complex certificates into multiple simpler ones is a cunning approach—like unraveling a big ball of yarn so your subsequent logic deals with normal, single-action certificates. Great for maintainability.

@KtorZ KtorZ force-pushed the jeluard/dreps branch 2 times, most recently from 4bd5b6e to b2581e4 Compare March 6, 2025 17:46
jeluard and others added 22 commits March 7, 2025 19:49
Signed-off-by: jeluard <[email protected]>
Signed-off-by: jeluard <[email protected]>
Signed-off-by: jeluard <[email protected]>
  Due to trying to perform slot arithmetic on a point that is at the origin; used previously as a workaround by the import command to incrementally import snapshot. It is therefore time to get rid of that little hack and convey the intention properly with a flag.

Signed-off-by: KtorZ <[email protected]>
…g accounts.

  This is a bit subtle, but since we might now generate account::add commands from either the delegation to a pool or a drep, we must ensure that storing a binding to one doesn't invalidate a binding to the other. The solution in this commit is only mildly satisfactory because it kind of pushes some of the state management logic to the store. A better way would be to perhaps not use Option but a more elaborate enum: 'Bind/Unbind/Unchanged' to clearly indicate whether something must be added, removed or simply left unchanged (even though the case 'unbind' will never occur in practice...)

Signed-off-by: KtorZ <[email protected]>
 The 'name:' field is actually pointless / ignored by the framework for reporting.

Signed-off-by: KtorZ <[email protected]>
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