Skip to content

Commit

Permalink
Fix reading Plutus V2 cost models with 175 params in Babbage
Browse files Browse the repository at this point in the history
  • Loading branch information
carbolymer committed Jul 1, 2024
1 parent af84845 commit 1cdc3b3
Show file tree
Hide file tree
Showing 9 changed files with 1,727 additions and 2 deletions.
4 changes: 4 additions & 0 deletions cardano-api/cardano-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ library internal
, iproute
, memory
, microlens
, microlens-aeson
, mtl
, network
, optparse-applicative-fork
Expand Down Expand Up @@ -316,6 +317,7 @@ test-suite cardano-api-test
, cardano-crypto-class ^>= 2.1.2
, cardano-crypto-test ^>= 1.5
, cardano-crypto-tests ^>= 2.1
, cardano-ledger-alonzo
, cardano-ledger-api ^>= 1.9
, cardano-ledger-binary
, cardano-ledger-core:{cardano-ledger-core, testlib} >= 1.8
Expand All @@ -332,6 +334,7 @@ test-suite cardano-api-test
, ouroboros-consensus-cardano
, ouroboros-consensus-protocol
, ouroboros-network-api
, plutus-ledger-api
, QuickCheck
, tasty
, tasty-hedgehog
Expand All @@ -341,6 +344,7 @@ test-suite cardano-api-test
other-modules: Test.Cardano.Api.Crypto
Test.Cardano.Api.EpochLeadership
Test.Cardano.Api.Eras
Test.Cardano.Api.Genesis
Test.Cardano.Api.IO
Test.Cardano.Api.Json
Test.Cardano.Api.KeysByron
Expand Down
85 changes: 83 additions & 2 deletions cardano-api/internal/Cardano/Api/Genesis.hs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Cardano.Api.Genesis
( ShelleyGenesis(..)
, shelleyGenesisDefaults
, alonzoGenesisDefaults
, decodeAlonzoGenesis
, conwayGenesisDefaults

-- ** Configuration
Expand All @@ -26,13 +30,19 @@ module Cardano.Api.Genesis
, ConwayGenesisFile
) where

import Cardano.Api.Eon.AlonzoEraOnwards
import Cardano.Api.Eon.ConwayEraOnwards
import Cardano.Api.Eon.ShelleyBasedEra
import Cardano.Api.Eras.Core
import Cardano.Api.IO
import Cardano.Api.Monad.Error (MonadError (throwError), liftEither, liftMaybe)
import Cardano.Api.Utils (unsafeBoundedRational)

import qualified Cardano.Chain.Genesis
import qualified Cardano.Crypto.Hash.Blake2b
import qualified Cardano.Crypto.Hash.Class
import Cardano.Ledger.Alonzo.Genesis (AlonzoGenesis (..))
import qualified Cardano.Ledger.Alonzo.Genesis as L
import Cardano.Ledger.Alonzo.Scripts (ExUnits (..), Prices (..))
import Cardano.Ledger.Api (CoinPerWord (..))
import Cardano.Ledger.BaseTypes as Ledger
Expand All @@ -42,25 +52,39 @@ import Cardano.Ledger.Conway.PParams (DRepVotingThresholds (..),
PoolVotingThresholds (..), UpgradeConwayPParams (..))
import Cardano.Ledger.Crypto (StandardCrypto)
import Cardano.Ledger.Plutus (Language (..))
import qualified Cardano.Ledger.Plutus as L
import Cardano.Ledger.Plutus.CostModels (mkCostModelsLenient)
import Cardano.Ledger.Shelley.Core
import Cardano.Ledger.Shelley.Genesis (NominalDiffTimeMicro, ShelleyGenesis (..),
emptyGenesisStaking)
import qualified Cardano.Ledger.Shelley.Genesis as Ledger
import qualified Ouroboros.Consensus.Shelley.Eras as Shelley
import qualified PlutusLedgerApi.Common as V2
import qualified PlutusLedgerApi.V2 as V2

import Control.Monad
import Control.Monad.Error.Class (modifyError)
import Control.Monad.Trans.Fail.String (errorFail)
import Control.Monad.Trans.Maybe
import qualified Data.Aeson as A
import qualified Data.Aeson.KeyMap as A
import Data.Bifunctor (first)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Default.Class as DefaultClass
import Data.Functor.Identity (Identity)
import Data.Int (Int64)
import qualified Data.ListMap as ListMap
import qualified Data.Map.Strict as M
import qualified Data.Map.Strict as Map
import Data.Maybe
import Data.Ratio
import Data.Text (Text)
import qualified Data.Time as Time
import Data.Typeable
import GHC.Stack (HasCallStack)
import Lens.Micro
import qualified Lens.Micro.Aeson as AL

