Skip to content

Commit a3245c5

Browse files
authored
[Peras #1] Introduce basic types to support Peras (#1673)
This pull request introduces some base datatypes and functionality required for **Peras**, along with supporting tests, benchmarks, and documentation. Specifically, it adds: * `PerasWeight` and `PerasCert`, which associate a weight with a block. * `PerasWeightSnapshot`, a type for capturing the known weight state of blocks * Functions for computing weights of chains and fragments. * Tests, microbenchmarks, documentation, and runnable examples for weight-related utilities. --- ### **Peras types and weight computation** * Added module `Ouroboros.Consensus.Block.SupportsPeras`, defining types and logic for Peras weights and certificates. * Key elements are re-exported via `Ouroboros.Consensus.Block`. * Added module `Ouroboros.Consensus.Peras.Weight`, introducing `PerasWeightSnapshot` and weight computation functions for chains and fragments. --- ### **Documentation** * Added glossary entries for **Peras**, **weight boost**, and **chain fragment weight** in `glossary.md`. * Created a new benchmarks page explaining how to run Peras-related microbenchmarks and their purpose. --- ### **Tests and benchmarks** * Introduced benchmark suite `PerasCertDB-bench`, including a weight computation microbenchmark (`bench/PerasCertDB-bench/Main.hs`). * Added module `Test.Consensus.Peras.WeightSnapshot` with property-based tests for weight computation on chains and fragments. --- ### **Regression** The new code added with this PR is not actually used, so it shouldn't introduce regressions.
2 parents 0107b1e + 3db7a62 commit a3245c5

File tree

11 files changed

+887
-0
lines changed

11 files changed

+887
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Consensus benchmarks
2+
3+
We are in the process of adding component level microbenchmarks for Consensus.
4+
5+
We check for regressions in performance on CI.
6+
7+
## Mempool Benchmark
8+
9+
We started with microbenchmarks for adding transactions to the mempool. The
10+
mempool benchmarks can be run using the following command.
11+
12+
```sh
13+
cabal new-run ouroboros-consensus:mempool-bench
14+
```
15+
16+
## ChainSync Client Benchmark
17+
18+
To aid the refactoring of the ChainSync client, we added a benchmark for it in [PR#823](https://github.com/IntersectMBO/ouroboros-consensus/pull/823). The benchmark could be invoked as follows:
19+
20+
```sh
21+
cabal new-run ouroboros-consensus:ChainSync-client-bench -- 10 10
22+
```
23+
24+
## PerasCertDB Benchmark
25+
26+
We have a microbenchmark for the boosted chain fragment weight calculation, which could be run as follows:
27+
28+
```sh
29+
cabal run ouroboros-consensus:PerasCertDB-bench -- +RTS -T -A32m -RTS
30+
```
31+
32+
We request GHC runtime system statistics with `-T` to get a memory usage estimate, and also request a large nursery with `-A32m` to minimise garbage collection. See `tasty-bench` [documentation](https://github.com/Bodigrim/tasty-bench?tab=readme-ov-file#troubleshooting) for more tips.

docs/website/contents/references/glossary.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,19 @@ These kinds are maintained by the Networking layer:
472472
- [Public root peers](#public-root-peers).
473473
- [Shared peers](#shared-peers).
474474

475+
## ;Peras ;weight ;boost
476+
477+
Peras is an extension of Praos enabling faster settlement under optimistic conditions.
478+
To this end, Peras can result in a block `B` receiving a *boost*, which means that any chain containing `B` gets additional weight when being compared to other chains.
479+
480+
Consider a chain fragment `F`:
481+
482+
- Its ;*weight boost* is the sum of all boosts received by points on this fragment (excluding the anchor). Note that the same point can be boosted multiple times.
483+
484+
- Its ;*total weight* is its tip block number plus its weight boost.
485+
486+
Note that these notions are always relative to a particular anchor, so different chain fragments must have the same anchor when their total weight is to be compared.
487+
475488
## ;Phases
476489

477490
Byron, Shelley, Goguen (current one as of August 2023), Basho, Voltaire.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
{-# LANGUAGE ImportQualifiedPost #-}
2+
{-# LANGUAGE LambdaCase #-}
3+
4+
-- | This module contains benchmarks for Peras chain weight calculation as
5+
-- implemented by the by the
6+
-- 'Ouroboros.Consensus.Peras.Weight.weightBoostOfFragment' function.
7+
--
8+
-- We benchmark the calculation on a static sequence of chain fragments of
9+
-- increasing length, ranging from 0 to 'fragmentMaxLength', with a step size
10+
-- of 'fragmentLengthStepSize'. The chain fragments are instantiated with
11+
-- 'TestBlock', and every 'boostedBlockGap' blocks there is a booster block
12+
-- with weight 'boostWeight'. All parameters are set in 'benchmarkParams'.
13+
module Main (main) where
14+
15+
import Data.List (iterate')
16+
import Data.Word (Word64)
17+
import Numeric.Natural (Natural)
18+
import Ouroboros.Consensus.Block (PerasWeight (PerasWeight), SlotNo (..))
19+
import Ouroboros.Consensus.Peras.Weight
20+
( PerasWeightSnapshot
21+
, mkPerasWeightSnapshot
22+
, weightBoostOfFragment
23+
)
24+
import Ouroboros.Network.AnchoredFragment qualified as AF
25+
import Test.Ouroboros.Storage.TestBlock (TestBlock (..), TestBody (..), TestHeader (..))
26+
import Test.Ouroboros.Storage.TestBlock qualified as TestBlock
27+
import Test.Tasty.Bench
28+
29+
data BenchmarkParams = BenchmarkParams
30+
{ slotGap :: Word64
31+
-- ^ The slot gap between blocks on the fragments, ie the inverse of the
32+
-- active slot coefficient. Measured in slots.
33+
, fragmentLengthStepSize :: Natural
34+
-- ^ Step size for the fragment lengths between different benchmarks, in
35+
-- blocks.
36+
, fragmentMaxLength :: Natural
37+
-- ^ The maximum length of a fragment, in blocks.
38+
, boostedBlockGap :: Natural
39+
-- ^ How often boosted blocks occur, in blocks.
40+
, boostWeight :: PerasWeight
41+
-- ^ The weight of the boost.
42+
}
43+
44+
benchmarkParams :: BenchmarkParams
45+
benchmarkParams =
46+
BenchmarkParams
47+
{ -- On Cardano mainnet, the active slot coefficient f=1/20, so there are 20
48+
-- slots between blocks on average assuming nominal chain density.
49+
slotGap = 20
50+
, -- Represents a decent balance between the number of benchmarks we run and
51+
-- the granularity at which we can observe results.
52+
fragmentLengthStepSize = 100
53+
, -- This is the maximum size of header fragments while syncing (the current
54+
-- selection (k) plus one forecast window under nominal chain density
55+
-- (3k), where k=2160 on Cardano mainnet).
56+
fragmentMaxLength = 2160 + 3 * 2160
57+
, -- A plausible value for the Peras round length is 90 slots, which means
58+
-- that we expect to see 4-5 blocks per Peras round (and therefore between
59+
-- boosted blocks) on mainnet where the active slot coefficient f=1/20.
60+
boostedBlockGap = 5
61+
, -- This is a plausible mainnet value (the exact value does not impact the
62+
-- benchmark).
63+
boostWeight = PerasWeight 15
64+
}
65+
66+
main :: IO ()
67+
main =
68+
Test.Tasty.Bench.defaultMain $ map benchWeightBoostOfFragment inputs
69+
where
70+
-- NOTE: we do not use the 'env' combinator to set up the test data since
71+
-- it requires 'NFData' for 'AF.AnchoredFragment'. While the necessary
72+
-- instances could be provided, we do not think is necessary for this
73+
-- benchmark, as the input data is rather small.
74+
inputs :: [(Natural, (PerasWeightSnapshot TestBlock, AF.AnchoredFragment TestBlock))]
75+
inputs =
76+
getEveryN (fragmentLengthStepSize benchmarkParams) $
77+
take (fromIntegral $ fragmentMaxLength benchmarkParams) $
78+
zip [0 ..] $
79+
zip (map uniformWeightSnapshot fragments) fragments
80+
81+
benchWeightBoostOfFragment ::
82+
(Natural, (PerasWeightSnapshot TestBlock, AF.AnchoredFragment TestBlock)) -> Benchmark
83+
benchWeightBoostOfFragment (i, (weightSnapshot, fragment)) =
84+
bench ("weightBoostOfFragment of length " <> show i) $
85+
whnf (weightBoostOfFragment weightSnapshot) fragment
86+
87+
-- | An infinite list of chain fragments
88+
fragments :: [AF.AnchoredFragment TestBlock]
89+
fragments = iterate' addSuccessorBlock genesisFragment
90+
where
91+
genesisFragment :: AF.AnchoredFragment TestBlock
92+
genesisFragment = AF.Empty AF.AnchorGenesis
93+
94+
addSuccessorBlock :: AF.AnchoredFragment TestBlock -> AF.AnchoredFragment TestBlock
95+
addSuccessorBlock = \case
96+
AF.Empty _ -> (AF.Empty AF.AnchorGenesis) AF.:> (TestBlock.firstBlock 0 dummyBody)
97+
(xs AF.:> x) ->
98+
let nextBlockSlot = SlotNo (slotGap benchmarkParams) + thSlotNo (testHeader x)
99+
in (xs AF.:> x) AF.:> TestBlock.mkNextBlock x nextBlockSlot dummyBody
100+
101+
dummyBody :: TestBody
102+
dummyBody = TestBody{tbForkNo = 0, tbIsValid = True}
103+
104+
-- | Given a chain fragment, construct a weight snapshot where there's a boosted block every 90 slots
105+
uniformWeightSnapshot :: AF.AnchoredFragment TestBlock -> PerasWeightSnapshot TestBlock
106+
uniformWeightSnapshot fragment =
107+
let pointsToBoost =
108+
map snd
109+
. getEveryN (boostedBlockGap benchmarkParams)
110+
. zip [0 ..]
111+
. map AF.blockPoint
112+
. AF.toOldestFirst
113+
$ fragment
114+
weights = repeat (boostWeight benchmarkParams)
115+
in mkPerasWeightSnapshot $ pointsToBoost `zip` weights
116+
117+
getEveryN :: Natural -> [(Natural, a)] -> [(Natural, a)]
118+
getEveryN n = filter (\(i, _) -> (i `mod` n) == 0)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!--
2+
### Patch
3+
4+
- A bullet item for the Patch category.
5+
6+
-->
7+
<!--
8+
### Non-Breaking
9+
10+
- A bullet item for the Non-Breaking category.
11+
12+
-->
13+
### Breaking
14+
15+
- Introduce `Ouroboros.Consensus.Block.SupportsPeras` with types related to Peras.
16+
- All new types are re-exported through `Ouroboros.Consensus.Block`.
17+
- Introduce `Ouroboros.Consensus.Peras.Weight` with weight computation related types and functions for chains and fragments.
18+
- Introduce a new benchmark suite `PerasCertDB-bench`
19+
- Add property tests and benchmarks for weight computation on chain and fragments

ouroboros-consensus/ouroboros-consensus.cabal

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ library
8383
Ouroboros.Consensus.Block.RealPoint
8484
Ouroboros.Consensus.Block.SupportsDiffusionPipelining
8585
Ouroboros.Consensus.Block.SupportsMetrics
86+
Ouroboros.Consensus.Block.SupportsPeras
8687
Ouroboros.Consensus.Block.SupportsProtocol
8788
Ouroboros.Consensus.Block.SupportsSanityCheck
8889
Ouroboros.Consensus.BlockchainTime
@@ -197,6 +198,7 @@ library
197198
Ouroboros.Consensus.Node.Run
198199
Ouroboros.Consensus.Node.Serialisation
199200
Ouroboros.Consensus.NodeId
201+
Ouroboros.Consensus.Peras.Weight
200202
Ouroboros.Consensus.Protocol.Abstract
201203
Ouroboros.Consensus.Protocol.BFT
202204
Ouroboros.Consensus.Protocol.LeaderSchedule
@@ -596,6 +598,7 @@ test-suite consensus-test
596598
Test.Consensus.MiniProtocol.ChainSync.CSJ
597599
Test.Consensus.MiniProtocol.ChainSync.Client
598600
Test.Consensus.MiniProtocol.LocalStateQuery.Server
601+
Test.Consensus.Peras.WeightSnapshot
599602
Test.Consensus.Util.MonadSTM.NormalForm
600603
Test.Consensus.Util.Versioned
601604

@@ -827,6 +830,19 @@ benchmark ChainSync-client-bench
827830
unstable-consensus-testlib,
828831
with-utf8,
829832

833+
benchmark PerasCertDB-bench
834+
import: common-bench
835+
type: exitcode-stdio-1.0
836+
hs-source-dirs: bench/PerasCertDB-bench
837+
main-is: Main.hs
838+
other-modules:
839+
build-depends:
840+
base,
841+
ouroboros-consensus,
842+
ouroboros-network-api,
843+
tasty-bench,
844+
unstable-consensus-testlib,
845+
830846
test-suite doctest
831847
import: common-test
832848
main-is: doctest.hs

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Block.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ import Ouroboros.Consensus.Block.NestedContent as X
88
import Ouroboros.Consensus.Block.RealPoint as X
99
import Ouroboros.Consensus.Block.SupportsDiffusionPipelining as X
1010
import Ouroboros.Consensus.Block.SupportsMetrics as X
11+
import Ouroboros.Consensus.Block.SupportsPeras as X
1112
import Ouroboros.Consensus.Block.SupportsProtocol as X
1213
import Ouroboros.Consensus.Block.SupportsSanityCheck as X

0 commit comments

Comments
 (0)