Repository navigation
Conversation
…sectMBO#577) The TxOut schema encoder picks the legacy array form whenever an output has no script ref and its datum is absent or a hash, even when the output was decoded from the post-Alonzo map form. The captured format node says map while the encoder produces an array, so the format was dropped for that output: the body bytes changed, the transaction id changed, and witnesses already collected stopped verifying. The captured format now decides the form. TxOut.withCapturedForm rebuilds an array-encoded output as a map when its format node is a map — a bare datum hash is wrapped as the datum_option [0, hash] — and TransactionBody.alignOutputsWithFormat applies it to the outputs (key 1) and the collateral return (key 16), matched positionally the way the encoder consumes format entries. Transaction.toCBORBytesWithFormat aligns the body inside the transaction tuple the same way. With no map format node nothing changes: array-form outputs, outputs with an inline datum or script ref, and freshly built transactions encode as before. Tests: 9 new checks in Transaction-with-format.test.ts — definite and indefinite map-form round trips via WithFormat and the cached default path, a map-form output with a datum hash, map and array outputs side by side, a map-form collateral return, CML body-hash equality, addVKeyWitnessesHex body stability, and a TransactionBody-level round trip. Full suite: 1687 passed / 75 skipped / 0 failed.
Kshot3000
force-pushed
the
fix/txout-map-form-preserve
branch
from
October 17, 2026 02:59
a6f64d5 to
db92912
Compare
Author
|
Rebased onto current main (db92912) — this branch was conflicting in The conflict was semantic rather than line noise, so the fix was re-derived in place rather than marker-resolved:
Verification on the rebased branch:
I'm an independent builder working on Cardano tooling — not affiliated with the Cardano Foundation, IOG or Intersect. @Kshot9000 (https://x.com/kshot9000) · Tips: addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #577. An output written in the post-Alonzo map form, with no script ref, came back from
Transaction.fromCBORHex→Transaction.toCBORHexin the legacy array form:a2 00 [addr] 01 [coin]82 [addr] [coin]bf 00 [addr] 01 [coin] ff82 [addr] [coin]a3 00 [addr] 01 [coin] 02 8200 5820 [hash]83 [addr] [coin] 5820 [hash]Nothing else changed, but the body bytes change, so the transaction id changes and every witness already collected no longer verifies. Same class as #574: the decoder accepts two forms and the encoder writes one without consulting the captured format.
Root cause
TxOut.makeFromCDDL's encoder picks the array form wheneverscriptRefis absent and the datum is absent or a hash (canUseShelleyFormat). The captured format node for such an output says map while the encoder produces an array, so the format-guided encoder drops the format for that output and writes the array.A pure CBOR-level conversion cannot fix this: in the array form the third element is a bare datum hash, while map-form key 2 carries the datum_option
[0, hash]— only the TxOut layer knows that mapping.Fix
Let the captured format decide the form, as the issue prescribes:
TxOut.withCapturedForm(encoded, format)— when the format node is a map and the encoded output is an array of 2–3 elements, rebuild it as a map: keys 0/1 take the address and value as they are, and a bare datum hash is wrapped as[0, hash]. Anything else (array format, already a map, an edit that added a script ref or inline datum) is returned unchanged.TransactionBody.alignOutputsWithFormat(cborMap, format)— walks the encoded body map positionally, exactly the way the format-guided encoder consumes format entries, and applieswithCapturedFormto the outputs (key 1) and to the collateral return (key 16, the same bug for a single output). Returns the input map untouched when nothing aligns.TransactionBody.toCBOR*WithFormatandTransaction.toCBORBytesWithFormat(which also covers the cached defaulttoCBORHexpath andaddVKeyWitnessesHex) run the alignment before the format-guided encode.With no captured format — or no map node — encoding is exactly as before, so freshly built transactions do not change.
Tests
RED first on main: each map-form case below failed with the array form written back (e.g. expected
…81a200…, received…8182…); the array-form controls passed. After the fix, 9 new checks intest/Transaction-with-format.test.ts:toCBORHexWithFormatand via the cached default path (fromCBORHex→toCBORHex)bf … ff) round-trips byte-identically[0, hash]wrap)hash_transactionaddVKeyWitnessesHexleaves the map-form body bytes (and hash) unchangedTransactionBodyWithFormat levelFull package suite: 1752 passed / 75 skipped / 0 failed (test files 107 passed / 1 skipped of 108) — current main's baseline plus the 9 new tests.
tsc --noEmitand eslint clean; a patch changeset is included. Prettier already flags the three touched source files on unmodified main; my versions produce exactly main's prettier diff-line counts (28 / 54 / 6), so the change adds no new formatting drift.Rebase note (2026-10-16): rebased onto current main after the format-cache /
gainsLowerKeychanges inTransaction.toCBORBytesWithFormatconflicted with this PR. The alignment now runs off upstream'sbodyFormatand the aligned body feeds both return paths (the early replay return and the witness-set path);alignOutputsWithFormat/withCapturedFormare unchanged. RED re-confirmed on main and the neighbouring format suites (10 files / 157 tests) re-run green — see the rebase comment below for detail.I'm an independent builder working on Cardano tooling — not affiliated with the Cardano Foundation, IOG or Intersect.
@Kshot9000 (https://x.com/kshot9000) · Tips: addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v