Fix non-canonical inline datum JSON round-trip - #1238
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a JSON round-trip bug for TxOut values containing inline datums whose underlying CBOR bytes are non-canonical (yet ledger-valid), by ensuring FromJSON preserves the original datum bytes when inlineDatumRaw is available.
Changes:
- Add
parseInlineDatumhelper to prefer decodingHashableScriptDatafrominlineDatumRawCBOR bytes, with a JSON-based fallback for older/external JSON. - Apply the inline-datum parsing fix across Babbage/Conway/Dijkstra for both
CtxTxandCtxUTxOFromJSON (TxOut ...)instances. - Add a property test (plus generator) that constructs a non-canonical inline datum and asserts JSON round-trip identity.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| cardano-api/test/cardano-api-test/Test/Cardano/Api/Json.hs | Adds a property test covering JSON round-tripping of TxOut with non-canonical inline datum CBOR. |
| cardano-api/src/Cardano/Api/Tx/Internal/Output.hs | Introduces parseInlineDatum and updates FromJSON (TxOut ...) to preserve original inline datum CBOR via inlineDatumRaw. |
| cardano-api/gen/Test/Gen/Cardano/Api/Typed.hs | Adds a generator for non-canonical HashableScriptData used by the new property test. |
|
Thanks for getting on this so quickly @v0d1ch! This is currently causing my hydra heads to fail to restart when a non-canonical datum is present in the ledger and the hydra-node crashes for any reason and attempts to validate its snapshot state from the state database. |
|
Any change of getting this merged? @Jimbo4350 |
|
@Jimbo4350 I think this PR is ready to be merged now. |
…ps (#2746) cardano-api's FromJSON for TxOut ignores the inlineDatumRaw field and reconstructs HashableScriptData via scriptDataFromJson, which re-serialises canonically. For outputs whose datum was encoded with definite-length CBOR arrays (valid on L1 but non-canonical), H(canonical) ≠ H(original), causing "Inline datum not equivalent to inline datum hash" on event replay. Add parseTxOutFromJSON that reads inlineDatumRaw, deserialises the original bytes directly, and patches inlineDatumhash to the canonical hash before delegating to the standard parser — then restores the original bytes. Add parseUTxOFromJSON that applies this per entry. Wire it in via an OVERLAPPING orphan FromJSON UTxO instance in IsTx.hs so all deserialization paths (Snapshot, HTTP handlers, event replay) are fixed without touching the IsTx typeclass interface. Add property tests: one pending the upstream cardano-api fix (xprop), one verifying our parseTxOutFromJSON preserves the original datum hash. Upstream PR: IntersectMBO/cardano-api#1238 <!-- Describe your change here --> --- <!-- Consider each and tick it off one way or the other --> * [x] CHANGELOG updated or not needed * [x] Documentation updated or not needed * [x] Haddocks updated or not needed * [x] No new TODOs introduced or explained herafter
|
This PR is stale because it has been open 45 days with no activity. |
carbolymer
left a comment
There was a problem hiding this comment.
Looks correct. I have some code style remarks.
Thanks!
ab45d84 to
8f79cb1
Compare
|
Thanks @carbolymer I think your comments are addressed now. |
8f79cb1 to
430f8bd
Compare
|
@v0d1ch Thanks, LGTM! Could you squash the commits, please? |
FromJSON (TxOut CtxUTxO era) ignores inlineDatumRaw and reconstructs HashableScriptData via scriptDataFromJson, which re-serialises to canonical CBOR bytes. For datums whose original CBOR uses definite-length arrays (non-canonical), H(canonical) ≠ H(original), causing "Inline datum not equivalent to inline datum hash" on parse. Signed-off-by: Sasha Bogicevic <sasha.bogicevic@iohk.io>
430f8bd to
00600d0
Compare
● Fix non-canonical inline datum JSON round-trip
Problem
FromJSON (TxOut) crashes when parsing a TxOut whose inline datum was encoded with non-canonical CBOR bytes (e.g. definite-length arrays instead of the indefinite-length form Plutus normally emits).
The error:
"Inline datum not equivalent to inline datum hash"
This happens because ToJSON and FromJSON disagree on how to reconstruct HashableScriptData:
Non-canonical Plutus CBOR is valid and accepted by the ledger. It arises when scripts use definite-length array encoding for constructor fields instead of the canonical indefinite-length form. Any node that stores such a UTxO entry and later replays it from a JSON snapshot (e.g. from a SQLite event log) would crash on
deserialisation.
Fix
inlineDatumRaw is always present in JSON produced by ToJSON (as null for non-inline outputs, as hex for inline ones). FromJSON now reads this field first and uses deserialiseFromCBOR AsHashableScriptData to reconstruct HashableScriptData with the original bytes preserved. The scriptDataFromJson path is kept as a fallback for JSON
produced by external tools or older versions that may not include inlineDatumRaw.
The fix is applied to all six affected sites: Babbage, Conway, and Dijkstra eras in both FromJSON (TxOut CtxTx era) and FromJSON (TxOut CtxUTxO era). The repeated logic is extracted into a parseInlineDatum helper.
Testing
Added prop_json_roundtrip_txout_noncanonical_inline_datum — a property test that generates a TxOut with a non-canonical inline datum (definite-length CBOR constructor) and asserts the JSON round-trip is identity. The test fails without the fix and passes with it.
Checklist
.changes/