IntersectMBO / IntersectMBO/cardano-cli

Add `transaction validate` command (online phase 1 + phase 2)

Open
#1,380 4 comments 1 reaction 1 assignee Claimed by @palas View on GitHub
enhancement epic
Dominant language
Haskell
Stars
71
Forks
24
Avg merge
23h 40m
Merged PRs (30d)
14

Description

## Context

Follow-up from investigation in #1367. This issue scopes the first implementation step: an online `transaction validate` command that runs phase 1 (ledger rules via [`applyTx`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:applyTx)) and phase 2 (Plutus scripts via [`evaluateTransactionExecutionUnits`](https://cardano-api.cardano.intersectmbo.org/cardano-api/Cardano-Api-Experimental.html#v:evaluateTransactionExecutionUnits)) against the current ledger state, **without submitting** the transaction.

## Scope

- New command: `cardano-cli latest transaction validate --tx-file signed.tx --socket-path node.socket`
- Phase 1 and phase 2 run independently — user sees all errors even if phase 1 fails
- Online only (requires node connection)
- Text output for now (structured JSON is a follow-up)

## Key design decisions

### Getting [`Globals`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-core/Cardano-Ledger-BaseTypes.html#t:Globals) for [`applyTx`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:applyTx)

[`constructGlobals`](https://cardano-api.cardano.intersectmbo.org/cardano-api/Cardano-Api-LedgerState.html#v:constructGlobals) needs [`ShelleyGenesis`](https://cardano-api.cardano.intersectmbo.org/cardano-api/Cardano-Api-Genesis.html#t:ShelleyGenesis) + [`EpochInfo`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-core/Cardano-Ledger-Slot.html#t:EpochInfo). The existing [`QueryGenesisParameters`](https://cardano-api.cardano.intersectmbo.org/cardano-api/Cardano-Api-Query.html#v:queryGenesisParameters) returns [`GenesisParameters`](https://cardano-api.cardano.intersectmbo.org/cardano-api/Cardano-Api-Genesis.html#t:GenesisParameters) which contains all the fields needed for [`Globals`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-core/Cardano-Ledger-BaseTypes.html#t:Globals).

We need to add a `mkGlobalsFromGenesisParameters :: GenesisParameters era -> EpochInfo (Either Text) -> Globals` function in `cardano-api` that constructs `Globals` from these fields (with minor type conversions: `Int` → `Word64`, `Rational` → `ActiveSlotCoeff`, `Coin` → `Word64`) and calls `computeStabilityWindow`/`computeRandomnessStabilisationWindow` for the derived fields.

### Getting the ledger state for `applyTx`

[`mkMempoolEnv`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:mkMempoolEnv) and [`mkMempoolState`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:mkMempoolState) both only access `nesEs` (the [`EpochState`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-LedgerState.html#t:EpochState) inside [`NewEpochState`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-LedgerState.html#t:NewEpochState)). So we don't necessarily need the full [`NewEpochState`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-LedgerState.html#t:NewEpochState).

**Provisional approach: Use [`QueryCurrentEpochState`](https://cardano-api.cardano.intersectmbo.org/cardano-api/src/Cardano.Api.Query.Internal.Type.QueryInMode.html#QueryCurrentEpochState)** — returns [`EpochState`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-LedgerState.html#t:EpochState) directly. Construct `MempoolEnv`/`MempoolState` from it manually (mirroring what [`mkMempoolEnv`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:mkMempoolEnv)/[`mkMempoolState`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:mkMempoolState) do, but skipping the [`NewEpochState`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-LedgerState.html#t:NewEpochState) unwrap). This is lighter than [`QueryDebugLedgerState`](https://cardano-api.cardano.intersectmbo.org/cardano-api/src/Cardano.Api.Query.Internal.Type.QueryInMode.html#QueryDebugLedgerState).

**Problem**: On mainnet, transferring the full [`EpochState`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-LedgerState.html#t:EpochState) over the socket and deserializing it is slow.

**Potential solution: Run [`applyTx`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:applyTx) inside the node** — add a new query (or a validate-only variant of the `LocalTxSubmission` mini-protocol) in `ouroboros-consensus` / `cardano-node` that takes a `Tx`, runs [`applyTx`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:applyTx) server-side against the in-memory ledger state, and returns `Either ApplyTxError ()` without adding the tx to the mempool. This avoids transferring state entirely, gives the same performance as `submit`, and guarantees state consistency. Requires changes in `ouroboros-consensus`, `ouroboros-network`, `cardano-node`, `cardano-api`, but each change is small and well-defined.

### Independent phase 1 + phase 2

Phase 2 ([`evaluateTransactionExecutionUnits`](https://cardano-api.cardano.intersectmbo.org/cardano-api/Cardano-Api-Experimental.html#v:evaluateTransactionExecutionUnits)) already exists and works standalone. We run it alongside phase 1 and merge results. Phase 2 gives richer script errors (per-script traces, execution units) than [`applyTx`](https://cardano-ledger.cardano.intersectmbo.org/cardano-ledger-shelley/Cardano-Ledger-Shelley-API-Mempool.html#v:applyTx)'s `ValidationTagMismatch (PlutusFailure Text ByteString)`.

## Output format

```
# Both pass:
Transaction is valid.
Phase 1: passed
Phase 2: passed (3 scripts evaluated)
SpendingScript 0: passed (mem: 234000, steps: 89000000)
MintingScript 0: passed (mem: 120000, steps: 45000000)
SpendingScript 1: passed (mem: 180000, steps: 67000000)

# Phase 1 fails, phase 2 passes:
Transaction validation failed.
Phase 1: FAILED
FeeTooSmallUTxO: minimum fee is 300000 lovelace, transaction specifies 170000
Phase 2: passed (1 script evaluated)
SpendingScript 0: passed (mem: 234000, steps: 89000000)

# Both fail:
Transaction validation failed.
Phase 1: FAILED
ValueNotConservedUTxO: consumed 5000000, produced 6000000
Phase 2: FAILED
SpendingScript 0: FAILED
ScriptErrorEvaluationFailed: ...
```

## Known limitations

- **No offline mode**: This is online-only. Offline validation is a follow-up (see #1367).
- **Text output only**: Structured JSON output is a follow-up.
- **No hints**: cquisitor-style advisory warnings are a follow-up.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.