diff --git a/cabal.project b/cabal.project index 7d699e8d362..11d36b00248 100644 --- a/cabal.project +++ b/cabal.project @@ -34,6 +34,52 @@ constraints: package acts flags: -finitary +-- BEGIN SRP STANZAS MANAGED BY STANZAMAN -- + +source-repository-package + type: git + location: https://github.com/IntersectMBO/cardano-api.git + tag: 13b452f32cf053a803988f455551a697726c65cc + subdir: cardano-api + --sha256: 1fq901pjq1k3hiqygnmifw6w9pydqb4xr4i987n69r2jp1k3025m + +source-repository-package + type: git + location: https://github.com/IntersectMBO/ouroboros-network.git + tag: 65da1ad619473f9467d3336a6d6e9a2a8714913d + subdir: network-mux + --sha256: 1r5y5px0kj4nhb65s9i7pr0sikxghfqwfkzmsgiql2maic4nglwi + +source-repository-package + type: git + location: https://github.com/IntersectMBO/ouroboros-network.git + tag: 65da1ad619473f9467d3336a6d6e9a2a8714913d + subdir: cardano-diffusion + --sha256: 1r5y5px0kj4nhb65s9i7pr0sikxghfqwfkzmsgiql2maic4nglwi + +source-repository-package + type: git + location: https://github.com/IntersectMBO/ouroboros-network.git + tag: 65da1ad619473f9467d3336a6d6e9a2a8714913d + subdir: ouroboros-network + --sha256: 1r5y5px0kj4nhb65s9i7pr0sikxghfqwfkzmsgiql2maic4nglwi + +source-repository-package + type: git + location: https://github.com/IntersectMBO/cardano-cli.git + tag: c632e6e114b1f2d7715afd34ec44750a0595efc1 + subdir: cardano-cli + --sha256: 0d4vs59z3wlhnl2ns8g8lqrc7xgm2nfid5cfyq0kms2jgmi89wiz + +source-repository-package + type: git + location: https://github.com/IntersectMBO/ouroboros-consensus.git + tag: bca18fd361d8342bdbfb8a08366e86479677ce30 + subdir: . + --sha256: 11znnl9zk59bxr6dn76zrni6i0q32bqnlxqf4wprvxikg4r666pa + +-- END SRP STANZAS MANAGED BY STANZAMAN -- + packages: cardano-node cardano-node-capi @@ -81,12 +127,22 @@ package plutus-scripts-bench allow-newer: , katip:Win32 +-- The SRP stanza for cardano-api pins it to a version with plutus-{core,ledger-api} ^>=1.61, +-- while plutus-scripts-bench requires ^>=1.63. Allow the relaxation. +allow-newer: + , cardano-api:plutus-core + , cardano-api:plutus-ledger-api + -- There is a suspected bug in `cabal` (https://github.com/haskell/cabal/issues/11663) -- that can be worked around with the following allow-newer stanzas allow-newer: , io-sim:time , io-classes:time +allow-newer: + , cardano-diffusion:trace-dispatcher + , ouroboros-network:trace-dispatcher + -- IMPORTANT -- Do NOT add more source-repository-package stanzas here unless they are strictly -- temporary! Please read the section in CONTRIBUTING about updating dependencies. diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs index 3577fe2ed2b..63db18778a1 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs @@ -508,6 +508,7 @@ nodeToClientVersionToInt = \case NodeToClientV_21 -> 21 NodeToClientV_22 -> 22 NodeToClientV_23 -> 23 + NodeToClientV_24 -> 24 nodeToNodeVersionToInt :: NodeToNodeVersion -> Int nodeToNodeVersionToInt = \case diff --git a/cardano-node/src/Cardano/Tracing/OrphanInstances/Shelley.hs b/cardano-node/src/Cardano/Tracing/OrphanInstances/Shelley.hs index d067a4a2e4b..08e1aff82bc 100644 --- a/cardano-node/src/Cardano/Tracing/OrphanInstances/Shelley.hs +++ b/cardano-node/src/Cardano/Tracing/OrphanInstances/Shelley.hs @@ -1368,6 +1368,7 @@ instance ToJSON ShelleyNodeToClientVersion where toJSON ShelleyNodeToClientVersion13 = String "ShelleyNodeToClientVersion13" toJSON ShelleyNodeToClientVersion14 = String "ShelleyNodeToClientVersion14" toJSON ShelleyNodeToClientVersion15 = String "ShelleyNodeToClientVersion15" + toJSON ShelleyNodeToClientVersion16 = String "ShelleyNodeToClientVersion16" -------------------------------------------------------------------------------- -- Conway related diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index 3d24101a0ad..31ed2d5790a 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -220,6 +220,8 @@ test-suite cardano-testnet-test Cardano.Testnet.Test.Cli.Transaction Cardano.Testnet.Test.Cli.Transaction.BuildEstimate Cardano.Testnet.Test.Cli.Transaction.RegisterDeregisterStakeAddress + Cardano.Testnet.Test.Cli.Transaction.Validate + Cardano.Testnet.Test.Cli.Transaction.ValidateErrors Cardano.Testnet.Test.Cli.Transaction.WithdrawalReward Cardano.Testnet.Test.DumpConfig Cardano.Testnet.Test.FoldEpochState diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Transaction/Validate.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Transaction/Validate.hs new file mode 100644 index 00000000000..4cbb1364289 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Transaction/Validate.hs @@ -0,0 +1,525 @@ +{-# LANGUAGE DisambiguateRecordFields #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Cardano.Testnet.Test.Cli.Transaction.Validate + ( hprop_transaction_validate + ) where + +import Cardano.Api + +import Cardano.Testnet + +import Prelude + +import Control.Monad (void) +import Data.Default.Class +import qualified Data.Text as Text +import GHC.IO.Exception (ExitCode (..)) +import System.FilePath (()) + +import Testnet.Components.Configuration +import Testnet.Components.Query (findLargestUtxoForPaymentKey, + findLargestUtxoWithAddress, getEpochStateView, retryUntilJustM, + TestnetWaitPeriod (..), waitForBlocks) +import Testnet.Defaults (plutusV3Script, plutusV3SupplementalDatumScript) +import Testnet.Process.Run (execCli', execCliAny, mkExecConfig) +import Testnet.Property.Util (integrationRetryWorkspace) +import Testnet.Types + +import Hedgehog (Property) +import qualified Hedgehog as H +import qualified Hedgehog.Extras as H + +-- Deterministic signing key for reproducible golden tests. +-- Key hash: fe8c2213ed33ae44e3629706a3c7d34f0dce29753646602177756a5f +-- Address (testnet-magic 42): addr_test1vrlgcgsna5e6u38rv2tsdg786d8smn3fw5myvcppwa6k5hc4u8utx +testSigningKey :: String +testSigningKey = + "{\"type\":\"PaymentSigningKeyShelley_ed25519\",\"description\":\"Payment Signing Key\",\"cborHex\":\"5820aabbccddee0011223344556677889900aabbccddee00112233445566778899aa\"}" + +-- | Integration test for @cardano-cli latest transaction validate@. +-- +-- Spins up a testnet then validates several transactions (both valid and +-- invalid) using the new validate command, comparing output against golden +-- files. +-- +-- Execute me with: +-- @DISABLE_RETRIES=1 cabal test cardano-testnet-test --test-options '-p "/transaction validate/"'@ +-- If you want to recreate golden files, set RECREATE_GOLDEN_FILES=1 +hprop_transaction_validate :: Property +hprop_transaction_validate = integrationRetryWorkspace 2 "transaction validate" $ \tempAbsBasePath' -> H.runWithDefaultWatchdog_ $ do + conf@Conf{tempAbsPath} <- mkConf tempAbsBasePath' + let tempAbsPath' = unTmpAbsPath tempAbsPath + work <- H.createDirectoryIfMissing $ tempAbsPath' "work" + + let sbe = ShelleyBasedEraConway + era = toCardanoEra sbe + cEra = AnyCardanoEra era + tempBaseAbsPath = makeTmpBaseAbsPath $ TmpAbsolutePath tempAbsPath' + creationOptions = def{creationEra = AnyShelleyBasedEra sbe} + + TestnetRuntime + { configurationFile + , testnetMagic + , testnetNodes + , wallets = wallet0 : wallet1 : _ + } <- + createAndRunTestnet creationOptions def conf + + node <- H.headM testnetNodes + poolSprocket1 <- H.noteShow $ nodeSprocket node + execConfig <- mkExecConfig tempBaseAbsPath poolSprocket1 testnetMagic + epochStateView <- getEpochStateView configurationFile (nodeSocketPath node) + + -- Write deterministic key to file and derive address + let testSKeyFile = work "test-skey.json" + testVKeyFile = work "test-vkey.json" + H.writeFile testSKeyFile testSigningKey + void $ execCli' execConfig + [ "latest", "key", "verification-key" + , "--signing-key-file", testSKeyFile + , "--verification-key-file", testVKeyFile + ] + testAddr <- filter (/= '\n') <$> + execCli' execConfig + [ "latest", "address", "build" + , "--payment-verification-key-file", testVKeyFile + ] + H.note_ $ "Deterministic test address: " <> testAddr + + -- ==================================================================== + -- Setup: Fund the deterministic address with known amounts + -- ==================================================================== + H.note_ "=== Setup: funding deterministic address ===" + let fundingAmount = 10_000_000 :: Int + wallet0SKeyFile = signingKeyFp $ paymentKeyInfoPair wallet0 + wallet0Addr = Text.unpack $ paymentKeyInfoAddr wallet0 + + txinFund <- findLargestUtxoForPaymentKey epochStateView sbe wallet0 + let fundTxBody = work "fund-tx.body" + fundTxSigned = work "fund-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build" + , "--change-address", wallet0Addr + , "--tx-in", Text.unpack $ renderTxIn txinFund + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--out-file", fundTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", fundTxBody + , "--signing-key-file", wallet0SKeyFile + , "--out-file", fundTxSigned + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "submit" + , "--tx-file", fundTxSigned + ] + + H.noteShowM_ $ waitForBlocks epochStateView 1 + + -- Find the funded UTxOs at our deterministic address + testUtxos <- retryUntilJustM epochStateView (WaitForBlocks 3) $ do + utxos <- findLargestUtxoWithAddress epochStateView sbe $ Text.pack testAddr + case utxos of + Just _ -> return $ Just () + Nothing -> return Nothing + + _ <- H.note $ "Test UTxOs funded: " <> show testUtxos + + -- ==================================================================== + -- Test 1: Valid simple ADA transfer + -- ==================================================================== + H.note_ "=== Test 1: Valid simple transaction ===" + + txin1 <- findLargestUtxoForPaymentKey epochStateView sbe wallet0 + let validTxBody = work "valid-tx.body" + validTxSigned = work "valid-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build" + , "--change-address", wallet0Addr + , "--tx-in", Text.unpack $ renderTxIn txin1 + , "--tx-out", Text.unpack (paymentKeyInfoAddr wallet1) <> "+" <> show @Int 5_000_000 + , "--out-file", validTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", validTxBody + , "--signing-key-file", wallet0SKeyFile + , "--out-file", validTxSigned + ] + + (exitCode1, stdout1, stderr1) <- execCliAny execConfig + [ anyEraToString cEra, "transaction", "validate" + , "--tx-file", validTxSigned + ] + + H.note_ $ "Exit code: " <> show exitCode1 + H.note_ $ "Stdout: " <> stdout1 + H.note_ $ "Stderr: " <> stderr1 + + exitCode1 H.=== ExitSuccess + H.diffVsGoldenFile + stdout1 + "test/cardano-testnet-test/files/golden/tx_validate/valid_simple.out" + + -- ==================================================================== + -- Test 2: Fee too low (build-raw with deterministic UTxO) + -- The deterministic UTxO has exactly 10,000,000 lovelace. + -- output = 10,000,000 - 100 = 9,999,900 so value IS conserved, + -- but fee=100 < min_fee=194. + -- ==================================================================== + H.note_ "=== Test 2: Fee too low ===" + + testTxin2 <- fmap fst . H.nothingFailM $ + findLargestUtxoWithAddress epochStateView sbe $ Text.pack testAddr + let feeTooLowTxBody = work "fee-too-low-tx.body" + feeTooLowTxSigned = work "fee-too-low-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn testTxin2 + , "--tx-out", testAddr <> "+9999900" + , "--fee", "100" + , "--out-file", feeTooLowTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", feeTooLowTxBody + , "--signing-key-file", testSKeyFile + , "--out-file", feeTooLowTxSigned + ] + + (exitCode2, stdout2, stderr2) <- execCliAny execConfig + [ anyEraToString cEra, "transaction", "validate" + , "--tx-file", feeTooLowTxSigned + ] + + H.note_ $ "Exit code: " <> show exitCode2 + H.note_ $ "Stdout: " <> stdout2 + H.note_ $ "Stderr: " <> stderr2 + + exitCode2 H.=== ExitFailure 1 + H.diffVsGoldenFile + stdout2 + "test/cardano-testnet-test/files/golden/tx_validate/fee_too_low.out" + + -- ==================================================================== + -- Test 3: Value not conserved + -- Deterministic UTxO has 10,000,000 lovelace. + -- output = 999,999,000,000, fee = 200,000 + -- consumed = 10,000,000, produced = 999,999,200,000 + -- ==================================================================== + H.note_ "=== Test 3: Value not conserved ===" + + let valueNotConservedTxBody = work "value-not-conserved-tx.body" + valueNotConservedTxSigned = work "value-not-conserved-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn testTxin2 + , "--tx-out", testAddr <> "+999999000000" + , "--fee", "200000" + , "--out-file", valueNotConservedTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", valueNotConservedTxBody + , "--signing-key-file", testSKeyFile + , "--out-file", valueNotConservedTxSigned + ] + + (exitCode3, stdout3, stderr3) <- execCliAny execConfig + [ anyEraToString cEra, "transaction", "validate" + , "--tx-file", valueNotConservedTxSigned + ] + + H.note_ $ "Exit code: " <> show exitCode3 + H.note_ $ "Stdout: " <> stdout3 + H.note_ $ "Stderr: " <> stderr3 + + exitCode3 H.=== ExitFailure 1 + H.diffVsGoldenFile + stdout3 + "test/cardano-testnet-test/files/golden/tx_validate/value_not_conserved.out" + + -- ==================================================================== + -- Test 4: Missing witness + -- Build a tx spending from the deterministic key's UTxO, + -- but sign with wallet1's key instead. + -- Expected missing key hash: fe8c2213ed33ae44e3629706a3c7d34f0dce29753646602177756a5f + -- ==================================================================== + H.note_ "=== Test 4: Missing witness ===" + + let missingWitnessTxBody = work "missing-witness-tx.body" + missingWitnessTxSigned = work "missing-witness-tx.signed" + wallet1SKeyFile = signingKeyFp $ paymentKeyInfoPair wallet1 + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn testTxin2 + , "--tx-out", testAddr <> "+9999600" + , "--fee", "400" + , "--out-file", missingWitnessTxBody + ] + + -- Sign with wallet1's key instead of the deterministic test key + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", missingWitnessTxBody + , "--signing-key-file", wallet1SKeyFile + , "--out-file", missingWitnessTxSigned + ] + + (exitCode4, stdout4, stderr4) <- execCliAny execConfig + [ anyEraToString cEra, "transaction", "validate" + , "--tx-file", missingWitnessTxSigned + ] + + H.note_ $ "Exit code: " <> show exitCode4 + H.note_ $ "Stdout: " <> stdout4 + H.note_ $ "Stderr: " <> stderr4 + + exitCode4 H.=== ExitFailure 1 + H.diffVsGoldenFile + stdout4 + "test/cardano-testnet-test/files/golden/tx_validate/missing_witness.out" + + -- ==================================================================== + -- Test 5: Valid Plutus script transaction (always-succeeds) + -- Uses plutusV3Script which is a trivial always-succeeds PlutusV3 script. + -- ==================================================================== + H.note_ "=== Test 5: Valid Plutus script ===" + let alwaysSucceedsScript = work "always-succeeds.plutusV3" + H.writeFile alwaysSucceedsScript $ Text.unpack plutusV3Script + + alwaysSucceedsAddr <- filter (/= '\n') <$> + execCli' execConfig + [ "latest", "address", "build" + , "--payment-script-file", alwaysSucceedsScript + ] + + scriptDatumHash <- filter (/= '\n') <$> + execCli' execConfig + [ "latest", "transaction", "hash-script-data" + , "--script-data-value", "0" + ] + + -- Send ADA to the always-succeeds script address + txinForScript <- findLargestUtxoForPaymentKey epochStateView sbe wallet0 + let sendToScriptTxBody = work "send-to-script-tx.body" + sendToScriptTxSigned = work "send-to-script-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build" + , "--change-address", wallet0Addr + , "--tx-in", Text.unpack $ renderTxIn txinForScript + , "--tx-out", alwaysSucceedsAddr <> "+" <> show @Int 5_000_000 + , "--tx-out-datum-hash", scriptDatumHash + , "--out-file", sendToScriptTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", sendToScriptTxBody + , "--signing-key-file", wallet0SKeyFile + , "--out-file", sendToScriptTxSigned + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "submit" + , "--tx-file", sendToScriptTxSigned + ] + + -- Wait for the UTxO to appear at the script address + txinCollateral <- findLargestUtxoForPaymentKey epochStateView sbe wallet1 + alwaysSucceedsTxIn <- fmap fst . retryUntilJustM epochStateView (WaitForBlocks 3) $ + findLargestUtxoWithAddress epochStateView sbe $ Text.pack alwaysSucceedsAddr + + -- Spend from the always-succeeds script address + let spendScriptTxBody = work "spend-script-tx.body" + spendScriptTxSigned = work "spend-script-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build" + , "--change-address", Text.unpack $ paymentKeyInfoAddr wallet1 + , "--tx-in-collateral", Text.unpack $ renderTxIn txinCollateral + , "--tx-in", Text.unpack $ renderTxIn alwaysSucceedsTxIn + , "--tx-in-script-file", alwaysSucceedsScript + , "--tx-in-datum-value", "0" + , "--tx-in-redeemer-value", "0" + , "--tx-out", Text.unpack (paymentKeyInfoAddr wallet1) <> "+" <> show @Int 2_000_000 + , "--out-file", spendScriptTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", spendScriptTxBody + , "--signing-key-file", wallet1SKeyFile + , "--out-file", spendScriptTxSigned + ] + + (exitCode5, stdout5, stderr5) <- execCliAny execConfig + [ anyEraToString cEra, "transaction", "validate" + , "--tx-file", spendScriptTxSigned + ] + + H.note_ $ "Exit code: " <> show exitCode5 + H.note_ $ "Stdout: " <> stdout5 + H.note_ $ "Stderr: " <> stderr5 + + exitCode5 H.=== ExitSuccess + H.diffVsGoldenFile + stdout5 + "test/cardano-testnet-test/files/golden/tx_validate/valid_plutus.out" + + -- ==================================================================== + -- Test 6: Failing Plutus script (supplemental datum not found) + -- Uses plutusV3SupplementalDatumScript which requires a supplemental + -- datum with hash of integer 1 to be present in the transaction. + -- We intentionally omit it so the script fails during evaluation. + -- ==================================================================== + H.note_ "=== Test 6: Failing Plutus script ===" + let supplementalDatumScript = work "supplemental-datum.plutusV3" + H.writeFile supplementalDatumScript $ Text.unpack plutusV3SupplementalDatumScript + + supplementalDatumScriptAddr <- filter (/= '\n') <$> + execCli' execConfig + [ "latest", "address", "build" + , "--payment-script-file", supplementalDatumScript + ] + + -- Send ADA to the supplemental datum script address + txinForScript2 <- findLargestUtxoForPaymentKey epochStateView sbe wallet1 + let sendToScript2TxBody = work "send-to-script-2-tx.body" + sendToScript2TxSigned = work "send-to-script-2-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build" + , "--change-address", Text.unpack $ paymentKeyInfoAddr wallet1 + , "--tx-in", Text.unpack $ renderTxIn txinForScript2 + , "--tx-out", supplementalDatumScriptAddr <> "+" <> show @Int 5_000_000 + , "--tx-out-datum-hash", scriptDatumHash + , "--out-file", sendToScript2TxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", sendToScript2TxBody + , "--signing-key-file", wallet1SKeyFile + , "--out-file", sendToScript2TxSigned + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "submit" + , "--tx-file", sendToScript2TxSigned + ] + + -- Wait for the UTxO to appear at the script address + txinCollateral2 <- findLargestUtxoForPaymentKey epochStateView sbe wallet0 + supplementalDatumTxIn <- fmap fst . retryUntilJustM epochStateView (WaitForBlocks 3) $ + findLargestUtxoWithAddress epochStateView sbe $ Text.pack supplementalDatumScriptAddr + + -- Build spending tx WITHOUT the supplemental datum → script will fail + let failScriptTxBody = work "fail-script-tx.body" + failScriptTxSigned = work "fail-script-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "query", "protocol-parameters" + , "--out-file", work "pparams.json" + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in-collateral", Text.unpack $ renderTxIn txinCollateral2 + , "--tx-in", Text.unpack $ renderTxIn supplementalDatumTxIn + , "--tx-in-script-file", supplementalDatumScript + , "--tx-in-datum-value", "0" + , "--tx-in-redeemer-value", "0" + , "--tx-in-execution-units", "(5000000000, 10000000)" + , "--tx-out", wallet0Addr <> "+" <> show @Int 2_000_000 + , "--fee", "3000000" + , "--protocol-params-file", work "pparams.json" + , "--out-file", failScriptTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", failScriptTxBody + , "--signing-key-file", wallet0SKeyFile + , "--out-file", failScriptTxSigned + ] + + (exitCode6, stdout6, stderr6) <- execCliAny execConfig + [ anyEraToString cEra, "transaction", "validate" + , "--tx-file", failScriptTxSigned + ] + + H.note_ $ "Exit code: " <> show exitCode6 + H.note_ $ "Stdout: " <> stdout6 + H.note_ $ "Stderr: " <> stderr6 + + exitCode6 H.=== ExitFailure 1 + H.diffVsGoldenFile + stdout6 + "test/cardano-testnet-test/files/golden/tx_validate/failing_plutus.out" + + -- ==================================================================== + -- Test 7: Multiple errors (fee too low + value not conserved) + -- Deterministic UTxO has 10,000,000 lovelace. + -- output = 999,999,000,000, fee = 100 + -- consumed = 10,000,000, produced = 999,999,000,100 + -- min_fee = 198, fee = 100 + -- ==================================================================== + H.note_ "=== Test 7: Multiple errors ===" + + -- Use a different deterministic UTxO + testTxin7 <- fmap fst . H.nothingFailM $ + findLargestUtxoWithAddress epochStateView sbe $ Text.pack testAddr + let multiErrorTxBody = work "multi-error-tx.body" + multiErrorTxSigned = work "multi-error-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn testTxin7 + , "--tx-out", testAddr <> "+999999000000" + , "--fee", "100" + , "--out-file", multiErrorTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", multiErrorTxBody + , "--signing-key-file", testSKeyFile + , "--out-file", multiErrorTxSigned + ] + + (exitCode7, stdout7, stderr7) <- execCliAny execConfig + [ anyEraToString cEra, "transaction", "validate" + , "--tx-file", multiErrorTxSigned + ] + + H.note_ $ "Exit code: " <> show exitCode7 + H.note_ $ "Stdout: " <> stdout7 + H.note_ $ "Stderr: " <> stderr7 + + exitCode7 H.=== ExitFailure 1 + H.diffVsGoldenFile + stdout7 + "test/cardano-testnet-test/files/golden/tx_validate/multi_error.out" + + H.success diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Transaction/ValidateErrors.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Transaction/ValidateErrors.hs new file mode 100644 index 00000000000..462cbdc512a --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Transaction/ValidateErrors.hs @@ -0,0 +1,543 @@ +{-# LANGUAGE DisambiguateRecordFields #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Cardano.Testnet.Test.Cli.Transaction.ValidateErrors + ( hprop_transaction_validate_all_errors + ) where + +import Cardano.Api + +import Cardano.Testnet + +import Prelude + +import Control.Monad (void) +import Data.Default.Class +import Data.List (intercalate, isInfixOf) +import qualified Cardano.Api.UTxO as Utxo +import qualified Data.Text as Text +import GHC.IO.Exception (ExitCode (..)) +import System.FilePath (()) + +import Testnet.Components.Configuration +import Testnet.Components.Query (findLargestUtxoForPaymentKey, + findLargestUtxoWithAddress, findUtxosWithAddress, + getEpochStateView, retryUntilJustM, + TestnetWaitPeriod (..), waitForBlocks) +import Testnet.Defaults (plutusV3Script, plutusV3SupplementalDatumScript) +import Testnet.Process.Run (execCli', execCliAny, mkExecConfig) +import Testnet.Property.Util (integrationRetryWorkspace) +import Testnet.Types + +import Hedgehog (Property) +import qualified Hedgehog as H +import qualified Hedgehog.Extras as H + +-- Same deterministic signing key as in the Validate test. +-- Key hash: fe8c2213ed33ae44e3629706a3c7d34f0dce29753646602177756a5f +testSigningKey :: String +testSigningKey = + "{\"type\":\"PaymentSigningKeyShelley_ed25519\",\"description\":\"Payment Signing Key\",\"cborHex\":\"5820aabbccddee0011223344556677889900aabbccddee00112233445566778899aa\"}" + +-- | Comprehensive integration test for @cardano-cli latest transaction validate@. +-- +-- Exercises every category of validation error: +-- +-- Phase 1 (ledger rules): +-- ConwayMempoolFailure, OutputTooSmallUTxO, FeeTooSmallUTxO, ValueNotConservedUTxO, +-- OutsideValidityIntervalUTxO, MissingVKeyWitnessesUTXOW, MaxTxSizeUTxO, +-- ExUnitsTooBigUTxO, TooManyCollateralInputs, NoCollateralInputs, +-- InsufficientCollateral, IncorrectTotalCollateralField, +-- MissingScriptWitnessesUTXOW +-- +-- Phase 2 (Plutus script evaluation): +-- Script evaluation failure (CekError) +-- +-- Execute me with: +-- @DISABLE_RETRIES=1 cabal test cardano-testnet-test --test-options '-p "/transaction validate all errors/"'@ +hprop_transaction_validate_all_errors :: Property +hprop_transaction_validate_all_errors = integrationRetryWorkspace 2 "validate-errors" $ \tempAbsBasePath' -> H.runWithDefaultWatchdog_ $ do + conf@Conf{tempAbsPath} <- mkConf tempAbsBasePath' + let tempAbsPath' = unTmpAbsPath tempAbsPath + work <- H.createDirectoryIfMissing $ tempAbsPath' "work" + + let sbe = ShelleyBasedEraConway + era = toCardanoEra sbe + cEra = AnyCardanoEra era + tempBaseAbsPath = makeTmpBaseAbsPath $ TmpAbsolutePath tempAbsPath' + creationOptions = def{creationEra = AnyShelleyBasedEra sbe} + + TestnetRuntime + { configurationFile + , testnetMagic + , testnetNodes + , wallets = wallet0 : wallet1 : _ + } <- + createAndRunTestnet creationOptions def conf + + node <- H.headM testnetNodes + poolSprocket1 <- H.noteShow $ nodeSprocket node + execConfig <- mkExecConfig tempBaseAbsPath poolSprocket1 testnetMagic + epochStateView <- getEpochStateView configurationFile (nodeSocketPath node) + + -- ==================================================================== + -- Setup: deterministic key + fund address with 6 UTxOs + -- ==================================================================== + let testSKeyFile = work "test-skey.json" + testVKeyFile = work "test-vkey.json" + H.writeFile testSKeyFile testSigningKey + void $ execCli' execConfig + [ "latest", "key", "verification-key" + , "--signing-key-file", testSKeyFile + , "--verification-key-file", testVKeyFile + ] + testAddr <- filter (/= '\n') <$> + execCli' execConfig + [ "latest", "address", "build" + , "--payment-verification-key-file", testVKeyFile + ] + H.note_ $ "Deterministic test address: " <> testAddr + + let fundingAmount = 10_000_000 :: Int + wallet0SKeyFile = signingKeyFp $ paymentKeyInfoPair wallet0 + wallet0Addr = Text.unpack $ paymentKeyInfoAddr wallet0 + wallet1SKeyFile = signingKeyFp $ paymentKeyInfoPair wallet1 + + txinFund <- findLargestUtxoForPaymentKey epochStateView sbe wallet0 + let fundTxBody = work "fund-tx.body" + fundTxSigned = work "fund-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build" + , "--change-address", wallet0Addr + , "--tx-in", Text.unpack $ renderTxIn txinFund + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--tx-out", testAddr <> "+" <> show fundingAmount + , "--out-file", fundTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", fundTxBody + , "--signing-key-file", wallet0SKeyFile + , "--out-file", fundTxSigned + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "submit" + , "--tx-file", fundTxSigned + ] + + H.noteShowM_ $ waitForBlocks epochStateView 1 + + _ <- retryUntilJustM epochStateView (WaitForBlocks 3) $ + findLargestUtxoWithAddress epochStateView sbe $ Text.pack testAddr + + testUtxosList <- Utxo.toList <$> findUtxosWithAddress epochStateView sbe (Text.pack testAddr) + let testTxIns = map fst testUtxosList + H.note_ $ "Found " <> show (length testTxIns) <> " UTxOs at test address" + H.assert $ length testTxIns >= 6 + + -- ==================================================================== + -- Setup: always-succeeds Plutus V3 script address + fund it + -- ==================================================================== + let alwaysSucceedsScript = work "always-succeeds.plutusV3" + H.writeFile alwaysSucceedsScript $ Text.unpack plutusV3Script + + alwaysSucceedsAddr <- filter (/= '\n') <$> + execCli' execConfig + [ "latest", "address", "build" + , "--payment-script-file", alwaysSucceedsScript + ] + + scriptDatumHash <- filter (/= '\n') <$> + execCli' execConfig + [ "latest", "transaction", "hash-script-data" + , "--script-data-value", "0" + ] + + txinForScript <- findLargestUtxoForPaymentKey epochStateView sbe wallet0 + let sendToScriptTxBody = work "send-to-script-tx.body" + sendToScriptTxSigned = work "send-to-script-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build" + , "--change-address", wallet0Addr + , "--tx-in", Text.unpack $ renderTxIn txinForScript + , "--tx-out", alwaysSucceedsAddr <> "+5000000" + , "--tx-out-datum-hash", scriptDatumHash + , "--out-file", sendToScriptTxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", sendToScriptTxBody + , "--signing-key-file", wallet0SKeyFile + , "--out-file", sendToScriptTxSigned + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "submit" + , "--tx-file", sendToScriptTxSigned + ] + + scriptTxIn <- fmap fst . retryUntilJustM epochStateView (WaitForBlocks 3) $ + findLargestUtxoWithAddress epochStateView sbe $ Text.pack alwaysSucceedsAddr + + -- ==================================================================== + -- Setup: supplemental-datum Plutus V3 script address + fund it + -- ==================================================================== + let supplementalDatumScript = work "supplemental-datum.plutusV3" + H.writeFile supplementalDatumScript $ Text.unpack plutusV3SupplementalDatumScript + + supplementalDatumScriptAddr <- filter (/= '\n') <$> + execCli' execConfig + [ "latest", "address", "build" + , "--payment-script-file", supplementalDatumScript + ] + + txinForScript2 <- findLargestUtxoForPaymentKey epochStateView sbe wallet1 + let sendToScript2TxBody = work "send-to-script-2-tx.body" + sendToScript2TxSigned = work "send-to-script-2-tx.signed" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build" + , "--change-address", Text.unpack $ paymentKeyInfoAddr wallet1 + , "--tx-in", Text.unpack $ renderTxIn txinForScript2 + , "--tx-out", supplementalDatumScriptAddr <> "+5000000" + , "--tx-out-datum-hash", scriptDatumHash + , "--out-file", sendToScript2TxBody + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", sendToScript2TxBody + , "--signing-key-file", wallet1SKeyFile + , "--out-file", sendToScript2TxSigned + ] + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "submit" + , "--tx-file", sendToScript2TxSigned + ] + + supplementalDatumTxIn <- fmap fst . retryUntilJustM epochStateView (WaitForBlocks 3) $ + findLargestUtxoWithAddress epochStateView sbe $ Text.pack supplementalDatumScriptAddr + + -- Fetch protocol parameters for build-raw with scripts + void $ execCli' execConfig + [ anyEraToString cEra, "query", "protocol-parameters" + , "--out-file", work "pparams.json" + ] + + -- Helper: assert that stdout contains an expected substring + let assertContains :: String -> String -> H.Integration () + assertContains output expected = do + H.note_ $ " Checking for: " <> expected + H.assert $ expected `isInfixOf` output + + -- Helper: sign tx body and run validate, returning (exitCode, stdout, stderr) + let signAndValidate skey txBody txSigned = do + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "sign" + , "--tx-body-file", txBody + , "--signing-key-file", skey + , "--out-file", txSigned + ] + execCliAny execConfig + [ anyEraToString cEra, "transaction", "validate" + , "--tx-file", txSigned + ] + + -- ==================================================================== + -- Test 1: ConwayMempoolFailure (non-existent input) + -- The all-zeros TxIn is caught by the mempool shortcircuit check + -- before the UTxO rules can fire BadInputsUTxO. + -- ==================================================================== + H.note_ "=== Test 1: ConwayMempoolFailure (non-existent input) ===" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", "0000000000000000000000000000000000000000000000000000000000000000#0" + , "--tx-out", testAddr <> "+9800000" + , "--fee", "200000" + , "--out-file", work "bad-input-tx.body" + ] + + (ec1, out1, _stderr1) <- signAndValidate testSKeyFile + (work "bad-input-tx.body") (work "bad-input-tx.signed") + + H.note_ $ "Output:\n" <> out1 + ec1 H.=== ExitFailure 1 + assertContains out1 "Phase 1: FAILED" + assertContains out1 "ConwayMempoolFailure:" + + -- ==================================================================== + -- Test 2: Mega combo — 5 Phase 1 errors in one transaction + -- OutputTooSmallUTxO + FeeTooSmallUTxO + ValueNotConservedUTxO + -- + OutsideValidityIntervalUTxO + MissingVKeyWitnessesUTXOW + -- + -- Uses a real input (10M lovelace): + -- output₁ = 1 lovelace (too small) + -- output₂ = 999,999,000,000 lovelace (blows up value conservation) + -- fee = 100 (too small) + -- --invalid-hereafter 1 (expired) + -- signed with wallet1's key (wrong key for testAddr) + -- ==================================================================== + H.note_ "=== Test 2: Mega combo (5 Phase 1 errors) ===" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn (head testTxIns) + , "--tx-out", testAddr <> "+1" + , "--tx-out", testAddr <> "+999999000000" + , "--fee", "100" + , "--invalid-hereafter", "1" + , "--out-file", work "mega-combo-tx.body" + ] + + (ec2, out2, _stderr2) <- signAndValidate wallet1SKeyFile + (work "mega-combo-tx.body") (work "mega-combo-tx.signed") + + H.note_ $ "Output:\n" <> out2 + ec2 H.=== ExitFailure 1 + assertContains out2 "Phase 1: FAILED" + -- Values are deterministic: input = 10M, outputs = 1 + 999,999,000,000, fee = 100 + -- Missing key hash = fe8c22... (the deterministic test key) + -- Slot 1 is the expired validity upper bound (current slot is dynamic) + assertContains out2 "BabbageOutputTooSmallUTxO:" + assertContains out2 "FeeTooSmallUTxO: minimum fee is" + assertContains out2 "ValueNotConservedUTxO: supplied 10000000 lovelace, expected = 999999000101 lovelace" + assertContains out2 "OutsideValidityIntervalUTxO:" + assertContains out2 "MissingVKeyWitnessesUTXOW: fe8c2213ed33ae44e3629706a3c7d34f0dce29753646602177756a5f" + + -- ==================================================================== + -- Test 3: MaxTxSizeUTxO + -- Attach enormous metadata (300 × 64-byte strings ≈ 21 KB > 16 KB limit). + -- ==================================================================== + H.note_ "=== Test 3: MaxTxSizeUTxO ===" + + let metadataEntry i = " \"" <> show @Int i <> "\": \"" <> replicate 64 'A' <> "\"" + largeMetadata = "{\n" <> intercalate ",\n" (map metadataEntry [0..299]) <> "\n}" + H.writeFile (work "large-metadata.json") largeMetadata + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn (head testTxIns) + , "--tx-out", testAddr <> "+9800000" + , "--fee", "200000" + , "--json-metadata-no-schema" + , "--metadata-json-file", work "large-metadata.json" + , "--out-file", work "max-tx-size-tx.body" + ] + + (ec3, out3, _stderr3) <- signAndValidate testSKeyFile + (work "max-tx-size-tx.body") (work "max-tx-size-tx.signed") + + H.note_ $ "Output:\n" <> out3 + ec3 H.=== ExitFailure 1 + assertContains out3 "Phase 1: FAILED" + -- 300 entries × ~68 CBOR bytes = ~20 KB > 16384 byte limit + assertContains out3 "MaxTxSizeUTxO: supplied 20657 bytes, expected ≤ 16384 bytes" + H.diffVsGoldenFile out3 + "test/cardano-testnet-test/files/golden/tx_validate/errors_max_tx_size.out" + + -- ==================================================================== + -- Test 4: ExUnitsTooBigUTxO + TooManyCollateralInputs + -- Script input with execution units far above max (140M mem, 10B steps). + -- 4 collateral inputs (max allowed is 3). + -- ==================================================================== + H.note_ "=== Test 4: ExUnitsTooBigUTxO + TooManyCollateralInputs ===" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn scriptTxIn + , "--tx-in-script-file", alwaysSucceedsScript + , "--tx-in-datum-value", "0" + , "--tx-in-redeemer-value", "0" + , "--tx-in-execution-units", "(999999999999, 999999999999)" + , "--tx-in", Text.unpack $ renderTxIn (head testTxIns) + , "--tx-in-collateral", Text.unpack $ renderTxIn (testTxIns !! 1) + , "--tx-in-collateral", Text.unpack $ renderTxIn (testTxIns !! 2) + , "--tx-in-collateral", Text.unpack $ renderTxIn (testTxIns !! 3) + , "--tx-in-collateral", Text.unpack $ renderTxIn (testTxIns !! 4) + , "--tx-out", testAddr <> "+12000000" + , "--fee", "3000000" + , "--protocol-params-file", work "pparams.json" + , "--out-file", work "exunits-collateral-tx.body" + ] + + (ec4, out4, _stderr4) <- signAndValidate testSKeyFile + (work "exunits-collateral-tx.body") (work "exunits-collateral-tx.signed") + + H.note_ $ "Output:\n" <> out4 + ec4 H.=== ExitFailure 1 + assertContains out4 "Phase 1: FAILED" + -- ExUnits (999999999999, 999999999999) far exceed max (140000000, 10000000000) + assertContains out4 "ExUnitsTooBigUTxO:" + -- 4 collateral inputs > maxCollateralInputs (3) + assertContains out4 "TooManyCollateralInputs: supplied 4, expected ≤ 3" + + -- ==================================================================== + -- Test 5: NoCollateralInputs + -- Script transaction with no --tx-in-collateral at all. + -- ==================================================================== + H.note_ "=== Test 5: NoCollateralInputs ===" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn scriptTxIn + , "--tx-in-script-file", alwaysSucceedsScript + , "--tx-in-datum-value", "0" + , "--tx-in-redeemer-value", "0" + , "--tx-in-execution-units", "(1000000, 2000000)" + , "--tx-out", testAddr <> "+2000000" + , "--fee", "3000000" + , "--protocol-params-file", work "pparams.json" + , "--out-file", work "no-collateral-tx.body" + ] + + (ec5, out5, _stderr5) <- signAndValidate testSKeyFile + (work "no-collateral-tx.body") (work "no-collateral-tx.signed") + + H.note_ $ "Output:\n" <> out5 + ec5 H.=== ExitFailure 1 + assertContains out5 "Phase 1: FAILED" + assertContains out5 "NoCollateralInputs" + -- 0 collateral, required = fee 3M × 150% = 4.5M + assertContains out5 "InsufficientCollateral: actual collateral is 0 lovelace, required collateral is 4500000 lovelace" + H.diffVsGoldenFile out5 + "test/cardano-testnet-test/files/golden/tx_validate/errors_no_collateral.out" + + -- ==================================================================== + -- Test 6: InsufficientCollateral + -- Script tx with fee = 10M → required collateral = 15M (150%). + -- Collateral UTxO has only 10M → insufficient. + -- Uses a second regular input (10M) so that value is conserved: + -- consumed = 5M (script) + 10M (regular) = 15M + -- produced = 5M (output) + 10M (fee) = 15M + -- ==================================================================== + H.note_ "=== Test 6: InsufficientCollateral ===" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn scriptTxIn + , "--tx-in-script-file", alwaysSucceedsScript + , "--tx-in-datum-value", "0" + , "--tx-in-redeemer-value", "0" + , "--tx-in-execution-units", "(1000000, 2000000)" + , "--tx-in", Text.unpack $ renderTxIn (head testTxIns) + , "--tx-in-collateral", Text.unpack $ renderTxIn (testTxIns !! 1) + , "--tx-out", testAddr <> "+5000000" + , "--fee", "10000000" + , "--protocol-params-file", work "pparams.json" + , "--out-file", work "insufficient-collateral-tx.body" + ] + + (ec6, out6, _stderr6) <- signAndValidate testSKeyFile + (work "insufficient-collateral-tx.body") (work "insufficient-collateral-tx.signed") + + H.note_ $ "Output:\n" <> out6 + ec6 H.=== ExitFailure 1 + assertContains out6 "Phase 1: FAILED" + -- 10M collateral < 10M fee × 150% = 15M required + assertContains out6 "InsufficientCollateral: actual collateral is 10000000 lovelace, required collateral is 15000000 lovelace" + + -- ==================================================================== + -- Test 7: IncorrectTotalCollateralField + -- Declares --tx-total-collateral 1 but the actual collateral is 10M. + -- Value-balanced, fee reasonable, so only the total-collateral mismatch fires. + -- ==================================================================== + H.note_ "=== Test 7: IncorrectTotalCollateralField ===" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn scriptTxIn + , "--tx-in-script-file", alwaysSucceedsScript + , "--tx-in-datum-value", "0" + , "--tx-in-redeemer-value", "0" + , "--tx-in-execution-units", "(1000000, 2000000)" + , "--tx-in", Text.unpack $ renderTxIn (head testTxIns) + , "--tx-in-collateral", Text.unpack $ renderTxIn (testTxIns !! 1) + , "--tx-total-collateral", "1" + , "--tx-out", testAddr <> "+12000000" + , "--fee", "3000000" + , "--protocol-params-file", work "pparams.json" + , "--out-file", work "incorrect-total-collateral-tx.body" + ] + + (ec7, out7, _stderr7) <- signAndValidate testSKeyFile + (work "incorrect-total-collateral-tx.body") (work "incorrect-total-collateral-tx.signed") + + H.note_ $ "Output:\n" <> out7 + ec7 H.=== ExitFailure 1 + assertContains out7 "Phase 1: FAILED" + -- Declared --tx-total-collateral 1, but actual sum of collateral inputs is 10M + assertContains out7 "IncorrectTotalCollateralField: declared total collateral is 1 lovelace, actual total collateral is 10000000 lovelace" + + -- ==================================================================== + -- Test 8: MissingScriptWitnessesUTXOW + -- Spend a UTxO at the script address without providing the script. + -- ==================================================================== + H.note_ "=== Test 8: MissingScriptWitnessesUTXOW ===" + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in", Text.unpack $ renderTxIn scriptTxIn + , "--tx-out", testAddr <> "+2000000" + , "--fee", "3000000" + , "--out-file", work "missing-script-witness-tx.body" + ] + + (ec8, out8, _stderr8) <- signAndValidate testSKeyFile + (work "missing-script-witness-tx.body") (work "missing-script-witness-tx.signed") + + H.note_ $ "Output:\n" <> out8 + ec8 H.=== ExitFailure 1 + assertContains out8 "Phase 1: FAILED" + -- The always-succeeds script hash + assertContains out8 "MissingScriptWitnessesUTXOW: 186e32faa80a26810392fda6d559c7ed4721a65ce1c9d4ef3e1c87b4" + H.diffVsGoldenFile out8 + "test/cardano-testnet-test/files/golden/tx_validate/errors_missing_script_witness.out" + + -- ==================================================================== + -- Test 9: Phase 2 script evaluation failure + -- The supplemental-datum script requires datum with hash of integer 1 + -- in the transaction. We omit it → script fails at evaluation. + -- ==================================================================== + H.note_ "=== Test 9: Phase 2 script failure ===" + + txinCollateral9 <- findLargestUtxoForPaymentKey epochStateView sbe wallet0 + + void $ execCli' execConfig + [ anyEraToString cEra, "transaction", "build-raw" + , "--tx-in-collateral", Text.unpack $ renderTxIn txinCollateral9 + , "--tx-in", Text.unpack $ renderTxIn supplementalDatumTxIn + , "--tx-in-script-file", supplementalDatumScript + , "--tx-in-datum-value", "0" + , "--tx-in-redeemer-value", "0" + , "--tx-in-execution-units", "(5000000000, 10000000)" + , "--tx-out", wallet0Addr <> "+2000000" + , "--fee", "3000000" + , "--protocol-params-file", work "pparams.json" + , "--out-file", work "phase2-fail-tx.body" + ] + + (ec9, out9, _stderr9) <- signAndValidate wallet0SKeyFile + (work "phase2-fail-tx.body") (work "phase2-fail-tx.signed") + + H.note_ $ "Output:\n" <> out9 + ec9 H.=== ExitFailure 1 + assertContains out9 "Phase 2: FAILED" + H.diffVsGoldenFile out9 + "test/cardano-testnet-test/files/golden/tx_validate/errors_phase2_failure.out" + + H.success diff --git a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs index 49fe74b79a4..44fb2253c31 100644 --- a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs +++ b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs @@ -19,6 +19,8 @@ import qualified Cardano.Testnet.Test.Cli.StakeSnapshot import qualified Cardano.Testnet.Test.Cli.Transaction import qualified Cardano.Testnet.Test.Cli.Transaction.BuildEstimate import qualified Cardano.Testnet.Test.Cli.Transaction.RegisterDeregisterStakeAddress +import qualified Cardano.Testnet.Test.Cli.Transaction.Validate +import qualified Cardano.Testnet.Test.Cli.Transaction.ValidateErrors import qualified Cardano.Testnet.Test.Cli.Transaction.WithdrawalReward import qualified Cardano.Testnet.Test.DumpConfig import qualified Cardano.Testnet.Test.FoldEpochState @@ -119,6 +121,8 @@ tests = do , ignoreOnWindows "simple transaction build" Cardano.Testnet.Test.Cli.Transaction.hprop_transaction , ignoreOnWindows "Transaction Build Estimate" Cardano.Testnet.Test.Cli.Transaction.BuildEstimate.hprop_tx_build_estimate , ignoreOnWindows "register deregister stake address in transaction build" Cardano.Testnet.Test.Cli.Transaction.RegisterDeregisterStakeAddress.hprop_tx_register_deregister_stake_address + , ignoreOnWindows "transaction validate" Cardano.Testnet.Test.Cli.Transaction.Validate.hprop_transaction_validate + , ignoreOnWindows "transaction validate all errors" Cardano.Testnet.Test.Cli.Transaction.ValidateErrors.hprop_transaction_validate_all_errors , ignoreOnWindows "transaction build with withdrawal" Cardano.Testnet.Test.Cli.Transaction.WithdrawalReward.hprop_tx_withdrawal_reward , ignoreOnWindows "transaction build with plutus withdrawal" Cardano.Testnet.Test.Cli.Transaction.WithdrawalReward.hprop_tx_withdrawal_reward_plutus_v3 -- FIXME diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_max_tx_size.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_max_tx_size.out new file mode 100644 index 00000000000..d45c7f31178 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_max_tx_size.out @@ -0,0 +1,4 @@ +Transaction validation failed. +Phase 1: FAILED + MaxTxSizeUTxO: supplied 20657 bytes, expected ≤ 16384 bytes +Phase 2: passed (0 scripts evaluated) diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_missing_script_witness.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_missing_script_witness.out new file mode 100644 index 00000000000..f1397405b72 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_missing_script_witness.out @@ -0,0 +1,4 @@ +Transaction validation failed. +Phase 1: FAILED + MissingScriptWitnessesUTXOW: 186e32faa80a26810392fda6d559c7ed4721a65ce1c9d4ef3e1c87b4 +Phase 2: passed (0 scripts evaluated) diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_no_collateral.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_no_collateral.out new file mode 100644 index 00000000000..5165a0ab58f --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_no_collateral.out @@ -0,0 +1,6 @@ +Transaction validation failed. +Phase 1: FAILED + InsufficientCollateral: actual collateral is 0 lovelace, required collateral is 4500000 lovelace + NoCollateralInputs +Phase 2: passed (1 script evaluated) + Spend:0 passed (mem: 500, steps: 64100) diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_phase2_failure.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_phase2_failure.out new file mode 100644 index 00000000000..b8711d6d07c --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/errors_phase2_failure.out @@ -0,0 +1,8 @@ +Transaction validation failed. +Phase 1: passed +Phase 2: FAILED + Spend:0 FAILED + CekError An error has occurred: + The machine terminated because of an error, either from a built-in function or from an explicit use of 'error'. + Caused by: error + Logs: PT5 diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/failing_plutus.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/failing_plutus.out new file mode 100644 index 00000000000..b8711d6d07c --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/failing_plutus.out @@ -0,0 +1,8 @@ +Transaction validation failed. +Phase 1: passed +Phase 2: FAILED + Spend:0 FAILED + CekError An error has occurred: + The machine terminated because of an error, either from a built-in function or from an explicit use of 'error'. + Caused by: error + Logs: PT5 diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/fee_too_low.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/fee_too_low.out new file mode 100644 index 00000000000..fe431f228ad --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/fee_too_low.out @@ -0,0 +1,4 @@ +Transaction validation failed. +Phase 1: FAILED + FeeTooSmallUTxO: minimum fee is 193 lovelace, transaction specifies 100 lovelace +Phase 2: passed (0 scripts evaluated) diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/missing_witness.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/missing_witness.out new file mode 100644 index 00000000000..e25bc9eea69 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/missing_witness.out @@ -0,0 +1,4 @@ +Transaction validation failed. +Phase 1: FAILED + MissingVKeyWitnessesUTXOW: fe8c2213ed33ae44e3629706a3c7d34f0dce29753646602177756a5f +Phase 2: passed (0 scripts evaluated) diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/multi_error.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/multi_error.out new file mode 100644 index 00000000000..2efa6be2b57 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/multi_error.out @@ -0,0 +1,5 @@ +Transaction validation failed. +Phase 1: FAILED + FeeTooSmallUTxO: minimum fee is 197 lovelace, transaction specifies 100 lovelace + ValueNotConservedUTxO: supplied 10000000 lovelace, expected = 999999000100 lovelace +Phase 2: passed (0 scripts evaluated) diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/valid_plutus.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/valid_plutus.out new file mode 100644 index 00000000000..7075c6ad8a7 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/valid_plutus.out @@ -0,0 +1,4 @@ +Transaction is valid. +Phase 1: passed +Phase 2: passed (1 script evaluated) + Spend:0 passed (mem: 500, steps: 64100) diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/valid_simple.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/valid_simple.out new file mode 100644 index 00000000000..c4a2600d75e --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/valid_simple.out @@ -0,0 +1,3 @@ +Transaction is valid. +Phase 1: passed +Phase 2: passed (0 scripts evaluated) diff --git a/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/value_not_conserved.out b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/value_not_conserved.out new file mode 100644 index 00000000000..6d1efc8819d --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/golden/tx_validate/value_not_conserved.out @@ -0,0 +1,4 @@ +Transaction validation failed. +Phase 1: FAILED + ValueNotConservedUTxO: supplied 10000000 lovelace, expected = 999999200000 lovelace +Phase 2: passed (0 scripts evaluated)