Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project: cardano-cli
pr: 1452
kind:
- refactoring
description: |
Add `readAnyScriptHash` to `Cardano.CLI.Read`, which returns the hash of a script file in any script language cardano-api supports, including Plutus V4. Add `MainnetEra` to `Cardano.CLI.EraBased.Option`, the era used by the `latest` command group and the top-level `node` and `query` commands.
6 changes: 6 additions & 0 deletions .changes/cardano_era_all_eras.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project: cardano-cli
pr: 1452
kind:
- bugfix
description: |
`CARDANO_ERA` now accepts every era cardano-api supports, including `dijkstra`, in any letter case, and an unknown value stops the command with an error that lists the valid eras.
6 changes: 6 additions & 0 deletions .changes/plutus-v4-script-readers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project: cardano-cli
pr: 1452
kind:
- bugfix
description: |
`transaction policyid` and `address build` with `--payment-script-file` or `--stake-script-file` now accept Plutus V4 scripts, instead of failing with "Plutus language PlutusScriptV4 is not supported in the Conway era". They also accept simple scripts with a `guard`, which used to crash them.
6 changes: 6 additions & 0 deletions .changes/plutus-v4-script-witnesses.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project: cardano-cli
pr: 1452
kind:
- test
description: |
Add golden tests for Plutus V4 scripts: script files as direct witnesses for every script purpose in `dijkstra transaction build-raw`, `hash script`, `transaction policyid`, `address build` with payment and stake scripts, and the error for a V4 reference script in a Conway transaction.
6 changes: 6 additions & 0 deletions .changes/reference-script-unsupported-language.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project: cardano-cli
pr: 1452
kind:
- bugfix
description: |
A transaction output whose reference script is written in a Plutus language not supported by the transaction's era now makes the command fail with a clear error, instead of silently building the transaction with that reference script dropped from the output.
2 changes: 2 additions & 0 deletions cardano-cli/cardano-cli.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -487,11 +487,13 @@ test-suite cardano-cli-golden
Test.Golden.Conway.Transaction.Assemble
Test.Golden.Conway.Transaction.BuildRaw
Test.Golden.Conway.Transaction.CreateWitness
Test.Golden.Conway.Transaction.ReferenceScript
Test.Golden.CreateStaked
Test.Golden.CreateTestnetData
Test.Golden.Debug.TransactionView
Test.Golden.Dijkstra.StakePool.RegistrationCertificate
Test.Golden.Dijkstra.Transaction.Assemble
Test.Golden.Dijkstra.Transaction.BuildRaw
Test.Golden.Dijkstra.Transaction.SubTransaction
Test.Golden.ErrorsSpec
Test.Golden.Genesis.Common
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ readRefScript sbe = \case
ReferenceScriptAnyEraNone -> pure L.SNothing
ReferenceScriptAnyEra fp -> do
script <- readFileScriptInAnyLang fp
pure $ maybe L.SNothing (L.SJust . toShelleyScript) (toScriptInEra sbe script)
scriptInEra <-
fromMaybeCli
(TxOutputReferenceScriptLanguageNotSupportedInEra script (anyCardanoEra $ toCardanoEra sbe))
$ toScriptInEra sbe script
Comment on lines +142 to +145

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rejection itself is correct: a native script with a guard is only valid from Dijkstra onwards, so a Conway output cannot carry it, and the transaction is rejected at build time.
The message names the script language because cardano-api's toScriptInEra only returns Nothing, and its simpleScriptHasGuard check is not exported.
A guard-specific message is better added in cardano-api than by copying that traversal into cardano-cli, so I'm leaving it as is here.

pure . L.SJust $ toShelleyScript scriptInEra

toTxOutValueInShelleyBasedEra
:: ShelleyBasedEra era
Expand Down
46 changes: 30 additions & 16 deletions cardano-cli/src/Cardano/CLI/Environment.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@ where

import Cardano.Api
( AnyCardanoEra (..)
, CardanoEra (..)
, NetworkId (..)
, NetworkMagic (..)
, forEraMaybeEon
, pretty
)
import Cardano.Api.Experimental qualified as Exp

import Control.Monad.IO.Class (MonadIO (..))
import Data.Char (toLower)
import Data.Foldable (find)
import Data.List (intercalate)
import Data.Traversable (forM)
import Data.Type.Equality
import Data.Word (Word32)
import GHC.Stack (HasCallStack)
import System.Environment qualified as IO
import System.IO qualified as IO
import Text.Read (readMaybe)
Expand Down Expand Up @@ -84,21 +90,29 @@ getEnvNetworkId = do
getEnvSocketPath :: IO (Maybe FilePath)
getEnvSocketPath = IO.lookupEnv "CARDANO_NODE_SOCKET_PATH"

