Skip to content

Conversation

@rodrigomd94
Copy link

@rodrigomd94 rodrigomd94 commented Oct 23, 2025

mk_plutus_script_redeemer_pointers didn't include a check for withdrawals, which was causing phase 1 validation to fail for transactions that include plutus scripts in the withdrawals. This is relevant for the "withdraw 0 trick".

Summary by CodeRabbit

  • New Features

    • Validation now recognizes stake reward withdrawals from script-backed stake addresses and adds corresponding Plutus redeemer pointers so those withdrawals are validated correctly.
    • Withdrawal analysis has been extended (in addition to inputs and minting) to detect and include Reward redeemer pointers for qualifying stake withdrawals.
  • Improvements

    • Withdrawals are deterministically ordered before processing to ensure stable redeemer pointer generation.

@coderabbitai
Copy link

coderabbitai bot commented Oct 23, 2025

Walkthrough

Adds deterministic handling of stake withdrawals when building Plutus redeemer pointers: withdrawals are sorted by network and stake payload, parsed as Address/StakePayload, and script-backed stake withdrawals that match phase‑2 scripts produce Reward redeemer pointers.

Changes

Cohort / File(s) Summary
Stake withdrawal handling & redeemer pointers
pallas-validate/src/phase1/conway.rs
Extends mk_plutus_script_redeemer_pointers to inspect transaction withdrawals and append Reward RedeemersKey entries for script-backed stake addresses that map to phase‑2 Plutus scripts. Adds an explicit unreachable branch for invalid reward addresses.
Deterministic sorting of reward accounts
pallas-validate/src/phase1/conway.rs
Adds sort_reward_accounts helper comparator and sorts withdrawals by network tag and stake payload (Script vs Stake, then script/stake hash) before processing to ensure stable ordering of Reward redeemer pointers.
Imports and utilities extended
pallas-validate/src/phase1/conway.rs
Adds imports: itertools::Itertools, Address, Network, StakePayload, and Ordering to enable address parsing, payload inspection, and deterministic ordering logic. Minor formatting blank-line change.

Sequence Diagram(s)

sequenceDiagram
    participant Tx as Transaction
    participant Func as mk_plutus_script_redeemer_pointers
    participant Inputs as InputsAnalyzer
    participant Mint as MintAnalyzer
    participant Withdrawals as WithdrawalProcessor
    rect rgb(230, 240, 255)
    Tx->>Func: request redeemer pointers
    end

    Note over Func,Inputs: existing: analyze inputs
    Func->>Inputs: inspect inputs
    Inputs-->>Func: input redeemer pointers

    Note over Func,Mint: existing: analyze mint entries
    Func->>Mint: inspect mint entries
    Mint-->>Func: mint redeemer pointers

    rect rgb(240, 255, 240)
    Note over Func,Withdrawals: new: deterministic withdrawal processing
    Func->>Withdrawals: collect withdrawals
    Withdrawals->>Withdrawals: sort_reward_accounts (network, payload, hash)
    Withdrawals->>Withdrawals: parse stake address -> Address / StakePayload
    alt script-backed & matches phase‑2
        Withdrawals-->>Func: emit Reward redeemer pointer (RedeemersKey::Reward, index)
    else non-script or non-matching
        Withdrawals-->>Func: no pointer
    else invalid address
        Withdrawals-->>Func: unreachable (invalid reward addr)
    end
    end

    Func-->>Tx: return complete redeemer pointer set
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I hop through bytes where stake rewards hide,
I sort by net and payload with tidy stride,
Where script-hashes match, a Reward pointer springs,
Stable lines, small changes — neat, happy things,
Conway nods as redeemers glide.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: add withdrawals to redeemers pointers check" directly and accurately describes the main change in the changeset. The title clearly indicates that withdrawals handling has been added to the redeemers pointers check function (mk_plutus_script_redeemer_pointers), which aligns precisely with the summary of changes. The title is concise, uses specific technical terms, and avoids vague language or unnecessary noise. While it does not detail every aspect of the implementation (such as the sorting logic or helper function), this level of detail is not expected per the guidelines. A teammate reviewing the repository history would immediately understand that this commit addresses withdrawals in redeemer pointer validation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f17967 and 34cb253.

📒 Files selected for processing (1)
  • pallas-validate/src/phase1/conway.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pallas-validate/src/phase1/conway.rs (3)
pallas-traverse/src/tx.rs (1)
  • withdrawals (476-492)
pallas-validate/src/phase1/babbage.rs (1)
  • is_phase_2_script (989-1004)
pallas-traverse/src/witnesses.rs (3)
  • plutus_v1_scripts (82-104)
  • plutus_v2_scripts (196-213)
  • plutus_v3_scripts (215-227)
🔇 Additional comments (1)
pallas-validate/src/phase1/conway.rs (1)

15-15: LGTM!

The Address import is necessary for parsing stake addresses in the withdrawal handling logic and is consistent with the existing import patterns.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34cb253 and 455fb0b.

📒 Files selected for processing (1)
  • pallas-validate/src/phase1/conway.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pallas-validate/src/phase1/conway.rs (4)
pallas-traverse/src/tx.rs (1)
  • withdrawals (476-492)
pallas-validate/src/phase2/script_context.rs (1)
  • withdrawals (413-419)
pallas-validate/src/phase1/babbage.rs (1)
  • is_phase_2_script (989-1004)
pallas-traverse/src/witnesses.rs (3)
  • plutus_v1_scripts (82-104)
  • plutus_v2_scripts (196-213)
  • plutus_v3_scripts (215-227)
