|
| 1 | +# FetchBlock tx deserialisation phases |
| 2 | + |
| 3 | +Populates `Block.body.tx` in the FetchBlock response. |
| 4 | +Each phase compiles independently and can be reviewed as a separate commit. |
| 5 | + |
| 6 | +## Background |
| 7 | + |
| 8 | +FetchBlock currently returns `nativeBytes` (raw CBOR), `header` (slot, hash, height), and `timestamp`. |
| 9 | +The `body.tx` field is empty. |
| 10 | +The proto `Tx` message has 14 fields - implementing them all at once is too large to review. |
| 11 | +This plan breaks the work into incremental phases, each adding a subset of fields. |
| 12 | + |
| 13 | +The parsed block is already available via `Consensus.GetBlock` in the same `getBlockComponent` call. |
| 14 | +`fromConsensusBlock` + `getBlockTxs` gives `[Tx era]` - no new consensus APIs needed. |
| 15 | + |
| 16 | +## Existing building blocks in Type.hs |
| 17 | + |
| 18 | +- `txOutToUtxoRpcTxOutput` - TxOutput (address, coin, assets, datum, script) |
| 19 | +- `scriptDataToUtxoRpcPlutusData` - PlutusData |
| 20 | +- `simpleScriptToUtxoRpcNativeScript` - NativeScript |
| 21 | +- `referenceScriptToUtxoRpcScript` - Script (reference scripts) |
| 22 | +- `utxoRpcBigIntToInteger` - BigInt (inverse) |
| 23 | +- `scriptWitnessIndexToRedeemerPurpose` - redeemer purpose mapping |
| 24 | +- `mkProtoRedeemer` - Redeemer construction |
| 25 | + |
| 26 | +Missing: TxInput, Withdrawal, Certificate, WitnessSet, AuxData, Collateral, Multiasset (for mint), GovernanceActionProposal, and the top-level `Tx era -> Proto Tx` composition. |
| 27 | + |
| 28 | +## Phase 1: Scaffold - extract txs from block, populate hash + fee |
| 29 | + |
| 30 | +Minimal end-to-end wiring with the simplest fields. |
| 31 | + |
| 32 | +- Change `fetchBlock` to also return the parsed `BlockInMode` (via `fromConsensusBlock` on `GetBlock`) |
| 33 | +- In `fetchBlockMethod`, call `getBlockTxs` to get `[Tx era]` |
| 34 | +- For each tx, populate: |
| 35 | + - `hash` - `getTxId` serialised to raw bytes |
| 36 | + - `fee` - from `txFee` on the tx body |
| 37 | + - `successful` - `True` (all blocks in ChainDB have validated txs; phase 1 does not distinguish collateral-return scripts) |
| 38 | +- Set `Block.body.tx` on the response |
| 39 | +- Add an E2E test asserting tx count, hash, and fee for a submitted tx |
| 40 | + |
| 41 | +Build: `cabal build cardano-rpc` |
| 42 | + |
| 43 | +## Phase 2: Inputs, outputs, reference inputs |
| 44 | + |
| 45 | +The core spending data. Reuses existing `txOutToUtxoRpcTxOutput`. |
| 46 | + |
| 47 | +- Add `txInputToProto :: TxIn -> Proto TxInput` (tx_hash + output_index, without as_output or redeemer - those come later) |
| 48 | +- Map `txIns` -> `Tx.inputs` |
| 49 | +- Map `txOuts` -> `Tx.outputs` (reuse `txOutToUtxoRpcTxOutput`) |
| 50 | +- Map `txInsReference` -> `Tx.reference_inputs` |
| 51 | +- Extend E2E test to assert input/output fields |
| 52 | + |
| 53 | +Build: `cabal build cardano-rpc` |
| 54 | + |
| 55 | +## Phase 3: Validity, mint, withdrawals |
| 56 | + |
| 57 | +Simple scalar/list fields. |
| 58 | + |
| 59 | +- Map `txValidityLowerBound` + `txValidityUpperBound` -> `Tx.validity` (TxValidity: start, ttl) |
| 60 | +- Map `txMintValue` -> `Tx.mint` (repeated Multiasset: policy_id + assets) |
| 61 | +- Map `txWithdrawals` -> `Tx.withdrawals` (repeated Withdrawal: reward_account + coin, no redeemer yet) |
| 62 | +- Extend E2E test with a minting tx |
| 63 | + |
| 64 | +Build: `cabal build cardano-rpc` |
| 65 | + |
| 66 | +## Phase 4: Collateral |
| 67 | + |
| 68 | +- Map `txInsCollateral` -> `Collateral.collateral` |
| 69 | +- Map `txReturnCollateral` -> `Collateral.collateral_return` |
| 70 | +- Map `txTotalCollateral` -> `Collateral.total_collateral` |
| 71 | +- Set `Tx.collateral` |
| 72 | +- `Tx.successful` - refine: check `IsValid` flag on Alonzo+ txs |
| 73 | + |
| 74 | +Build: `cabal build cardano-rpc` |
| 75 | + |
| 76 | +## Phase 5: Witnesses |
| 77 | + |
| 78 | +- Map vkey witnesses -> `WitnessSet.vkeywitness` |
| 79 | +- Map scripts (Plutus + native) -> `WitnessSet.script` |
| 80 | +- Map plutus datums -> `WitnessSet.plutus_datums` (reuse `scriptDataToUtxoRpcPlutusData`) |
| 81 | +- Map redeemers -> `WitnessSet.redeemers` (reuse `mkProtoRedeemer`) |
| 82 | +- Map bootstrap witnesses -> `WitnessSet.bootstrapWitnesses` |
| 83 | +- Wire redeemers to `TxInput.redeemer` and `Withdrawal.redeemer` |
| 84 | + |
| 85 | +Build: `cabal build cardano-rpc` |
| 86 | + |
| 87 | +## Phase 6: Certificates |
| 88 | + |
| 89 | +The largest proto message (19 oneof variants). |
| 90 | + |
| 91 | +- Map each `TxCert era` variant to the corresponding `Certificate` oneof |
| 92 | +- Pre-Conway certs: stake registration/deregistration, delegation, pool registration/retirement, genesis delegation, MIR |
| 93 | +- Conway+ certs: reg, unreg, vote deleg, stake vote deleg, committee hot/cold, DRep reg/unreg/update |
| 94 | +- Wire certificate redeemers |
| 95 | + |
| 96 | +Build: `cabal build cardano-rpc` |
| 97 | + |
| 98 | +## Phase 7: Auxiliary data + governance proposals |
| 99 | + |
| 100 | +- Map tx metadata -> `AuxData.metadata` |
| 101 | +- Map auxiliary scripts -> `AuxData.scripts` |
| 102 | +- Map governance action proposals -> `Tx.proposals` (Conway+) |
| 103 | +- This completes all 14 Tx fields |
| 104 | + |
| 105 | +Build: `cabal build cardano-rpc` |
| 106 | + |
| 107 | +## Phase dependency graph |
| 108 | + |
| 109 | +``` |
| 110 | +Phase 1 (scaffold: hash, fee, successful) |
| 111 | + | |
| 112 | + v |
| 113 | +Phase 2 (inputs, outputs, reference inputs) |
| 114 | + | |
| 115 | + v |
| 116 | +Phase 3 (validity, mint, withdrawals) |
| 117 | + | |
| 118 | + v |
| 119 | +Phase 4 (collateral, successful refinement) |
| 120 | + | |
| 121 | + v |
| 122 | +Phase 5 (witnesses + redeemer wiring) |
| 123 | + | |
| 124 | + v |
| 125 | +Phase 6 (certificates) |
| 126 | + | |
| 127 | + v |
| 128 | +Phase 7 (auxiliary data, governance proposals) |
| 129 | +``` |
| 130 | + |
| 131 | +## Design decisions |
| 132 | + |
| 133 | +- **Incremental field population.** |
| 134 | + Each phase adds fields to the same `Tx` proto message. |
| 135 | + Unset proto fields default to empty/zero, so partial responses are valid. |
| 136 | + |
| 137 | +- **No new consensus APIs.** |
| 138 | + `fromConsensusBlock` + `getBlockTxs` already exist in cardano-api. |
| 139 | + The work is entirely in the cardano-rpc mapping layer. |
| 140 | + |
| 141 | +- **Reuse existing conversions.** |
| 142 | + `txOutToUtxoRpcTxOutput`, `scriptDataToUtxoRpcPlutusData`, `mkProtoRedeemer` etc. are already battle-tested in the Query and Submit handlers. |
| 143 | + |
| 144 | +- **Witnesses and redeemers split from inputs/withdrawals.** |
| 145 | + Phase 2-3 populate inputs and withdrawals without redeemers. |
| 146 | + Phase 5 adds the witness set and wires redeemers back to their inputs/withdrawals. |
| 147 | + This avoids a large cross-cutting change. |
| 148 | + |
| 149 | +- **Certificates are a separate phase.** |
| 150 | + The Certificate oneof has 19 variants spanning pre-Conway and Conway eras. |
| 151 | + Isolating it keeps reviews focused. |
| 152 | + |
| 153 | +- **Dijkstra safety.** |
| 154 | + `conwayEraOnwardsConstraints` crashes at runtime for Dijkstra. |
| 155 | + Phases 6-7 (certificates, governance proposals) must use |
| 156 | + `caseShelleyToBabbageOrConwayOrDijkstra` and pattern match on concrete |
| 157 | + `ConwayEraOnwards` constructors in the right arm. |
| 158 | + The cardano-api lenses `proposalProceduresTxBodyL` and `votingProceduresTxBodyL` |
| 159 | + use `conwayEraOnwardsConstraints` internally - avoid them, go through the ledger |
| 160 | + tx body directly with explicit era constraints. |
| 161 | + |
| 162 | +- **Work through the ledger tx directly.** |
| 163 | + `ShelleyTxBody` is deprecated. |
| 164 | + Access the ledger `Tx` via the `ShelleyTx` constructor and use ledger lenses |
| 165 | + (`bodyTxL`, `witsTxL`, `auxDataTxL`, `isValidTxL`) rather than cardano-api wrappers. |
0 commit comments