import Test.Cardano.Ledger.Core.Rational ((%!))
import Test.Cardano.Ledger.Plutus (testingCostModelV3)
Expand Down Expand Up @@ -152,7 +176,7 @@ shelleyGenesisDefaults =
unsafeBR = unsafeBoundedRational

-- | Some reasonable starting defaults for constructing a 'ConwayGenesis'.
-- | Based on https://github.com/IntersectMBO/cardano-node/blob/master/cardano-testnet/src/Testnet/Defaults.hs
-- Based on https://github.com/IntersectMBO/cardano-node/blob/master/cardano-testnet/src/Testnet/Defaults.hs
conwayGenesisDefaults :: ConwayGenesis StandardCrypto
conwayGenesisDefaults = ConwayGenesis { cgUpgradePParams = defaultUpgradeConwayParams
, cgConstitution = DefaultClass.def
Expand Down Expand Up @@ -195,8 +219,63 @@ conwayGenesisDefaults = ConwayGenesis { cgUpgradePParams = defaultUpgradeConwayP
, dvtCommitteeNoConfidence = 0 %! 1
}

decodeAlonzoGenesis :: MonadError String m
=> AlonzoEraOnwards era
-> LBS.ByteString
-> m AlonzoGenesis
decodeAlonzoGenesis aeo genesisBs = modifyError ("Cannot decode Alonzo genesis: " <>) $ do
genesisValue :: A.Value <- liftEither $ A.eitherDecode genesisBs
let genesisValue' = (AL.key "costModels" . AL.key "PlutusV2" . AL._Object) %~ setDefaultValues $ genesisValue
genesis <- case A.fromJSON genesisValue' of
A.Success a -> pure a
A.Error e -> throwError e
forEraInEon @ConwayEraOnwards (toCardanoEra aeo)
-- chop off v2 params if we're < Conway
(chopOffOptionalV2Params genesis)
(pure . const genesis)
where
setDefaultValues cm = A.union cm costModelV2Extension

costModelV2Extension :: A.Object
costModelV2Extension = errorFail $ do
A.Object obj <- pure . A.toJSON . M.fromList $
zip optionalV2costModelParams (repeat @Int64 maxBound)
pure obj

optionalV2costModelParams = map V2.showParamName
[ V2.IntegerToByteString'cpu'arguments'c0
, V2.IntegerToByteString'cpu'arguments'c1
, V2.IntegerToByteString'cpu'arguments'c2
, V2.IntegerToByteString'memory'arguments'intercept
, V2.IntegerToByteString'memory'arguments'slope
, V2.ByteStringToInteger'cpu'arguments'c0
, V2.ByteStringToInteger'cpu'arguments'c1
, V2.ByteStringToInteger'cpu'arguments'c2
, V2.ByteStringToInteger'memory'arguments'intercept
, V2.ByteStringToInteger'memory'arguments'slope
]

chopOffOptionalV2Params g = fmap (fromMaybe g) . runMaybeT $ do
costModelValues <- hoistMaybe
. fmap L.getCostModelParams
. M.lookup L.PlutusV2
. L.costModelsValid
$ L.agCostModels g
let expectedParamsCount = L.costModelParamsCount L.PlutusV2
trimmedParams = take expectedParamsCount costModelValues
-- this is a redundant check, but lets us know that we're doing right things here
when (length trimmedParams /= expectedParamsCount) $ do
throwError $ "Expected " <> show expectedParamsCount <> " V2 cost model parameters, but got " <> show (length trimmedParams)
updatedCostModel <- liftEither . first show $ L.mkCostModel L.PlutusV2 trimmedParams
let updatedCostModels = L.updateCostModels
(L.agCostModels g)
(L.mkCostModels $ M.singleton L.PlutusV2 updatedCostModel)

pure $ g { L.agCostModels = updatedCostModels }


-- | Some reasonable starting defaults for constructing a 'AlonzoGenesis'.
-- | Based on https://github.com/IntersectMBO/cardano-node/blob/master/cardano-testnet/src/Testnet/Defaults.hs
-- Based on https://github.com/IntersectMBO/cardano-node/blob/master/cardano-testnet/src/Testnet/Defaults.hs
alonzoGenesisDefaults :: AlonzoGenesis
alonzoGenesisDefaults = AlonzoGenesis { agPrices = Prices { prSteps = 721 %! 10000000
, prMem = 577 %! 10000
Expand Down Expand Up @@ -242,4 +321,6 @@ alonzoGenesisDefaults = AlonzoGenesis { agPrices = Prices { prSteps = 721 %! 100
, 82523, 4, 265318, 0, 4, 0, 85931, 32, 205665, 812, 1, 1, 41182, 32, 212342, 32, 31220
, 32, 32696, 32, 43357, 32, 32247, 32, 38314, 32, 35892428, 10, 9462713, 1021, 10, 38887044
, 32947, 10
-- TODO add here those new alonzo cost parametes in conway era
, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 -- FIXME: REMOVEME
]
1 change: 1 addition & 0 deletions cardano-api/internal/Cardano/Api/LedgerState.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,7 @@ readAlonzoGenesis (File file) expectedGenesisHash = do
content <- modifyError id $ handleIOExceptT (AlonzoGenesisReadError file . textShow) $ BS.readFile file
let genesisHash = GenesisHashAlonzo (Cardano.Crypto.Hash.Class.hashWith id content)
checkExpectedGenesisHash genesisHash
-- FIXME!!!
liftEither . first (AlonzoGenesisDecodeError file . Text.pack) $ Aeson.eitherDecodeStrict' content
where
checkExpectedGenesisHash :: GenesisHashAlonzo -> t m ()
Expand Down
137 changes: 137 additions & 0 deletions cardano-api/test/cardano-api-test/Test/Cardano/Api/Genesis.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}

{-# OPTIONS_GHC -Wno-orphans #-}

module Test.Cardano.Api.Genesis
( tests
) where

import Cardano.Api.Eras
import Cardano.Api.Genesis
import Cardano.Api.SerialiseCBOR
import Cardano.Api.Shelley

import qualified Cardano.Ledger.Alonzo.Genesis as L
import qualified Cardano.Ledger.Plutus as L
import qualified PlutusLedgerApi.V2 as V2

import Data.Int (Int64)
import qualified Data.Map.Strict as M
import Data.Maybe
import GHC.Stack

import Hedgehog as H
import qualified Hedgehog.Extras as H
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testProperty)

-- TODO add comment explaining what's happening here

prop_reading_plutus_v2_costmodel
:: forall era. IsCardanoEra era
=> AlonzoEraOnwards era
-> PlutusV2CostModelFormat
-> Property
prop_reading_plutus_v2_costmodel aeo cmf = H.propertyOnce $ do
H.noteShow_ $ "Era: " <> pshow aeo
H.noteShow_ $ "Cost model type: " <> show cmf
(genesis, costModelValues) <- loadPlutusV2CostModelFromGenesis aeo (getGenesisFile cmf)

H.noteShow_ costModelValues

let isConwayOnwards = isJust $ maybeEon @ConwayEraOnwards @era
last10CostModelValues = reverse . take 10 $ reverse costModelValues

if isConwayOnwards
then do
length costModelValues === 185
if getCostModelFileParamCount cmf < 185
then last10CostModelValues === replicate 10 maxBound
else last10CostModelValues === replicate 10 1
else
length costModelValues === 175

-- TODO test cbor round trip here!
-- genesis'
-- genesis' === genesis

prop_verify_plutus_v2_costmodel :: Property
prop_verify_plutus_v2_costmodel = H.propertyOnce $ do
let lastParamName = maxBound
last10Params = (toEnum . subtract 9 $ fromEnum lastParamName) `enumFromTo` lastParamName :: [V2.ParamName]
H.note_ "Check that last 10 params of PlutusV2 cost models are exactly the ones we expect"
-- TODO add comment why we need this
last10Params ===
[ V2.IntegerToByteString'cpu'arguments'c0
, V2.IntegerToByteString'cpu'arguments'c1
, V2.IntegerToByteString'cpu'arguments'c2
, V2.IntegerToByteString'memory'arguments'intercept
, V2.IntegerToByteString'memory'arguments'slope
, V2.ByteStringToInteger'cpu'arguments'c0
, V2.ByteStringToInteger'cpu'arguments'c1
, V2.ByteStringToInteger'cpu'arguments'c2
, V2.ByteStringToInteger'memory'arguments'intercept
, V2.ByteStringToInteger'memory'arguments'slope
]


-- * Utilities

data PlutusV2CostModelFormat
= Map175
| Map185
| Array175
| Array185
deriving Show

getGenesisFile :: PlutusV2CostModelFormat -> FilePath
getGenesisFile = ("./test/cardano-api-test/files/input/genesis/spec.alonzo-v2-cost-model-" <>) . \case
Map175 -> "map-175.json"
Map185 -> "map-185.json"
Array175 -> "array-175.json"
Array185 -> "array-185.json"

getCostModelFileParamCount :: PlutusV2CostModelFormat -> Int
getCostModelFileParamCount = \case
Map175 -> 175
Map185 -> 185
Array175 -> 175
Array185 -> 185

loadPlutusV2CostModelFromGenesis
:: HasCallStack
=> MonadIO m
=> MonadTest m
=> AlonzoEraOnwards era
-> FilePath
-> m (L.AlonzoGenesis, [Int64])
loadPlutusV2CostModelFromGenesis aeo filePath = withFrozenCallStack $ do
genesisBs <- H.lbsReadFile filePath
genesis <- H.leftFail $ decodeAlonzoGenesis aeo genesisBs
fmap ((genesis,) . L.getCostModelParams)
. H.nothingFail
. M.lookup L.PlutusV2
. L.costModelsValid
$ L.agCostModels genesis

deriving instance Show V2.ParamName

-- * List all test cases

tests :: TestTree
tests = testGroup "Test.Cardano.Api.Genesis"
[ testProperty "Read Alonzo genesis with PlutusV2 cost model map with 175 params - Babbage" $ prop_reading_plutus_v2_costmodel AlonzoEraOnwardsBabbage Map175
, testProperty "Read Alonzo genesis with PlutusV2 cost model map with 175 params - Conway" $ prop_reading_plutus_v2_costmodel AlonzoEraOnwardsConway Map175
, testProperty "Read Alonzo genesis with PlutusV2 cost model map with 185 params - Babbage" $ prop_reading_plutus_v2_costmodel AlonzoEraOnwardsBabbage Map185
, testProperty "Read Alonzo genesis with PlutusV2 cost model map with 185 params - Conway" $ prop_reading_plutus_v2_costmodel AlonzoEraOnwardsConway Map185
, testProperty "Read Alonzo genesis with PlutusV2 cost model array with 175 params - Babbage" $ prop_reading_plutus_v2_costmodel AlonzoEraOnwardsBabbage Array175
, testProperty "Read Alonzo genesis with PlutusV2 cost model array with 175 params - Conway" $ prop_reading_plutus_v2_costmodel AlonzoEraOnwardsConway Array175
, testProperty "Read Alonzo genesis with PlutusV2 cost model array with 185 params - Babbage" $ prop_reading_plutus_v2_costmodel AlonzoEraOnwardsBabbage Array185
, testProperty "Read Alonzo genesis with PlutusV2 cost model array with 185 params - Conway" $ prop_reading_plutus_v2_costmodel AlonzoEraOnwardsConway Array185
, testProperty "Make sure that last 10 PlutusV2 cost model parameters are the ones we expect" prop_verify_plutus_v2_costmodel
]

2 changes: 2 additions & 0 deletions cardano-api/test/cardano-api-test/cardano-api-test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import qualified Test.Gen.Cardano.Api.Byron
import qualified Test.Cardano.Api.Crypto
import qualified Test.Cardano.Api.EpochLeadership
import qualified Test.Cardano.Api.Eras
import qualified Test.Cardano.Api.Genesis
import qualified Test.Cardano.Api.IO
import qualified Test.Cardano.Api.Json
import qualified Test.Cardano.Api.KeysByron
Expand Down Expand Up @@ -42,6 +43,7 @@ tests =
, Test.Cardano.Api.Crypto.tests
, Test.Cardano.Api.EpochLeadership.tests
, Test.Cardano.Api.Eras.tests
, Test.Cardano.Api.Genesis.tests
, Test.Cardano.Api.IO.tests
, Test.Cardano.Api.Json.tests
, Test.Cardano.Api.KeysByron.tests
Expand Down
Loading

0 comments on commit 1cdc3b3

Please sign in to comment.