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

Use thiserror in block validation #145

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

Conversation

yHSJ
Copy link
Contributor

@yHSJ yHSJ commented Mar 13, 2025

This PR updates the error handling to use thiserror, making it more in line with error handling in the rest of Amaru.

This PR relies on #129 being merged first

Summary by CodeRabbit

  • New Features

    • Expanded public APIs with enhanced validations for transactions and blocks, including checks on metadata, output sizes, and reference inputs.
    • Improved hashing capabilities and dependency integrations offer increased data integrity and overall reliability.
  • Refactor

    • Streamlined error handling and reporting across validation modules to ensure clearer feedback and more robust compliance verification.

Copy link

coderabbitai bot commented Mar 13, 2025

Walkthrough

This pull request introduces dependency updates and public export modifications in the amaru-kernel crate by adding the pallas-traverse.workspace = true entry and expanding re-exported types. It also overhauls validation logic in the amaru-ledger crate by updating block validation functions to return a unified RuleViolation, removing custom error structs, and introducing new transaction validation modules with detailed error reporting.

Changes

Files Change Summary
crates/amaru-kernel/Cargo.toml
crates/amaru-kernel/src/lib.rs
Added pallas-traverse.workspace = true dependency and expanded public exports with new types (Bytes, AuxiliaryData, PseudoTransactionOutput, TransactionBody, ComputeHash, OriginalHash).
crates/amaru-ledger/src/rules.rs
crates/amaru-ledger/src/rules/block/body_size.rs
crates/amaru-ledger/src/rules/block/ex_units.rs
crates/amaru-ledger/src/rules/block/header_size.rs
Updated block validation functions to return RuleViolation, removed custom error structs, and enhanced error variants for blocks and transactions.
crates/amaru-ledger/src/rules/transaction/disjoint_ref_inputs.rs
crates/amaru-ledger/src/rules/transaction/metadata.rs
crates/amaru-ledger/src/rules/transaction/output_size.rs
crates/amaru-ledger/src/rules/transaction/mod.rs
Introduced new modules and functions for transaction validations covering disjoint inputs, metadata, and output size checks, alongside detailed error enumerations.

Sequence Diagram(s)

sequenceDiagram
   participant Caller as Caller
   participant BV as Block Validator
   participant TV as Transaction Validator
   participant DI as Disjoint Checker
   participant MV as Metadata Validator
   participant OS as Output Size Validator

   Caller->>BV: validate_block(block)
   BV->>BV: block_header_size_valid(header)
   BV->>BV: block_body_size_valid(block)
   BV->>BV: block_ex_units_valid(ex_units)
   BV->>TV: validate_transaction(tx)
   TV->>DI: disjoint_ref_inputs(tx)
   TV->>MV: validate_metadata(tx, aux_data)
   TV->>OS: validate_output_size(tx, protocol_params)
   TV-->>BV: tx validation result
   BV-->>Caller: block validation result
Loading

Possibly related PRs

Suggested reviewers

  • KtorZ
  • abailly

Poem

In the realm of code where bytes do play,
Pallas-traverse leads the vibrant array.
Validation checks now dance in delight,
With errors tamed and functions ever so bright.
Cheers to these changes, tip your hat and enjoy the byte!

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under 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
🪧 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 generate docstrings to generate docstrings for this 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.

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: 2

🧹 Nitpick comments (6)
crates/amaru-ledger/src/rules/transaction/disjoint_ref_inputs.rs (1)

5-20: Solid implementation for checking disjoint inputs!

The function correctly identifies any overlap between reference inputs and normal inputs. However, it could benefit from some documentation to explain its purpose.

Consider adding documentation and an explicit type annotation for clarity:

