Repository navigation
Conversation
ADR 0003 M0. The raw views move with their package to a new module with no model on its classpath, so the build keeps models out of the reader. EraRules becomes the public EraSpec. RawDatum.toPlutusData and RawScript.toScript need models, so they become RawModels helpers in transaction-spec (Q2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
ADR 0003 I5 and I6 ask that a migrated entry point accepts the same inputs and returns the same models. Before moving certificates onto the reader, record for every public certificate decoder, over the dataset's Conway samples of the rules a certificate is made of, valid and invalid alike, how many samples are accepted and a digest of the outcomes, models included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
CustomCborEncoder wrote bfff back as bf alone, which is not well-formed CBOR: the BREAK was only written after a map's entries. The recursive encoder before it did the same, and the iterative one kept that on purpose. ADR 0003 D7 has the public overloads re-encode their item for the reader, which then cannot read such an item, so the side-by-side run of the certificate decoders found accepted inputs that became rejected. Close it as the array case already does, here and in the oracle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
ADR 0003 M1. FormatPolicy, RecordSpec, FieldSpec and FieldType are D6's vocabulary, and RecordSpec.walk is one walk for keyed records, tagged unions and positional records, driven by the tables. A table's depth bounds the walk's, and recursive types are leaves, so it is stack-safe. CertKind is the table of the certificate rule: all 19 tags across eras, read from the CDDL at f649f975. RawCertificate and its nested views find items through the table by type, so a position is written down only there. RawCertificate.of reads as D5 says. RawCertificate.lenient reads what the models have always accepted, which D7 and I5 keep; D5 rejects a wrong arity and the models accept a long Conway certificate, so the two need separate entry points. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
ADR 0003 M1. Certificate and every other certificate decoder, with Anchor, DRep and the credential decoders, become mappers over RawCertificate; the public overloads re-encode their item and take the same path (D7). A mapper holds no key, position or tag. No input changes outcome: CertificateOutcomesTest passes against the file generated from the old decoders, and a side-by-side run of old and new found no difference. Rejected inputs may be rejected with another exception: ClassCastException and IndexOutOfBoundsException became CborRuntimeException. CertificateLeniencyTest names what the models accept beyond the ledger's rules; the same test passes on the old decoders. One check in RawModels knows an encoding, against D1: an anchor must have a definite length, as the models have rejected since #717. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
ADR 0003 M1's keyed-record proof. TxBodyField and WitnessSetField are the tables of transaction_body and transaction_witness_set, Shelley to Conway. A key has a row per era range with its own type or requirement: key 3 is required only in Shelley, key 6 ends at Babbage, key 14 is required_signers to Conway. RawTx.walk takes the bytes and not a view, as a view rejects a repeated key when it is created and a policy must see that entry. The proof is a policy with no key number in it that rejects what Yano's reader rejects before reading a value: a body with no fee, an unknown body or witness key, a repeated key, equal keys encoded differently, a field outside its era. I9: a walk and the accessors stay stack-safe at 200,000 levels, on the default stack and on a virtual thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
ADR 0003 I3. tools/cddl-table-check reads the CDDL of each era with git show and the Java tables as text, and compares certificate tags and items, transaction body and witness set keys with their required flags and types, and the rules a certificate is made of. Python with only its standard library, run by hand and on each ledger pin bump. At f649f975 every table matches, Shelley to Conway. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
A view read a record's items again for its tag and its arity, and a number at a lenient position went through a tree even when it was an integer. Time and allocation of Certificate.deserialize fall by about half and a third, still above the old decoder's. Removes the tables' spec() accessors, which nothing used. LedgerReaderConformanceTest: every valid Conway sample of the certificate, body, witness set and transaction rules reads and walks with no structural complaint (ADR 0003 D5, I13). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…pe's table A policy checks a value by its type alone (ADR 0003 D6), so it could not reach a pool owner's hash length or definite encoding through a plain SET. KEY_HASH_SET names the CDDL's set<addr_keyhash>, and the table check now requires it wherever the CDDL has it. FieldType.record() is public so that a caller can walk one nested record, such as a credential or a DRep, as it can already walk a certificate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…e JVM A manual check for moving the decoders onto one reader (ADR 0003 I5, I6, I10). Two sides, each a directory of jars, run in isolated class loaders over the same inputs. Probes loaded in each side return an outcome string: accepted with the model as canonical JSON, rejected, or a stack overflow. A field coverage walker pins that the JSON misses no field. Commands: dump (a sample of raw blocks from a node), diff (over a sample or a stream, with an approved list whose fields are checked against values computed from the original bytes), diff-dataset (the cardano-cbor-dataset's rules), and bench (the I10 baseline). side.sh resolves the jars of a side. Standalone Gradle build, as with tools/chain-parse-check. Not part of the root build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
CborSpan.arrayItem finds an item of nested arrays by its index path, reading only the heads on the way, the items before each index and the item itself, so whatever follows may be malformed or missing. A span must walk all of an array to be created, which is what a damaged block's header cannot have. ADR 0002 D8 promised this and ADR 0001 left it for when a need showed. It takes every array form and looks through tags, so Yaci's CborSlice.arrayItem can be it. Also pins that CborSpan.at does not read trailing bytes and CborSpan.skip does: a caller that must fail on a malformed value after a block, as Yaci's BlockSerializer does, walks them with skip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…yout ADR 0003 M2 on the CCL side. EraSpec is complete for Shelley to Conway: each era names a block layout (PARALLEL_4, PARALLEL_5) and a header layout (FLAT_15, NESTED_10), each implemented once. EraSpec.newest() names the last row. BlockEnvelope reads [era, block], optionally in tag 24, and accepts any era index, Byron and unknown ones included. RawBlock.of(envelope) reads a known era and raises UnsupportedEraException, a CborRuntimeException, for the others. RawBlock.readAs(envelope, era) reads with a named era's layouts. RawBlock.of(bytes) keeps what it accepts. RawHeader reads both layouts by table: block number, slot, previous hash, keys, VRF results, body size and hash, operational certificate, protocol version and signature, each when it is asked for, leniently as D5 sets out. hash() is blake2b256 of the header's original bytes (I8, D8's correction). RawBlock.header() stays a span, so the view is RawBlock.rawHeader(). RawHeader.of(span) takes its layout from the shape, for a caller with no era. BlockEnvelope.eraIndexOf and headerOf read only as far as they need, so a block damaged after its header still reports its number while RawBlock.of stays strict. The I3 tool checks the layouts against the pinned CDDL. Its split_top took the '>' of '=>' for a closing bracket, which merged the items after a map; fixed. Also fixes a stale Javadoc link in RawTx that failed :ledger-cbor:javadoc, and wraps two long lines of the previous commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Per block, RawHeader's block number, slot, hash and body hash against the raw bytes, and the block number and slot against Yaci's header reader. A block hash that differs from Yaci's is counted, not reported, so a G3 run answers how many real headers ADR 0003 D8's correction changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…s it was decoded ADR 0003 M3 reads a transaction body from spans in place of a tree, and has to keep what the tree accepts. CborSerializationUtil.checkTags rejects the only thing decoding rejects and a span's walker does not: tag 30 without two integers and a denominator other than 0, and tag 38 without two text strings, in one pass over the heads. A test holds it to decoding on hand-made items and on random mutations. deserialize(CborSpan) decodes a span without a copy, and CborSpan.isTagged tells a tag number of 2^64 - 1, which tag() returns as the -1 of no tag. The re-encode route of D7 lost two inputs that the old decoders accepted. A tag number of 2^63 or more was written with cbor-java's signed comparisons, as invalid bytes (ff for 2^64 - 1); it is written in 8 bytes now, here and in the oracle. cbor-java's decoder also sets a tag on its shared null, true, false, undefined and BREAK for the whole JVM when it reads d818f6, so a tree's nulls were written tagged, and read back as no null. Such a tag is not written now: Yaci's agent found this rejecting certificates with a null field. Walking an item with no indefinite length needs no frames, only a count, so CborSpan.skip allocates nothing for most items. This keeps ccl-tx within I10 with a body walked at each level it reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…s, outputs, values and mint ADR 0003 M3. RawTx.body(era) is a typed view driven by TxBodyField: inputs, outputs, fee, ttl, certificates, withdrawals, mint, hashes, network id, collateral, required signers and reference inputs. Update and the governance keys stay spans until M5. A key a later row gives another meaning reads as absent, through RecordSpec.replacedIn, which is how key 14 stays era-aware, and a field that is only outside its era stays readable (D5). RawOutput keeps its span accessors in both forms and gains addressBytes, amount, inlineDatumBytes and scriptRefBytes; RawValue, RawMultiAsset, RawWithdrawal and RawInput are new. As M1 did for certificates, RawTx.lenient, RawTxBody.lenient, RawOutput.lenient and RawValue.lenient read what the models have always accepted, which D7 keeps. The tables gain what Yano's policy needs for these records, within D6's walk contract: positional tables for an input and a legacy output, with an optional last item; RecordSpec.entries, a keyed record of like entries with a key type and a value type per era, which tells mint from withdrawals; a row's own table, for what a set's items are; and Shape.BY_MAJOR_TYPE, where the item's major type picks the record, for an output and a value. A policy sees the record picked, in the shapes it knows. Types the policy has no rule for yet are not checked by it, but it does see the tables now, so its rules on inputs, outputs and mint apply as they are. A certificate's items are read once: CertKind.of and the view share one list, a union's walk hands it to its member, and RawCertificate.walk walks a view without listing them again. On the corpus's certificates a walk and a view go from 4377 to 1466 bytes. From Yaci's pilot: RawRelay.of, relays read when asked for, public RawItems, RawMir.namedPot, TxBodyField.key and spec, and Optional in place of null in RawRelay. A relay's accessors change type, which Yaci's mapper has to follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
ADR 0003 M3, D1 and D7. Transaction, TransactionBody, TransactionInput, TransactionOutput, Value, MultiAsset and Withdrawal map the views, with no key, position or tag in the mapper. Their signatures stay, and the overloads taking a tree re-encode it and share the path. Transaction.deserialize no longer decodes the body to a tree or re-encodes a certificate. The witness set and the aux data stay on the decoded tree until M4, and update and the governance keys are decoded from their spans until M5. Accepting and rejecting is unchanged, over the corpus, the dataset and mutated bodies; a rejected input may now raise another class, and an overload that declares CborDeserializationException raises it for all of them, in place of ClassCastException and NullPointerException. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…n every transaction cddl-table-check compares transaction_input, both forms of an output, datum_option, value, multiasset, mint and withdrawals, era by era. chain-parse-check reads every typed field of a body and walks its tables on each transaction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…diff Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
RawTxBody.tolerant, RawOutput.tolerant, RawValue.tolerant and RawWithdrawal.tolerant read what Yaci's serializers read, so they can become mappers (ADR 0003 M3): a repeated record key means its last value, a value array may be indefinite or longer than two items, and a withdrawal may be any integer. RawTxBody.certificateItems gives the certificates as encoded. Nothing that exists changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
A policy has to tell a transaction index, a block body size and a VRF proof from a plain uint or byte string, as the CDDL does (uint .size 2, uint .size 4, bytes .size 80), so these get their own FieldTypes. RawHeader.walk walks a header and its body in the layout it has, for a caller with no era; RawHeader.bodySpan gives the body as encoded; RawOutput.of is public, for a caller holding one output. No reading changes, and the table check still matches the pinned CDDL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Its walk is public, and a policy has no need of the table: the rows hold in every era, so the walk tells it the newest, with no lookup of a layout's first era. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
A record is now read twice, by a policy's walk and by its views, and each listing of a container skips over every child, so re-listing costs a scan of all it holds at every level (ADR 0003 I10). A span keeps its listing. The list it hands out cannot be modified; no caller modifies one. A racing second listing is equal and harmless. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…he whole transaction ADR 0003 M4. RawTx.witnessSet(era) and RawTx.auxData(era) are typed views over WitnessSetField and the new AuxDataField: vkey and bootstrap witnesses, scripts, datums, both forms of redeemers, and aux data in all three forms. Scripts, Plutus data and metadata stay spans. RawWitnessSet.lenient, RawAuxData.lenient and RawRedeemer.lenient read what the models have always accepted, which D7 keeps: a redeemer map has one entry per key as a decoded map has it, a field of neither redeemer form holds none, the tag closest to a map says what aux data it is, and a redeemer value and the aux array are definite unless a tree makes them so (RawItems.indefiniteWhenDecoded). RawTx.redeemers, scripts, witnessDatums and auxScripts read through the views, with the behaviour they had. The tables gain what Yano's policy needs, within D6's walk contract. Witness set rows name the table of an item: positional vkeywitness, bootstrap_witness and redeemer, and a leaf per item for the recursive types. Redeemers are by major type: a list, or the map of like entries, whose keys and values have tables. Auxiliary data is by major type, with a row for a map in its tag (RecordSpec.tag). RawTx.walk now walks the transaction's own array, with the form without the flag having rows to Mary and the form with it from Alonzo, so an out of era form is the policy's to reject. A key of an array of scalars counts its occurrences as decoded, so [0, 0] and [0, 1800] are one key. KeyedRecordPolicyTest.acceptAllSeesRepeatedAndUnknownKeysInSourceOrder now looks at the body's entries only, as the walk reports the transaction's positional entries as well, with no key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
… reader ADR 0003 M4, D1 and D7. TransactionWitnessSet, VkeyWitness, BootstrapWitness, Redeemer, ExUnits and AuxiliaryData map the views, with no key, position or tag in the mapper. Their signatures stay, and the overloads taking a tree re-encode it and share the path. PlutusData, NativeScript, the Plutus script models and CBORMetadata still decode their recursive content, from the source bytes of its span. Transaction.deserialize reads no tree of its own now, and only M5's governance items in TransactionBody still decode from spans. plutus now depends on ledger-cbor, for Redeemer and ExUnits. Accepting and rejecting is unchanged over the corpus, the dataset and mutated witness sets and aux data, and a rejected input to an overload now raises CborDeserializationException in place of ClassCastException or CborRuntimeException. One difference is known: a tree's Plutus data map that lost a pair no longer reports it through PlutusData.mapsThatLostPairs when it goes through an overload taking a tree, as re-encoding drops the mark. From bytes, as Transaction.deserialize reads it, the mark stays, and WitnessAndAuxMappingTest pins both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…lk every transaction cddl-table-check compares the table of each witness field's items, vkeywitness, bootstrap_witness, both forms of redeemers, ex_units, the three forms of auxiliary data and the transaction's own array, era by era. chain-parse-check reads the typed witness set and aux data of each transaction and walks all its tables, reporting any field that is unknown or outside its era and any required field missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
… without copying The same reason as listing a span's children once (ADR 0003 I10). A repeated-key check of small keys needs no set, and a byte string key's identity needs no copy of its bytes. Nothing a caller sees changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
encodeHexString built a String for every byte in a synchronized StringBuffer, and decodeHexString a substring for every pair; it was a fifth of a transaction's read in Yano's parse (ADR 0003 I10). The same characters come out, lower case, and the same input is rejected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…r does ADR 0003 M4, for Yaci. RawWitnessSet.tolerant and RawAuxData.tolerant read what a decoded tree accepts, as RawTxBody.tolerant does: a repeated key means its last value, a redeemer map's value may be indefinite, the aux data array may be indefinite and longer than two items, and an item of no aux data form has nothing in it. What decoding to a tree rejects is still rejected. A redeemer keeps its encoding: RawRedeemer.span is the item of the array form or the value of the map form, and RawRedeemer.key the map form's key. A repeated key keeps its first encoding with its last value. RawAuxData.has says whether the keyed form has a field, which an empty list of scripts cannot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…s in a mask ADR 0003 I10. RawTx.walk takes the span of a view as well as bytes, so a caller that walks and then reads lists each container once: a span keeps its listing. A keyed walk counts unsigned keys below 64 in a mask, however their heads are written, and keeps a map only for any other key or a repeat, so a record's usual walk allocates no BigInteger and no map. What a policy is told does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…irectory from the environment side.sh and the README named one developer's directories; READER_DIFF_HOME now says where m2/ and sides/ are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
The walk compared a key that is no integer, array or byte string by its whole encoding, and a byte string by its content, so a text key "aa" and the byte string key h'6161' counted as one key. Inside an array key it compared a byte string by its whole encoding, so a voter, gov action id or committee credential whose hash has a longer length head counted as another key, where the decoded map has one. Every key that is no integer now goes through RawItems.identity, alone or as an item of an array key. Found by Yano's M5 (governance maps keyed by records) and the ADR 0003 review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…ers take, and tell [+ a] from nonempty_list A record whose shape follows its item's major type reported nothing of its own, so a form outside its era with an empty container reached a policy as an enter alone: an array value in Shelley, a map output in Alonzo, aux data in tag 259 or as an array in Shelley, map redeemers in Babbage. A policy with no layout knowledge (ADR 0003 I13) could not reject them. The walk now reports the row the item picks as an entry with no key, whose key is the major type, before it walks the row's table, as a positional record reports each position. NONEMPTY_LIST stood for both Conway's nonempty_list<a>, which may be in tag 258 (witness fields 0, 1, 2 and 4), and the list form of redeemers, [+ redeemer], which may not (decodeNonEmptyList without allowTag, Alonzo/TxWits.hs at 6176e413). The list form of redeemers is now NONEMPTY_ARRAY, and cddl-table-check maps [+ a] to it. Found by the ADR 0003 review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…integers, and keep RawItems to this module A view read a fee, a collateral, a withdrawal or a parameter's coin as an unsigned integer, and a certificate's deposit, pledge and cost, a proposal's deposit, a value's coin and a MIR's coin as any integer or bignum. Every coin is now read as the ledger's Coin decodes one (decodeWord64, Coin.hs at 6176e413), an unsigned integer with tags looked through, through RawItems.coin. The lenient readings keep what the models have always read (ADR 0003 D7), so CCL's models and Yaci read what they did; a walk still gives a policy a value's coin of any integer or bignum to check. Delta coins and asset quantities stay signed. The strict reading of a redeemer read its index as any 64-bit integer, a negative one included; tag and index are now read as the lenient readings read them. RawItems keeps public only what Yaci and plutus call: array, bytes and indefiniteWhenDecoded. Found by the ADR 0003 review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
… envelope readers stop; fix a block's message A RawBlock of two items said it was a Dijkstra block in any era; it now says how many items the era's block has, and EraSpec still names Dijkstra for era 8. RawBlock and BlockEnvelope say that eraIndexOf and headerOf, unlike of, do not look into tag 24. CborSpan says why its memo fields need not be volatile: each memo is reached only through final fields, so a thread that sees one sees it whole (JLS 17.5). Found by the ADR 0003 review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Section 7.1 records how each milestone's exit was met, with the final G3, full-chain, dataset, Yano report and I10 numbers; the decisions taken during implementation; the corrections to D2, D3, D5, D6, D7 and D8; the differences for approval; and what is left open (I10 for Yano's validation path, indefinite-length headers, yano#238). The status stays Accepted; its note now says M0 to M6 are implemented. Q6 is implemented as (a). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Contributor
|
Initial automated review · Aster Hi @satran004, thanks for the PR! 🙏 This is an automatic first check by an AI reviewer. It does not replace maintainer review and may be wrong. I’ve noted that this is still a draft. Reviewed commit: Issues found:
Not tested: Code was read only; builds, tests, and chain comparisons were not run. |
The ledger counts the values of a bootstrap address's unknown attributes in its attribute size (bootstrapAddressAttrsSize), so a validator needs them from the same decoder that checks the address. ByronAddress.getUnknownAttributes returns every attribute other than the derivation path and the protocol magic, by key, as the ledger's unknownAttributes keeps them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Haskell decodes by era (eraDecoder at eraProtVerLow), so Conway always decodes at 9. Section 7.1 "Left open" now says M7 adds Yano's Dijkstra policy at version 12 and checks the auxiliary data forms whose decoders change there. No other text changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…ntal ADR 0003 r6: new D4a reads Dijkstra and Leios in ledger-cbor as experimental work for the Leios branch in Yaci, pinned to cardano-ledger-dijkstra 0.5.0.0 (95b36905, codecs equal to 0.4.0.0 in cardano-node 11.2.0) and ouroboros-consensus 5.2.0.0. The block body hash is one hash over the original block_body; the Dijkstra header on the wire is the 10-item Praos header, with the 12-item Leios header also read. M7 splits into M7a (now) and M7b (era final); new I14 and I15; I12 re-pinned; test vectors pinned per source. ADR 0004 r1: Dijkstra in CCL's transaction model as an opt-in era: new fields, era propagation to nested writers, public constructors kept, Conway bytes unchanged (W1). Both reviewed before commit by a Fable pass and a Codex pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
95e9e7c2 to 4fd977b2. Every Conway file of 95e9e7c2 is byte-identical at 4fd977b2, which adds 1,437; run at both pins, the 59,076 outcomes they share are identical. The model gate goes from 2,705 to 2,837 valid samples, all 132 new ones passing; certificate-outcomes.txt gains the new samples' lines. Its Dijkstra files are those of d4709b12, which the Dijkstra gate (next commit) lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Era.Dijkstra(8) with an EraSpec row: the unified block layout, its body hash over the whole block_body, and the 10-item Praos header node 11.2.0 sends as well as the 12-item Leios header. Transactions in all three forms; sub-transactions read and walked with the body table's sub-transaction rows; guards, keys 23-27, Plutus V4 scripts, the pool BLS key slot, parameter keys 34-56, Leios and Peras certificates and endorser-block references. A later era's key in a keyed record is an unknown key, so Conway walks are as before. Every new API is marked experimental. Tests: 43 Haskell or real-network vectors (Musashi, DijkstraNet, Leios devnet blocks, transactions with sub-transactions and Plutus V4, endorser-block references), each with expected values from its source; 7,576 Dijkstra dataset samples of 50 rules under a strict test policy (one dataset label disputed); the Conway gates unchanged. Reviewed by Fable and Codex over two rounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…era (ADR 0004) Model fields for guards, sub-transactions, required top-level guards, direct deposits, account balance intervals, Plutus V4 scripts, the pool BLS key and parameter keys 34-56, and RequireGuard, PlutusV4Script, Language.PLUTUS_V4 and RedeemerTag.Guarding. They are written only for Dijkstra: Transaction.serialize(Era) is public, the era reaches proposals and parameter updates, a Dijkstra-only field written for an earlier era is an error, and Dijkstra writes the 3-item transaction and Haskell's encodings. Transaction.deserialize(bytes, Era) reads them. Public constructors keep their signatures (Lombok's all-args is private), so 814 existing signatures, Conway bytes and toJson output are unchanged. ledger-cbor: lenient and tolerant reads in Dijkstra accept indefinite-length records, as Haskell does from version 12; new RawCertificate.lenient(kind, span, era) and RawBlock.transaction(int) for Yaci. Reviewed by Fable and Codex over two rounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Q7 decided as (a) by the maintainer: Yaci's models gain Dijkstra's fields now, mapped from CCL's views, null when absent, with ADR 0004's constructor rule. requiredSigners in Dijkstra are the key guards, as Haskell's reqSignerHashesTxBodyG. Keys 38 and 53 round-trip in three states through any ObjectMapper (CCL's nil-or-value codec made public, with a deserializer). Yaci's parameter writer writes keys 34-56 and its proposal reader takes an era. Sub-transaction UTxO application, Plutus V4 and the guarding purpose, and Yano's proposal reload are listener prerequisites before Dijkstra. New I16 and M7c. Reviewed by Fable (two rounds) and Codex (three rounds), both approved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
…SON for Yaci's Dijkstra models (ADR 0003 M7c) A sub-transaction's body view hides the six top-level-only keys (2, 13, 16, 17, 23, 27), as the ledger guards them with STopTx, in the reader and in CCL's own models. RawAuxData hides key 5 (Plutus V4) before Dijkstra in has() and its accessor; keys 0-4 read as before. The nil-or-value JSON serializer is public, with a matching contextual deserializer, so keys 38 and 53 round-trip in three states through a bare ObjectMapper; Rational gains a JSON creator and ProtocolParamUpdate's nOpt its "nopt" name, with emitted JSON unchanged. ADR 0003 r10 records M7c in section 7.3. Reviewed by Fable (two rounds) and Codex (three rounds), both approved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
1 of 19 tasks
9ef2a45 (ADR 0004) added a fourth test to CborCorpusConformanceTest, so the guard that fails a skipped gate, which wants exactly three, failed although every test passed. Locally at dataset 4fd977b2: tests="4" skipped="0" failures="0" errors="0". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
Section 8 gains a row, and I16 and section 7.3's JSON gate list it: the six pins of three preprod blocks match once metadataCbor is re-encoded canonically (yaci-store#1215; yaci#243 does the same on Yaci's 0.4 line). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth
|
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
Implements ADR 0003 (one era-aware ledger CBOR reader), milestones M0 to M6, as one PR, as the maintainer chose. M7
(Dijkstra) and M8 (the listener ADR) are not part of it.
cardano-client-ledger-cbor. The raw views move into it, and it holds the ledger format in one place:EraSpecand the record, field and certificate tables;BlockEnvelope,RawBlock.ofandRawBlock.readAs,RawHeader;RawTx.walkwithFormatPolicy(D6).transaction-specmaps CCL's models from the reader. The models keep their API (D7).commonwriters are fixed: an empty indefinite map gets its break, tag 2^64-1 is written in full, and no tags go oncbor-java's shared simple values.
tools/:cddl-table-checkchecks every table against the pinned CDDL (I3);reader-diffcompares two CCL and Yaci builds in one JVM, over a corpus, the dataset or a chain stream, and alsobenches (G7, as a local tool);
chain-parse-checknow also reads every header, body and witness set through the views, and walks everytransaction.
that need approval.
Yaci (
feat/ledger-cbor-reader) and Yano (feat/ledger-cbor-reader) were built and tested against local snapshots ofthis branch at every milestone. Those branches are local and follow once this is released.
Results (final state; details in ADR section 7.1)
bf→bfffwriter fixTransaction.deserialize−40% time, −63% allocation. YaciBlockSerializer−12.5% time, −24% allocationPoints needing a maintainer decision
because the walk checks every leaf for the policy and the views then read the leaves again. CCL and Yaci are faster
than before.
RecDand reject them. cardano-ledger3b0a49bc90(incardano-protocol-0.2.0.0) accepts them, and the datasetfollows it. Yano follows the newer ledger code, as it did before this work.
Accepted by the maintainer on 2026-11-06: Q6 as (a);
costModelsHashfrom the original bytes; thecommonwriterfixes; Yaci deprecating its tree entry points rather than its classes; and the differences listed in ADR section 7.1.
Release notes (pre-release API)
cardano-client-ledger-cbor.RawDatum.toPlutusDataandRawScript.toScriptmove toRawModels(Q2).RawBlock.of(bytes, era)is replaced byreadAs.RawRelay's optional items areOptional.CborSpan.items()andentries()return the same read-only list on eachcall.
Coindoes.CborRuntimeExceptionorCborDeserializationExceptionwhere theythrew
IllegalArgumentExceptionorClassCastException:Anchor,Voter,GovActionId,VotingProcedure,Constitution,DRep,ProtocolVersion,CostMdls,Withdrawal.TransactionWitnessSet.deserialize(Map)loses aPlutusDatamap's lost-pairs mark, whichthe bytes path keeps.
plutusdepends onledger-cbor.Related
Test plan
./gradlew test -PcborDataset=<dataset>on the whole repository (JDK 21): 6,204 tests, 0 failures, 15 skipped(Postgres, benchmarks, one annotation-processor test)
tools/cddl-table-checkat the pin and at6176e413🤖 Generated with Claude Code
https://claude.ai/code/session_01MJrQUDVVQMG6z1k4eszgth