Fix non-canonical inline datum JSON round-trip#1238
Open
v0d1ch wants to merge 4 commits into
Open
Conversation
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. Adds genNonCanonicalHashableScriptData and a failing property test that will pass once FromJSON reads inlineDatumRaw to preserve original bytes. Signed-off-by: Sasha Bogicevic <sasha.bogicevic@iohk.io>
FromJSON (TxOut) for Babbage, Conway and Dijkstra ignored inlineDatumRaw and reconstructed HashableScriptData via scriptDataFromJson, which always re-serialises to canonical CBOR. For datums whose original on-chain CBOR uses definite-length arrays (non-canonical Plutus encoding), this caused H(canonical) ≠ H(original), triggering "Inline datum not equivalent to inline datum hash" when parsing JSON produced by the ToJSON instance. ToJSON always writes inlineDatumRaw containing the original CBOR bytes. FromJSON now reads that field first and uses deserialiseFromCBOR to reconstruct HashableScriptData with the original bytes intact, falling back to scriptDataFromJson only for JSON produced without inlineDatumRaw. Fix applied to both CtxTx and CtxUTxO instances for all three eras. The repeated logic is extracted into a parseInlineDatum helper. Signed-off-by: Sasha Bogicevic <sasha.bogicevic@iohk.io>
Contributor
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. |
Signed-off-by: Sasha Bogicevic <sasha.bogicevic@iohk.io>
|
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. |
Open
4 tasks
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.
● 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/