Skip to content

Commit 35b7b21

Browse files
authored
Merge pull request #5020 from IntersectMBO/nm/ledger-state-benchmarks
Update and fix `ledger-state` benchmarks
2 parents 43f2d0a + d90370f commit 35b7b21

File tree

6 files changed

+230
-65
lines changed

6 files changed

+230
-65
lines changed

libs/ledger-state/README.md

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,64 +9,107 @@ the memory overhead.
99
In order to be able to use the tool we need to get ahold of current ledger
1010
state. For this we need to start a cardano node and wait for it to sync.
1111

12-
```haskell
13-
$ export CARDANO_DATA="${HOME}/iohk/chain/mainnet"
14-
$ mkdir -p "${CARDANO_DATA}"/db
12+
```shell
13+
$ export CARDANO_DATA=${HOME}/iohk/chain/mainnet
14+
$ mkdir -p "${CARDANO_DATA}/db"
1515
$ cd "${CARDANO_DATA}"
1616
```
1717

18-
Download all the [mainnet related config files](https://developers.cardano.org/docs/get-started/running-cardano/#mainnet--production):
18+
Download all the [mainnet related config files](https://developers.cardano.org/docs/get-started/cardano-node/running-cardano/#configuration-files):
19+
20+
```shell
21+
curl -O -J https://book.play.dev.cardano.org/environments/mainnet/config.json
22+
curl -O -J https://book.play.dev.cardano.org/environments/mainnet/db-sync-config.json
23+
curl -O -J https://book.play.dev.cardano.org/environments/mainnet/submit-api-config.json
24+
curl -O -J https://book.play.dev.cardano.org/environments/mainnet/topology.json
25+
curl -O -J https://book.play.dev.cardano.org/environments/mainnet/byron-genesis.json
26+
curl -O -J https://book.play.dev.cardano.org/environments/mainnet/shelley-genesis.json
27+
curl -O -J https://book.play.dev.cardano.org/environments/mainnet/alonzo-genesis.json
28+
curl -O -J https://book.play.dev.cardano.org/environments/mainnet/conway-genesis.json
1929
```
20-
curl -O -J https://hydra.iohk.io/build/7370192/download/1/mainnet-config.json
21-
curl -O -J https://hydra.iohk.io/build/7370192/download/1/mainnet-byron-genesis.json
22-
curl -O -J https://hydra.iohk.io/build/7370192/download/1/mainnet-shelley-genesis.json
23-
curl -O -J https://hydra.iohk.io/build/7370192/download/1/mainnet-alonzo-genesis.json
24-
curl -O -J https://hydra.iohk.io/build/7370192/download/1/mainnet-topology.json
30+
31+
Download or build copies of `cardano-node` and `cardano-cli`. A convenient way of doing this is to download the assets from one of the `cardano-node` [releases](https://github.com/IntersectMBO/cardano-node/releases) on GitHub. The Linux executables are statically linked, so will run on any system.
32+
33+
Download a snapshot of the node db using [mithril](https://mithril.network/doc/manual/getting-started/bootstrap-cardano-node/#bootstrap-a-cardano-node-from-a-testnet-mithril-cardano-db-snapshot). This will greatly speed up the process of syncing the node. There's a convenient nix-based script for doing it in `scripts/mithril-download.sh`:
34+
35+
```shell
36+
$ scripts/mithril-download.sh -d "${CARDANO_DATA}/db" mainnet
2537
```
2638

39+
Note that you will need to get a snapshot that's compatible with the version of `cardano-node` you're using.
40+
2741
Start the node and wait for it to fully sync
2842

29-
```
43+
```shell
3044
$ export CARDANO_NODE_SOCKET_PATH="${CARDANO_DATA}/db/node.socket"
31-
$ cardano-node run
32-
--topology "${CARDANO_DATA}/mainnet-topology.json" \
33-
--database-path "${CARDANO_DATA}/db" \
34-
--socket-path "${CARDANO_NODE_SOCKET_PATH}" \
35-
--host-addr 127.0.0.1 \
36-
--port 3001 \
37-
--config "${CARDANO_DATA}/mainnet-config.json" &
45+
$ cardano-node run \
46+
--topology "${CARDANO_DATA}/topology.json" \
47+
--config "${CARDANO_DATA}/config.json" \
48+
--database-path "${CARDANO_DATA}/db" \
49+
--socket-path "${CARDANO_NODE_SOCKET_PATH}" \
50+
--host-addr 0.0.0.0 \
51+
--port 3001 &
52+
```
53+
54+
Dump the ledger state and utxo:
55+
56+
```shell
57+
$ cardano-cli query ledger-state --mainnet \
58+
--socket-path "${CARDANO_NODE_SOCKET_PATH}" \
59+
--out-file "${CARDANO_DATA}/new-epoch-state.bin"
60+
$ cardano-cli query utxo --mainnet --whole-utxo --output-cbor \
61+
--socket-path "${CARDANO_NODE_SOCKET_PATH}" \
62+
--out-file "${CARDANO_DATA}/utxo.hex"
3863
```
3964

