Skip to content

Commit

Permalink
Merge branch 'master' into neilmayhew/orphan-golden-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
neilmayhew authored Apr 11, 2024
2 parents 40df9b8 + a152b00 commit 0e68431
Show file tree
Hide file tree
Showing 6 changed files with 410 additions and 62 deletions.
8 changes: 5 additions & 3 deletions cardano-testnet/cardano-testnet.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ library
, scientific
, si-timers
, stm
, tasty
, tasty ^>= 1.5
, tasty-expected-failure
, tasty-hedgehog
, text
Expand All @@ -89,6 +89,7 @@ library
Testnet.Start.Byron
Testnet.Start.Types
Testnet.Components.Configuration
Testnet.Components.DReps
Testnet.Components.SPO
Testnet.Components.Query
Testnet.Defaults
Expand Down Expand Up @@ -152,7 +153,7 @@ test-suite cardano-testnet-golden
, hedgehog-extras
, process
, regex-compat
, tasty
, tasty ^>= 1.5
, tasty-hedgehog
, text

Expand Down Expand Up @@ -185,6 +186,7 @@ test-suite cardano-testnet-test
Cardano.Testnet.Test.LedgerEvents.Gov.ProposeNewConstitution
Cardano.Testnet.Test.LedgerEvents.Gov.InfoAction
Cardano.Testnet.Test.LedgerEvents.Gov.ProposeNewConstitutionSPO
Cardano.Testnet.Test.LedgerEvents.Gov.DRepDeposits
Cardano.Testnet.Test.LedgerEvents.SanityCheck
Cardano.Testnet.Test.LedgerEvents.TreasuryGrowth

Expand Down Expand Up @@ -220,7 +222,7 @@ test-suite cardano-testnet-test
, mtl
, process
, regex-compat
, tasty
, tasty ^>= 1.5
, text
, time
, transformers
Expand Down
220 changes: 220 additions & 0 deletions cardano-testnet/src/Testnet/Components/DReps.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}

module Testnet.Components.DReps
( generateDRepKeyPair
, generateRegistrationCertificate
, createDRepRegistrationTxBody
, signTx
, submitTx
, failToSubmitTx
) where

import Cardano.Api (AnyCardanoEra (..), FileDirection (In), ShelleyBasedEra (..),
renderTxIn)

import Cardano.CLI.Types.Common (File (..))

import Prelude

import Control.Monad (void)
import Control.Monad.Catch (MonadCatch)
import Control.Monad.IO.Class (MonadIO)
import qualified Data.Text as Text
import GHC.IO.Exception (ExitCode (..))
import GHC.Stack (HasCallStack)
import qualified GHC.Stack as GHC
import System.FilePath ((</>))

import Testnet.Components.Query (EpochStateView, findLargestUtxoForPaymentKey)
import qualified Testnet.Process.Run as H
import Testnet.Runtime (PaymentKeyInfo (paymentKeyInfoAddr), PaymentKeyPair (..))
import Testnet.Start.Types (anyEraToString)

import Hedgehog (MonadTest)
import qualified Hedgehog.Extras as H

-- | Generates a key pair for a decentralized representative (DRep) using @cardano-cli@.
--
-- The function takes three parameters:
--
-- * 'execConfig': Specifies the CLI execution configuration.
-- * 'work': Base directory path where keys will be stored.
-- * 'prefix': Name for the subfolder that will be created under 'work' folder to store the output keys.
--
-- Returns the generated 'PaymentKeyPair' containing paths to the verification and
-- signing key files.
generateDRepKeyPair :: (MonadTest m, MonadCatch m, MonadIO m, HasCallStack)
=> H.ExecConfig
-> FilePath
-> String
-> m PaymentKeyPair
generateDRepKeyPair execConfig work prefix = do
baseDir <- H.createDirectoryIfMissing $ work </> prefix
let dRepKeyPair = PaymentKeyPair { paymentVKey = baseDir </> "verification.vkey"
, paymentSKey = baseDir </> "signature.skey"
}
void $ H.execCli' execConfig [ "conway", "governance", "drep", "key-gen"
, "--verification-key-file", paymentVKey dRepKeyPair
, "--signing-key-file", paymentSKey dRepKeyPair
]
return dRepKeyPair

