Skip to content

Lots of fixes and updated to the latest node version#624

Open
ginnun wants to merge 13 commits intotxpipe:mainfrom
blockfrost:blockfrost
Open

Lots of fixes and updated to the latest node version#624
ginnun wants to merge 13 commits intotxpipe:mainfrom
blockfrost:blockfrost

Conversation

@ginnun
Copy link
Copy Markdown
Contributor

@ginnun ginnun commented Mar 7, 2025

This PR contains lots of fixes, improvements and fixes some breaking changes that comes with the latest Cardano node version (for Haskell text generation)

Summary by CodeRabbit

  • New Features

    • Structured reward-account format with on-wire encode/decode.
    • Optional Plutus V4 cost model and PlutusV4 language tagging.
    • Reusable mismatch container type and new error variants for richer diagnostics.
  • Changes

    • Many protocol types now support ordering.
    • Transaction decoding tightened; numeric handling expanded (larger integers).
    • Error/serialization APIs now return fallible results.
  • Tests

    • Updated to cover Plutus V4, new account format, mismatch handling, and revised decoding behavior.


pub type RewardAccount = Bytes;
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct FieldedRewardAccount{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is the FieldedRewardAccount struct defined as part of some spec or is it a quality-of-life improvement for dealing with credentials?

If its the latter, I would prefer to have this in a higher layer of the stack and not as part of the codec of the miniprotocol.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@scarmuega It's there to be able to produce the same error message with Haskell codebase. Since Rust doesn't have type specialization, old-good RewardAccount (which is a Byte) can't be used for HaskellDisplay to distinguish.

So not sure if this is a quality-of-life improvement or not. If do you think so, I could refactor it. Just tell me what layer it should be in that case. Thanks.

@ginnun ginnun changed the title Fix Credential decoding Lots of fixes and updated to the latest node version May 28, 2025
@ginnun
Copy link
Copy Markdown
Contributor Author

ginnun commented May 29, 2025

Please note that failing clippy checks are all unrelated to the changes I made.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Oct 7, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00af84eb-73d0-426a-8b53-e0ace5e702a1

📥 Commits

Reviewing files that changed from the base of the PR and between a03ae02 and ed422bc.

📒 Files selected for processing (2)
  • pallas-hardano/src/display/haskell_error.rs
  • pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs

📝 Walkthrough

Walkthrough

Adds FieldedRewardAccount (CBOR Encode/Decode), Plutus v4 cost-model support, new mismatch container MismatchArr, public DecoderError/DeserialiseFailure, manual minicbor Decode for Tx<'b>, widespread replacement of DisplayRewardAccount with FieldedRewardAccount, and numerous ordering/typing adjustments across network, protocol, display, and error layers.

Changes

Cohort / File(s) Summary
Codec utils
pallas-codec/src/utils.rs
Nullable Decode: treat Null/Undefined by calling d.skip() instead of d.null()/d.undefined(); encode unchanged.
Display & error rendering
pallas-hardano/src/display/haskell_display.rs, pallas-hardano/src/display/haskell_error.rs
Replace DisplayRewardAccount with FieldedRewardAccount; add as_mismatch and new formatting traits/impls; introduce public DecoderError enum and DeserialiseFailure; change error-serialization APIs to return Result and add as_cbor_decode_failure.
Localstate v16 types & codec
pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs, pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs
Add public FieldedRewardAccount type with TryFrom and minicbor Encode/Decode; add plutus_v4: Option<CostModel> (CBOR n(3)) to CostModels; derive Ord/PartialOrd on many protocol types; add CurrentProtocolParam.
Local tx submission: primitives, codec, protocol
pallas-network/src/miniprotocols/localtxsubmission/primitives.rs, pallas-network/src/miniprotocols/localtxsubmission/codec.rs, pallas-network/src/miniprotocols/localtxsubmission/protocol.rs
Retag Credential variants and add KeyHashObj; PoolRegistration.reward_account → FieldedRewardAccount; add MismatchArr<T> (CBOR [supplied, expected]) with Encode/Decode; add VRFKeyHashAlreadyRegistered; many failure variants updated to use FieldedRewardAccount; numeric bound type adjustments.
Conway primitives & tests
pallas-primitives/src/conway/model.rs, pallas-validate/tests/conway.rs
CostModels gains plutus_v4: Option<CostModel> (n(3)); Tx<'b> removes derived Decode and adds manual minicbor::Decode that decodes ordered fields (transaction_body, witness_set, success, auxiliary_data); tests initialize plutus_v4: None.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CBOR as "CBOR Decoder"
  participant TxDec as "Tx<'b>::Decode"
  participant Body as "TransactionBody"
  participant Wits as "WitnessSet"

  CBOR->>TxDec: start decode()
  TxDec->>CBOR: read array header
  TxDec->>Body: decode(transaction_body) with KeepRaw context
  TxDec->>Wits: decode(witness_set) with KeepRaw context
  alt success field present
    TxDec->>CBOR: decode bool success
  else
    Note right of TxDec: success defaults to true
  end
  alt auxiliary_data present
    TxDec->>CBOR: decode Nullable<AuxiliaryData>
  else
    Note right of TxDec: auxiliary_data defaults to Null
  end
  TxDec-->>CBOR: return Tx
Loading
sequenceDiagram
  autonumber
  participant Enc as "Encoder"
  participant FE as "FieldedRewardAccount"
  participant Buf as "Raw CBOR Bytes"

  Enc->>FE: request serialization (Encode)
  FE-->>Enc: provide 29-byte payload (1-byte header + 28-byte stake hash)
  Enc->>Buf: write raw bytes

  Buf->>FE: raw bytes read (Decode)
  FE->>FE: TryFrom<&[u8]> parsing and validate 29 bytes
  FE-->>Buf: FieldedRewardAccount instance
Loading
sequenceDiagram
  autonumber
  participant Codec as "localtxsubmission::codec"
  participant Stream as "Input Stream"
  participant Err as "DecoderError"

  Codec->>Stream: attempt to decode error payload
  alt decodes known error tag (incl. VRFKeyHashAlreadyRegistered)
    Stream-->>Codec: Ok(error struct)
    Codec-->>Codec: return Some(error)
  else
    Stream-->>Codec: Err(e)
    Codec->>Err: propagate/convert to DecoderError variants
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • scarmuega

Poem

I nibble bytes and hum a tune,
Fields and hashes under moon.
Mismatches labeled, errors neat,
Decoders dance on tiny feet.
Hop—new models—code complete! 🥕🐇

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.85% 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 is vague and generic, using broad terms like 'fixes' and 'updated' without specifying the main changes or which node version updates are being addressed. Provide a more specific title that describes the primary change, such as 'Add PlutusV4 cost models and FieldedRewardAccount support' or 'Update for Cardano node version X.Y.Z compatibility'.
✅ 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 unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d34b143 and 433045a.

📒 Files selected for processing (9)
  • pallas-codec/src/utils.rs (1 hunks)
  • pallas-hardano/src/display/haskell_display.rs (29 hunks)
  • pallas-hardano/src/display/haskell_error.rs (5 hunks)
  • pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs (1 hunks)
  • pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs (14 hunks)
  • pallas-network/src/miniprotocols/localtxsubmission/codec.rs (0 hunks)
  • pallas-network/src/miniprotocols/localtxsubmission/primitives.rs (3 hunks)
  • pallas-network/src/miniprotocols/localtxsubmission/protocol.rs (10 hunks)
  • pallas-primitives/src/conway/model.rs (2 hunks)
💤 Files with no reviewable changes (1)
  • pallas-network/src/miniprotocols/localtxsubmission/codec.rs
🧰 Additional context used
🧬 Code graph analysis (3)
pallas-network/src/miniprotocols/localtxsubmission/protocol.rs (1)
pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs (8)
  • from (635-654)
  • from (844-846)
  • from (908-910)
  • from (914-916)
  • from (920-922)
  • from (929-931)
  • from (935-937)
  • from (958-960)
pallas-primitives/src/conway/model.rs (1)
pallas-codec/src/utils.rs (18)
  • decode (22-33)
  • decode (148-161)
  • decode (326-345)
  • decode (420-430)
  • decode (495-501)
  • decode (537-543)
  • decode (593-597)
  • decode (631-637)
  • decode (672-685)
  • decode (758-769)
  • decode (849-866)
  • decode (896-917)
  • decode (1065-1073)
  • decode (1162-1172)
  • decode (1273-1285)
  • decode (1340-1355)
  • d (151-151)
  • d (329-329)
pallas-hardano/src/display/haskell_display.rs (3)
pallas-hardano/src/display/haskells_show_string.rs (1)
  • haskell_show_string (4-98)
pallas-network/src/miniprotocols/localtxsubmission/codec.rs (16)
  • encode (39-54)
  • encode (62-87)
  • encode (130-140)
  • encode (154-169)
  • encode (184-194)
  • encode (232-253)
  • encode (320-328)
  • encode (344-353)
  • encode (384-402)
  • encode (413-420)
  • encode (435-446)
  • encode (468-509)
  • encode (632-766)
  • encode (784-795)
  • encode (842-872)
  • encode (899-919)
pallas-traverse/src/governance.rs (1)
  • reward_account (24-28)
⏰ 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). (4)
  • GitHub Check: Test Suite (windows-latest, stable)
  • GitHub Check: Test Suite (macOS-latest, stable)
  • GitHub Check: Check (windows-latest, stable)
  • GitHub Check: Test Suite (ubuntu-latest, stable)
