Skip to content

Dijkstra hard fork readiness #752

Open

Description

@rober-m

Hi team! Thank you for all your work to get this library ready for the HF! The feat/ledger-cbor branch (#748) already covers a lot of Dijkstra. However, I ran a few tests on a devnet with cardano-node 11.2.0, against the releases and that branch, and found several things to work on before it's ready. Since this project is (obviously) critical for the DApp ecosystem, please let me know if we (I, my team at AlwaysTrue, or anyone at IOG) can help with this!:

Testing scenario

Tested on 2026-11-11: 0.7.3 and 0.8.0-pre7 from Maven Central, and the feat/ledger-cbor branch at d3324ec3 built with JDK 21, against cardano-node 11.2.0 on a cardano-testnet devnet (magic 42) that hard-forked from Conway to Dijkstra and was raised to protocol version 12. The library built and signed every transaction, which was then submitted as raw CBOR through cardano-submit-api 11.2.0. UTxOs and protocol parameters came from cardano-cli, so the Blockfrost, Koios, and Ogmios backends were not tested against Dijkstra. Also not tested: required top-level guards (key 24), starting balance intervals (key 27), pool registration with a BLS key, and parameter-change proposals.

What already works:

  • 0.7.3 and 0.8.0-pre7: payments, metadata, and a PlutusV3 lock and spend are accepted. So are RegCert and UnregCert swapped in through preBalanceTx.

  • The branch: models written with serialize(Era.Dijkstra) and signed over the bytes are accepted for each of these cases:

    • a three-element payment
    • a direct deposit (the account balance rose by the deposit)
    • a guard
    • an account balance interval
    • a PlutusV4 reference script
    • a PlutusV4 lock and spend
    • a top-level transaction with one sub-transaction

    The node rejects a guard on a key that didn't sign and an interval the balance falls outside, as it should.

  • Reading on the branch: the 25 transactions of an earlier devnet run, read with deserialize(bytes, Era.Dijkstra), all give the on-chain id through RawTx.

  • The branch's tests: common-spec, ledger-cbor, transaction-spec, and plutus run 1,100 tests with 0 failures, without the external CBOR dataset.

To reproduce:
The devnet scripts are in scripts/readiness/devnet. Its README builds the devnet, and jvm-probe/ccl-e2e has the QuickTx harness (E2E.java) and the branch probe (BranchProbe.java). Logs and the CBOR of every submitted transaction: 2026-11-11-ccl.

Issues

Use the deposit certificates in QuickTx stake registration

registerStakeAddress and deregisterStakeAddress add StakeRegistration and StakeDeregistration (certificate types 0 and 1), which Dijkstra removes (StakeRegistrationIntent.java#L120, StakeDeregistrationIntent.java#L179). This is the same on master and the branch. With 0.7.3 and 0.8.0-pre7, the node rejects both transactions:

EraMismatch {ledgerEraName = "Dijkstra", otherEraName = "Conway"}

The same transactions with RegCert and UnregCert instead (plus the stake key witness that RegCert needs) are accepted. These certificates are also valid in Conway. Tools that register stake keys through QuickTx, such as yaci-devkit's cluster setup, hit this too.

Keep the Dijkstra fields when signing a Transaction object

On the branch, TransactionSigner.sign(Transaction, …) serializes in the transaction's era and signs, then re-reads the result with era-less Transaction.deserialize(signedTxBytes) (TransactionSigner.java#L32-L59). That reads the bytes as Conway, so body keys 23-27 are lost. A direct deposit signed this way loses key 25 and is rejected:

ValueNotConservedUTxO … InvalidWitnessesUTXOW

The same transaction signed with sign(byte[], …) is accepted. Account.sign(tx) and the QuickTx signers use the Transaction path.

Keep the id of a key-hash key 14

A Conway body with required_signers (key hashes in key 14) that is accepted after the fork stays on chain in that form. The branch reads key 14 as guards and writes it back as credentials. getTxHash() on the model then differs from the on-chain id, and re-signing or re-submitting that model changes its body. RawTx gives the right id.

Era-less reads drop Dijkstra fields silently

Transaction.deserialize(bytes) still reads as Conway on the branch. On a direct deposit or a sub-transaction, it drops the field without an error and computes a different id, as 0.8.0-pre7 does today. Callers that don't pass an era will get these wrong: the backends' transaction parsing, the evaluators, Account.sign(String), and downstream codecs.

PlutusV4 and the new fields in QuickTx and the backends

The branch's ADR 0004 leaves QuickTx and the builders (W-M2) for later. Until then, QuickTx can't build any of the following:

  • a three-element transaction
  • a PlutusV4 script
  • a guard, sub-transaction, direct deposit, or balance interval

Fee and balance calculation also don't account for direct deposits or sub-transaction execution units. The backends map cost models for V1-V3 only (Ogmios ProtocolParametersDto.java#L81-L83, Koios KoiosEpochService.java#L251-L278), so a V4 cost model never reaches the builder. #707 tracks the PlutusV4 part.

Reference-script fee parameters

The reference-script fee uses a fixed multiplier of 1.2 and a fixed stride of 25,600 bytes (FeeCalculationServiceImpl.java#L229-L230). Dijkstra makes them protocol parameters 36 (stride) and 37 (multiplier). The devnet's values equal the constants, so fees are right today, but a parameter change would make the fee too low.

Backport the duplicate-witness fix to 0.7.x

Dijkstra rejects witness sets with duplicates. In 0.7.3, adding the same key twice with withSigner writes two identical vkey witnesses, and the node rejects the transaction. 0.8 already removes the duplicate (bd9e1c82), but release/0.7.x doesn't have it. Unrelated to Dijkstra, 0.7.3 also computes the script data hash wrongly when two spent outputs share a datum hash, so the node rejects it with ScriptIntegrityHashMismatch, on Conway too. The fix is in 0.8 (a39f5924, #603).

Merge #748 and release it

No release has the Dijkstra models yet; the newest is 0.8.0-pre7. Yaci, Yaci Store, and Yano follow CCL's release (ADR 0003), so they can't ship Dijkstra block reading before it either.

Activity

  1. satran004 commented on Nov 13, 2026

    @satran004
    Member

    Thanks, @rober-m. Yes, we have started with CBOR parsing for the Dijkstra era, as this will also be used by other projects like Yaci, Yano and Yaci Store.

    Thanks for the list of gaps and issues. I think it will help us move faster. We'll look into them soon and update the progress here.

    The goal is to get the CBOR encoding/decoding layer ready first and publish a release so that it can be used by downstream projects like Yaci, instead of maintaining a separate parsing layer there, as we do today.

    After that, we'll start looking into higher level transaction builder like QuickTx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions