Skip to content

Conversation

@rrruko
Copy link
Contributor

@rrruko rrruko commented Jan 8, 2026

Summary by CodeRabbit

  • New Features

    • Conway-era block, transaction, witness-set, and transaction-body representations added
    • New non-empty collection types: NonEmptySet and NonEmptyKeyValuePairs
  • Improvements

    • Robust CBOR decoding and stricter field validation for transaction bodies
    • New function-based original-hash retrieval replacing prior method usage
    • Public API consolidations and clearer conversion paths between minted/raw and owned types

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

Walkthrough

Introduces generic CBOR-backed pseudo-types for blocks, transactions, and transaction bodies; adds non-empty container types; adds conversions from minted (raw CBOR) variants to owned types; and replaces the OriginalHash trait/method with a get_original_hash function across dependent crates.

Changes

Cohort / File(s) Summary
Block types & conversions
crates/amaru-kernel/src/block.rs
Adds PseudoBlock<T...>, Block, MintedBlock<'b> and From<MintedBlock<'b>> for Block conversion using KeepRaw unwraps.
Transaction types & conversions
crates/amaru-kernel/src/transaction.rs, crates/amaru-kernel/src/transaction_body.rs
Adds PseudoTx<T...>/Tx/MintedTx<'b> and PseudoTransactionBody<T> with full CBOR decode pipeline; implements From<Minted...> conversions and get_original_hash helper.
Witness set types
crates/amaru-kernel/src/witness_set.rs
Adds WitnessSet, MintedWitnessSet<'b> and From<MintedWitnessSet<'b>> for WitnessSet mapping, handling KeepRaw fields.
Non-empty containers
crates/amaru-kernel/src/non_empty_key_value_pairs.rs, crates/amaru-kernel/src/non_empty_set.rs
Adds NonEmptyKeyValuePairs<K,V> (Def/Indef) and NonEmptySet<T> with CBOR Encode/Decode, conversions, and pallas interop (to_pallas/from_pallas).
Kernel public API surface
crates/amaru-kernel/src/lib.rs
Reworks re-exports: removes NonEmptyKeyValuePairs from pallas_codec utils, adds modules (non_empty_key_value_pairs, non_empty_set, transaction, transaction_body, block, witness_set), and introduces aliases PallasNonEmptyKeyValuePairs, PallasVotingProcedures, PallasRequiredSigners plus RequiredSigners/VotingProcedures type aliases.
Memoized transaction output
crates/amaru-kernel/src/memoized/transaction_output.rs
Switches to explicit use of pallas_primitives::NonEmptyKeyValuePairs and adds .to_pallas() conversion in serialization/deserialization paths.
Hash API migration (OriginalHash → get_original_hash)
crates/amaru-ledger/src/rules/... (multiple) , crates/amaru-plutus/src/script_context/...
Replaces imports and call sites from .original_hash() / OriginalHash to get_original_hash(&...) across ledger and plutus rule modules and tests.
Tests
crates/amaru-ledger/src/rules/transaction/phase_one/collateral.rs, ...
Comments out a "no-collateral" test case (Conway transactions cannot have empty collateral); updates tests to use get_original_hash.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jeluard
  • KtorZ

Poem

Ah, new types land like a slick film cut, mate —
Raw CBOR kept, then polished into state.
Hashes now called by a tidy new charm,
Non-empty sets stand firm and keep harm from harm.
🍀🎮 Code’s tighter now — roll credits, celebrate.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'pedantic conway txbody' is vague and lacks specificity about what changes are being made to the Conway transaction body implementation. Consider a more descriptive title like 'Refactor transaction body representation with generic pseudotypes and conversions' or 'Add Conway-era transaction body types with minted variants' to better convey the substantial changes being made.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
crates/amaru-kernel/src/non_empty_key_value_pairs.rs (1)

163-184: The wildcard arm might be unreachable in practice.

So here's the thing – if datatype returns something other than Map or MapIndef, the map_iter_with() call at line 167 will bail out with its own error before you ever reach the match statement. It's like having a bouncer AND a second bouncer – the first one already handled the dodgy cases.

Not a bug by any means – just defensive programming. The #[allow(clippy::wildcard_enum_match_arm)] is technically masking dead code, but if you're happy with the belt-and-suspenders approach, that's fair dinkum.

crates/amaru-kernel/src/non_empty_set.rs (2)

22-29: Good structure, but consider const over static for the tag.

G'day mate! The NonEmptySet type is looking solid—nice enforcement of non-empty semantics. One small nitpick: TAG_SET is a simple integer literal, so const would be more idiomatic than static here. It's like choosing between a hardcoded cheat code vs. a runtime lookup—const is fully inlined at compile time.

Also, PartialOrd on a set type is a bit unusual since sets typically don't have a natural ordering (unless you want subset semantics). Worth checking if this is intentional.

🔧 Suggested tweak
-static TAG_SET: u64 = 258;
+const TAG_SET: u64 = 258;

68-75: Consider adding IntoIterator for owned NonEmptySet<T> as well.

You've got the borrow iterator sorted, which is ace. But for completeness, an owned iterator (impl IntoIterator for NonEmptySet<T>) could be handy when you want to consume the set. Not blocking, just a nice-to-have like unlocking that extra character in a fighting game.

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

279-351: set_tx_body_field is solid, but consider returning a typed error.

The function returns Result<(), String>, which works but is a bit loose. A dedicated error enum could make error handling more robust downstream. That said, since this is an internal helper and errors are immediately converted to minicbor::decode::Error::message, it's not a blocker—more of a "nice-to-have" refactor.


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c7e12b and d58c8d9.

