Skip to content

fix(evolution): measure transaction size the way the ledger does, without isValid (#179) - #646

Open
Kshot3000 wants to merge 1 commit into
IntersectMBO:mainfrom
Kshot3000:fix/tx-size-exclude-isvalid
Open

Kshot3000 wants to merge 1 commit into
IntersectMBO:mainfrom
Kshot3000:fix/tx-size-exclude-isvalid

Conversation

@Kshot3000

@Kshot3000 Kshot3000 commented Oct 15, 2026 •

Copy link
Copy Markdown

Fixes #179.

Transaction size was measured from the full 4-element CBOR encoding [body, witnessSet, isValid, auxiliaryData]. The ledger measures a transaction differently — toCBORForSizeComputation in the Alonzo Tx module:

toCBORForSizeComputation AlonzoTx {atBody, atWits, atAuxData} =
  encodeListLen 3        -- 3 elements, no IsValid
    <> encCBOR atBody
    <> encCBOR atWits
    <> encodeNullStrictMaybe encCBOR atAuxData

That one ledger size (tx ^. sizeTxF) feeds both the minimum fee and the maxTxSize check. The full encoding is exactly one byte longer — the boolean; the array header is one byte at both lengths — so every built transaction overpaid the minimum fee by minFeeCoefficient lovelace (44 lovelace at the usual coefficient), fee validation could reject a transaction whose fee was exactly the ledger minimum, and the maxTxSize check compared against a size one byte larger than the node's.

Change

  • New Transaction.toCBORBytesForSizeComputation: the 3-element [body, witnessSet, auxiliaryData] bytes, each component written exactly as toCBORBytes writes it under the default options, joined as the full encoding joins them (auxiliary data present → its bytes; absent → the null the ledger's encodeNullStrictMaybe writes).
  • calculateTransactionSize in the builder (fee estimation and the maxTxSize check, via build.ts) now measures those bytes.
  • FeeValidation.validateTransactionFee now measures those bytes.

Submission paths are untouched: providers still serialize the full 4-element transaction, which is what gets submitted.

Tests

Six new cases in test/Transaction.SizeComputation.test.ts, each run individually against main first:

  • the size-computation bytes for a transaction without auxiliary data are the hand-derived 83…f6 (3-element header, no boolean) — RED on main (the function does not exist there);
  • the same with auxiliary data present (83…a10000) — RED on main;
  • the bytes do not depend on the isValid flag's value;
  • the size encoding is exactly one byte shorter than the full encoding, with header 0x83;
  • calculateTransactionSize returns 17 for the fixture, not 18 — RED on main (expected 18 to be 17);
  • validateTransactionFee accepts a fee set to exactly the ledger minimum (200,000 = 44 × 17 + 199,252) — RED on main, which demanded 200,044.

Existing expectations that encode the old size moved by exactly the fee difference — 44 lovelace, or one byte of size — in TxBuilder.EdgeCases, TxBuilder.InsufficientChange, TxBuilder.Reselection, TxBuilder.SendAll, TxBuilder.UnfrackDrain and TxBuilder.UnfrackChangeHandling (in the last, only the residual change output and the one exact-fee assertion move; the token-bundle outputs are minUTxO-driven and unchanged).

Results: the new file passes 6/6 and the affected builder files pass; the full package suite passes 1749 passed / 75 skipped / 0 failed (current main baseline 1743 + 6 new), test files 108 passed / 1 skipped of 109. tsc --noEmit and eslint are clean, and the touched source files' prettier diff counts match main's exactly. A patch changeset is included.

Rebased onto current main (9f3fec7). The codec-options work (#652) had changed calculateTransactionSize to take the caller's codec options, so the size function now threads them: Transaction.toCBORBytesForSizeComputation accepts the same options and writes the body and witness set with them, exactly as toCBORBytes joins its components. The rebase also surfaced one test file recorded after this PR was opened, TxBuilder.CodecOptions (#652): its default-options goldens are re-recorded — every one of the 16 cases moves by exactly the same −44 lovelace fee difference, with no other change — and its checkWritten fee assertion now measures the ledger size of the bytes it checks (the full encoding minus the one-byte isValid flag), which is the size the fee is computed from.


Independent builder — not affiliated with IntersectMBO, the Cardano Foundation or IOG.

@Kshot9000 (https://x.com/kshot9000) · ADA: addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v

cc @cardano-foundation @IntersectMBO

@Kshot3000
Kshot3000 force-pushed the fix/tx-size-exclude-isvalid branch from 3d72627 to 9f3fec7 Compare October 17, 2026 02:10
@Kshot3000

Copy link
Copy Markdown
Author

Rebased onto current main (9f3fec7) — this PR had gone conflicting after the codec-options and redeemer-indexing work landed upstream (#652/#656/#658 series).

Two semantic adjustments came with the rebase, both verified rather than mechanical:

  • calculateTransactionSize gained an options parameter upstream in feat(builders): add codecOptions to build #652. The ledger-size function now accepts and threads those options (toCBORBytesForSizeComputation writes the body and witness set with them, as toCBORBytes does), instead of silently dropping the parameter the builder passes.
  • feat(builders): add codecOptions to build #652's new TxBuilder.CodecOptions test file postdates this PR and encoded the old size in two places: its default-options goldens (all 16 cases re-recorded — each fee moves by exactly −44 lovelace, nothing else) and its checkWritten helper, which asserted the fee covers the full byte length; it now measures the ledger size, the one-byte-smaller size the fee is actually computed from.

Red/green re-verified on the rebased branch: all six new tests fail individually on current main and pass with the fix; the full suite is 1749 passed / 75 skipped / 0 failed (main baseline 1743 + 6 new). tsc and eslint are clean, and prettier diff counts on the touched files match main's exactly.


Independent builder — not affiliated with IntersectMBO, the Cardano Foundation or IOG.
@Kshot9000 (https://x.com/kshot9000) · ADA: addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v
cc @cardano-foundation @IntersectMBO

This branch has not been deployed

No deployments
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.

improvement: tx size includes IsValid field, Haskell omits it

1 participant