Skip to content

Add transaction validate command (online phase 1 + phase 2) #1380

@palas

Description

@palas

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) and phase 2 (Plutus scripts via 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 for applyTx

constructGlobals needs ShelleyGenesis + EpochInfo. The existing QueryGenesisParameters returns GenesisParameters which contains all the fields needed for 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: IntWord64, RationalActiveSlotCoeff, CoinWord64) and calls computeStabilityWindow/computeRandomnessStabilisationWindow for the derived fields.

Getting the ledger state for applyTx

mkMempoolEnv and mkMempoolState both only access nesEs (the EpochState inside NewEpochState). So we don't necessarily need the full NewEpochState.

Provisional approach: Use QueryCurrentEpochState — returns EpochState directly. Construct MempoolEnv/MempoolState from it manually (mirroring what mkMempoolEnv/mkMempoolState do, but skipping the NewEpochState unwrap). This is lighter than QueryDebugLedgerState.

Problem: On mainnet, transferring the full EpochState over the socket and deserializing it is slow.

Potential solution: Run 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 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) 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'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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions