phase1: withdrawal redeemers rejected as UnneededRedeemer (Babbage + Conway)
Summary
pallas-validate's phase-1 validator builds the set of expected redeemer pointers by iterating only tx_body.inputs (for RedeemerTag::Spend) and tx_body.mint (for RedeemerTag::Mint). It never iterates tx_body.withdrawals, certificates, voting_procedures, or proposal_procedures, so no Reward/Cert/Vote/Propose pointers are ever emitted.
As a consequence, any structurally valid tx that includes a withdrawal-purpose redeemer (the "withdraw-zero trick" used by Pyth-style pull oracles, Indigo, etc.) is rejected with:
an unnecessary redeemer is present
…which surfaces over JSON-RPC as code -32004 (e.g. via dolos).
The same bug exists in both the Babbage and Conway phase-1 modules.
Affected versions
pallas-validate 1.0.0-alpha.4 — Babbage path
pallas-validate 1.0.0-alpha.5 — Conway path
Reproduced downstream via dolos 1.0.3, which links both. Cardano node (preview) accepts the same tx, confirming the bug is in pallas-validate, not in tx construction.
Where the bug lives
crates/pallas-validate/src/phase1/babbage.rs:947 — mk_plutus_script_redeemer_pointers
crates/pallas-validate/src/phase1/babbage.rs:1006 — redeemer_pointers_coincide
crates/pallas-validate/src/phase1/conway.rs:1150 — same defect, duplicated for Conway
mk_plutus_script_redeemer_pointers returns the list of expected pointers; redeemer_pointers_coincide compares the tx's actual redeemers against that list and raises UnneededRedeemer for anything missing. Because the expected list is incomplete, every withdrawal redeemer is flagged.
Steps
- Compile any always-true Plutus V3 validator and publish it as a reference UTxO at
script_ref. Use its script hash as script_hash.
- Build and submit
withdraw_zero(0) against a dolos devnet (which links pallas-validate).
- Submit the same tx against a real
cardano-node (preview).
Result
-
Real cardano-node: phase-1 passes, phase-2 runs the validator, tx is accepted.
-
dolos (pallas-validate phase-1): rejected with
an unnecessary redeemer is present
surfaced over JSON-RPC as code -32004.
The tx body is identical in both cases — only the validator differs.
phase1: withdrawal redeemers rejected asUnneededRedeemer(Babbage + Conway)Summary
pallas-validate's phase-1 validator builds the set of expected redeemer pointers by iterating onlytx_body.inputs(forRedeemerTag::Spend) andtx_body.mint(forRedeemerTag::Mint). It never iteratestx_body.withdrawals,certificates,voting_procedures, orproposal_procedures, so noReward/Cert/Vote/Proposepointers are ever emitted.As a consequence, any structurally valid tx that includes a withdrawal-purpose redeemer (the "withdraw-zero trick" used by Pyth-style pull oracles, Indigo, etc.) is rejected with:
…which surfaces over JSON-RPC as code
-32004(e.g. viadolos).The same bug exists in both the Babbage and Conway phase-1 modules.
Affected versions
pallas-validate 1.0.0-alpha.4— Babbage pathpallas-validate 1.0.0-alpha.5— Conway pathReproduced downstream via
dolos 1.0.3, which links both. Cardano node (preview) accepts the same tx, confirming the bug is inpallas-validate, not in tx construction.Where the bug lives
crates/pallas-validate/src/phase1/babbage.rs:947—mk_plutus_script_redeemer_pointerscrates/pallas-validate/src/phase1/babbage.rs:1006—redeemer_pointers_coincidecrates/pallas-validate/src/phase1/conway.rs:1150— same defect, duplicated for Conwaymk_plutus_script_redeemer_pointersreturns the list of expected pointers;redeemer_pointers_coincidecompares the tx's actual redeemers against that list and raisesUnneededRedeemerfor anything missing. Because the expected list is incomplete, every withdrawal redeemer is flagged.Steps
script_ref. Use its script hash asscript_hash.withdraw_zero(0)against adolosdevnet (which linkspallas-validate).cardano-node(preview).Result
Real
cardano-node: phase-1 passes, phase-2 runs the validator, tx is accepted.dolos(pallas-validatephase-1): rejected withsurfaced over JSON-RPC as code
-32004.The tx body is identical in both cases — only the validator differs.