📒 Files selected for processing (6)
  • crates/amaru-kernel/src/block.rs
  • crates/amaru-kernel/src/non_empty_key_value_pairs.rs
  • crates/amaru-kernel/src/non_empty_set.rs
  • crates/amaru-kernel/src/transaction.rs
  • crates/amaru-kernel/src/transaction_body.rs
  • crates/amaru-kernel/src/witness_set.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/amaru-kernel/src/block.rs
  • crates/amaru-kernel/src/transaction.rs
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 370
File: crates/amaru-kernel/src/transaction_pointer.rs:36-44
Timestamp: 2025-08-08T14:35:35.562Z
Learning: In the amaru project, when decoding CBOR arrays, prefer using minicbor_extra::heterogenous_array with the expected length to validate definite-length arrays and correctly handle indefinite-length arrays. Example: crates/amaru-kernel/src/transaction_pointer.rs Decode should use heterogenous_array(d, 2, …) instead of ignoring the length from d.array().
📚 Learning: 2025-12-16T21:32:37.668Z
Learnt from: rkuhn
Repo: pragma-org/amaru PR: 584
File: crates/amaru-network/src/handshake/tests.rs:40-47
Timestamp: 2025-12-16T21:32:37.668Z
Learning: In Rust, shadowing a binding with a new let does not drop the previous binding until the end of the scope. All shadowed bindings in a scope are dropped in reverse-declaration order when the scope ends. Therefore, multiple let _guard = register_*() calls will keep all guards alive until the end of the function (or the surrounding scope). When reviewing code, be mindful that resources tied to shadowed bindings persist longer than the most recent binding; to release early, constrain the lifetime in an inner block or explicitly drop guards when appropriate.

Applied to files:

  • crates/amaru-kernel/src/witness_set.rs
  • crates/amaru-kernel/src/transaction_body.rs
  • crates/amaru-kernel/src/non_empty_set.rs
  • crates/amaru-kernel/src/non_empty_key_value_pairs.rs
📚 Learning: 2025-08-08T14:35:35.562Z
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 370
File: crates/amaru-kernel/src/transaction_pointer.rs:36-44
Timestamp: 2025-08-08T14:35:35.562Z
Learning: In the amaru project, when decoding CBOR arrays, prefer using minicbor_extra::heterogenous_array with the expected length to validate definite-length arrays and correctly handle indefinite-length arrays. Example: crates/amaru-kernel/src/transaction_pointer.rs Decode should use heterogenous_array(d, 2, …) instead of ignoring the length from d.array().

Applied to files:

  • crates/amaru-kernel/src/transaction_body.rs
🧬 Code graph analysis (3)
crates/amaru-kernel/src/witness_set.rs (1)
crates/amaru-kernel/src/non_empty_set.rs (1)
  • from (62-65)
crates/amaru-kernel/src/transaction_body.rs (3)
crates/amaru-kernel/src/non_empty_key_value_pairs.rs (2)
  • d (167-167)
  • decode (164-183)
crates/amaru-kernel/src/memoized/transaction_output.rs (2)
  • d (74-74)
  • decode (49-59)
crates/amaru-kernel/src/non_empty_set.rs (1)
  • decode (81-98)
crates/amaru-kernel/src/non_empty_key_value_pairs.rs (1)
crates/amaru-kernel/src/non_empty_set.rs (8)
  • decode (81-98)
  • try_from (52-58)
  • into_iter (72-74)
  • to_vec (32-34)
  • from_vec (36-38)
  • from (62-65)
  • deref (44-46)
  • encode (105-114)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: End-to-end snapshot tests (preview, 2, 680, true, 15, 60, 10.5.3)
  • GitHub Check: End-to-end snapshot tests (preprod, 1, 182, 15, 60, 10.5.3)
  • GitHub Check: Build x86_64/windows
  • GitHub Check: Build aarch64/macos
  • GitHub Check: Build riscv32
  • GitHub Check: Build x86_64/linux
  • GitHub Check: Build wasm32
  • GitHub Check: Build aarch64/linux
  • GitHub Check: Test the CLI
  • GitHub Check: Benches
  • GitHub Check: Test coverage
  • GitHub Check: clippy
🔇 Additional comments (23)
crates/amaru-kernel/src/non_empty_key_value_pairs.rs (6)

22-37: Solid enum design, mate!

The doc comment nicely explains the Vec rationale for maintaining ordering, which is crucial for CBOR canonicalization – like keeping your vinyl collection in proper order, you know? The serde attributes with try_from/into through Vec are spot-on for JSON interop, though worth noting this will flatten the Def/Indef distinction for non-CBOR formats. That's probably the intended behaviour here.


39-53: IntoIterator looks good!

Unlike a certain "choose your own adventure" game where you can't go back, this properly consumes self and yields an owning iterator. The match arms correctly extract and consume the inner Vec regardless of variant. Clean as a whistle!


55-85: Helper methods are consistent and well-designed.

Nice one! from_vec defaulting to Def is the right call since raw Vec doesn't carry any CBOR encoding info – it's like how a movie adaptation can't always capture every nuance of the book. The from_pallas/to_pallas conversions correctly preserve the variant, which is essential for interop. Good stuff!


116-141: TryFrom correctly preserves encoding variant.

This is crucial for maintaining CBOR round-trip fidelity – if the source was indefinite-length encoded, the result stays that way. It's like respecting the director's cut, yeah? Solid implementation.


143-156: Deref implementation is on point.

Standard pattern matching what's in non_empty_set.rs. Gives users access to all those lovely slice methods without extra ceremony.


186-219: Encode implementation nails the definite/indefinite distinction.

This is like the encoding equivalent of choosing between widescreen and IMAX – both valid, just different presentations. The Def variant writes a known length upfront with e.map(len), while Indef uses the break-terminated style with begin_map()/end(). Proper CBOR canonicalization preserved. Good work!

crates/amaru-kernel/src/non_empty_set.rs (5)

31-39: LGTM!

Clean and simple API for construction and extraction. The from_vec returning Option<Self> is a nice touch for safe construction.


41-59: LGTM!

The Deref to Vec<T> gives ergonomic read access, and the TryFrom impl returning the original Vec<T> on error is thoughtful—like getting your tokens back when the arcade machine rejects your coin!


61-66: LGTM!

Solid conversion from the KeepRaw-wrapped variant. This ties in nicely with the minted-to-owned conversion pattern I see in the other files.


77-99: Decode looks solid, but watch out for asymmetry with Encode.

Right, so the decode path accepts CBOR data with or without the 258 tag, which is correct for Conway era compatibility. However, the encode path (lines 101-115) always writes the tag. This asymmetry is fine if it's intentional (i.e., you want to normalize on output), but it means a round-trip won't preserve the original encoding if the input lacked the tag.

Just flagging this for awareness—if pedantic round-trip preservation is required, you'd need to track whether the tag was present. Otherwise, looks good to me!


101-115: LGTM!

The encode path is straightforward—tag first, then the inner vec. Nicely symmetric with the decode logic's expectation.

crates/amaru-kernel/src/transaction_body.rs (8)

23-86: Struct looks comprehensive for Conway era!

Fair dinkum, this is a proper Conway-era transaction body with all the new governance fields (voting_procedures, proposal_procedures, treasury_value, donation). The field indices align with the Cardano CDDL spec. Nice work!

One observation: field index 6 is skipped (jumping from 5 to 7), which is correct since field 6 was update in pre-Conway eras and is removed. Just confirming that's intentional.


90-112: TxBodyField enum is well-structured.

The enum variants mirror the struct fields nicely. The Option wrapping for optional fields is appropriate.


114-208: Field key 6 and 10/12 are intentionally skipped—looks correct.

The decode function correctly maps keys 0-22 (minus 6, 10, 12) to their respective fields. Keys 6, 10, and 12 were used in earlier eras but aren't part of Conway transaction bodies.

However, if someone sends CBOR with keys 6, 10, or 12, this will return an error. That's probably the right call for strict Conway validation, but worth documenting if interop with pre-Conway data is ever needed.


250-277: LGTM!

Clean factory function for building the base transaction body with required fields. All optional fields default to None as expected.


353-407: Decode impl handles required fields and duplicates well!

Ripper work here, mate! The decode logic:

  1. Requires inputs, outputs, and fee (lines 360-374) ✓
  2. Rejects duplicate keys (lines 376-381) ✓
  3. Validates non-empty mint (lines 402-404) ✓

The comment on lines 393-395 about using a NonEmpty Vec type to eliminate the unreachable branch is a fair point. Could be a future enhancement, but the current code is correct.


409-440: LGTM on the conversions and hash helper!

The From<MintedTransactionBody> impl correctly maps all fields, including the collateral_return which needs special handling. The get_original_hash function is a clean utility for deriving the CBOR hash from raw bytes.


442-584: Excellent test coverage!

Love it—you've got tests for:

  • Happy path decode (decode_simple_tx)
  • Missing required fields (reject_empty_tx, reject_tx_missing_outputs, reject_tx_missing_fee)
  • Empty non-empty containers (reject_empty_present_mint, reject_empty_present_certs, etc.)
  • Duplicate keys (reject_duplicate_keys)

This is like having a full QA playthrough before the game ships. The test at line 454 has a redundant type annotation (let tx: TransactionBody = tx;), but it's harmless.


210-248: No action needed—the code correctly handles indefinite map breaks.

The pattern of checking Type::Break followed by d.skip()? is the established and correct approach throughout the codebase, including in the minicbor-extra helpers. The skip() method properly consumes break markers in indefinite-length structures.

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

15-21: LGTM!

Clean imports and a straightforward re-export of BootstrapWitness for downstream consumers.


23-49: WitnessSet struct is well-defined!

All witness types are wrapped in Option<NonEmptySet<...>> which enforces non-empty semantics at the type level. The field indices (0-7) match the expected CBOR map keys for Conway witness sets.


79-92: LGTM on the conversion!

The From<MintedWitnessSet> impl correctly unwraps the KeepRaw wrappers. I see native_script and plutus_data use .map(Into::into) which leverages the From<NonEmptySet<KeepRaw<'_, T>>> for NonEmptySet<T> impl from non_empty_set.rs. The redeemer uses .map(|x| x.unwrap()) since it's a single KeepRaw rather than a set.

Solid work, like perfectly executing a combo in Street Fighter!


51-77: Check the inconsistency on #[b(4)] vs #[n(...)] — Right so, I clocked this one right. Your plutus_data field on line 66 is rocking #[b(4)] while the structurally identical native_script on line 58 (also a NonEmptySet<KeepRaw<'b, ...>>) is using #[n(1)]. In minicbor, #[b] means "borrow from the CBOR input stream" whereas #[n] just assigns the map index — they're fundamentally different operations.

The head-scratcher here is that both fields have exactly the same type structure yet different attributes. If borrowing is needed for PlutusData, it'd make sense to apply it to NativeScript too. If not, plutus_data should probably just be #[n(4)] for consistency.

Need a call on whether the #[b] is intentional (and if so, why only this field) or if it should align with the others.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rrruko rrruko force-pushed the rrruko/pedantic-txbody-2 branch from 4e1bd3b to e3fbb2f Compare January 8, 2026 00:32
@rrruko rrruko force-pushed the rrruko/pedantic-txbody-2 branch from e3fbb2f to 2c7e12b Compare January 8, 2026 00:37
Copy link
Contributor

@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

🧹 Nitpick comments (4)
crates/amaru-ledger/src/rules/transaction/phase_one/collateral.rs (1)

331-336: G'day mate! Time to say goodbye to this commented-out code.

Right, so I see you've left this test case commented out like a save file you're not sure you'll need again. While the explanation on line 331 is absolutely brilliant documentation (seriously, it's clearer than the plot of Inception), keeping the commented test code itself is a bit like leaving debug console.logs in production – it's clutter that doesn't serve us anymore.

Since Conway transactions now enforce non-empty collateral at the CBOR decode level, this test scenario literally can't happen for Conway era. The validation logic at lines 210-212 is still needed for backwards compatibility with earlier eras (Alonzo, Babbage), but this specific test is kaput.

Recommendation: Keep the explanatory comment (line 331) but remove the commented test code (lines 332-336). If you want to preserve this for historical context, a proper doc comment or commit message would be more appropriate than zombie code.

♻️ Proposed cleanup
-    // This tx now fails to decode because conway txes cannot have empty collateral
-    //#[test_case(
-    //    fixture!("3b13b5c319249407028632579ee584edc38eaeb062dac5156437a627d126fbb1", "no-collateral") =>
-    //    matches Err(InvalidCollateral::NoCollateral);
-    //    "no collateral"
-    //)]
+    // Note: The "no-collateral" test case was removed because Conway transactions
+    // cannot have empty collateral (enforced at CBOR decode time). The NoCollateral
+    // error variant is still used for pre-Conway eras (Alonzo, Babbage).
crates/amaru-kernel/src/non_empty_set.rs (1)