-- DRep registration certificate generation

data DRepRegistrationCertificate

-- | Generates a registration certificate for a decentralized representative (DRep)
-- using @cardano-cli@.
--
-- The function takes five parameters:
--
-- * 'execConfig': Specifies the CLI execution configuration.
-- * 'work': Base directory path where the certificate file will be stored.
-- * 'prefix': Prefix for the output certificate file name. The extension will be @.regcert@.
-- * 'drepKeyPair': Payment key pair associated with the DRep. Can be generated using
-- 'generateDRepKeyPair'.
-- * 'depositAmount': Deposit amount required for DRep registration. The right amount
-- can be obtained using 'getMinDRepDeposit'.
--
-- Returns the generated @File DRepRegistrationCertificate In@ file path to the
-- registration certificate.
generateRegistrationCertificate
:: (MonadTest m, MonadCatch m, MonadIO m, HasCallStack)
=> H.ExecConfig
-> FilePath
-> String
-> PaymentKeyPair
-> Integer
-> m (File DRepRegistrationCertificate In)
generateRegistrationCertificate execConfig work prefix drepKeyPair depositAmount = do
let dRepRegistrationCertificate = File (work </> prefix <> ".regcert")
void $ H.execCli' execConfig [ "conway", "governance", "drep", "registration-certificate"
, "--drep-verification-key-file", paymentVKey drepKeyPair
, "--key-reg-deposit-amt", show @Integer depositAmount
, "--out-file", unFile dRepRegistrationCertificate
]
return dRepRegistrationCertificate

-- DRep registration transaction composition (without signing)

data TxBody

-- | Composes a decentralized representative (DRep) registration transaction body
-- (without signing) using @cardano-cli@.
--
-- This function takes seven parameters:
--
-- * 'execConfig': Specifies the CLI execution configuration.
-- * 'epochStateView': Current epoch state view for transaction building. It can be obtained
-- using the 'getEpochStateView' function.
-- * 'sbe': The Shelley-based era (e.g., 'ShelleyEra') in which the transaction will be constructed.
-- * 'work': Base directory path where the transaction body file will be stored.
-- * 'prefix': Prefix for the output transaction body file name. The extension will be @.txbody@.
-- * 'drepRegCert': The file name of the registration certificate for the DRep, obtained using
-- 'generateRegistrationCertificate'.
-- * 'wallet': Payment key information associated with the transaction,
-- as returned by 'cardanoTestnetDefault'.
--
-- Returns the generated @File TxBody In@ file path to the transaction body.
createDRepRegistrationTxBody
:: (H.MonadAssertion m, MonadTest m, MonadCatch m, MonadIO m)
=> H.ExecConfig
-> EpochStateView
-> ShelleyBasedEra era
-> FilePath
-> String
-> File DRepRegistrationCertificate In
-> PaymentKeyInfo
-> m (File TxBody In)
createDRepRegistrationTxBody execConfig epochStateView sbe work prefix drepRegCert wallet = do
let dRepRegistrationTxBody = File (work </> prefix <> ".txbody")
walletLargestUTXO <- findLargestUtxoForPaymentKey epochStateView sbe wallet
void $ H.execCli' execConfig
[ "conway", "transaction", "build"
, "--change-address", Text.unpack $ paymentKeyInfoAddr wallet
, "--tx-in", Text.unpack $ renderTxIn walletLargestUTXO
, "--certificate-file", unFile drepRegCert
, "--witness-override", show @Int 2
, "--out-file", unFile dRepRegistrationTxBody
]
return dRepRegistrationTxBody

-- Transaction signing

data SignedTx