40-
Dump the ledger state and focus back onto the node:
65+
Bring the node back into the foreground and use Ctrl-C to stop it:
4166

4267
```shell
43-
$ cardano-cli query ledger-state --mainnet --out-file "${CARDANO_DATA}/ledger-state.bin"
4468
$ fg
69+
$ ^C
4570
```
46-
Hit Ctr-C to stop the node
4771

48-
## Populate sqlite db
72+
## Populate sqlite db from `NewEpochState` file
4973

5074
```shell
51-
$ cabal run -- ledger-state:ledger-state --new-epoch-state-cbor="${CARDANO_DATA}/ledger-state.bin" --sqlite-db="${CARDANO_DATA}/ledger-state.sqlite"
75+
$ cabal run -- ledger-state:ledger-state \
76+
--new-epoch-state-cbor="${CARDANO_DATA}/new-epoch-state.bin" \
77+
--sqlite-db="${CARDANO_DATA}/epoch-state.sqlite"
5278
```
5379

54-
## Create NewEpochState from sqlite db
80+
## Create `EpochState` file from sqlite db
5581

5682
```shell
57-
$ cabal run -- ledger-state:ledger-state --epoch-state-cbor="${CARDANO_DATA}/ledger-state.bin" --sqlite-db="${CARDANO_DATA}/ledger-state.sqlite"
83+
$ cabal run -- ledger-state:ledger-state \
84+
--epoch-state-cbor="${CARDANO_DATA}/epoch-state.bin" \
85+
--sqlite-db="${CARDANO_DATA}/epoch-state.sqlite"
5886
```
5987

6088
## Running benchmarks
6189

6290
### Memory
6391

6492
```shell
65-
$ cabal bench ledger-state:memory --benchmark-options="--new-epoch-state-cbor=\"${CARDANO_DATA}/ledger-state.bin\" --new-epoch-state-sqlite=\"${CARDANO_DATA}/ledger-state.sqlite\""
93+
$ cabal build ledger-state:memory
94+
$ cabal bench ledger-state:memory -v0 \
95+
--benchmark-option="--new-epoch-state-cbor=${CARDANO_DATA}/new-epoch-state.bin" \
96+
--benchmark-option="--epoch-state-cbor=${CARDANO_DATA}/epoch-state.bin" \
97+
--benchmark-option="--sqlite-db=${CARDANO_DATA}/epoch-state.sqlite" |
98+
tee ledger-state:memory.txt
6699
```
67100
### Performance
68101

69102
Performance benchmarks need an actual mainnet ledger state and genesis config
70103
file to run properly. It is not possible to add extra arguments to criterion cli
71104
menu, therefore paths to those files must be supplied as environment variables.
72105

106+
```shell
107+
$ export BENCH_GENESIS_PATH=${CARDANO_DATA}/shelley-genesis.json
108+
$ export BENCH_LEDGER_STATE_PATH=${CARDANO_DATA}/new-epoch-state.bin
109+
$ export BENCH_UTXO_PATH=${CARDANO_DATA}/utxo.hex
110+
$ cabal bench ledger-state:performance --benchmark-option=--csv=ledger-state:performance.csv
111+
```
112+
113+
The csv file will be saved in the `libs/ledger-state` directory.
114+
115+
Since the `performance` benchmark uses only `new-epoch-state.bin` you don't need to run the `sqlite.db` steps above if you want to run only the `performance` benchmark.

