From 34cb253fcc544e333e66a7ab34a871381c51cf90 Mon Sep 17 00:00:00 2001 From: rodrigomd94 Date: Thu, 23 Oct 2025 07:20:12 -0600 Subject: [PATCH 1/5] fix: add withdrawals to redeemers pointers check --- pallas-validate/src/phase1/conway.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/pallas-validate/src/phase1/conway.rs b/pallas-validate/src/phase1/conway.rs index eb1d6feba..d59132e5b 100644 --- a/pallas-validate/src/phase1/conway.rs +++ b/pallas-validate/src/phase1/conway.rs @@ -12,7 +12,7 @@ use crate::utils::{ ValidationError::{self, *}, ValidationResult, }; -use pallas_addresses::{ScriptHash, ShelleyAddress, ShelleyPaymentPart}; +use pallas_addresses::{Address, ScriptHash, ShelleyAddress, ShelleyPaymentPart}; use pallas_codec::utils::{Bytes, KeepRaw}; use pallas_primitives::{ babbage, @@ -1129,6 +1129,7 @@ fn check_redeemers( None => Vec::new(), }; + let plutus_scripts: Vec = mk_plutus_script_redeemer_pointers( plutus_v1_scripts, plutus_v2_scripts, @@ -1190,6 +1191,28 @@ fn mk_plutus_script_redeemer_pointers( } } } + if let Some(withdrawals) = &tx_body.withdrawals { + for (index, (stake_key_hash_bytes, _amount)) in withdrawals.iter().enumerate() { + let addr = Address::from_bytes(stake_key_hash_bytes).expect("Invalid stake address bytes"); + if let Address::Stake(stake_addr) = addr { + if stake_addr.is_script() { + let script_hash = stake_addr.payload().as_hash(); + if is_phase_2_script( + &script_hash, + plutus_v1_scripts, + plutus_v2_scripts, + plutus_v3_scripts, + reference_scripts, + ) { + res.push(RedeemersKey { + tag: pallas_primitives::conway::RedeemerTag::Reward, + index: index as u32, + }) + } + } + } + } + } res } From 455fb0b4fc6e2c91b85c67a8205d09c88ac5ea9f Mon Sep 17 00:00:00 2001 From: rodrigomd94 Date: Thu, 23 Oct 2025 07:34:25 -0600 Subject: [PATCH 2/5] fix: remove panic-inducing .expect() call on address parsing --- pallas-validate/src/phase1/conway.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallas-validate/src/phase1/conway.rs b/pallas-validate/src/phase1/conway.rs index d59132e5b..cf7f6e1ad 100644 --- a/pallas-validate/src/phase1/conway.rs +++ b/pallas-validate/src/phase1/conway.rs @@ -1193,7 +1193,8 @@ fn mk_plutus_script_redeemer_pointers( } if let Some(withdrawals) = &tx_body.withdrawals { for (index, (stake_key_hash_bytes, _amount)) in withdrawals.iter().enumerate() { - let addr = Address::from_bytes(stake_key_hash_bytes).expect("Invalid stake address bytes"); + let addr = Address::from_bytes(stake_key_hash_bytes) + .ok_or(PostAlonzo(InputDecoding))?; if let Address::Stake(stake_addr) = addr { if stake_addr.is_script() { let script_hash = stake_addr.payload().as_hash(); From 0324f8ff8f1c687343c39c211121e87f598b8c06 Mon Sep 17 00:00:00 2001 From: rodrigomd94 Date: Thu, 23 Oct 2025 07:39:58 -0600 Subject: [PATCH 3/5] fix: fix type mismatch when handling address parsing error --- pallas-validate/src/phase1/conway.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pallas-validate/src/phase1/conway.rs b/pallas-validate/src/phase1/conway.rs index cf7f6e1ad..31fa393f0 100644 --- a/pallas-validate/src/phase1/conway.rs +++ b/pallas-validate/src/phase1/conway.rs @@ -1193,9 +1193,7 @@ fn mk_plutus_script_redeemer_pointers( } if let Some(withdrawals) = &tx_body.withdrawals { for (index, (stake_key_hash_bytes, _amount)) in withdrawals.iter().enumerate() { - let addr = Address::from_bytes(stake_key_hash_bytes) - .ok_or(PostAlonzo(InputDecoding))?; - if let Address::Stake(stake_addr) = addr { + if let Some(Address::Stake(stake_addr)) = Address::from_bytes(stake_key_hash_bytes) { if stake_addr.is_script() { let script_hash = stake_addr.payload().as_hash(); if is_phase_2_script( From fd4bbcfd50c0450867ecf2f87d2974fb87a97330 Mon Sep 17 00:00:00 2001 From: rodrigomd94 Date: Thu, 23 Oct 2025 11:42:32 -0600 Subject: [PATCH 4/5] fix: sort reward accounts before getting redeemer pointers --- pallas-validate/src/phase1/conway.rs | 41 ++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/pallas-validate/src/phase1/conway.rs b/pallas-validate/src/phase1/conway.rs index 31fa393f0..a8a48f4b5 100644 --- a/pallas-validate/src/phase1/conway.rs +++ b/pallas-validate/src/phase1/conway.rs @@ -12,7 +12,8 @@ use crate::utils::{ ValidationError::{self, *}, ValidationResult, }; -use pallas_addresses::{Address, ScriptHash, ShelleyAddress, ShelleyPaymentPart}; +use itertools::Itertools; +use pallas_addresses::{Address, Network, ScriptHash, ShelleyAddress, ShelleyPaymentPart, StakePayload}; use pallas_codec::utils::{Bytes, KeepRaw}; use pallas_primitives::{ babbage, @@ -23,6 +24,7 @@ use pallas_primitives::{ AddrKeyhash, Hash, PlutusData, PlutusScript, PolicyId, PositiveCoin, TransactionInput, }; use pallas_traverse::{MultiEraInput, MultiEraOutput, OriginalHash}; +use std::cmp::Ordering; use std::ops::Deref; pub fn validate_conway_tx( @@ -1148,6 +1150,35 @@ fn sort_inputs(unsorted_inputs: &[TransactionInput]) -> Vec { res } +// Sorting function for reward accounts (withdrawals). +fn sort_reward_accounts(a: &Bytes, b: &Bytes) -> Ordering { + let addr_a = Address::from_bytes(a).expect("invalid reward address in withdrawals."); + let addr_b = Address::from_bytes(b).expect("invalid reward address in withdrawals."); + + fn network_tag(network: Network) -> u8 { + match network { + Network::Testnet => 0, + Network::Mainnet => 1, + Network::Other(tag) => tag, + } + } + + if let (Address::Stake(accnt_a), Address::Stake(accnt_b)) = (addr_a, addr_b) { + if accnt_a.network() != accnt_b.network() { + return network_tag(accnt_a.network()).cmp(&network_tag(accnt_b.network())); + } + + match (accnt_a.payload(), accnt_b.payload()) { + (StakePayload::Script(..), StakePayload::Stake(..)) => Ordering::Less, + (StakePayload::Stake(..), StakePayload::Script(..)) => Ordering::Greater, + (StakePayload::Script(hash_a), StakePayload::Script(hash_b)) => hash_a.cmp(hash_b), + (StakePayload::Stake(hash_a), StakePayload::Stake(hash_b)) => hash_a.cmp(hash_b), + } + } else { + unreachable!("invalid reward address in withdrawals."); + } +} + fn mk_plutus_script_redeemer_pointers( plutus_v1_scripts: &[PolicyId], plutus_v2_scripts: &[PolicyId], @@ -1192,8 +1223,12 @@ fn mk_plutus_script_redeemer_pointers( } } if let Some(withdrawals) = &tx_body.withdrawals { - for (index, (stake_key_hash_bytes, _amount)) in withdrawals.iter().enumerate() { - if let Some(Address::Stake(stake_addr)) = Address::from_bytes(stake_key_hash_bytes) { + for (index, (stake_key_hash_bytes, _amount)) in withdrawals + .iter() + .sorted_by(|(accnt_a, _), (accnt_b, _)| sort_reward_accounts(accnt_a, accnt_b)) + .enumerate() + { + if let Ok(Address::Stake(stake_addr)) = Address::from_bytes(stake_key_hash_bytes) { if stake_addr.is_script() { let script_hash = stake_addr.payload().as_hash(); if is_phase_2_script( From 955073cc618806b9b50a407c9e1df1da299848a7 Mon Sep 17 00:00:00 2001 From: rodrigomd94 Date: Thu, 23 Oct 2025 13:21:25 -0600 Subject: [PATCH 5/5] chore: fix lint format --- pallas-validate/src/phase1/conway.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallas-validate/src/phase1/conway.rs b/pallas-validate/src/phase1/conway.rs index a8a48f4b5..00e57baaa 100644 --- a/pallas-validate/src/phase1/conway.rs +++ b/pallas-validate/src/phase1/conway.rs @@ -13,7 +13,9 @@ use crate::utils::{ ValidationResult, }; use itertools::Itertools; -use pallas_addresses::{Address, Network, ScriptHash, ShelleyAddress, ShelleyPaymentPart, StakePayload}; +use pallas_addresses::{ + Address, Network, ScriptHash, ShelleyAddress, ShelleyPaymentPart, StakePayload, +}; use pallas_codec::utils::{Bytes, KeepRaw}; use pallas_primitives::{ babbage,