-- | Calls @cardano-cli@ to signs a transaction body using the specified key pairs.
--
-- This function takes five parameters:
--
-- * 'execConfig': Specifies the CLI execution configuration.
-- * 'cEra': Specifies the current Cardano era.
-- * 'work': Base directory path where the signed transaction file will be stored.
-- * 'prefix': Prefix for the output signed transaction file name. The extension will be @.tx@.
-- * 'txBody': Transaction body to be signed, obtained using 'createDRepRegistrationTxBody' or similar.
-- * 'signatoryKeyPairs': List of payment key pairs used for signing the transaction.
--
-- Returns the generated @File SignedTx In@ file path to the signed transaction file.
signTx :: (MonadTest m, MonadCatch m, MonadIO m)
=> H.ExecConfig
-> AnyCardanoEra
-> FilePath
-> String
-> File TxBody In
-> [PaymentKeyPair]
-> m (File SignedTx In)
signTx execConfig cEra work prefix txBody signatoryKeyPairs = do
let signedTx = File (work </> prefix <> ".tx")
void $ H.execCli' execConfig $
[ anyEraToString cEra, "transaction", "sign"
, "--tx-body-file", unFile txBody
] ++ (concat [["--signing-key-file", paymentSKey kp] | kp <- signatoryKeyPairs]) ++
[ "--out-file", unFile signedTx
]
return signedTx

-- | Submits a signed transaction using @cardano-cli@.
--
-- This function takes two parameters:
--
-- * 'execConfig': Specifies the CLI execution configuration.
-- * 'cEra': Specifies the current Cardano era.
-- * 'signedTx': Signed transaction to be submitted, obtained using 'signTx'.
submitTx
:: (MonadTest m, MonadCatch m, MonadIO m)
=> H.ExecConfig
-> AnyCardanoEra
-> File SignedTx In
-> m ()
submitTx execConfig cEra signedTx =
void $ H.execCli' execConfig
[ anyEraToString cEra, "transaction", "submit"
, "--tx-file", unFile signedTx
]

-- | Attempts to submit a transaction that is expected to fail using @cardano-cli@.
--
-- This function takes two parameters:
--
-- * 'execConfig': Specifies the CLI execution configuration.
-- * 'cEra': Specifies the current Cardano era.
-- * 'signedTx': Signed transaction to be submitted, obtained using 'signTx'.
--
-- If the submission fails (the expected behavior), the function succeeds.
-- If the submission succeeds unexpectedly, it raises a failure message that is
-- meant to be caught by @Hedgehog@.
failToSubmitTx
:: (MonadTest m, MonadCatch m, MonadIO m)
=> H.ExecConfig
-> AnyCardanoEra
-> File SignedTx In
-> m ()
failToSubmitTx execConfig cEra signedTx = GHC.withFrozenCallStack $ do
(exitCode, _, _) <- H.execFlexAny' execConfig "cardano-cli" "CARDANO_CLI"
[ anyEraToString cEra, "transaction", "submit"
, "--tx-file", unFile signedTx
]
case exitCode of
ExitSuccess -> H.failMessage GHC.callStack "Transaction submission was expected to fail but it succeeded"
_ -> return ()
72 changes: 39 additions & 33 deletions cardano-testnet/src/Testnet/Components/Query.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Testnet.Components.Query
( QueryTip
, EpochStateView
, checkDRepsNumber
, checkDRepState
, getEpochState
, getMinDRepDeposit
, queryTip
Expand All @@ -22,8 +23,7 @@ module Testnet.Components.Query
) where

import Cardano.Api as Api
import Cardano.Api.Ledger (StandardCrypto)
import qualified Cardano.Api.Ledger as L
import Cardano.Api.Ledger (Credential, DRepState, KeyRole (DRepRole), StandardCrypto)
import Cardano.Api.Shelley (ShelleyLedgerEra, fromShelleyTxIn, fromShelleyTxOut)

import Cardano.CLI.Types.Output
Expand Down Expand Up @@ -238,55 +238,62 @@ checkDRepsNumber ::
-> H.ExecConfig
-> Int
-> m ()
checkDRepsNumber sbe configurationFile socketPath execConfig expectedDRepsNb = do
QueryTipLocalStateOutput{mEpoch} <- P.execCliStdoutToJson execConfig [ "query", "tip" ]
currentEpoch <- H.evalMaybe mEpoch
let terminationEpoch = succ . succ $ currentEpoch
void $ H.evalMaybeM $ checkDRepsNumber' sbe configurationFile socketPath terminationEpoch expectedDRepsNb
checkDRepsNumber sbe configurationFile socketPath execConfig expectedDRepsNb =
checkDRepState sbe configurationFile socketPath execConfig
(\m -> if length m == expectedDRepsNb then Just () else Nothing)