libs/ledger-state/app/Main.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import System.IO
1414
-- | Insight into options:
1515
--
1616
-- * `optsNewEpochStateBinaryFile` is for reading a previously serialized
17-
-- * `NewEpochState` produced by cadano-cli` and is used to populate sqlite
17+
-- * `NewEpochState` produced by cardano-cli` and is used to populate sqlite
1818
-- * database
1919
--
2020
-- * `optsEpochStateBinaryFile` is used for grabbing data from sqlite,
@@ -26,7 +26,7 @@ data Opts = Opts
2626
-- load into sqlite database
2727
, optsEpochStateBinaryFile :: Maybe FilePath
2828
-- ^ Path to the CBOR encoded EpochState data type, which will have data
29-
-- from sqlite database written into.
29+
-- from sqlite database written into it.
3030
, optsSqliteDbFile :: Maybe FilePath
3131
-- ^ Path to Sqlite database file.
3232
}
@@ -62,7 +62,7 @@ optsParser =
6262
<> value Nothing
6363
<> help
6464
( "Path to Sqlite database file. When supplied then new-epoch-state "
65-
<> "will be loaded into the databse. Requires --new-epoch-state-cbor"
65+
<> "will be loaded into the database. Requires --new-epoch-state-cbor"
6666
)
6767
)
6868

@@ -92,7 +92,7 @@ main = do
9292
epochState <- loadEpochState dbFp
9393
putStrLn "Loaded EpochState from the database"
9494
writeEpochState binFp epochState
95-
putStrLn $ "Written EpochState into: " ++ dbFpStr
95+
putStrLn $ "Written EpochState into: " ++ binFp
9696

9797
-- forM_ (optsSqliteDbFile opts) $ \dbFpStr -> do
9898
-- let dbFp = T.pack dbFpStr