+/// Validates that reference inputs are disjoint from normal inputs.
+/// 
+/// Reference inputs should not appear in the normal inputs list to prevent
+/// double-counting of the same input.
+/// 
+/// # Arguments
+/// * `transaction` - The transaction body to validate
+/// 
+/// # Returns
+/// * `Ok(())` if all reference inputs are disjoint from normal inputs
+/// * `Err` with the overlapping inputs if any are found
 pub fn disjoint_ref_inputs(transaction: &TransactionBody) -> Result<(), TransactionRuleViolation> {
     let intersection = match &transaction.reference_inputs {
         Some(ref_inputs) => ref_inputs
             .iter()
             .filter(|ref_input| transaction.inputs.contains(ref_input))
             .cloned()
-            .collect(),
+            .collect::<Vec<_>>(),
         None => Vec::new(),
     };
crates/amaru-ledger/src/rules/block/body_size.rs (2)

5-6: New doc lines
These doc comments provide a helpful summary of the validation logic. Cheers for the clarity!

Maybe add a cheeky mention of the expected data bounds for completeness.


24-43: calculate_block_body_size function
This is a neat little function! If you fancy more robust handling of encoding mishaps, you might consider returning a Result in future, but no rush if it stays squeaky clean.

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

17-17: Mind that clone usage.
There’s a grand old “TODO” referencing a plan to clean up clones and introduce lifetimes. If time allows, reducing excessive clones might improve memory usage, especially if transactions are big.

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

13-14: Handy new transaction validations.
Bringing in disjoint_ref_inputs and validate_metadata broadens your quality checks. The single line import is tidy, though watch out for the “kitchen sink” effect if you add more functions.


75-90: ExUnits extraction logic.
Nicely done flattening and collecting those redeemers. If performance ever becomes a wee bit of a bottleneck, you might consider an iterator-based or streaming approach. For now, it’s perfectly readable.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c32e7da and d8e575a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • crates/amaru-kernel/Cargo.toml (1 hunks)
  • crates/amaru-kernel/src/lib.rs (1 hunks)
  • crates/amaru-ledger/src/rules.rs (3 hunks)
  • crates/amaru-ledger/src/rules/block/body_size.rs (1 hunks)
  • crates/amaru-ledger/src/rules/block/ex_units.rs (2 hunks)
  • crates/amaru-ledger/src/rules/block/header_size.rs (1 hunks)
  • crates/amaru-ledger/src/rules/transaction/disjoint_ref_inputs.rs (1 hunks)
  • crates/amaru-ledger/src/rules/transaction/metadata.rs (1 hunks)
  • crates/amaru-ledger/src/rules/transaction/mod.rs (1 hunks)
  • crates/amaru-ledger/src/rules/transaction/output_size.rs (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • 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
  • GitHub Check: Snapshots (preprod, 10.1.4)
🔇 Additional comments (27)
crates/amaru-kernel/Cargo.toml (1)

23-23: Looking good mate - adding the pallas-traverse dependency.

This change properly adds the pallas-traverse crate as a workspace dependency, consistent with how other dependencies are managed in this project.

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

1-3: Top-notch module structure!

Good job organizing these transaction validation rules into separate modules. This structure will make the codebase more maintainable and easier to navigate.

crates/amaru-ledger/src/rules/block/ex_units.rs (2)

8-8: Neat shift to RuleViolation
This line neatly aligns the function with the unified error approach. Great call tidying up the error types!


17-23: Direct usage of TooManyExUnitsBlock
Nicely done returning RuleViolation::TooManyExUnitsBlock directly. Keeps the code breezy as a sea breeze in Southend.

crates/amaru-ledger/src/rules/block/header_size.rs (2)

8-8: Function signature refactor
Swapping in Result<(), RuleViolation> spotlights a more streamlined approach. Top-notch consistency there, mate!


15-18: BlockHeaderSizeTooBig variant
Brilliant use of RuleViolation::BlockHeaderSizeTooBig; top marks for placing all errors under one tidy umbrella.

crates/amaru-ledger/src/rules/block/body_size.rs (4)

1-3: Import adjustments
Good show removing the old error struct references and roping in the new RuleViolation. Simpler than tea and biscuits.


9-10: Signature changes
Passing block directly is ace for checking actual sizes. Good move toward more robust validation, chum.


11-13: Calculating block body size
Reading block_header.block_body_size as usize then pulling an actual size is a fair approach. No pressing concerns here.


14-17: BlockBodySizeMismatch
Well played returning an error variant that captures both supplied and actual sizes. Super handy for debugging!

crates/amaru-ledger/src/rules/transaction/metadata.rs (3)

1-3: Nice use of thiserror and consistent imports.
It’s great to see you tidying up error handling by leveraging thiserror. This approach makes your errors more uniform around the project. Well done!


4-15: Spot-on enumeration of metadata errors.
Defining clear variants such as MissingTransactionMetadata and ConflictingMetadataHash is top-drawer, ensuring that any future debugging is a piece of cake. The error messages are descriptive, which will help maintainers and users quickly figure out the cause of any validation errors.


18-45: Clear and cogent metadata validation.
The function neatly handles all combinations of missing or present auxiliary data and hash. Great job ensuring the mismatch scenario raises a distinct error. Consider adding a unit test to ensure all error paths are covered, especially around that mismatch.

Would you like me to generate a quick script to locate potential test coverage references for validate_metadata across your test suite?

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

31-31: Splendid re-export of Bytes.
Centralising re-exports keeps usage consistent throughout the codebase. Nicely done!


38-42: Extended re-exports for conway types.
Re-exporting TransactionBody in place of MintedTransactionBody is a good move, presumably to unify logic across modules. Just be sure to update all references in the ledger code to prevent any mix-ups with minted vs. regular transactions.


46-46: Handy hashing utilities added.
ComputeHash and OriginalHash will certainly come in handy. This approach matches the rest of the ledger’s design. Cheers for that!

crates/amaru-ledger/src/rules.rs (11)

2-2: Transaction module reveal.
Introducing mod transaction; signals your new validation structure. Watch out for cyclical dependencies if you expand the module further.


5-7: Expanded imports for block and transaction elements.
Looks like you’re using AuxiliaryData, TransactionBody, and others. Centralising them here helps keep references neat as a pin. Good show.


10-10: Modular block size validations.
Importing these helpers in a straightforward manner is delightful. Helps keep block validations well-organised.


16-24: New BlockValidationError enumerations.
Your error definitions, especially the descriptive Cascading rule violations variant, provide clarity for diagnosing tricky multi-level errors. Perfect for sleuthing out complicated ledger issues.


26-41: Concise RuleViolation coverage.
Capturing violations such as TooManyExUnitsBlock presents a thorough approach to ledger constraints. The structured message format helps ensure quick debugging.


42-55: Transaction-level rule checking.
TransactionRuleViolation elegantly enumerates concerns like referencing inputs twice (NonDisjointRefInputs) or small outputs. This kind of specificity is invaluable in diagnosing subtle transaction issues.


71-71: Seamless injection of block_body_size_valid.
You’re hooking the function in properly. Keep an eye on potential synergy with block_header_size_valid to maintain consistent checking.


95-101: Transacting with care.
Assigning transactions, raw_transactions, witness_sets, and invalid_transactions in separate lines is easy for the eyes. Maintaining clarity in these crucial sets helps keep the ledger code tidy.


106-138: Snake-charming iteration over transactions.
Neatly iterating over transactions and witness sets while accounting for invalid ones is well done. The typed variant referencing the original hash is an excellent detail for debugging.

It may be worth verifying if the block can contain zero witness sets or zero transactions for an edge case. Shall I prepare a script to cross-check your tests for this scenario?


143-155: validate_transaction with layered checks.
The structure is quite comprehensive, testing metadata, reference inputs, and output sizes. It might be wise to add further checks or guidance around _is_valid, if some transactions are expected to pass partial validations. But overall, smashing job!


203-209: Test coverage for BlockHeaderSizeTooBig.
The step where you confirm the rule violation is spot-on. This helps ensure your validation logic stays consistent if max header size changes in the future.

Comment on lines +7 to +8
// clean up clones, replace with lifetimes
#[allow(clippy::panic)]
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

TODO comment needs addressing

There's a comment indicating that clones should be replaced with lifetimes. This should be addressed before finalizing the PR.


🏁 Script executed:

#!/bin/bash
# Check if this comment appears elsewhere in the codebase
rg "clean up clones, replace with lifetimes" --type rust

Length of output: 158


Heads up – the "clean up clones, replace with lifetimes" comment still needs your attention!

It appears that the TODO comment remains in the file at crates/amaru-ledger/src/rules/transaction/output_size.rs (around lines 7-8). There’s no sign elsewhere in the codebase, confirming that the issue hasn't been touched on in this PR.

  • Please either implement the recommended refactoring to replace the clones with lifetimes or remove the comment if it’s no longer relevant.

Thanks for giving it a proper once-over, mate!

Comment on lines +9 to +34
pub fn validate_output_size(
transaction: &TransactionBody,
protocol_parameters: &ProtocolParameters,
) -> Result<(), TransactionRuleViolation> {
let coins_per_utxo_byte = protocol_parameters.coins_per_utxo_byte;
let outputs_too_small = transaction
.outputs
.clone()
.into_iter()
.filter(|output| {
let mut bytes = Vec::new();
cbor::encode(output, &mut bytes)
.unwrap_or_else(|_| panic!("Failed to serialize output"));

let lovelace = output_lovelace(output);

lovelace <= bytes.len() as u64 * coins_per_utxo_byte
})
.collect::<Vec<_>>();

if outputs_too_small.is_empty() {
Ok(())
} else {
Err(TransactionRuleViolation::OutputTooSmall { outputs_too_small })
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Function works but needs some polish

The function correctly validates output sizes, but there are a few issues to address:

  1. The .clone() operation is inefficient
  2. The unwrap_or_else with panic isn't ideal for error handling
  3. The comparison could be clearer with parentheses

Let's spruce this up a bit:

 // clean up clones, replace with lifetimes
-#[allow(clippy::panic)]
+/// Validates that transaction outputs contain enough lovelace based on their size.
+/// 
+/// According to the protocol parameters, each output must have at least
+/// (size_in_bytes * coins_per_utxo_byte) lovelace.
 pub fn validate_output_size(
     transaction: &TransactionBody,
     protocol_parameters: &ProtocolParameters,
 ) -> Result<(), TransactionRuleViolation> {
     let coins_per_utxo_byte = protocol_parameters.coins_per_utxo_byte;
     let outputs_too_small = transaction
         .outputs
-        .clone()
-        .into_iter()
+        .iter()
         .filter(|output| {
             let mut bytes = Vec::new();
-            cbor::encode(output, &mut bytes)
-                .unwrap_or_else(|_| panic!("Failed to serialize output"));
+            // This serialization should never fail for valid outputs
+            if let Err(e) = cbor::encode(output, &mut bytes) {
+                // Log the error or handle it more gracefully
+                return false; // Skip this output if we can't serialize it
+            }
 
             let lovelace = output_lovelace(output);
 
-            lovelace <= bytes.len() as u64 * coins_per_utxo_byte
+            lovelace <= (bytes.len() as u64 * coins_per_utxo_byte)
         })
+        .cloned() // Only clone the outputs that fail validation
         .collect::<Vec<_>>();
 
     if outputs_too_small.is_empty() {
         Ok(())
     } else {
         Err(TransactionRuleViolation::OutputTooSmall { outputs_too_small })
     }
 }

This change is like changing from a rusty old spanner to a shiny new wrench - it'll do the same job but more efficiently and with less chance of breaking!

📝 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 validate_output_size(
transaction: &TransactionBody,
protocol_parameters: &ProtocolParameters,
) -> Result<(), TransactionRuleViolation> {
let coins_per_utxo_byte = protocol_parameters.coins_per_utxo_byte;
let outputs_too_small = transaction
.outputs
.clone()
.into_iter()
.filter(|output| {
let mut bytes = Vec::new();
cbor::encode(output, &mut bytes)
.unwrap_or_else(|_| panic!("Failed to serialize output"));
let lovelace = output_lovelace(output);
lovelace <= bytes.len() as u64 * coins_per_utxo_byte
})
.collect::<Vec<_>>();
if outputs_too_small.is_empty() {
Ok(())
} else {
Err(TransactionRuleViolation::OutputTooSmall { outputs_too_small })
}
}
/// Validates that transaction outputs contain enough lovelace based on their size.
///
/// According to the protocol parameters, each output must have at least
/// (size_in_bytes * coins_per_utxo_byte) lovelace.
pub fn validate_output_size(
transaction: &TransactionBody,
protocol_parameters: &ProtocolParameters,
) -> Result<(), TransactionRuleViolation> {
let coins_per_utxo_byte = protocol_parameters.coins_per_utxo_byte;
let outputs_too_small = transaction
.outputs
.iter()
.filter(|output| {
let mut bytes = Vec::new();
// This serialization should never fail for valid outputs
if let Err(e) = cbor::encode(output, &mut bytes) {
// Log the error or handle it more gracefully
return false; // Skip this output if we can't serialize it
}
let lovelace = output_lovelace(output);
lovelace <= (bytes.len() as u64 * coins_per_utxo_byte)
})
.cloned() // Only clone the outputs that fail validation
.collect::<Vec<_>>();
if outputs_too_small.is_empty() {
Ok(())
} else {
Err(TransactionRuleViolation::OutputTooSmall { outputs_too_small })
}
}

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