-- | @checkDRepsNumber' config socket terminationEpoch n@
-- wait until @terminationEpoch@ for the number of DReps being @n@. If
-- this number is not attained before @terminationEpoch@, the test is failed.
-- So if you call this function, you are expecting the number of DReps to already
-- be @n@, or to be @n@ before @terminationEpoch@
checkDRepsNumber' ::
-- | @checkDRepState sbe configurationFile socketPath execConfig f@
-- This functions helps check properties about the DRep state.
-- It waits up to two epochs for the result of applying @f@ to the DRepState
-- to become 'Just'. If @f@ keeps returning 'Nothing' the test fails.
-- If @f@ returns 'Just', the contents of the 'Just' are returned.
checkDRepState ::
(HasCallStack, MonadCatch m, MonadIO m, MonadTest m)
=> ShelleyBasedEra ConwayEra -- ^ The era in which the test runs
-> NodeConfigFile In
-> SocketPath
-> EpochNo -- ^ The termination epoch: the constitution proposal must be found *before* this epoch
-> Int -- ^ The expected numbers of DReps. If this number is not reached until the termination epoch, this function fails the test.
-> m (Maybe [L.DRepState StandardCrypto]) -- ^ The DReps when the expected number of DReps was attained.
checkDRepsNumber' sbe nodeConfigFile socketPath maxEpoch expectedDRepsNb = do
result <- runExceptT $ foldEpochState nodeConfigFile socketPath QuickValidation maxEpoch Nothing
-> H.ExecConfig
-> (Map (Credential 'DRepRole StandardCrypto)
(DRepState StandardCrypto) -> Maybe a) -- ^ A function that checks whether the DRep state is correct or up to date
-- and potentially inspects it.
-> m a
checkDRepState sbe configurationFile socketPath execConfig f = do
QueryTipLocalStateOutput{mEpoch} <- P.execCliStdoutToJson execConfig [ "query", "tip" ]
currentEpoch <- H.evalMaybe mEpoch
let terminationEpoch = succ . succ $ currentEpoch
result <- runExceptT $ foldEpochState configurationFile socketPath QuickValidation terminationEpoch Nothing
$ \(AnyNewEpochState actualEra newEpochState) _slotNb _blockNb -> do
case testEquality sbe actualEra of
Just Refl -> do
let dreps = Map.elems $ shelleyBasedEraConstraints sbe newEpochState
^. L.nesEsL
. L.esLStateL
. L.lsCertStateL
. L.certVStateL
. L.vsDRepsL
if length dreps == expectedDRepsNb then do
put $ Just dreps
pure ConditionMet
else
pure ConditionNotMet
let dreps = shelleyBasedEraConstraints sbe newEpochState
^. L.nesEsL
. L.esLStateL
. L.lsCertStateL
. L.certVStateL
. L.vsDRepsL
case f dreps of
Nothing -> pure ConditionNotMet
Just a -> do put $ Just a
pure ConditionMet
Nothing -> do
error $ "Eras mismatch! expected: " <> show sbe <> ", actual: " <> show actualEra
case result of
Left (FoldBlocksApplyBlockError (TerminationEpochReached epochNo)) -> do
H.note_ $ unlines
[ "waitDRepsNumber: drep number did not become " <> show expectedDRepsNb <> " before termination epoch: " <> show epochNo
[ "checkDRepState: condition not met before termination epoch: " <> show epochNo
, "This is likely an error of this test." ]
H.failure
Left err -> do
H.note_ $ unlines
[ "waitDRepsNumber: could not reach termination epoch: " <> docToString (prettyError err)
[ "checkDRepState: could not reach termination epoch: " <> docToString (prettyError err)
, "This is probably an error unrelated to this test." ]
H.failure
Right (_, val) ->
Right (_, Nothing) -> do
H.note_ $ unlines
[ "checkDRepState: foldEpochState returned Nothing: "
, "This is probably an error related to foldEpochState." ]
H.failure
Right (_, Just val) ->
return val

-- | Obtain minimum deposit amount for DRep registration from node
Expand All @@ -304,4 +311,3 @@ getMinDRepDeposit execConfig = do
. _Integral

H.evalMaybe mMinDRepDeposit

Loading

0 comments on commit 0e68431

Please sign in to comment.