-- | If the environment variable @CARDANO_ERA@ is set, then return the set value.
-- | If the environment variable @CARDANO_ERA@ is set to a known era, then return that era.
-- If it is set to an unknown era, fail with an error listing the valid eras.
-- Otherwise, return 'Nothing'.
getCardanoEra :: IO (Maybe AnyCardanoEra)
getCardanoEra :: (HasCallStack, MonadIO m) => m (Maybe AnyCardanoEra)
getCardanoEra = do
mEraString <- IO.lookupEnv "CARDANO_ERA"
mEraString <- liftIO $ IO.lookupEnv "CARDANO_ERA"
forM mEraString $ \eraString ->
maybe (error $ invalidEraMessage eraString) pure $ parseCardanoEra eraString
where
invalidEraMessage :: String -> String
invalidEraMessage eraString =
mconcat
[ "The era specified in CARDANO_ERA is invalid: " <> eraString <> "."
, " It should be one of: " <> intercalate ", " (cardanoEraName <$> allCardanoEras) <> "."
]

case mEraString of
Just eraString ->
case eraString of
"byron" -> pure $ Just $ AnyCardanoEra ByronEra
"shelley" -> pure $ Just $ AnyCardanoEra ShelleyEra
"allegra" -> pure $ Just $ AnyCardanoEra AllegraEra
"mary" -> pure $ Just $ AnyCardanoEra MaryEra
"alonzo" -> pure $ Just $ AnyCardanoEra AlonzoEra
"babbage" -> pure $ Just $ AnyCardanoEra BabbageEra
"conway" -> pure $ Just $ AnyCardanoEra ConwayEra
unknown -> error $ "Unknown era: " <> unknown -- TODO improve error handling
Nothing -> pure Nothing
-- Parse an era name, ignoring case, using the names that cardano-api renders for its eras.
parseCardanoEra :: String -> Maybe AnyCardanoEra
parseCardanoEra eraString =
find ((== map toLower eraString) . cardanoEraName) allCardanoEras

allCardanoEras :: [AnyCardanoEra]
allCardanoEras = enumFrom minBound

cardanoEraName :: AnyCardanoEra -> String
cardanoEraName = map toLower . show . pretty
10 changes: 8 additions & 2 deletions cardano-cli/src/Cardano/CLI/EraBased/Option.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
module Cardano.CLI.EraBased.Option
( pCmds
, pAnyEraCommand
, MainnetEra
)
where

import Cardano.Api.Experimental
import Cardano.Api.Pretty (prettyShow)

import Cardano.CLI.Environment
import Cardano.CLI.EraBased.Command
Expand All @@ -29,6 +31,9 @@ import Data.Maybe
import Options.Applicative (Parser)
import Options.Applicative qualified as Opt

-- | The era running on mainnet, used by the latest, top-level node and top-level query commands.
type MainnetEra = ConwayEra

pCmds :: forall era. IsEra era => EnvCli -> Parser (Cmds era)
pCmds envCli = do
asum $
Expand Down Expand Up @@ -59,6 +64,7 @@ pAnyEraCommand envCli =
Opt.progDesc "Dijkstra era commands"
, Opt.hsubparser $
commandWithMetavar "latest" $
Opt.info (AnyEraCommandOf ConwayEra <$> pCmds @ConwayEra envCli) $
Opt.progDesc "Latest era commands (Conway)"
Opt.info (AnyEraCommandOf (useEra @MainnetEra) <$> pCmds @MainnetEra envCli) $
Opt.progDesc $
"Latest era commands (" <> prettyShow (useEra @MainnetEra) <> ")"
]
9 changes: 3 additions & 6 deletions cardano-cli/src/Cardano/CLI/EraBased/Transaction/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1530,12 +1530,9 @@ runTransactionPolicyIdCmd
-> CIO e ()
runTransactionPolicyIdCmd
Cmd.TransactionPolicyIdCmdArgs
{ scriptFile = File sFile
} = do
script <-
readAnyScript @_ @ConwayEra sFile
let hash = fromShelleyScriptHash $ Exp.hashAnyScript script
liftIO . Text.putStrLn $ serialiseToRawBytesHexText hash
{ scriptFile
} =
liftIO . Text.putStrLn . serialiseToRawBytesHexText =<< readAnyScriptHash scriptFile

-- ----------------------------------------------------------------------------
-- Other misc small commands
Expand Down
26 changes: 8 additions & 18 deletions cardano-cli/src/Cardano/CLI/EraIndependent/Address/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ module Cardano.CLI.EraIndependent.Address.Run
where