🔇 Additional comments (1)
pallas-codec/src/utils.rs (1)

1342-1348: LGTM – approve compatibility fix, verify Nullable decoding
The change from d.null()/d.undefined() to d.skip() is safe given the prior d.datatype()? check. Please run your pallas-codec tests locally (e.g. cargo test -p pallas-codec) to confirm no regressions in Nullable decoding.

Copy link
Copy Markdown

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

🧹 Nitpick comments (2)
pallas-network/src/miniprotocols/localtxsubmission/codec.rs (1)

335-347: Consider validating the CBOR array length for robustness.

The Decode implementation calls d.array()? but discards the returned length. Since MismatchArr is explicitly documented as a 2-element array, validating the length would catch malformed input early (e.g., extra elements would be silently ignored with the current implementation).

That said, this pattern of not checking length is common in this codebase (e.g., VotingProcedure::decode), so this is a minor suggestion.

🔧 Optional: Add length validation
 impl<'b, T, C> Decode<'b, C> for super::MismatchArr<T>
 where
     T: Decode<'b, C>,
 {
     fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, decode::Error> {
-        d.array()?;
+        let len = d.array()?;
+        if len != Some(2) {
+            return Err(decode::Error::message("Expected array of length 2 for MismatchArr"));
+        }
         let t0 = d.decode_with(ctx)?;
         let t1 = d.decode_with(ctx)?;
         Ok(super::MismatchArr(t0, t1))
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pallas-network/src/miniprotocols/localtxsubmission/codec.rs` around lines 335
- 347, The Decode impl for super::MismatchArr currently calls d.array()? but
ignores the returned length; update it to capture the length (let len =
d.array()?) and validate that it is exactly 2 when fixed-length (or allow None
for indefinite-length CBOR), returning a decode error for any Some(n) where n !=
2; then proceed to call d.decode_with(ctx)? twice and return
super::MismatchArr(t0, t1). Ensure you reference the same symbols: Decode for
super::MismatchArr, d.array(), d.decode_with(ctx) and super::MismatchArr(t0, t1)
when implementing the check.
pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs (1)

460-524: Consider reducing duplication between ProtocolParam and CurrentProtocolParam

CurrentProtocolParam appears to have the same fields as ProtocolParam. If they are semantically identical, consider using a type alias or extracting a shared base to reduce maintenance burden. If there are subtle differences (e.g., different CBOR encoding requirements as hinted by the TODO on line 392), please add a comment explaining why both are needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs` around lines
460 - 524, CurrentProtocolParam duplicates ProtocolParam field-for-field; either
unify them by replacing CurrentProtocolParam with a type alias to ProtocolParam
or extract a shared struct used by both (e.g., rename ProtocolParam ->
ProtocolParamBase and make ProtocolParam and CurrentProtocolParam newtype
wrappers), and update usages to the chosen shared type; if there are encoding
differences (see the TODO about CBOR encoding near the ProtocolParam
implementation), add a clear comment on why two distinct types must remain and
document the specific encoding/semantic difference on ProtocolParam and
CurrentProtocolParam so future maintainers understand the rationale.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pallas-hardano/src/display/haskell_display.rs`:
- Around line 526-535: The fallback use of Debug in DecoderError::to_haskell_str
breaks the Haskell wire format; replace the catch-all "{:?}" branch with
explicit, stable Haskell-style string formatting for every DecoderError variant.
Update the impl HaskellDisplay for DecoderError::to_haskell_str to match each
variant (not just DecoderError::DeserialiseFailure) and return a deterministic
string (e.g., "DecoderErrorVariantName <fields...>") using existing helper
methods like to_haskell_str()/to_haskell_str_p() where appropriate so all
variants produce stable, predictable output.
- Line 397: The TooManyCollateralInputs message is passing the arguments to
as_mismatch in the wrong order (as_mismatch expects (expected, supplied));
change the call in the TooManyCollateralInputs formatter from
as_mismatch(actual, max) to as_mismatch(max, actual) and make the same swap for
the other occurrences referenced (the similar block around the 664-672 range) so
the serialized mismatch uses expected=max and supplied=actual.

In `@pallas-hardano/src/display/haskell_error.rs`:
- Around line 22-29: The helper as_cbor_decode_failure currently panics by
calling unwrap() on serde_json::to_string; change its signature from fn
as_cbor_decode_failure(message: String, position: u64) -> String to return
Result<String, serde_json::Error>, remove the unwrap and return the Result from
serde_json::to_string(&error), and update any callers of as_cbor_decode_failure
to handle the Result (propagate or map_err as appropriate). Keep the
construction of DecoderError::DeserialiseFailure, TxCmdError::TxReadError and
TxSubmitFail::TxSubmitFail unchanged — only alter the return type and remove the
unwrap so serialization errors are propagated.

---

Nitpick comments:
In `@pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs`:
- Around line 460-524: CurrentProtocolParam duplicates ProtocolParam
field-for-field; either unify them by replacing CurrentProtocolParam with a type
alias to ProtocolParam or extract a shared struct used by both (e.g., rename
ProtocolParam -> ProtocolParamBase and make ProtocolParam and
CurrentProtocolParam newtype wrappers), and update usages to the chosen shared
type; if there are encoding differences (see the TODO about CBOR encoding near
the ProtocolParam implementation), add a clear comment on why two distinct types
must remain and document the specific encoding/semantic difference on
ProtocolParam and CurrentProtocolParam so future maintainers understand the
rationale.

In `@pallas-network/src/miniprotocols/localtxsubmission/codec.rs`:
- Around line 335-347: The Decode impl for super::MismatchArr currently calls
d.array()? but ignores the returned length; update it to capture the length (let
len = d.array()?) and validate that it is exactly 2 when fixed-length (or allow
None for indefinite-length CBOR), returning a decode error for any Some(n) where
n != 2; then proceed to call d.decode_with(ctx)? twice and return
super::MismatchArr(t0, t1). Ensure you reference the same symbols: Decode for
super::MismatchArr, d.array(), d.decode_with(ctx) and super::MismatchArr(t0, t1)
when implementing the check.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 433045a and e8681d3.

📒 Files selected for processing (10)
  • pallas-codec/src/utils.rs
  • pallas-hardano/src/display/haskell_display.rs
  • pallas-hardano/src/display/haskell_error.rs
  • pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs
  • pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs
  • pallas-network/src/miniprotocols/localtxsubmission/codec.rs
  • pallas-network/src/miniprotocols/localtxsubmission/primitives.rs
  • pallas-network/src/miniprotocols/localtxsubmission/protocol.rs
  • pallas-primitives/src/conway/model.rs
  • pallas-validate/tests/conway.rs

Copy link
Copy Markdown
Contributor

@michalrus michalrus left a comment

Choose a reason for hiding this comment

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

Couple of comments, since we’re depending on this branch, and it hasn’t been been approved yet:

#[derive(Debug, Serialize)]
#[serde(tag = "tag", content = "contents")]
pub enum TxCmdError {
SocketEnvError(String),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems currently unused, but I wonder, shouldn’t this be also renamed to TxCmdSocketEnvError? See here:

https://github.com/input-output-hk/cardano-node/blob/0d697f14ee10a775f08c60e3839becc9131dca75/cardano-submit-api/src/Cardano/TxSubmit/Types.hs#L93

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I changed and renamed some in ed422bc

Comment on lines +73 to +74
#[derive(Debug, Serialize)]
pub enum DecoderError {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmmm, you're deriving Serialize, so by default this will result in "tagged" JSON (e.g {"DeserialiseFailure":[...]}).

But cardano-node is using toJSON = Aeson.String . textShow, so this means the will just have a string "DecoderErrorDeserialiseFailure \"Shelley Tx\" ..."? See:

https://github.com/IntersectMBO/cardano-node/blob/10.6.2/cardano-submit-api/src/Cardano/TxSubmit/Orphans.hs#L17-L18

Is this unused currently? Why don’t we test this path with testgen-hs? Or am I wrong?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did the intent clearer in ed422bc.
In short I replaced (serialize_with = "use_haskell_display") with a custom Serializer.

To your comment:
The #[derive(Serialize)] could have been safely removed, since it was never used on DecoderError. When TxCmdError::TxReadError(Vec<DecoderError>) is serialized, the #[serde(serialize_with = "use_haskell_display")] custom serialize was kicking in (line 134), which calls to_haskell_str() on each element and writes it as a plain string — matching Haskell's textShow behavior.

The test at line 156 confirms this:
"contents":["DecoderErrorDeserialiseFailure \"Shelley Tx\" (DeserialiseFailure 0 (\"expected list len or indef\"))"]
It's a string, not tagged JSON. So there is no problem in the implementation.

e: &mut minicbor::Encoder<W>,
_ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
let mut prefix: u8 = self.network.clone().into();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn’t we have:

prefix |= 0b1110_0000;

before the ScriptHash check? See here:

/// Builds the header for this address
pub fn to_header(&self) -> u8 {
let type_id = self.typeid();
let type_id = type_id << 4;
let network = self.0.value();
type_id | network
}

And then here:

/// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 {
match &self.1 {
StakePayload::Stake(_) => 0b1110,
StakePayload::Script(_) => 0b1111,
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

per CIP-19 we should. This was not effecting our use-case, thus slipped. Fixed in a03ae02.

pub struct OHashMap<K, V>(pub Vec<(K, V)>);

#[derive(Encode, Decode, Debug, Clone, Eq, PartialEq)]
#[derive(Encode, Decode, Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmmm, as a fieldless enum, I think this should derive Copy for ergonomics (no need to call .clone()). It will end up as a single byte after compilation anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Practially no difference, only cosmetics. I included this suggestion in a bigger commit: a03ae02

#[n(0)]
StakePoolNotRegisteredOnKeyPOOL(#[n(0)] KeyHash),
#[n(1)]
StakePoolRetirementWrongEpochPOOL(#[n(0)] Mismatch<EpochNo>, #[n(1)] Mismatch<EpochNo>),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So the derived Encode will write 4 EpochNo fields to CBOR, right? But the wire protocol has only 3, according to the custom decode:

impl<'b, C> Decode<'b, C> for ShelleyPoolPredFailure {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, decode::Error> {
d.array()?;
let tag = d.u16()?;
use ShelleyPoolPredFailure::*;
match tag {
0 => Ok(StakePoolNotRegisteredOnKeyPOOL(d.decode_with(ctx)?)),
1 => {
let gt_expected: EpochNo = d.decode_with(ctx)?;
let lt_supplied: EpochNo = d.decode_with(ctx)?;
let lt_expected: EpochNo = d.decode_with(ctx)?;

Are we fine with this?

Perhaps we should automatically add one more test to each existing test, like cbor_data == encode(decode(cbor_data))?

To make sure our codecs are completely invertible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs`:
- Around line 639-679: The current TryFrom<&[u8]> impl for FieldedRewardAccount
only validates the upper nibble (header_type) but reads the network via bytes[0]
& 0b0000_0001, which accepts non-canonical values like 0xe2/0xf3; update the
header/network validation in TryFrom to also verify the full lower nibble
(bytes[0] & 0x0F) is exactly the canonical values (0x0 for Testnet or 0x1 for
Mainnet) and return InvalidRewardAccount::InvalidHeaderType (or add a new
InvalidNetwork variant) when it is not; locate the check around header_type and
bytes[0] in the TryFrom impl and replace the single-bit test with a strict
lower-nibble match before mapping to Network.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dffa7f09-ba08-4f2e-be56-5f72beb877c8

📥 Commits

Reviewing files that changed from the base of the PR and between 529f5d6 and a03ae02.

📒 Files selected for processing (3)
  • pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs
  • pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs
  • pallas-network/src/miniprotocols/localtxsubmission/protocol.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs

Comment on lines +639 to +679
pub enum InvalidRewardAccount {
InvalidLength(usize),
InvalidHeaderType(u8),
}

impl fmt::Display for InvalidRewardAccount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength(len) => {
write!(f, "invalid reward account length: expected 29, got {len}")
}
Self::InvalidHeaderType(header) => {
write!(f, "invalid reward account header type: 0x{header:02x}")
}
}
}
}