libs/ledger-state/bench/Performance.hs

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE BangPatterns #-}
22
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE LambdaCase #-}
34
{-# LANGUAGE NamedFieldPuns #-}
45
{-# LANGUAGE OverloadedStrings #-}
56
{-# LANGUAGE TypeApplications #-}
@@ -11,6 +12,10 @@ import Cardano.Ledger.Api.Era
1112
import Cardano.Ledger.Api.State.Query (queryStakePoolDelegsAndRewards)
1213
import Cardano.Ledger.BaseTypes
1314
import Cardano.Ledger.Binary
15+
import Cardano.Ledger.Conway.Rules (
16+
ConwayLedgerPredFailure (ConwayUtxowFailure),
17+
ConwayUtxowPredFailure (InvalidWitnessesUTXOW),
18+
)
1419
import Cardano.Ledger.Core
1520
import Cardano.Ledger.Shelley.API.Mempool
1621
import Cardano.Ledger.Shelley.API.Wallet (getFilteredUTxO, getUTxO)
@@ -21,54 +26,77 @@ import Cardano.Ledger.Shelley.Genesis (
2126
)
2227
import Cardano.Ledger.Shelley.LedgerState
2328
import Cardano.Ledger.State
24-
import Cardano.Ledger.State.UTxO (CurrentEra, readNewEpochState)
29+
import Cardano.Ledger.State.UTxO (CurrentEra, readHexUTxO, readNewEpochState)
2530
import Cardano.Ledger.UMap
2631
import Cardano.Ledger.Val
2732
import Cardano.Slotting.EpochInfo (fixedEpochInfo)
2833
import Cardano.Slotting.Time (mkSlotLength)
2934
import Control.DeepSeq
35+
import Control.Monad (when)
3036
import Criterion.Main
3137
import Data.Aeson
32-
import Data.Bifunctor (first)
38+
import Data.Bifunctor (bimap, first)
3339
import Data.ByteString.Base16.Lazy as BSL16
3440
import Data.ByteString.Lazy (ByteString)
3541
import Data.Foldable as F
42+
import Data.List.NonEmpty (NonEmpty ((:|)))
3643
import Data.Map.Strict (Map)
3744
import qualified Data.Map.Strict as Map
3845
import Data.MapExtras (extractKeys, extractKeysSmallSet)
3946
import Data.Set (Set)
4047
import qualified Data.Set as Set
41-
import Lens.Micro ((^.))
48+
import GHC.Stack (HasCallStack)
49+
import Lens.Micro ((&), (.~), (^.))
4250
import System.Environment (getEnv)
51+
import System.Exit (die)
4352
import System.Random.Stateful
4453
import Test.Cardano.Ledger.Api.State.Query (getFilteredDelegationsAndRewardAccounts)
4554
import Test.Cardano.Ledger.Core.Arbitrary (uniformSubSet)
4655

4756
main :: IO ()
4857
main = do
49-
let ledgerVarName = "BENCH_LEDGER_STATE_PATH"
50-
genesisVarName = "BENCH_GENESIS_PATH"
51-
ledgerStateFilePath <- getEnv ledgerVarName
58+
let genesisVarName = "BENCH_GENESIS_PATH"
59+
utxoVarName = "BENCH_UTXO_PATH"
60+
ledgerStateVarName = "BENCH_LEDGER_STATE_PATH"
5261
genesisFilePath <- getEnv genesisVarName
62+
utxoFilePath <- getEnv utxoVarName
63+
ledgerStateFilePath <- getEnv ledgerStateVarName
64+
5365
genesis <- either error id <$> eitherDecodeFileStrict' genesisFilePath
66+
putStrLn $ "Importing UTxO from: " ++ show utxoFilePath
67+
utxo <- readHexUTxO utxoFilePath
68+
putStrLn "Done importing UTxO"
69+
putStrLn $ "Importing NewEpochState from: " ++ show ledgerStateFilePath
70+
es' <- readNewEpochState ledgerStateFilePath
71+
putStrLn "Done importing NewEpochState"
72+
73+
let nesUTxOL = nesEsL . esLStateL . lsUTxOStateL . utxoL
74+
es = es' & nesUTxOL .~ utxo
75+
utxoMap = unUTxO utxo
76+
utxoSize = Map.size utxoMap
77+
largeKeysNum = 100000
78+
stdGen = mkStdGen 2022
5479

5580
let toMempoolState :: NewEpochState CurrentEra -> MempoolState CurrentEra
5681
toMempoolState NewEpochState {nesEs = EpochState {esLState}} = esLState
5782
!globals = mkGlobals genesis
5883
!slotNo = SlotNo 55733343
84+
restrictError = \case
85+
ApplyTxError (ConwayUtxowFailure (InvalidWitnessesUTXOW [_]) :| []) -> ()
86+
otherErr -> error . show $ otherErr
5987
applyTx' mempoolEnv mempoolState =
60-
either (error . show) seqTuple
88+
-- TODO: revert this to `either (error . show) seqTuple` after tx's are fixed
89+
bimap restrictError seqTuple
6190
. applyTx globals mempoolEnv mempoolState
62-
reapplyTx' mempoolEnv mempoolState tx =
63-
case reapplyTx globals mempoolEnv mempoolState tx of
64-
Left err -> error (show err)
65-
Right st -> st
66-
putStrLn $ "Importing NewEpochState from: " ++ show ledgerStateFilePath
67-
es <- readNewEpochState ledgerStateFilePath
68-
putStrLn "Done importing NewEpochState"
69-
let largeKeysNum = 100000
70-
stdGen = mkStdGen 2022
71-
largeKeys <- selectRandomMapKeys 100000 stdGen (unUTxO (getUTxO es))
91+
reapplyTx' mempoolEnv mempoolState =
92+
either (error . show) id
93+
. reapplyTx globals mempoolEnv mempoolState
94+
95+
when (utxoSize < largeKeysNum) $
96+
die $
97+
"UTxO size is too small (" <> show utxoSize <> " < " <> show largeKeysNum <> ")"
98+
largeKeys <- selectRandomMapKeys 100000 stdGen utxoMap
99+
72100
defaultMain
73101
[ env (pure (mkMempoolEnv es slotNo, toMempoolState es)) $ \ ~(mempoolEnv, mempoolState) ->
74102
bgroup
@@ -92,22 +120,26 @@ main = do
92120
bench "Tx2" . whnf (applyTx' mempoolEnv mempoolState)
93121
, env (pure (extractTx validatedTx3)) $
94122
bench "Tx3" . whnf (applyTx' mempoolEnv mempoolState)
123+
, env
124+
(pure [validatedTx1, validatedTx2, validatedTx3])
125+
$ bench "Tx1+Tx2+Tx3"
126+
-- TODO: revert this to `foldl'` without `fmap` after tx's are fixed
127+
. whnf (F.foldlM (\ms -> fmap fst . applyTx' mempoolEnv ms . extractTx) mempoolState)
95128
]
96-
, env (pure (getUTxO es)) $ \utxo ->
129+
, env (pure utxo) $ \utxo' ->
97130
bgroup
98131
"UTxO"
99-
[ bench "balance" $ nf balance utxo
100-
, bench "coinBalance" $ nf coinBalance utxo
132+
[ bench "balance" $ nf balance utxo'
133+
, bench "coinBalance" $ nf coinBalance utxo'
101134
, -- We need to filter out all multi-assets to prevent `areAllAdaOnly`
102135
-- from short circuiting and producing results that are way better
103136
-- than the worst case
104-
env (pure $ Map.filter (\txOut -> isAdaOnly (txOut ^. valueTxOutL)) $ unUTxO utxo) $
137+
env (pure $ Map.filter (\txOut -> isAdaOnly (txOut ^. valueTxOutL)) $ unUTxO utxo') $
105138
bench "areAllAdaOnly" . nf areAllAdaOnly
106139
]
107140
, env (pure es) $ \newEpochState ->
108-
let utxo = getUTxO es
109-
(_, minTxOut) = Map.findMin $ unUTxO utxo
110-
(_, maxTxOut) = Map.findMax $ unUTxO utxo
141+
let (_, minTxOut) = Map.findMin utxoMap
142+
(_, maxTxOut) = Map.findMax utxoMap
111143
setAddr =
112144
Set.fromList [minTxOut ^. addrTxOutL, maxTxOut ^. addrTxOutL]
113145
in bgroup
@@ -137,10 +169,10 @@ main = do
137169
]
138170
, bgroup
139171
"DeleteTxOuts"
140-
[ extractKeysBench (unUTxO (getUTxO es)) largeKeysNum largeKeys
141-
, extractKeysBench (unUTxO (getUTxO es)) 9 (Set.take 9 largeKeys)
142-
, extractKeysBench (unUTxO (getUTxO es)) 5 (Set.take 5 largeKeys)
143-
, extractKeysBench (unUTxO (getUTxO es)) 2 (Set.take 2 largeKeys)
172+
[ extractKeysBench utxoMap largeKeysNum largeKeys
173+
, extractKeysBench utxoMap 9 (Set.take 9 largeKeys)
174+
, extractKeysBench utxoMap 5 (Set.take 5 largeKeys)
175+
, extractKeysBench utxoMap 2 (Set.take 2 largeKeys)
144176
]
145177
]
146178

@@ -176,10 +208,12 @@ selectRandomMapKeys n gen m = runStateGenT_ gen $ \g ->
176208
extractKeysNaive :: Ord k => Map k a -> Set.Set k -> (Map k a, Map k a)
177209
extractKeysNaive sm s = (Map.withoutKeys sm s, Map.restrictKeys sm s)
178210

179-
decodeTx :: ByteString -> Tx CurrentEra
211+
decodeTx :: HasCallStack => ByteString -> Tx CurrentEra
180212
decodeTx hex = either error id $ do
181213
bsl <- BSL16.decode hex
182-
first show $ decodeFull (eraProtVerHigh @CurrentEra) bsl
214+
tx <- first show $ decodeFull (eraProtVerHigh @BabbageEra) bsl
215+
-- TODO: remove this after the transactions below are updated
216+
first show $ upgradeTx tx
183217

184218
-- | Most basic ada-only transaction:
185219
--
@@ -235,8 +269,8 @@ validatedTx3 =
235269
\424643546f6b656e1a006cc9f2021a0002afe90e81581c780648b89ea2f11fa9bbdd67\
236270
\552db5dd020eda1c9a54142dd9f1b136a10081825820cf2477066091b565f87f044581\
237271
\7c4df726900b29af3f05d229309afdbf94296d584088444a5845b198a2d255175770be\
238-
\7120c2d3482751b14f06dd41d7ff023eeae6e63933b097c023c1ed19df6a061173c45aa\
239-
\54cceb568ff1886e2716e84e6260df5f6"
272+
\7120c2d3482751b14f06dd41d7ff023eeae6e63933b097c023c1ed19df6a061173c45a\
273+
\a54cceb568ff1886e2716e84e6260df5f6"
240274

241275
mkGlobals :: ShelleyGenesis -> Globals
242276
mkGlobals genesis =

libs/ledger-state/ledger-state.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ library
3636

3737
build-depends:
3838
base >=4.14 && <5,
39+
base16-bytestring,
3940
bytestring,
4041
cardano-crypto-class,
4142
cardano-ledger-alonzo,
@@ -124,6 +125,7 @@ benchmark performance
124125
cardano-data,
125126
cardano-ledger-api:{cardano-ledger-api, testlib},
126127
cardano-ledger-binary,
128+
cardano-ledger-conway,
127129
cardano-ledger-core:{cardano-ledger-core, testlib},
128130
cardano-ledger-shelley,
129131
cardano-slotting,

0 commit comments

Comments
 (0)