10-15: Consider enforcing set semantics or clarifying the doc comment, mate.

The doc comment on line 13 says the vec "should contain no duplicates" but there's no validation in from_vec (line 22) or TryFrom<Vec<T>> (line 38) to enforce this. It's like calling something a "set" but letting duplicates slide through like a bouncer on smoko.

If set semantics (no duplicates) are critical for correctness, consider adding validation. This would require T: Eq + Hash bounds and checking during construction. Alternatively, if duplicates are acceptable in practice and the caller is responsible for ensuring uniqueness, update the doc to say "may contain duplicates" or "caller must ensure no duplicates" to avoid confusion.

♻️ Option 1: Add duplicate validation (requires additional bounds)
+use std::collections::HashSet;
+
 /// Non-empty Set
 ///
-/// Optional 258 tag (until era after Conway, at which point is it required)
-/// with a vec of items which should contain no duplicates
+/// Optional 258 tag (until era after Conway, at which point is it required)
+/// with a vec of items which must contain no duplicates
 #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Serialize, Deserialize)]
 pub struct NonEmptySet<T>(Vec<T>);

-impl<T> NonEmptySet<T> {
+impl<T: Eq + Hash> NonEmptySet<T> {
     pub fn from_vec(x: Vec<T>) -> Option<Self> {
-        if x.is_empty() { None } else { Some(Self(x)) }
+        if x.is_empty() {
+            None
+        } else {
+            let set: HashSet<_> = x.iter().collect();
+            if set.len() != x.len() {
+                None  // or return Result with descriptive error
+            } else {
+                Some(Self(x))
+            }
+        }
     }
 }
♻️ Option 2: Clarify documentation if duplicates are caller's responsibility
 /// Non-empty Set
 ///
 /// Optional 258 tag (until era after Conway, at which point is it required)
-/// with a vec of items which should contain no duplicates
+/// with a vec of items. Callers are responsible for ensuring no duplicates.
 #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Serialize, Deserialize)]
 pub struct NonEmptySet<T>(Vec<T>);
crates/amaru-kernel/src/block.rs (1)

49-64: Consider using into_iter() to avoid extra clones

The current pattern .iter().cloned().map(|x| x.unwrap()) creates intermediate clones before consuming. Since you're taking ownership of x anyway, you could use .into_iter() directly—like skipping the tutorial and going straight to the action, ya know?

♻️ Optional simplification
         Block {
             header: x.header.unwrap().into(),
             transaction_bodies: pallas_primitives::MaybeIndefArray::Def(
                 x.transaction_bodies
-                    .iter()
-                    .cloned()
+                    .into_iter()
                     .map(|x| x.unwrap())
                     .map(TransactionBody::from)
                     .collect(),
             ),
             transaction_witness_sets: pallas_primitives::MaybeIndefArray::Def(
                 x.transaction_witness_sets
-                    .iter()
-                    .cloned()
+                    .into_iter()
                     .map(|x| x.unwrap())
                     .map(WitnessSet::from)
                     .collect(),
             ),
crates/amaru-kernel/src/transaction_body.rs (1)

361-387: Minor: Redundant processing of required fields

The loop on lines 361-387 processes all entries including keys 0, 1, 2 (inputs, outputs, fee), which were already extracted on lines 346-354 to create the basic tx body. This means set_tx_body_field is called again for these fields, overwriting with the same values.

This isn't buggy—it's just a wee bit redundant, like watching the intro cutscene you can't skip. Could add a filter to skip keys 0, 1, 2 in the loop if you wanted to tidy it up.

♻️ Optional: Skip already-processed fields
-        for (key, val) in entries {
+        for (key, val) in entries.into_iter().filter(|(k, _)| !matches!(k, 0 | 1 | 2)) {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f11305 and 2c7e12b.

📒 Files selected for processing (17)
  • crates/amaru-kernel/src/block.rs
  • crates/amaru-kernel/src/lib.rs
  • crates/amaru-kernel/src/memoized/transaction_output.rs
  • crates/amaru-kernel/src/non_empty_key_value_pairs.rs
  • crates/amaru-kernel/src/non_empty_set.rs
  • crates/amaru-kernel/src/transaction.rs
  • crates/amaru-kernel/src/transaction_body.rs
  • crates/amaru-kernel/src/witness_set.rs
  • crates/amaru-ledger/src/rules/block.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/collateral.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/mod.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/proposals.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/vkey_witness.rs
  • crates/amaru-ledger/src/rules/transaction/phase_two/mod.rs
  • crates/amaru-plutus/src/script_context/mod.rs
  • crates/amaru-plutus/src/script_context/v1.rs
  • crates/amaru-plutus/src/script_context/v3.rs
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 370
File: crates/amaru-kernel/src/transaction_pointer.rs:36-44
Timestamp: 2025-08-08T14:35:35.562Z
Learning: In the amaru project, when decoding CBOR arrays, prefer using minicbor_extra::heterogenous_array with the expected length to validate definite-length arrays and correctly handle indefinite-length arrays. Example: crates/amaru-kernel/src/transaction_pointer.rs Decode should use heterogenous_array(d, 2, …) instead of ignoring the length from d.array().
Learnt from: abailly
Repo: pragma-org/amaru PR: 75
File: crates/amaru/src/consensus/mod.rs:164-165
Timestamp: 2025-02-03T11:15:22.640Z
Learning: In the Amaru project, chain selection operations (roll_forward and rollback) should use separate result types to leverage the type system for preventing impossible states, rather than using runtime checks or panics.
Learnt from: jeluard
Repo: pragma-org/amaru PR: 387
File: crates/amaru-stores/src/lib.rs:40-40
Timestamp: 2025-08-20T13:02:25.763Z
Learning: In the amaru-stores crate, amaru_slot_arithmetic types like Epoch and EraHistory are used throughout the main crate code in modules like in_memory/mod.rs, rocksdb/consensus.rs, and rocksdb/ledger/columns/, not just in tests. This means amaru-slot-arithmetic should be a regular dependency, not a dev-dependency.
📚 Learning: 2025-12-16T21:32:37.668Z
Learnt from: rkuhn
Repo: pragma-org/amaru PR: 584
File: crates/amaru-network/src/handshake/tests.rs:40-47
Timestamp: 2025-12-16T21:32:37.668Z
Learning: In Rust, shadowing a binding with a new let does not drop the previous binding until the end of the scope. All shadowed bindings in a scope are dropped in reverse-declaration order when the scope ends. Therefore, multiple let _guard = register_*() calls will keep all guards alive until the end of the function (or the surrounding scope). When reviewing code, be mindful that resources tied to shadowed bindings persist longer than the most recent binding; to release early, constrain the lifetime in an inner block or explicitly drop guards when appropriate.

Applied to files:

  • crates/amaru-ledger/src/rules/transaction/phase_one/collateral.rs
  • crates/amaru-plutus/src/script_context/v3.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/vkey_witness.rs
  • crates/amaru-kernel/src/block.rs
  • crates/amaru-ledger/src/rules/block.rs
  • crates/amaru-kernel/src/non_empty_key_value_pairs.rs
  • crates/amaru-plutus/src/script_context/v1.rs
  • crates/amaru-kernel/src/transaction.rs
  • crates/amaru-kernel/src/transaction_body.rs
  • crates/amaru-kernel/src/witness_set.rs
  • crates/amaru-plutus/src/script_context/mod.rs
  • crates/amaru-kernel/src/memoized/transaction_output.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/mod.rs
  • crates/amaru-ledger/src/rules/transaction/phase_two/mod.rs
  • crates/amaru-kernel/src/non_empty_set.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/proposals.rs
  • crates/amaru-kernel/src/lib.rs
📚 Learning: 2025-09-29T16:44:14.807Z
Learnt from: rkuhn
Repo: pragma-org/amaru PR: 471
File: crates/amaru-network/src/protocol.rs:94-106
Timestamp: 2025-09-29T16:44:14.807Z
Learning: In the amaru-network crate protocol.rs file, the correct Cardano mini-protocol ID assignments are: PROTO_N2N_KEEP_ALIVE = 8 and PROTO_N2N_PEER_SHARE = 10, as verified against the network specification by the maintainer.

Applied to files:

  • crates/amaru-plutus/src/script_context/v3.rs
  • crates/amaru-ledger/src/rules/block.rs
📚 Learning: 2025-08-23T15:44:44.318Z
Learnt from: rkuhn
Repo: pragma-org/amaru PR: 392
File: crates/ouroboros-traits/src/is_header/fake.rs:52-88
Timestamp: 2025-08-23T15:44:44.318Z
Learning: In the Amaru project, FakeHeader in crates/ouroboros-traits/src/is_header/fake.rs is specifically designed for testing purposes, and panic behavior (like copy_from_slice() panicking on malformed hex) is the preferred approach rather than graceful error handling, as it helps identify test data issues quickly during development.

Applied to files:

  • crates/amaru-ledger/src/rules/transaction/phase_one/vkey_witness.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/proposals.rs
📚 Learning: 2025-08-08T14:43:20.218Z
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 370
File: crates/amaru-kernel/src/point.rs:45-52
Timestamp: 2025-08-08T14:43:20.218Z
Learning: In crates/amaru-kernel/src/point.rs, Point::Specific stores a header hash that is always exactly 32 bytes by project invariant. Therefore, converting it with `impl From<&Point> for Hash<32>` without a runtime length check is acceptable; future reviews should not request an error-returning check inside this `From` impl. If validation is ever desired, it should be done via `TryFrom` or upstream constructors.

Applied to files:

  • crates/amaru-ledger/src/rules/transaction/phase_one/vkey_witness.rs
📚 Learning: 2025-08-20T13:02:25.763Z
Learnt from: jeluard
Repo: pragma-org/amaru PR: 387
File: crates/amaru-stores/src/lib.rs:40-40
Timestamp: 2025-08-20T13:02:25.763Z
Learning: In the amaru-stores crate, amaru_slot_arithmetic types like Epoch and EraHistory are used throughout the main crate code in modules like in_memory/mod.rs, rocksdb/consensus.rs, and rocksdb/ledger/columns/, not just in tests. This means amaru-slot-arithmetic should be a regular dependency, not a dev-dependency.

Applied to files:

  • crates/amaru-ledger/src/rules/block.rs
  • crates/amaru-plutus/src/script_context/mod.rs
  • crates/amaru-ledger/src/rules/transaction/phase_two/mod.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/proposals.rs
  • crates/amaru-kernel/src/lib.rs
📚 Learning: 2025-09-29T20:08:29.906Z
Learnt from: yHSJ
Repo: pragma-org/amaru PR: 453
File: crates/amaru-metrics/src/ledger.rs:60-104
Timestamp: 2025-09-29T20:08:29.906Z
Learning: In amaru-ledger/src/state.rs, the txs_processed field represents the number of transactions in the current block (block.transaction_bodies.len() as u64), not a cumulative total. Therefore, using a Counter with .add() in the metrics is correct for accumulating per-block transaction counts.

Applied to files:

  • crates/amaru-ledger/src/rules/block.rs
📚 Learning: 2025-08-08T14:35:35.562Z
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 370
File: crates/amaru-kernel/src/transaction_pointer.rs:36-44
Timestamp: 2025-08-08T14:35:35.562Z
Learning: In the amaru project, when decoding CBOR arrays, prefer using minicbor_extra::heterogenous_array with the expected length to validate definite-length arrays and correctly handle indefinite-length arrays. Example: crates/amaru-kernel/src/transaction_pointer.rs Decode should use heterogenous_array(d, 2, …) instead of ignoring the length from d.array().

Applied to files:

  • crates/amaru-kernel/src/non_empty_key_value_pairs.rs
  • crates/amaru-kernel/src/transaction_body.rs
  • crates/amaru-ledger/src/rules/transaction/phase_one/proposals.rs
  • crates/amaru-kernel/src/lib.rs
📚 Learning: 2025-08-08T14:39:50.527Z
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 370
File: crates/amaru-kernel/src/borrowed_datum.rs:32-39
Timestamp: 2025-08-08T14:39:50.527Z
Learning: In the amaru project, when converting BorrowedDatumOption::Data to an owned DatumOption in crates/amaru-kernel/src/borrowed_datum.rs, the call `.unwrap()` refers to pallas’s KeepRaw::unwrap, which is infallible (always returns the inner value) and is not a panic risk. Future reviews should not flag this unwrap as dangerous.

Applied to files:

  • crates/amaru-kernel/src/memoized/transaction_output.rs
📚 Learning: 2025-08-08T14:46:53.013Z
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 370
File: crates/amaru-kernel/src/pool_params.rs:107-116
Timestamp: 2025-08-08T14:46:53.013Z
Learning: In crates/amaru-kernel/src/pool_params.rs, when serializing Relay::SingleHostAddr IPv6 to text, the project intentionally reverses each 4-byte chunk before constructing std::net::Ipv6Addr. This matches cardano-ledger’s IPv6 representation (four little-endian Word32 chunks). Do not “simplify” by passing the raw 16 bytes directly to Ipv6Addr::from; that would break ledger compatibility.

Applied to files:

  • crates/amaru-kernel/src/memoized/transaction_output.rs
📚 Learning: 2025-08-08T14:34:06.105Z
Learnt from: KtorZ
Repo: pragma-org/amaru PR: 370
File: crates/minicbor-extra/src/lib.rs:50-55
Timestamp: 2025-08-08T14:34:06.105Z
Learning: Project uses Rust 1.88 stable; using Result::is_err_and is acceptable and should not be flagged as unstable. In particular, crates/minicbor-extra/src/lib.rs relies on is_err_and when checking end_of_input; future reviews should not suggest replacing it for stability reasons.

Applied to files:

  • crates/amaru-kernel/src/lib.rs
🧬 Code graph analysis (13)
crates/amaru-plutus/src/script_context/v3.rs (1)
crates/amaru-kernel/src/transaction_body.rs (1)
  • get_original_hash (424-426)
crates/amaru-ledger/src/rules/transaction/phase_one/vkey_witness.rs (1)
crates/amaru-kernel/src/transaction_body.rs (1)
  • get_original_hash (424-426)
crates/amaru-kernel/src/block.rs (3)
crates/amaru-kernel/src/transaction.rs (1)
  • from (37-44)
crates/amaru-kernel/src/transaction_body.rs (1)
  • from (398-421)
crates/amaru-kernel/src/witness_set.rs (1)
  • from (66-77)
crates/amaru-ledger/src/rules/block.rs (2)
crates/amaru-kernel/src/transaction_body.rs (1)
  • get_original_hash (424-426)
crates/amaru-ledger/src/state.rs (1)
  • network (224-226)
crates/amaru-plutus/src/script_context/v1.rs (1)
crates/amaru-kernel/src/transaction_body.rs (1)
  • get_original_hash (424-426)
crates/amaru-kernel/src/transaction.rs (2)
crates/amaru-kernel/src/transaction_body.rs (1)
  • from (398-421)
crates/amaru-kernel/src/witness_set.rs (1)
  • from (66-77)
crates/amaru-kernel/src/transaction_body.rs (3)
crates/amaru-kernel/src/non_empty_key_value_pairs.rs (3)
  • d (153-153)
  • decode (150-169)
  • from (78-83)
crates/amaru-kernel/src/non_empty_set.rs (2)
  • decode (67-84)
  • from (48-51)
crates/amaru-kernel/src/transaction.rs (1)
  • from (37-44)
crates/amaru-plutus/src/script_context/mod.rs (2)
crates/amaru-kernel/src/lib.rs (6)
  • from (304-306)
  • from (327-329)
  • from (365-367)
  • from (533-538)
  • from (542-549)
  • from (601-609)
crates/amaru-kernel/src/non_empty_key_value_pairs.rs (3)
  • from (78-83)
  • try_from (93-99)
  • try_from (109-126)
crates/amaru-kernel/src/memoized/transaction_output.rs (1)
crates/amaru-kernel/src/non_empty_key_value_pairs.rs (3)
  • try_from (93-99)
  • try_from (109-126)
  • from_vec (50-56)
crates/amaru-ledger/src/rules/transaction/phase_one/mod.rs (1)
crates/amaru-kernel/src/transaction_body.rs (1)
  • get_original_hash (424-426)
crates/amaru-ledger/src/rules/transaction/phase_two/mod.rs (1)
crates/amaru-kernel/src/transaction_body.rs (1)
  • get_original_hash (424-426)
crates/amaru-kernel/src/non_empty_set.rs (1)
crates/amaru-kernel/src/non_empty_key_value_pairs.rs (10)
  • decode (150-169)
  • to_vec (46-48)
  • from_vec (50-56)
  • deref (136-141)
  • try_from (93-99)
  • try_from (109-126)
  • from (78-83)
  • into_iter (33-38)
  • d (153-153)
  • encode (177-204)
crates/amaru-ledger/src/rules/transaction/phase_one/proposals.rs (1)
crates/amaru-kernel/src/transaction_body.rs (1)
  • get_original_hash (424-426)
🔇 Additional comments (41)
crates/amaru-kernel/src/non_empty_set.rs (3)

63-85: Nice work on the optional tag handling, champion!

The decode logic correctly handles the optional 258 tag (lines 69-75) while also validating non-empty constraint (lines 79-81). This is spot-on for the transition period before the tag becomes required in post-Conway eras. It's like having a backup plan that's also your main plan – proper belt-and-braces stuff.


87-101: Encode always writes the tag – intentional and future-proof.

The encode implementation always writes the TAG_SET (258) tag on line 96, even though the comment on line 12 notes it's "optional" until post-Conway. This asymmetry with decode (which handles it as optional) is actually solid forward-compatibility design – like packing an umbrella even when it's sunny. When the tag becomes required in future eras, this code won't need changes.


27-61: LGTM – solid boilerplate implementations!

The Deref, TryFrom, From, and IntoIterator implementations are all implemented correctly and follow idiomatic Rust patterns. The TryFrom returning the original Vec as the error type (line 36) is particularly nice – lets callers recover their data rather than losing it to the void.

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

48-48: Refactoring the exports – fair dinkum!

Removing NonEmptyKeyValuePairs from the pallas_codec::utils re-export (line 48) makes sense since it's now exported via its own dedicated module (lines 127-128). This keeps the module organization tidy and aligns with the PR's goal of consolidating non-empty container types.


62-71: Nice namespace hygiene with the Pallas prefixes!

Adding explicit aliases like PallasNonEmptyKeyValuePairs, PallasRequiredSigners, and PallasVotingProcedures (lines 64-71) creates clear separation between pallas re-exports and amaru's own wrapper types. The RedeemersKey as RedeemerKey rename also fixes the plurality – it's one key, not multiple keys, innit?


127-131: Module organization looking crisp!

The new module declarations for non_empty_key_value_pairs (line 127) and non_empty_set (line 130) with their corresponding public re-exports follow the established pattern in this file. Clean and consistent module organization – like Marie Kondo visited the codebase.


179-189: Type aliases add semantic clarity – bonzer!

The new module declarations (lines 179-189) and type aliases (lines 1040-1043) strengthen the type system by giving semantic names to structured types. RequiredSigners = NonEmptySet<AddrKeyhash> clearly communicates intent, and the nested VotingProcedures structure properly models the domain's non-empty constraints. This is exactly the kind of type-level documentation that makes codebases self-documenting.

Also applies to: 1040-1043

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

19-20: Clean refactor from trait to function, mate!

The import changes (lines 19-20) replacing OriginalHash trait with get_original_hash function are spot on. This simplifies the API by using a free function instead of requiring trait implementation, making it easier to use in generic contexts. Like swapping a Swiss Army knife for a purpose-built tool.


126-126: Correct usage of the new hash function!

The call to get_original_hash(transaction_body) on line 126 is correct. The function expects &KeepRaw<'a, T> and transaction_body is already &KeepRaw<'_, MintedTransactionBody<'_>> (from the function signature on line 76), so this passes the reference correctly. Should produce identical results to the previous transaction_body.original_hash() call.

crates/amaru-plutus/src/script_context/v1.rs (2)

285-285: Import addition looks sound, cobber!

Adding get_original_hash to the imports (line 285) is necessary for the usage in the test. The placement in the import list is logical alongside other kernel utilities.


326-326: Test updated correctly with the new API!

The call to get_original_hash(&transaction.transaction_body) on line 326 is correct. The transaction.transaction_body field is of type KeepRaw<'_, MintedTransactionBody<'_>>, and taking a reference (&) produces &KeepRaw<...> which matches the get_original_hash signature perfectly. The test should behave identically to before.

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

17-18: Consistent refactor across the codebase – good stuff!

The import changes (lines 17-18) follow the same pattern as the other modules in this PR. Removing the OriginalHash trait and adding the get_original_hash function creates consistency across the ledger and plutus crates. Like getting the band back together with matching outfits.


104-104: Proper usage of get_original_hash in phase one!

Line 104 correctly calls get_original_hash(&transaction_body) where transaction_body is KeepRaw<'_, MintedTransactionBody<'_>> (from line 97). The reference is taken and passed to get_original_hash which expects &KeepRaw<'a, T>, so this matches perfectly. The transaction_id will be computed correctly, ready for use in the validation logic below.

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

23-23: Solid import update, mate!

Right, so you've swapped the OriginalHash trait for the shiny new get_original_hash function. This is textbook API evolution - moving from a trait method to a standalone function. Cleaner and more flexible, like upgrading from a Nokia 3310 to something that can actually run apps.


214-214: LGTM - Textbook API migration!

You've nailed the transition here. The function call get_original_hash(&transaction) is spot-on with the signature expecting &KeepRaw<'a, T>. Same hash, new API - it's like when they rebooted Spider-Man but kept the web-slinging intact.

crates/amaru-plutus/src/script_context/v3.rs (2)

644-644: Test import looking crisp!

Adding get_original_hash to your test imports - no dramas here. It's all contained in the test module, so you're not polluting the production namespace. Good hygiene, like washing your hands after touching public transport poles.


687-687: Test code transition looks bonza!

You're drilling down to &transaction.transaction_body before getting the hash - that's the right move for the Plutus test harness. The TxInfo construction stays the same, just with a new hash supplier. It's like changing the oil in your car - same engine, fresh lubricant.

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

74-74: Import refactor on point!

Your test imports are getting the get_original_hash treatment. Keeping it consistent with the rest of the PR - I appreciate when a refactor doesn't try to reinvent the wheel in every file. It's like following the same map in Dark Souls; you know where the traps are.


128-128: Proposal test migration is mint!

You're constructing that transaction tuple with get_original_hash(&tx) - clean as. The function signature expects (TransactionId, TransactionPointer) and you're delivering the goods. This is the kind of mechanical refactor that makes me happy, like when all the Git conflicts resolve themselves.

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

130-130: Test import addition looking solid!

Adding get_original_hash to the vkey witness test imports - you're following the same refactoring pattern across the board. It's like speedrunning a game after you've learned all the routes; efficient and satisfying to watch.


292-292: Bootstrap witness test looking grouse!

You're computing the transaction_id with get_original_hash(&tx) before passing it to the witness validation. The signature verification needs the original hash to work, and you're serving it up fresh. It's like having the right key for the right lock - simple but essential.

crates/amaru-kernel/src/memoized/transaction_output.rs (2)

213-252: Type system alignment looking deliberate!

Right, so you're explicitly qualifying pallas_primitives::NonEmptyKeyValuePairs in the legacy value conversion path. This tells me there's likely both a local wrapper type and the Pallas primitive type floating around, and you need to be crystal clear about which one you're using.

The explicit type annotation at line 248 and the construction pattern are intentional - you're making sure the Value::Multiasset gets the Pallas-flavored pairs. It's like specifying you want the imported beer, not the local brew - sometimes you gotta be specific about what you're after.


316-322: Conversion pipeline looking choice!

You've set up a nice little assembly line here: Vec -> NonEmptyKeyValuePairs -> .to_pallas() -> pallas_primitives::NonEmptyKeyValuePairs. The .to_pallas() call at line 320 is the money shot - that's your bridge between the internal wrapper type and the external Pallas representation.

The error handling with .ok_or for empty bundles is good defensive coding. Can't have a non-empty collection that's... empty. That'd be like a decaf coffee - defeats the whole purpose.

crates/amaru-kernel/src/transaction.rs (2)

8-26: G'day mate! Looking solid here 🍀

The PseudoTx struct is cleanly laid out with proper CBOR field annotations. The generic approach with KeepRaw wrappers for the minted variant is a nice pattern—like having your save game state preserved while still being able to load it into a new engine.

One small nit: using std::clone::Clone in the where bounds (lines 11-13) is a tad verbose. You could just use Clone since it's in the prelude. Not a biggie though, more of a "would ya look at that" moment.


36-45: Conversion logic looks spot on!

This From implementation follows the same unwrap-and-convert pattern as the other minted-to-owned conversions in witness_set.rs and transaction_body.rs. It's like converting your raw footage to the final cut—preserving what matters.

crates/amaru-kernel/src/block.rs (2)

7-28: Nicely structured generic block type! 🎮

The PseudoBlock design is clean. I notice only T4 has the Clone bound at the struct level (line 10). This works because the derive macros will add the necessary bounds, but it's a bit asymmetric compared to how PseudoTx handles it.

Also, line 15 uses #[b(1)] while the others use #[n(x)]—I assume this is intentional for borrowing semantics during CBOR encoding?

Could you confirm the #[b(1)] annotation is intentional here? It differs from the other field annotations.


49-56: Minor: Always using Def variant

I see the conversion always wraps in MaybeIndefArray::Def, even if the original was Indef. This is probably fine for the owned representation since the raw CBOR is preserved in MintedBlock for hashing. Just flagging it in case preserving that distinction matters downstream.

crates/amaru-plutus/src/script_context/mod.rs (2)

20-27: Clean aliasing for type disambiguation 👍

Good call on renaming the imports with Pallas* prefixes. It's like giving your characters distinct names in an RPG—makes it crystal clear which NonEmptyKeyValuePairs you're dealing with when both the kernel and Pallas have their own versions.


955-959: Implementation signatures updated consistently

All the From and TryFrom implementations have been updated to use the new Pallas* type aliases. The actual conversion logic remains unchanged—just the type signatures. Like a texture pack update without touching the gameplay mechanics.

Also applies to: 1039-1044, 1137-1138

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

14-23: Well-designed non-empty container type 🎯

Nice work on the NonEmptyKeyValuePairs enum! The Def/Indef variants preserve the CBOR encoding style, which is important for round-trip fidelity. The serde attributes for try_from and into Vec are spot on—keeps the serialization layer simple while enforcing invariants.


144-170: CBOR Decode implementation looks solid

The decode flow is correct—you grab the datatype first to distinguish Map vs MapIndef, then consume the items and validate non-emptiness. The error messages are descriptive too. Like having proper error screens in your game instead of just crashing to desktop!

One thing to note: the learning from the project suggests using minicbor_extra::heterogenous_array for array decoding, but since this is a map structure (not an array), the current approach is appropriate.


172-205: Encode implementation mirrors decode correctly

The encoding properly distinguishes between definite-length (Def) and indefinite-length (Indef) maps. The symmetry with the decoder is on point—what goes in, comes out the same way.

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

9-35: Clean witness set structure with CBOR map encoding

The WitnessSet struct is well-organized with proper optional NonEmptySet wrappers for the witness components. Using #[cbor(map)] is the right call for this kind of structure.


52-53: Question on #[b(4)] annotation

I notice plutus_data uses #[b(4)] while other fields use #[n(x)]. Same pattern as transaction_bodies in block.rs. Is this for special borrowing semantics with KeepRaw? Just want to make sure it's intentional and not a typo, mate.


65-78: From implementation handles all the KeepRaw unwrapping correctly

The conversion logic correctly:

  • Copies fields that aren't wrapped in KeepRaw directly
  • Uses .map(Into::into) for NonEmptySet<KeepRaw<..>> fields
  • Uses .map(|x| x.unwrap()) for the redeemer which is KeepRaw<Redeemers>

This matches the established pattern in the codebase. Nice and consistent!

crates/amaru-kernel/src/transaction_body.rs (6)

9-72: Comprehensive Conway transaction body structure

This is a proper beast of a struct, covering all the Conway-era transaction body fields including the new governance additions (voting_procedures, proposal_procedures, treasury_value, donation). The CBOR field indices match the Cardano spec. Well done, like completing all the side quests!


100-194: Field decoder covers all Conway transaction body keys

The decode_tx_body_field function handles all valid field indices properly. The skipped indices (6, 10, 12) are correctly not part of the Conway transaction body spec. Unknown keys return an error—fail fast, just like a game that tells you when you're doing something wrong instead of silently corrupting your save.


200-234: TxBodyFields collector handles both map lengths correctly

The decode implementation handles both definite and indefinite-length CBOR maps. Accumulating values per key into a Vec enables proper duplicate detection downstream. Solid defensive coding—like having a checkpoint before the boss fight!


397-422: From conversion handles all 20+ fields correctly

The From<MintedTransactionBody> implementation correctly converts all fields, with proper .into() calls for the output types that need conversion. It's like a faithful port of a game to a new platform—everything's there!


424-426: Clean hash utility function

The get_original_hash function is a nice, simple utility replacing the previous trait-based approach. It computes the Blake2b-256 hash of the raw CBOR bytes preserved by KeepRaw. Short, sweet, and does exactly what it says on the tin!


428-570: Comprehensive test coverage for decoding edge cases 🧪

Love seeing all these negative test cases! You've covered:

  • Missing required fields (inputs, outputs, fee)
  • Empty values for NonEmptySet fields (certs, collateral, required_signers, proposals)
  • Empty values for NonEmptyKeyValuePairs fields (withdrawals, voting_procedures)
  • Empty mint validation
  • Zero donation rejection
  • Duplicate key rejection

This is the kind of test suite that lets you refactor with confidence. Like having a full achievement list and knowing you've 100%'d the game!

K: Clone,
V: Clone,
{
type Error = String;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not familiar with amaru error conventions, but String as error is a bit awkward to work with (callers have to convert it to their error type, and it forces an allocation). I think a better alternative would be to define an error type which implemented Error (like https://doc.rust-lang.org/src/core/num/error.rs.html#10)

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

3 participants