Skip to content

Commit

Permalink
Add anchor hash checks to transaction build
Browse files Browse the repository at this point in the history
  • Loading branch information
palas committed Oct 24, 2024
1 parent 2570f88 commit 4da36e3
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cardano-cli/cardano-cli.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ library
Cardano.CLI.EraBased.Commands.StakePool
Cardano.CLI.EraBased.Commands.TextView
Cardano.CLI.EraBased.Commands.Transaction
Cardano.CLI.EraBased.HashChecking
Cardano.CLI.EraBased.Options.Common
Cardano.CLI.EraBased.Options.Genesis
Cardano.CLI.EraBased.Options.Governance
Expand Down Expand Up @@ -207,6 +208,7 @@ library
cardano-crypto-wrapper ^>=1.5.1,
cardano-data >=1.1,
cardano-git-rev ^>=0.2.2,
cardano-ledger-api,
cardano-ping ^>=0.5,
cardano-prelude,
cardano-slotting ^>=0.2.0.0,
Expand Down
123 changes: 123 additions & 0 deletions cardano-cli/src/Cardano/CLI/EraBased/HashChecking.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{-# LANGUAGE ScopedTypeVariables #-}

module Cardano.CLI.EraBased.HashChecking
( checkCertificateHashes
, checkVotingProcedureHashes
, checkProposalHashes
)
where

import Cardano.Api (Certificate (..), ExceptT, firstExceptT)
import qualified Cardano.Api.Ledger as L
import qualified Cardano.Api.Shelley as Shelley

import Cardano.CLI.Run.Hash (carryHashChecks)
import Cardano.CLI.Types.Common (MustCheckHash (..), PotentiallyCheckedAnchor (..))
import Cardano.CLI.Types.Errors.TxCmdError (TxCmdError (..))
import qualified Cardano.Ledger.Api.Governance as L

import Control.Monad (forM_)
import Control.Monad.Trans.Except.Extra (left)

-- | Check the hash of the anchor data against the hash in the anchor
checkAnchorMetadataHash :: L.Anchor L.StandardCrypto -> ExceptT TxCmdError IO ()
checkAnchorMetadataHash anchor =
firstExceptT (TxCmdHashCheckError $ L.anchorUrl anchor) $
carryHashChecks
( PotentiallyCheckedAnchor
{ pcaMustCheck = CheckHash
, pcaAnchor = anchor
}
)

-- | Find references to anchor data and check the hashes are valid
-- and they match the linked data.
checkCertificateHashes :: Certificate era -> ExceptT TxCmdError IO ()
checkCertificateHashes c =
case c of
ShelleyRelatedCertificate _ shelleyCert ->
case shelleyCert of
L.ShelleyTxCertDelegCert shelleyDelegCert ->
case shelleyDelegCert of
L.ShelleyRegCert _ -> return ()
L.ShelleyUnRegCert _ -> return ()
L.ShelleyDelegCert _ _ -> return ()
L.ShelleyTxCertPool shelleyPoolCert ->
case shelleyPoolCert of
L.RegPool poolParams -> forM_ (L.ppMetadata poolParams) checkPoolMetadataHash
L.RetirePool _ _ -> return ()
L.ShelleyTxCertGenesisDeleg _ -> return ()
L.ShelleyTxCertMir _ -> return ()
ConwayCertificate ceo conwayCert ->
Shelley.conwayEraOnwardsConstraints ceo $
case conwayCert of
L.ConwayTxCertDeleg _ -> return ()
L.ConwayTxCertPool conwayPoolCert ->
case conwayPoolCert of
L.RegPool poolParams -> forM_ (L.ppMetadata poolParams) checkPoolMetadataHash
L.RetirePool _ _ -> return ()
L.ConwayTxCertGov govCert ->
case govCert of
L.ConwayRegDRep _ _ mAnchor -> forM_ mAnchor checkAnchorMetadataHash
L.ConwayUnRegDRep _ _ -> return ()
L.ConwayUpdateDRep _ mAnchor -> forM_ mAnchor checkAnchorMetadataHash
L.ConwayAuthCommitteeHotKey _ _ -> return ()
L.ConwayResignCommitteeColdKey _ mAnchor -> forM_ mAnchor checkAnchorMetadataHash
where
checkPoolMetadataHash :: L.PoolMetadata -> ExceptT TxCmdError IO ()
checkPoolMetadataHash (L.PoolMetadata{L.pmUrl = url, L.pmHash = hashBytes}) = do
let mHash = L.hashFromBytes hashBytes
hash <- maybe (left $ TxCmdPoolMetadataHashError url) return mHash
let safeHash = L.unsafeMakeSafeHash hash
checkAnchorMetadataHash
( L.Anchor
{ L.anchorUrl = url
, L.anchorDataHash = safeHash
}
)

-- | Find references to anchor data in voting procedures and check the hashes are valid
-- and they match the linked data.
checkVotingProcedureHashes
:: Shelley.ShelleyBasedEra era -> Shelley.VotingProcedures era -> ExceptT TxCmdError IO ()
checkVotingProcedureHashes eon (Shelley.VotingProcedures (L.VotingProcedures voterMap)) =
Shelley.shelleyBasedEraConstraints eon $
forM_
voterMap
( \vpMap ->
forM_
vpMap
( \(L.VotingProcedure _ mAnchor) ->
forM_ mAnchor checkAnchorMetadataHash
)
)

-- | Find references to anchor data in proposals and check the hashes are valid
-- and they match the linked data.
checkProposalHashes
:: forall era. Shelley.ShelleyBasedEra era -> Shelley.Proposal era -> ExceptT TxCmdError IO ()
checkProposalHashes
eon
( Shelley.Proposal
( L.ProposalProcedure
{ L.pProcGovAction = govAction
, L.pProcAnchor = anchor
}
)
) =
Shelley.shelleyBasedEraConstraints eon $ do
checkAnchorMetadataHash anchor
checkGovActionHashes govAction
where
checkGovActionHashes
:: L.GovAction (Shelley.ShelleyLedgerEra era) -> ExceptT TxCmdError IO ()
checkGovActionHashes govAction' =
Shelley.shelleyBasedEraConstraints eon $
case govAction' of
L.ParameterChange{} -> return ()
L.HardForkInitiation _ _ -> return ()
L.TreasuryWithdrawals _ _ -> return ()
L.NoConfidence _ -> return ()
L.UpdateCommittee{} -> return ()
L.NewConstitution _ constitution -> checkAnchorMetadataHash $ L.constitutionAnchor constitution
L.InfoAction -> return ()
10 changes: 10 additions & 0 deletions cardano-cli/src/Cardano/CLI/EraBased/Run/Transaction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import Cardano.Api.Shelley

import qualified Cardano.Binary as CBOR
import qualified Cardano.CLI.EraBased.Commands.Transaction as Cmd
import Cardano.CLI.EraBased.HashChecking (checkCertificateHashes, checkProposalHashes,
checkVotingProcedureHashes)
import Cardano.CLI.EraBased.Run.Genesis.Common (readProtocolParameters)
import Cardano.CLI.EraBased.Run.Query
import Cardano.CLI.Read
Expand All @@ -66,6 +68,7 @@ import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Containers.ListUtils (nubOrd)
import Data.Data ((:~:) (..))
import Data.Foldable (forM_)
import qualified Data.Foldable as Foldable
import Data.Function ((&))
import qualified Data.List as List
Expand Down Expand Up @@ -162,6 +165,9 @@ runTransactionBuildCmd
)
| (CertificateFile certFile, mSwit) <- certFilesAndMaybeScriptWits
]