impl std::error::Error for InvalidRewardAccount {}

impl TryFrom<&[u8]> for FieldedRewardAccount {
type Error = InvalidRewardAccount;

fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() != 29 {
return Err(InvalidRewardAccount::InvalidLength(bytes.len()));
}

// Header byte layout: [type(4 bits) | network(4 bits)]
// Upper nibble must be 0b1110 (key hash) or 0b1111 (script hash)
// per CIP-19 reward account address format.
let header_type = bytes[0] >> 4;
if !matches!(header_type, 0b1110 | 0b1111) {
return Err(InvalidRewardAccount::InvalidHeaderType(bytes[0]));
}

let network = if bytes[0] & 0b0000_0001 != 0 {
Network::Mainnet
} else {
Network::Testnet
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject non-canonical network nibbles in reward-account headers.

This still validates only the upper nibble. The bytes[0] & 0b0000_0001 check accepts headers like 0xe2 / 0xf3 and silently maps them to Testnet / Mainnet, so malformed reward accounts are still treated as valid and can be misreported downstream.

🔧 Proposed fix
 pub enum InvalidRewardAccount {
     InvalidLength(usize),
     InvalidHeaderType(u8),
+    InvalidNetwork(u8),
 }
 
 impl fmt::Display for InvalidRewardAccount {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             Self::InvalidLength(len) => {
                 write!(f, "invalid reward account length: expected 29, got {len}")
             }
             Self::InvalidHeaderType(header) => {
                 write!(f, "invalid reward account header type: 0x{header:02x}")
             }
+            Self::InvalidNetwork(network) => {
+                write!(f, "invalid reward account network id: 0x{network:01x}")
+            }
         }
     }
 }
@@
-        let network = if bytes[0] & 0b0000_0001 != 0 {
-            Network::Mainnet
-        } else {
-            Network::Testnet
-        };
+        let network = match bytes[0] & 0b0000_1111 {
+            0 => Network::Testnet,
+            1 => Network::Mainnet,
+            other => return Err(InvalidRewardAccount::InvalidNetwork(other)),
+        };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs` around lines
639 - 679, The current TryFrom<&[u8]> impl for FieldedRewardAccount only
validates the upper nibble (header_type) but reads the network via bytes[0] &
0b0000_0001, which accepts non-canonical values like 0xe2/0xf3; update the
header/network validation in TryFrom to also verify the full lower nibble
(bytes[0] & 0x0F) is exactly the canonical values (0x0 for Testnet or 0x1 for
Mainnet) and return InvalidRewardAccount::InvalidHeaderType (or add a new
InvalidNetwork variant) when it is not; locate the check around header_type and
bytes[0] in the TryFrom impl and replace the single-bit test with a strict
lower-nibble match before mapping to Network.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants