Skip to content

Fix non-canonical inline datum JSON round-trip#1238

Open
v0d1ch wants to merge 4 commits into
masterfrom
inline-datum-bug
Open

Fix non-canonical inline datum JSON round-trip#1238
v0d1ch wants to merge 4 commits into
masterfrom
inline-datum-bug

Conversation

@v0d1ch

@v0d1ch v0d1ch commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

● 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:

  • ToJSON serialises the inline datum as three fields:
    • inlineDatum — the human-readable JSON representation
    • inlineDatumhash — blake2b_256(originalCborBytes)
    • inlineDatumRaw — the original CBOR bytes as hex
  • FromJSON ignored inlineDatumRaw entirely and reconstructed HashableScriptData via scriptDataFromJson, which always re-serialises to canonical CBOR. For non-canonical original bytes:
    • H(canonical) ≠ H(original) = inlineDatumhash
    • → parse fails with the above error

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

  • Commit sequence broadly makes sense and commits have useful messages
  • New tests are added if needed and existing tests are updated. See Running tests for more details
  • Self-reviewed the diff
  • Changelog fragment added in .changes/

v0d1ch added 2 commits June 25, 2026 14:29
  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>
Copilot AI review requested due to automatic review settings June 25, 2026 14:54
@v0d1ch v0d1ch changed the title Inline datum bug Fix non-canonical inline datum JSON round-trip Jun 25, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 parseInlineDatum helper to prefer decoding HashableScriptData from inlineDatumRaw CBOR bytes, with a JSON-based fallback for older/external JSON.
  • Apply the inline-datum parsing fix across Babbage/Conway/Dijkstra for both CtxTx and CtxUTxO FromJSON (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.

Comment thread cardano-api/src/Cardano/Api/Tx/Internal/Output.hs
Signed-off-by: Sasha Bogicevic <sasha.bogicevic@iohk.io>
@v0d1ch v0d1ch self-assigned this Jun 25, 2026
@Crypto2099

Copy link
Copy Markdown

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread cardano-api/test/cardano-api-test/Test/Cardano/Api/Json.hs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.

3 participants