🔇 Additional comments (1)
pallas-validate/src/phase1/conway.rs (1)

15-15: LGTM!

The Address import is correctly added to support parsing stake address bytes in the withdrawals check.

scarmuega
scarmuega previously approved these changes Oct 23, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
pallas-validate/src/phase1/conway.rs (1)

1153-1180: Refactor to eliminate code duplication.

This function is duplicated from pallas-validate/src/phase2/script_context.rs (lines 1081-1107). Consider extracting it to a shared module to maintain a single source of truth and avoid maintenance overhead.

For example, you could move this function to a common utilities module and import it in both places:

// In a shared utilities module
pub fn sort_reward_accounts(a: &Bytes, b: &Bytes) -> Ordering {
    // ... implementation
}

Then import in both phase1/conway.rs and phase2/script_context.rs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0b95a7 and fd4bbcf.

📒 Files selected for processing (1)
  • pallas-validate/src/phase1/conway.rs (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pallas-validate/src/phase1/conway.rs (4)
pallas-validate/src/phase2/script_context.rs (3)
  • sort_reward_accounts (1082-1108)
  • network_tag (1086-1092)
  • withdrawals (413-419)
pallas-traverse/src/tx.rs (1)
  • withdrawals (476-492)
pallas-validate/src/phase1/babbage.rs (1)
  • is_phase_2_script (989-1004)
pallas-traverse/src/witnesses.rs (3)
  • plutus_v1_scripts (82-104)
  • plutus_v2_scripts (196-213)
  • plutus_v3_scripts (215-227)
🔇 Additional comments (2)
pallas-validate/src/phase1/conway.rs (2)

15-16: LGTM!

The new imports are necessary for withdrawal handling and deterministic sorting. All imports are used appropriately in the implementation.

Also applies to: 27-27


1225-1249: LGTM!

The withdrawals handling is correctly implemented:

  • Withdrawals are deterministically sorted before processing, ensuring stable redeemer pointer generation.
  • Invalid addresses are gracefully skipped using the if let Ok(...) pattern.
  • Script-backed stake addresses are correctly identified using is_script().
  • Phase-2 script verification ensures only Plutus scripts generate Reward redeemer pointers.

This properly addresses the "withdraw 0 trick" validation issue mentioned in the PR objectives.

@rodrigomd94 rodrigomd94 requested a review from scarmuega October 23, 2025 18:59
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd4bbcf and 955073c.

📒 Files selected for processing (1)
  • pallas-validate/src/phase1/conway.rs (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pallas-validate/src/phase1/conway.rs (4)
pallas-validate/src/phase2/script_context.rs (3)
  • sort_reward_accounts (1082-1108)
  • network_tag (1086-1092)
  • withdrawals (413-419)
pallas-traverse/src/tx.rs (1)
  • withdrawals (476-492)
pallas-validate/src/phase1/babbage.rs (1)
  • is_phase_2_script (989-1004)
pallas-traverse/src/witnesses.rs (3)
  • plutus_v1_scripts (82-104)
  • plutus_v2_scripts (196-213)
  • plutus_v3_scripts (215-227)
🔇 Additional comments (2)
pallas-validate/src/phase1/conway.rs (2)

15-18: LGTM!

The new imports support the withdrawals handling functionality. All are necessary for parsing stake addresses, sorting withdrawals, and creating reward redeemer pointers.

Also applies to: 29-29


1227-1251: Logic looks correct, but depends on fixing the sort function.

The withdrawals handling logic correctly:

  • Sorts withdrawals deterministically per Cardano ledger specifications
  • Parses stake addresses with appropriate error handling
  • Filters for script-backed stake withdrawals
  • Creates Reward redeemer pointers only for phase-2 Plutus scripts

This properly addresses the "withdraw 0 trick" mentioned in the PR objectives.

However, this code depends on sort_reward_accounts not panicking on invalid addresses. Please ensure the code duplication and error handling issues in sort_reward_accounts (lines 1155-1182) are addressed first, as the current .expect() calls will cause panics before this graceful error handling can execute.

Comment on lines +1155 to +1182
// 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.");
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Eliminate code duplication and fix inconsistent error handling.

This function duplicates code from pallas-validate/src/phase2/script_context.rs (lines 1081-1107). Additionally, the use of .expect() creates inconsistent error handling: if withdrawals contain invalid addresses, the sort will panic before the graceful error handling in the loop at line 1233 can process them.

Consider these solutions:

Solution 1 (preferred): Import from phase2 module

If the phase2 module is accessible from phase1, import the function:

+use crate::phase2::script_context::sort_reward_accounts;
+
-// 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.");
-    }
-}
-

Solution 2: Move to shared utilities module

If phase1 cannot access phase2, move this function to a shared location (e.g., crate::utils) and import it from both modules.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In pallas-validate/src/phase1/conway.rs around lines 1155-1182, this sorting
function duplicates code from phase2 and uses .expect() which can panic; replace
the local function with a call to the canonical implementation from
pallas-validate/src/phase2/script_context.rs (preferred) or move the shared
sorting logic into a common crate::utils module and import it from both phase1
and phase2; ensure the shared function does not call .expect() but instead
returns a Result (or otherwise propagates/handles invalid-address errors) so
invalid withdrawal addresses are handled consistently by the existing error path
rather than panicking.

pgrange added a commit to pgrange/dolos that referenced this pull request Nov 4, 2025
Using the patched version of pallas until next releases of both
pallas and dolos.

See: txpipe/pallas#708
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants