Skip to content

cardano-testnet | Add test submitting transaction with supplemental datums using cardano-api #6174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
19 changes: 9 additions & 10 deletions cardano-node/src/Cardano/Node/Protocol/Shelley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ readLeaderCredentialsSingleton
shelleyKESFile = Just kesFile
} = do
vrfSKey <-
firstExceptT FileError (newExceptT $ readFileTextEnvelope (AsSigningKey AsVrfKey) (File vrfFile))
firstExceptT FileError (newExceptT $ readFileTextEnvelope (File vrfFile))

(opCert, kesSKey) <- opCertKesKeyCheck (File kesFile) (File opCertFile)

Expand All @@ -172,9 +172,9 @@ opCertKesKeyCheck
-> ExceptT PraosLeaderCredentialsError IO (OperationalCertificate, SigningKey KesKey)
opCertKesKeyCheck kesFile certFile = do
opCert <-
firstExceptT FileError (newExceptT $ readFileTextEnvelope AsOperationalCertificate certFile)
firstExceptT FileError (newExceptT $ readFileTextEnvelope certFile)
kesSKey <-
firstExceptT FileError (newExceptT $ readFileTextEnvelope (AsSigningKey AsKesKey) kesFile)
firstExceptT FileError (newExceptT $ readFileTextEnvelope kesFile)
let opCertSpecifiedKesKeyhash = verificationKeyHash $ getHotKey opCert
suppliedKesKeyHash = verificationKeyHash $ getVerificationKey kesSKey
-- Specified KES key in operational certificate should match the one
Expand All @@ -201,9 +201,9 @@ readLeaderCredentialsBulk ProtocolFilepaths { shelleyBulkCredsFile = mfp } =
-> ExceptT PraosLeaderCredentialsError IO (ShelleyLeaderCredentials StandardCrypto)
parseShelleyCredentials ShelleyCredentials { scCert, scVrf, scKes } = do
mkPraosLeaderCredentials
<$> parseEnvelope AsOperationalCertificate scCert
<*> parseEnvelope (AsSigningKey AsVrfKey) scVrf
<*> parseEnvelope (AsSigningKey AsKesKey) scKes
<$> parseEnvelope scCert
<*> parseEnvelope scVrf
<*> parseEnvelope scKes

readBulkFile
:: Maybe FilePath
Expand Down Expand Up @@ -246,12 +246,11 @@ mkPraosLeaderCredentials

parseEnvelope ::
HasTextEnvelope a
=> AsType a
-> (TextEnvelope, String)
=> (TextEnvelope, String)
-> ExceptT PraosLeaderCredentialsError IO a
parseEnvelope as (te, loc) =
parseEnvelope (te, loc) =
firstExceptT (FileError . Api.FileError loc) . hoistEither $
deserialiseFromTextEnvelope as te
deserialiseFromTextEnvelope te


------------------------------------------------------------------------------
Expand Down
3 changes: 2 additions & 1 deletion cardano-testnet/cardano-testnet.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ test-suite cardano-testnet-test

main-is: cardano-testnet-test.hs

other-modules: Cardano.Testnet.Test.Cli.Conway.StakeSnapshot
other-modules: Cardano.Testnet.Test.Api.TxSupplementalDatum
Cardano.Testnet.Test.Cli.Conway.StakeSnapshot
Cardano.Testnet.Test.Cli.KesPeriodInfo
Cardano.Testnet.Test.Cli.LeadershipSchedule
Cardano.Testnet.Test.Cli.Query
Expand Down
13 changes: 12 additions & 1 deletion cardano-testnet/src/Testnet/Components/Query.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module Testnet.Components.Query
, checkDRepsNumber
, checkDRepState
, assertNewEpochState
, getProtocolParams
, getGovActionLifetime
, getKeyDeposit
, getDelegationState
Expand All @@ -44,7 +45,8 @@ module Testnet.Components.Query

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

import Cardano.Crypto.Hash (hashToStringAsHex)
Expand Down Expand Up @@ -558,6 +560,15 @@ assertNewEpochState epochStateView sbe maxWait lens expected = withFrozenCallSta
Refl <- H.leftFail $ assertErasEqual sbe actualEra
pure $ newEpochState ^. lens

-- | Return current protocol parameters from the governance state
getProtocolParams :: (H.MonadAssertion m, MonadTest m, MonadIO m)
=> EpochStateView
-> ConwayEraOnwards era
-> m (LedgerProtocolParameters era)
getProtocolParams epochStateView ceo = conwayEraOnwardsConstraints ceo $ do
govState :: ConwayGovState era <- getGovState epochStateView ceo
pure . LedgerProtocolParameters $ govState ^. L.cgsCurPParamsL


-- | Obtains the @govActionLifetime@ from the protocol parameters.
-- The @govActionLifetime@ or governance action maximum lifetime in epochs is
Expand Down
22 changes: 18 additions & 4 deletions cardano-testnet/src/Testnet/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module Testnet.Types
, testnetSprockets
, TestnetNode(..)
, nodeSocketPath
, node0ConnectionInfo
, isTestnetNodeSpo
, SpoNodeKeys(..)
, Delegator(..)
Expand Down Expand Up @@ -62,7 +63,6 @@ import Data.List (intercalate)
import Data.Maybe
import Data.MonoTraversable (Element, MonoFunctor (..))
import Data.Text (Text)
import Data.Time.Clock (UTCTime)
import GHC.Exts (IsString (..))
import GHC.Generics (Generic)
import qualified GHC.IO.Handle as IO
Expand All @@ -73,7 +73,9 @@ import qualified System.Process as IO

import Testnet.Start.Types

import Hedgehog (MonadTest)
import qualified Hedgehog as H
import qualified Hedgehog.Extras as H
import qualified Hedgehog.Extras.Stock as H
import Hedgehog.Extras.Stock.IO.Network.Sprocket (Sprocket (..))

Expand Down Expand Up @@ -115,7 +117,7 @@ data SKey k
data TestnetRuntime = TestnetRuntime
{ configurationFile :: !(NodeConfigFile In)
, shelleyGenesisFile :: !FilePath
, testnetMagic :: !Int
, testnetMagic :: !Int -- TODO change to Word32
, testnetNodes :: ![TestnetNode]
, wallets :: ![PaymentKeyInfo]
, delegators :: ![Delegator]
Expand Down Expand Up @@ -148,6 +150,18 @@ isTestnetNodeSpo = isJust . poolKeys
nodeSocketPath :: TestnetNode -> SocketPath
nodeSocketPath = File . H.sprocketSystemName . nodeSprocket

-- | Connection data for the first node in the testnet
node0ConnectionInfo :: MonadTest m => TestnetRuntime -> m LocalNodeConnectInfo
node0ConnectionInfo TestnetRuntime{testnetMagic, testnetNodes} = do
case testnetNodes of
[] -> H.note_ "There are no nodes in the testnet" >> H.failure
node0:_ -> do
pure LocalNodeConnectInfo
{ localNodeSocketPath= nodeSocketPath node0
, localNodeNetworkId=Testnet (NetworkMagic $ fromIntegral testnetMagic)
, localConsensusModeParams=CardanoModeParams $ EpochSlots 21600}


data SpoNodeKeys = SpoNodeKeys
{ poolNodeKeysCold :: KeyPair StakePoolKey
, poolNodeKeysVrf :: KeyPair VrfKey
Expand Down Expand Up @@ -187,14 +201,14 @@ getStartTime
=> HasCallStack
=> FilePath
-> TestnetRuntime
-> m UTCTime
-> m SystemStart
getStartTime tempRootPath TestnetRuntime{configurationFile} = withFrozenCallStack $ H.evalEither <=< H.evalIO . runExceptT $ do
byronGenesisFile <-
decodeNodeConfiguration configurationFile >>= \case
NodeProtocolConfigurationCardano NodeByronProtocolConfiguration{npcByronGenesisFile} _ _ _ _ _ ->
pure $ unGenesisFile npcByronGenesisFile
let byronGenesisFilePath = tempRootPath </> byronGenesisFile
G.gdStartTime . G.configGenesisData <$> decodeGenesisFile byronGenesisFilePath
SystemStart . G.gdStartTime . G.configGenesisData <$> decodeGenesisFile byronGenesisFilePath
where
decodeNodeConfiguration :: File NodeConfig In -> ExceptT String IO NodeProtocolConfiguration
decodeNodeConfiguration (File file) = do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Cardano.Testnet.Test.Api.TxSupplementalDatum
( hprop_tx_supp_datum
)
where

import Cardano.Api
import qualified Cardano.Api.Ledger as L
import qualified Cardano.Api.Network as Net
import qualified Cardano.Api.Network as Net.Tx
import Cardano.Api.Shelley

import Cardano.Testnet

import Prelude

import Control.Monad
import Data.Default.Class
import qualified Data.Map.Strict as M
import Data.Proxy
import Data.Set (Set)
import GHC.Exts (IsList (..))
import GHC.Stack
import Lens.Micro

import Testnet.Components.Query
import Testnet.Property.Util (integrationRetryWorkspace)
import Testnet.Types

import Hedgehog
import qualified Hedgehog as H
import qualified Hedgehog.Extras.Test.Base as H
import qualified Hedgehog.Extras.Test.TestWatchdog as H

hprop_tx_supp_datum :: Property
hprop_tx_supp_datum = integrationRetryWorkspace 2 "api-tx-supp-dat" $ \tempAbsBasePath' -> H.runWithDefaultWatchdog_ $ do
conf@Conf{tempAbsPath} <- mkConf tempAbsBasePath'
let tempAbsPath' = unTmpAbsPath tempAbsPath

let ceo = ConwayEraOnwardsConway
beo = convert ceo
sbe = convert ceo
eraProxy = proxyToAsType Proxy
options = def{cardanoNodeEra = AnyShelleyBasedEra sbe}

tr@TestnetRuntime
{ configurationFile
, testnetNodes = node0 : _
, wallets = wallet0@(PaymentKeyInfo _ addrTxt0) : wallet1 : _
} <-
cardanoTestnetDefault options def conf

systemStart <- H.noteShowM $ getStartTime tempAbsPath' tr
epochStateView <- getEpochStateView configurationFile (nodeSocketPath node0)
connectionInfo <- node0ConnectionInfo tr
pparams <- getProtocolParams epochStateView ceo

-- prepare tx inputs and output address
H.noteShow_ addrTxt0
addr0 <- H.nothingFail $ deserialiseAddress (AsAddressInEra eraProxy) addrTxt0

let (PaymentKeyInfo _ addrTxt1) = wallet1
H.noteShow_ addrTxt1
addr1 <- H.nothingFail $ deserialiseAddress (AsAddressInEra eraProxy) addrTxt1

-- read key witnesses
[wit0, wit1] :: [ShelleyWitnessSigningKey] <-
forM [wallet0, wallet1] $ \wallet ->
H.leftFailM . H.evalIO $
readFileTextEnvelopeAnyOf
[FromSomeType (proxyToAsType Proxy) WitnessGenesisUTxOKey]
(signingKey $ paymentKeyInfoPair wallet)

-- query node for era history
epochInfo <-
(H.leftFail <=< H.leftFailM) . H.evalIO $
executeLocalStateQueryExpr connectionInfo Net.VolatileTip $
fmap toLedgerEpochInfo <$> queryEraHistory

let scriptData1 = unsafeHashableScriptData $ ScriptDataBytes "HASH_1" -- hash
scriptData2 = unsafeHashableScriptData $ ScriptDataBytes "INLINE_1" -- inline
scriptData3 = unsafeHashableScriptData $ ScriptDataBytes "SUPPLEMENTAL_1" -- supplemental
-- 4e62677c3b9f3b247502efe39a85aadcc2f2d3a32aec544d62175ed86c57fe9b
H.noteShow_ $ hashScriptDataBytes scriptData1
-- c93bae5c7cb737e16eb224d1884e7fbe14dc038caf1b511e34a43e67d3eb9f63
H.noteShow_ $ hashScriptDataBytes scriptData2
-- 74ea77567269646d49e072bd83e701ff7e43574522ad90833bcfa554658c65bb
H.noteShow_ $ hashScriptDataBytes scriptData3
let txDatum1 =
TxOutDatumHash
(convert beo)
(hashScriptDataBytes scriptData1)
txDatum2 = TxOutDatumInline beo scriptData2
txDatum3 = TxOutSupplementalDatum (convert beo) scriptData3

tx1Utxo <- do
txIn <- findLargestUtxoForPaymentKey epochStateView sbe wallet0

-- prepare txout
let txOutValue = lovelaceToTxOutValue sbe 100_000_000
txOuts =
[ TxOut addr1 txOutValue txDatum1 ReferenceScriptNone
, TxOut addr1 txOutValue txDatum2 ReferenceScriptNone
, TxOut addr1 txOutValue txDatum3 ReferenceScriptNone
]

-- build a transaction
content =
defaultTxBodyContent sbe
& setTxIns [(txIn, pure $ KeyWitness KeyWitnessForSpending)]
& setTxOuts txOuts
& setTxProtocolParams (pure $ pure pparams)

utxo <- UTxO <$> findAllUtxos epochStateView sbe


BalancedTxBody _ txBody@(ShelleyTxBody _ lbody _ (TxBodyScriptData _ (L.TxDats' datums) _) _ _) _ fee <-
H.leftFail $
makeTransactionBodyAutoBalance
sbe
systemStart
epochInfo
pparams
mempty
mempty
mempty
utxo
content
addr0
Nothing -- keys override
H.noteShow_ fee

H.noteShowPretty_ lbody

lbody ^. L.scriptIntegrityHashTxBodyL /== L.SNothing

let bodyScriptData = fromList . map fromAlonzoData $ M.elems datums :: Set HashableScriptData

-- Only supplemental datum are included here
[ scriptData3 ] === bodyScriptData

let tx = signShelleyTransaction sbe txBody [wit0]
txId <- H.noteShow . getTxId $ getTxBody tx

H.noteShowPretty_ tx

submitTx sbe connectionInfo tx

-- wait till transaction gets included in the block
_ <- waitForBlocks epochStateView 1

-- test if it's in UTxO set
utxo1 <- findAllUtxos epochStateView sbe
let txUtxo = M.filterWithKey (\(TxIn txId' _) _ -> txId == txId') utxo1
-- H.noteShowPretty_ txUtxo
(length txOuts + 1) === length txUtxo

let chainTxOuts =
reverse
. drop 1
. reverse
. map snd
. toList
$ M.filterWithKey (\(TxIn txId' _) _ -> txId == txId') utxo1

(toCtxUTxOTxOut <$> txOuts) === chainTxOuts

pure txUtxo

do
let txDatum3' = TxOutDatumHash (convert beo) (hashScriptDataBytes scriptData3)
[(txIn1, _)] <- H.noteShowPretty $ filter (\(_, TxOut _ _ datum _) -> datum == txDatum1) $ toList tx1Utxo
[(txIn2, _)] <- H.noteShowPretty $ filter (\(_, TxOut _ _ datum _) -> datum == txDatum2) $ toList tx1Utxo
[(txIn3, _)] <- H.noteShowPretty $ filter (\(_, TxOut _ _ datum _) -> datum == txDatum3') $ toList tx1Utxo

let scriptData4 = unsafeHashableScriptData $ ScriptDataBytes "SUPPLEMENTAL_2"
txDatum = TxOutSupplementalDatum (convert beo) scriptData4
txOutValue = lovelaceToTxOutValue sbe 99_999_500
txOut = TxOut addr0 txOutValue txDatum ReferenceScriptNone
txInsReference = TxInsReference beo [txIn1, txIn3] $ pure [scriptData1, scriptData3, scriptData4]

let content =
defaultTxBodyContent sbe
& setTxIns [(txIn2, pure $ KeyWitness KeyWitnessForSpending)]
& setTxInsReference txInsReference
& setTxFee (TxFeeExplicit sbe 500)
& setTxOuts [txOut]
& setTxProtocolParams (pure $ pure pparams)

txBody@(ShelleyTxBody _ lbody _ (TxBodyScriptData _ (L.TxDats' datums) _) _ _) <-
H.leftFail $ createTransactionBody sbe content

let bodyScriptData = fromList . map fromAlonzoData $ M.elems datums :: Set HashableScriptData
[scriptData1, scriptData3, scriptData4] === bodyScriptData

H.noteShowPretty_ txBody

lbody ^. L.scriptIntegrityHashTxBodyL /== L.SNothing

let tx = signShelleyTransaction sbe txBody [wit1]
H.noteShowPretty_ tx
txId <- H.noteShow . getTxId $ getTxBody tx

submitTx sbe connectionInfo tx

-- wait till transaction gets included in the block
_ <- waitForBlocks epochStateView 1

-- test if it's in UTxO set
utxo1 <- findAllUtxos epochStateView sbe
let txUtxo = M.filterWithKey (\(TxIn txId' _) _ -> txId == txId') utxo1
[toCtxUTxOTxOut txOut] === M.elems txUtxo

H.failure

submitTx
:: MonadTest m
=> MonadIO m
=> HasCallStack
=> ShelleyBasedEra era
-> LocalNodeConnectInfo
-> Tx era
-> m ()
submitTx sbe connectionInfo tx =
withFrozenCallStack $
H.evalIO (submitTxToNodeLocal connectionInfo (TxInMode sbe tx)) >>= \case
Net.Tx.SubmitFail reason -> H.noteShowPretty_ reason >> H.failure
Net.Tx.SubmitSuccess -> H.success
Loading
Loading