import Cardano.Api
import Cardano.Api.Experimental.AnyScript qualified as Exp

import Cardano.CLI.Compatible.Exception
import Cardano.CLI.EraIndependent.Address.Command
import Cardano.CLI.EraIndependent.Address.Info.Run
import Cardano.CLI.EraIndependent.Key.Run qualified as Key
import Cardano.CLI.Read
import Cardano.CLI.Read qualified as Exp
import Cardano.CLI.Type.Common
import Cardano.CLI.Type.Error.AddressCmdError
import Cardano.CLI.Type.Key
Expand All @@ -40,6 +38,7 @@ import Cardano.CLI.Type.Key
import Control.Monad (void)
import Data.ByteString.Char8 qualified as BS
import Data.Function
import Data.Maybe (fromMaybe)
import Data.Text.IO qualified as Text
import Vary (Vary)
import Vary qualified
Expand Down Expand Up @@ -206,15 +205,11 @@ runAddressBuildCmd paymentVerifier mbStakeVerifier nw mOutFp = do
nonPaymentKey ->
throwCliError $ AddressCmdExpectedPaymentVerificationKey nonPaymentKey
return $ serialiseAddress (addr :: AddressAny)
PaymentVerifierScriptFile (File fp) -> do
script <-
readAnyScript @_ @ConwayEra fp

let hash = fromShelleyScriptHash $ Exp.hashAnyScript script
payCred = PaymentCredentialByScript hash
PaymentVerifierScriptFile scriptFile -> do
payCred <- PaymentCredentialByScript <$> readAnyScriptHash scriptFile

stakeAddressReference <-
maybe (return NoStakeAddress) makeStakeAddressRef mbStakeVerifier
fromMaybe NoStakeAddress <$> traverse makeStakeAddressRef mbStakeVerifier

return $ serialiseAddress . makeShelleyAddress nw payCred $ stakeAddressReference

Expand All @@ -233,14 +228,8 @@ makeStakeAddressRef stakeIdentifier =
stakeVKeyHash <-
readVerificationKeyOrHashOrFile stkVkeyOrFile
return . StakeAddressByValue $ StakeCredentialByKey stakeVKeyHash
StakeVerifierScriptFile (File fp) -> do
script <-
Exp.readAnyScript @_ @ConwayEra fp

let hash = fromShelleyScriptHash $ Exp.hashAnyScript script
stakeCred = StakeCredentialByScript hash

return (StakeAddressByValue stakeCred)
StakeVerifierScriptFile scriptFile ->
StakeAddressByValue . StakeCredentialByScript <$> readAnyScriptHash scriptFile
StakeIdentifierAddress stakeAddr ->
pure $ StakeAddressByValue $ stakeAddressCredential stakeAddr

Expand All @@ -251,4 +240,5 @@ buildShelleyAddress
-> CIO e (Address ShelleyAddr)
buildShelleyAddress vkey mbStakeVerifier nw =
makeShelleyAddress nw (PaymentCredentialByKey (verificationKeyHash vkey))
<$> maybe (return NoStakeAddress) makeStakeAddressRef mbStakeVerifier
. fromMaybe NoStakeAddress
<$> traverse makeStakeAddressRef mbStakeVerifier
10 changes: 3 additions & 7 deletions cardano-cli/src/Cardano/CLI/EraIndependent/Hash/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ module Cardano.CLI.EraIndependent.Hash.Run
where

import Cardano.Api
import Cardano.Api.Experimental.AnyScript qualified as Exp
import Cardano.Api.Ledger qualified as L

import Cardano.CLI.Compatible.Exception
Expand Down Expand Up @@ -88,15 +87,12 @@ runHashScriptCmd
:: forall e
. Cmd.HashScriptCmdArgs
-> CIO e ()
runHashScriptCmd Cmd.HashScriptCmdArgs{Cmd.toHash = File toHash, mOutFile} =
runHashScriptCmd Cmd.HashScriptCmdArgs{Cmd.toHash, mOutFile} =
do
script <-
readAnyScript @_ @DijkstraEra toHash
let hash = Exp.hashAnyScript script
hash <- readAnyScriptHash toHash
fromEitherIOCli @(FileError ()) $
writeTextOutput mOutFile $
serialiseToRawBytesHexText $
fromShelleyScriptHash hash
serialiseToRawBytesHexText hash

runHashGenesisFile :: GenesisFile -> CIO e ()
runHashGenesisFile (GenesisFile fpath) = do
Expand Down
6 changes: 2 additions & 4 deletions cardano-cli/src/Cardano/CLI/Option.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ module Cardano.CLI.Option
)
where