forM_ certsAndMaybeScriptWits (checkCertificateHashes . fst)

withdrawalsAndMaybeScriptWits <-
firstExceptT TxCmdScriptWitnessError $
readScriptWitnessFilesTuple eon withdrawals
Expand Down Expand Up @@ -193,11 +199,15 @@ runTransactionBuildCmd
(\w -> firstExceptT TxCmdVoteError $ ExceptT (readVotingProceduresFiles w voteFiles))
era'

forM_ votingProceduresAndMaybeScriptWits (checkVotingProcedureHashes eon . fst)

proposals <-
newExceptT $
first TxCmdProposalError
<$> readTxGovernanceActions eon proposalFiles

forM_ proposals (checkProposalHashes eon . fst)

-- the same collateral input can be used for several plutus scripts
let filteredTxinsc = nubOrd txinsc

Expand Down
9 changes: 9 additions & 0 deletions cardano-cli/src/Cardano/CLI/Types/Errors/TxCmdError.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ module Cardano.CLI.Types.Errors.TxCmdError
where

import Cardano.Api
import qualified Cardano.Api.Ledger as L
import Cardano.Api.Shelley

import Cardano.CLI.Read
import Cardano.CLI.Types.Common
import Cardano.CLI.Types.Errors.BootstrapWitnessError
import Cardano.CLI.Types.Errors.HashCmdError (HashCheckError)
import Cardano.CLI.Types.Errors.NodeEraMismatchError
import qualified Cardano.CLI.Types.Errors.NodeEraMismatchError as NEM
import Cardano.CLI.Types.Errors.ProtocolParamsError
Expand All @@ -29,6 +31,7 @@ import Cardano.CLI.Types.TxFeature
import qualified Cardano.Prelude as List
import Ouroboros.Consensus.Cardano.Block (EraMismatch (..))

import Control.Exception (displayException)
import Data.Text (Text)

{- HLINT ignore "Use let" -}
Expand Down Expand Up @@ -84,6 +87,8 @@ data TxCmdError
| TxCmdProtocolParamsConverstionError ProtocolParametersConversionError
| forall era. TxCmdTxGovDuplicateVotes (TxGovDuplicateVotes era)
| forall era. TxCmdFeeEstimationError (TxFeeEstimationError era)
| TxCmdPoolMetadataHashError L.Url
| TxCmdHashCheckError L.Url HashCheckError

renderTxCmdError :: TxCmdError -> Doc ann
renderTxCmdError = \case
Expand Down Expand Up @@ -217,6 +222,10 @@ renderTxCmdError = \case
prettyError e
TxCmdFeeEstimationError e ->
prettyError e
TxCmdPoolMetadataHashError url ->
"Hash of the pool metadata file is not valid. Url:" <+> pretty (L.urlToText url)
TxCmdHashCheckError url e ->
"Hash of the file is not valid. Url:" <+> pretty (L.urlToText url) <+> pretty (displayException e)

prettyPolicyIdList :: [PolicyId] -> Doc ann
prettyPolicyIdList =
Expand Down

0 comments on commit 4da36e3

Please sign in to comment.