Skip to content

fix(evolution): order withdrawal and voter redeemer indices type-first to match ledger Ord (#404) - #643

Open
Kshot3000 wants to merge 1 commit into
IntersectMBO:mainfrom
Kshot3000:fix/redeemer-index-ledger-ord
Open

Kshot3000 wants to merge 1 commit into
IntersectMBO:mainfrom
Kshot3000:fix/redeemer-index-ledger-ord

Conversation

@Kshot3000

@Kshot3000 Kshot3000 commented Oct 15, 2026 •

Copy link
Copy Markdown

Summary

Fixes #404. The builder assigned reward and vote redeemer indices in hash-only order, but the ledger resolves a redeemer pointer by the key's position in a map ordered by Ord — for credentials that is type-first (ScriptHashObj sorts before KeyHashObj), then by hash. When a transaction mixed a script credential and a key credential whose hashes interleaved against the type order, the SDK placed the redeemer at a different index than the ledger resolves, and the transaction was rejected (MissingRedeemers / ExtraRedeemers).

What was wrong

  • txBuilder.ts (assembly) sorted withdrawals by credential hash only (localeCompare on the hash hex), discarding the credential type.
  • Evaluation.ts mirrored the same hash-only withdrawal sort, so assembly and evaluation agreed with each other but not with the ledger.
  • voterToKey gave key-hash and script-hash DRep/committee voters the same key prefix, so sorting voter keys again ordered purely by hash and discarded the type.

Fix

  • New compareCredentials in TransactionBuilder.ts: rank script-hash credentials before key-hash credentials, then compare hash bytes (explicit </> on the hex, no localeCompare).
  • Withdrawals are sorted with it in Evaluation.ts and in the shared makeRedeemerIndexer helper in txBuilder.ts (assembly and the fee estimate both resolve redeemer indices through it since the upstream refactor), so every path assigns the same, ledger-correct index. The vote side needs no indexer change: the helper already sorts voterToKey keys lexicographically, and the ranked key prefixes make that sort reproduce Ord Voter.
  • voterToKey now encodes the credential's Ord rank in the key — cc:0: / drep:0: for script-hash credentials, cc:1: / drep:1: for key-hash credentials, pool: unchanged — so the existing lexicographic sort of voter keys reproduces Ord Voter (voter kind first, then script-before-key, then hash). All voter keys are produced through voterToKey, so the format change is internal and consistent across Vote, txBuilder and Evaluation.

Note the direction trap from the issue: this is deliberately not serialized-byte order — a reward account's header byte puts key-stake (0xE_) before script-stake (0xF_), the opposite of Ord Credential — so the sort is by explicit type rank, never by address/credential bytes.

Tests

New test/TxBuilder.RedeemerIndexOrd.test.ts, built through the real txBuilder with a stub (echo) evaluator, using a Plutus V3 script whose hash (a24924…) interleaves against a 0x11… key hash:

  • Script-credential withdrawal redeemer: emitted at index 1 on main, index 0 after the fix.
  • Script DRep vote redeemer against a lower-hash key DRep voter: index 1 on main, 0 after.
  • Script committee vote redeemer against a lower-hash key committee voter: index 1 on main, 0 after.
  • Control (script hash also the lower hash): index 0 both before and after.

Full suite: 1747 passed / 75 skipped / 0 failed (main baseline 1743 + 4 new), tsc --noEmit and eslint clean, prettier parity with main exact on all touched files. Patch changeset added.


Independent builder — not affiliated with the Cardano Foundation, IOG or Intersect.

@Kshot9000 (https://x.com/kshot9000) · ADA donations: addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v

cc @cardano-foundation @IntersectMBO

@Kshot3000
Kshot3000 force-pushed the fix/redeemer-index-ledger-ord branch from 56c5122 to c34c2b6 Compare October 17, 2026 00:53
@Kshot3000

Copy link
Copy Markdown
Author

Rebased onto current main (c34c2b6) — this PR had gone conflicting after the redeemer-indexing refactor landed upstream (#656/#658 series).

The refactor moved the assembly loop's inline index lookups into a shared makeRedeemerIndexer helper, so the fix now lives there instead of in the assembly loop:

  • The helper pre-sorted withdrawal hexes hash-only (localeCompare) — that is the txBuilder: order withdrawal and voter redeemer indices type-first to match ledger Ord #404 bug in its new home. It now sorts the withdrawal accounts with compareCredentials before mapping to hex, so assembly and the fee estimate (both go through the helper) get the ledger order. Evaluation.ts is unchanged from the original PR.
  • The vote case needed no change: the helper already sorts voterToKey keys lexicographically, and the ranked prefixes from this PR (cc:0:/cc:1:, drep:0:/drep:1:) make that sort reproduce Ord Voter. Only its key-format comment was updated.

Re-verified on the rebased branch: the three behavioural tests fail on current main run one at a time (the control passes both ways), and the full suite is 1747 passed / 75 skipped / 0 failed (main baseline 1743 + the 4 new tests), with tsc --noEmit, eslint and prettier parity clean. The diff is down to +247/−14 across the same five files.


Independent builder — not affiliated with the Cardano Foundation, IOG or Intersect.

@Kshot9000 (https://x.com/kshot9000) · ADA donations: addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v

cc @cardano-foundation @IntersectMBO

This branch has not been deployed

No deployments
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.

txBuilder: order withdrawal and voter redeemer indices type-first to match ledger Ord

1 participant