import Cardano.Api.Experimental (ConwayEra)

import Cardano.CLI.Byron.Parser (backwardsCompatibilityCommands, parseByronCommands)
import Cardano.CLI.Compatible.Option
import Cardano.CLI.Environment (EnvCli)
Expand Down Expand Up @@ -61,14 +59,14 @@ addressCmdsTopLevel envCli = AddressCommand <$> pAddressCmds envCli
-- The node related commands are shelley era agnostic for the time being.
-- There is no need to guard them by the era argument.
nodeCmdsTopLevel :: Parser ClientCommand
nodeCmdsTopLevel = NodeCommands <$> pNodeCmds @ConwayEra
nodeCmdsTopLevel = NodeCommands <$> pNodeCmds @MainnetEra

-- Queries actually depend on the node to client version which may coincide
-- with a hardfork but not necessarily. We will expose commands at the top level
-- regardless if they are compatible with the era or not. The help text should be
-- updated to make this clear. Gating commands behind eras
queryCmdsTopLevel :: EnvCli -> Parser ClientCommand
queryCmdsTopLevel envCli = QueryCommands @ConwayEra <$> pQueryCmdsTopLevel envCli
queryCmdsTopLevel envCli = QueryCommands @MainnetEra <$> pQueryCmdsTopLevel envCli

keyCmdsTopLevel :: Parser ClientCommand
keyCmdsTopLevel = KeyCommands <$> pKeyCmds
Expand Down
10 changes: 10 additions & 0 deletions cardano-cli/src/Cardano/CLI/Read.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module Cardano.CLI.Read
-- * Script
, ScriptDecodeError (..)
, readAnyScript
, readAnyScriptHash
, deserialiseScriptInAnyLang
, readFileScriptInAnyLang
, PlutusScriptDecodeError (..)
Expand Down Expand Up @@ -200,6 +201,15 @@ readVerificationKeySource extractHash = \case
VksKeyHashFile vKeyOrHashOrFile ->
L.KeyHashObj . extractHash <$> readVerificationKeyOrHashOrTextEnvFile vKeyOrHashOrFile

-- | Reads the script in cardano-api's newest era, so every script language decodes.
-- Returns only the era-independent script hash.
readAnyScriptHash :: MonadIO m => ScriptFile -> m ScriptHash
readAnyScriptHash scriptFile =
case maxBound of
Exp.Some (era :: Exp.Era era) ->
Exp.obtainCommonConstraints era $
fromShelleyScriptHash . Exp.hashAnyScript <$> readAnyScript @_ @era (unFile scriptFile)

readAnyScript
:: forall m era
. (MonadIO m, Exp.IsEra era)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{-# LANGUAGE OverloadedStrings #-}

module Test.Golden.Conway.Transaction.ReferenceScript where

import GHC.IO.Exception (ExitCode (ExitFailure))

import Test.Cardano.CLI.Util

import Hedgehog (Property, (===))
import Hedgehog.Extras.Test qualified as H

-- A reference script in a Plutus language the era does not support must fail
-- the command instead of being silently dropped from the output.

-- | Execute me with:
-- @cabal test cardano-cli-golden --test-options '-p "/golden conway build raw reference script unsupported language/"'@
hprop_golden_conway_build_raw_reference_script_unsupported_language :: Property
hprop_golden_conway_build_raw_reference_script_unsupported_language =
watchdogProp . propertyOnce . H.moduleWorkspace "tmp" $ \tempDir -> do
outFile <- noteTempFile tempDir "out.json"

(exitCode, _stdout, stderr) <-
execDetailCardanoCLI
[ "conway"
, "transaction"
, "build-raw"
, "--tx-in"
, "f62cd7bc15d8c6d2c8519fb8d13c57c0157ab6bab50af62bc63706feb966393d#0"
, "--tx-out"
, "addr_test1qpmxr8d8jcl25kyz2tz9a9sxv7jxglhddyf475045y8j3zxjcg9vquzkljyfn3rasfwwlkwu7hhm59gzxmsyxf3w9dps8832xh+5000000"
, "--tx-out-reference-script-file"
, "test/cardano-cli-golden/files/input/dijkstra/plutus/v4-always-succeeds.plutus"
, "--fee"
, "166777"
, "--out-file"
, outFile
]

exitCode === ExitFailure 1

H.diffVsGoldenFileExcludeTrace
stderr
"test/cardano-cli-golden/files/golden/conway/transaction/build-raw-reference-script-unsupported-language.out"
Loading
Loading