Skip to content
Merged
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
16 changes: 16 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,19 @@ source-repository-package
eras/byron/ledger/executable-spec
eras/byron/ledger/impl
eras/byron/crypto

allow-newer:
-- https://github.com/phadej/vec/issues/121
, ral:QuickCheck
, fin:QuickCheck
, bin:QuickCheck

-- Backported version of https://github.com/IntersectMBO/ouroboros-network/pull/5161
source-repository-package
type: git
location: https://github.com/IntersectMBO/ouroboros-network
tag: b07a86ed853b63881b5a83e57508902f1562ac01
--sha256: sha256-n/XX0+cQegq2a1cAfmGx30T64eix4oEXzpVEFCKqmg0=
subdir:
ouroboros-network-api
ouroboros-network
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Breaking

- In module `Ouroboros.Consensus.Node.GSM`, `GSMView` now has a monadic `getCandidateOverSelection :: STM m (selection -> chainSyncState -> CandidateVersusSelection)` instead of the previous pure `candidateOverSelection`. This is due to the fact that chain comparisons now depend on the set of Peras certificates (if Peras is enabled).
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,16 @@ data GsmView m upstreamPeer selection chainSyncState = GsmView
-- thundering herd phenomenon.
--
-- 'Nothing' should only be used for testing.
, candidateOverSelection ::
selection ->
chainSyncState ->
CandidateVersusSelection
, getCandidateOverSelection ::
STM
m
( selection ->
chainSyncState ->
CandidateVersusSelection
)
-- ^ Whether the candidate from the @chainSyncState@ is preferable to the
-- selection. This can depend on external state (Peras certificates boosting
-- blocks).
, peerIsIdle :: chainSyncState -> Bool
, durationUntilTooOld :: Maybe (selection -> m DurationFromNow)
-- ^ How long from now until the selection will be so old that the node
Expand Down Expand Up @@ -234,7 +240,7 @@ realGsmEntryPoints tracerArgs gsmView =

GsmView
{ antiThunderingHerd
, candidateOverSelection
, getCandidateOverSelection
, peerIsIdle
, durationUntilTooOld
, equivalent
Expand Down Expand Up @@ -383,6 +389,7 @@ realGsmEntryPoints tracerArgs gsmView =
-- long.
selection <- getCurrentSelection
candidates <- traverse StrictSTM.readTVar varsState
candidateOverSelection <- getCandidateOverSelection
let ok candidate =
WhetherCandidateIsBetter False
== candidateOverSelection selection candidate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,18 @@ initNodeKernel
gsmTracerArgs
GSM.GsmView
{ GSM.antiThunderingHerd = Just gsmAntiThunderingHerd
, GSM.candidateOverSelection = \(headers, _lst) state ->
case AF.intersectionPoint headers (csCandidate state) of
Nothing -> GSM.CandidateDoesNotIntersect
Just{} ->
GSM.WhetherCandidateIsBetter $ -- precondition requires intersection
preferAnchoredCandidate
(configBlock cfg)
headers
(csCandidate state)
, GSM.getCandidateOverSelection = do
weights <- ChainDB.getPerasWeightSnapshot chainDB
pure $ \(headers, _lst) state ->
case AF.intersectionPoint headers (csCandidate state) of
Nothing -> GSM.CandidateDoesNotIntersect
Just{} ->
GSM.WhetherCandidateIsBetter $ -- precondition requires intersection
preferAnchoredCandidate
(configBlock cfg)
(forgetFingerprint weights)
headers
(csCandidate state)
, GSM.peerIsIdle = csIdling
, GSM.durationUntilTooOld =
gsmDurationUntilTooOld
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ setupGsm isHaaSatisfied vars = do
(id, tracer)
GSM.GsmView
{ GSM.antiThunderingHerd = Nothing
, GSM.candidateOverSelection = \s (PeerState c _) -> candidateOverSelection s c
, GSM.getCandidateOverSelection = pure $ \s (PeerState c _) ->
candidateOverSelection s c
, GSM.peerIsIdle = isIdling
, GSM.durationUntilTooOld = Just durationUntilTooOld
, GSM.equivalent = (==) -- unsound, but harmless in this test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import qualified Ouroboros.Consensus.Node.GSM as GSM
import Ouroboros.Consensus.Node.Genesis (setGetLoEFragment)
import Ouroboros.Consensus.Node.GsmState
import Ouroboros.Consensus.NodeId
import Ouroboros.Consensus.Peras.Weight (emptyPerasWeightSnapshot)
import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB
import Ouroboros.Consensus.Storage.ChainDB.API (ChainDB)
import qualified Ouroboros.Consensus.Storage.ChainDB.API.Types.InvalidBlockPunishment as Punishment
Expand Down Expand Up @@ -279,7 +280,7 @@ mkGsmEntryPoints varChainSyncHandles chainDB writeGsmState =
GSM.realGsmEntryPoints
(id, nullTracer)
GSM.GsmView
{ GSM.candidateOverSelection
{ GSM.getCandidateOverSelection = pure candidateOverSelection
, GSM.peerIsIdle = csIdling
, GSM.equivalent = (==) `on` AF.headPoint
, GSM.getChainSyncStates = fmap cschState <$> cschcMap varChainSyncHandles
Expand All @@ -301,10 +302,13 @@ mkGsmEntryPoints varChainSyncHandles chainDB writeGsmState =
Just{} ->
-- precondition requires intersection
GSM.WhetherCandidateIsBetter $
preferAnchoredCandidate (configBlock cfg) selection candFrag
preferAnchoredCandidate (configBlock cfg) weights selection candFrag
where
candFrag = csCandidate candidateState

-- TODO https://github.com/tweag/cardano-peras/issues/67
weights = emptyPerasWeightSnapshot

forkGDD ::
forall m.
IOLike m =>
Expand Down
22 changes: 19 additions & 3 deletions ouroboros-consensus/bench/PerasCertDB-bench/Main.hs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}

-- | This module contains benchmarks for Peras chain weight calculation as
-- implemented by the by the
-- 'Ouroboros.Consensus.Peras.Weight.weightBoostOfFragment' function.
-- implemented in the 'Ouroboros.Consensus.Peras.Weight' module.
--
-- We benchmark the calculation on a static sequence of chain fragments of
-- increasing length, ranging from 0 to 'fragmentMaxLength', with a step size
Expand All @@ -12,13 +13,16 @@
-- with weight 'boostWeight'. All parameters are set in 'benchmarkParams'.
module Main (main) where

import Cardano.Ledger.BaseTypes.NonZero (knownNonZeroBounded)
import Data.List (iterate')
import Data.Word (Word64)
import Numeric.Natural (Natural)
import Ouroboros.Consensus.Block (PerasWeight (PerasWeight), SlotNo (..))
import Ouroboros.Consensus.Config.SecurityParam
import Ouroboros.Consensus.Peras.Weight
( PerasWeightSnapshot
, mkPerasWeightSnapshot
, takeVolatileSuffix
, weightBoostOfFragment
)
import Ouroboros.Network.AnchoredFragment qualified as AF
Expand Down Expand Up @@ -65,7 +69,11 @@ benchmarkParams =

main :: IO ()
main =
Test.Tasty.Bench.defaultMain $ map benchWeightBoostOfFragment inputs
Test.Tasty.Bench.defaultMain $
concat
[ map benchWeightBoostOfFragment inputs
, map benchTakeVolatileSuffix inputs
]
where
-- NOTE: we do not use the 'env' combinator to set up the test data since
-- it requires 'NFData' for 'AF.AnchoredFragment'. While the necessary
Expand All @@ -84,6 +92,14 @@ benchWeightBoostOfFragment (i, (weightSnapshot, fragment)) =
bench ("weightBoostOfFragment of length " <> show i) $
whnf (weightBoostOfFragment weightSnapshot) fragment

benchTakeVolatileSuffix ::
(Natural, (PerasWeightSnapshot TestBlock, AF.AnchoredFragment TestBlock)) -> Benchmark
benchTakeVolatileSuffix (i, (weightSnapshot, fragment)) =
bench ("takeVolatileSuffix of length " <> show i) $
whnf (takeVolatileSuffix weightSnapshot k) fragment
where
k = SecurityParam $ knownNonZeroBounded @2160

-- | An infinite list of chain fragments
fragments :: [AF.AnchoredFragment TestBlock]
fragments = iterate' addSuccessorBlock genesisFragment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Breaking

- Make the `ChainDB` aware of the `PerasCertDB`, and modify the chain selection function accordingly. In practice, it means that the candidate fragment is now selected based on its Peras weight, instead of its length.

Note that if Peras is disabled (which is the default), there is no observable difference.

- Add module `Ouroboros.Consensus.Peras.SelectView`, which introduces a `WeightedSelectView` to correctly measure the length of a chain fragment.
3 changes: 3 additions & 0 deletions ouroboros-consensus/ouroboros-consensus.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ library
Ouroboros.Consensus.Node.Run
Ouroboros.Consensus.Node.Serialisation
Ouroboros.Consensus.NodeId
Ouroboros.Consensus.Peras.SelectView
Ouroboros.Consensus.Peras.Weight
Ouroboros.Consensus.Protocol.Abstract
Ouroboros.Consensus.Protocol.BFT
Expand Down Expand Up @@ -278,6 +279,7 @@ library
Ouroboros.Consensus.TypeFamilyWrappers
Ouroboros.Consensus.Util
Ouroboros.Consensus.Util.AnchoredFragment
Ouroboros.Consensus.Util.AnchoredSeq
Ouroboros.Consensus.Util.Args
Ouroboros.Consensus.Util.Assert
Ouroboros.Consensus.Util.CBOR
Expand Down Expand Up @@ -905,6 +907,7 @@ benchmark PerasCertDB-bench
other-modules:
build-depends:
base,
cardano-ledger-core,
ouroboros-consensus,
ouroboros-network-api,
tasty-bench,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,42 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Ouroboros.Consensus.Config.SecurityParam (SecurityParam (..)) where
module Ouroboros.Consensus.Config.SecurityParam
( SecurityParam (..)
, maxRollbackWeight
) where

import Cardano.Binary
import Cardano.Ledger.BaseTypes.NonZero
import Data.Word
import GHC.Generics (Generic)
import NoThunks.Class (NoThunks)
import Ouroboros.Consensus.Block.SupportsPeras (PerasWeight (..))
import Quiet

-- | Protocol security parameter
--
-- We interpret this as the number of rollbacks we support.
-- In longest-chain protocols, we interpret this as the number of rollbacks we
-- support.
--
-- i.e., k == 1: we can roll back at most one block
-- k == 2: we can roll back at most two blocks, etc
--
-- NOTE: This talks about the number of /blocks/ we can roll back, not
-- the number of /slots/.
--
-- In weightiest-chain protocols (such as Ouroboros Peras), we interpret this as
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow overloading the meaning of a SecurityParameter implicitly makes me feel a bit uncomfortable. We collate the measure of "meters" with that of "grams" so to speak.

k in Praos means "count", and k in Peras means "weight", and there's an explicit relationship between them.

Do you see a way to perhaps be more explicit about this? Separating into Praos and Peras sub modules etc.

-- the maximum amount of weight we can roll back. Here, the total weight of a
-- chain (fragment) is defined to be its length plus the sum of all weight
-- boosts given to some of its blocks on the chain (fragment).
--
-- i.e. k == 30: we can roll back at most 30 unweighted blocks, or two blocks
-- each having additional weight 14. In the latter case, the chain fragment has
-- total weight @2 + 2 * 14 = 30@.
newtype SecurityParam = SecurityParam {maxRollbacks :: NonZero Word64}
deriving (Eq, Generic, NoThunks, ToCBOR, FromCBOR)
deriving Show via Quiet SecurityParam

-- | The maximum amount of weight we can roll back.
maxRollbackWeight :: SecurityParam -> PerasWeight
maxRollbackWeight = PerasWeight . unNonZero . maxRollbacks
Comment on lines +43 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just seen it used in takeVolatileSuffix. Upon reflection I'm thinking that perhaps maxRollbackWeight to be in Peras/SecurityParameter.hs and the Peras spin on the documentation attached to the SecurityParameter here move to the new module and attached to maxRollbackWeight

Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module Ouroboros.Consensus.Fragment.Diff
import Data.Word (Word64)
import GHC.Stack (HasCallStack)
import Ouroboros.Consensus.Block
import Ouroboros.Consensus.Peras.Weight
import Ouroboros.Network.AnchoredFragment
( AnchoredFragment
, AnchoredSeq (..)
Expand Down Expand Up @@ -73,12 +74,31 @@ getTip = castPoint . AF.headPoint . getSuffix
getAnchorPoint :: ChainDiff b -> Point b
getAnchorPoint = castPoint . AF.anchorPoint . getSuffix

-- | Return 'True' iff applying the 'ChainDiff' to a chain @C@ will result in
-- a chain shorter than @C@, i.e., the number of blocks to roll back is
-- greater than the length of the new elements in the suffix to add.
rollbackExceedsSuffix :: HasHeader b => ChainDiff b -> Bool
rollbackExceedsSuffix (ChainDiff nbRollback suffix) =
nbRollback > fromIntegral (AF.length suffix)
-- | Return 'True' iff applying the 'ChainDiff' to the given chain @C@ will
-- result in a chain with less weight than @C@, i.e., the suffix of @C@ to roll
-- back has more weight than suffix is adding.
rollbackExceedsSuffix ::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms keeping Peras/Praos line explicit, wdyt about having

forall b1 b2.
  ( HasHeader b1
  , HasHeader b2
  ) =>
  (AnchoredFragment b1 -> s) ->
  AnchoredFragment b1 ->
  ChainDiff b2 ->
  Bool
rollbackExceedsSuffix summarize curChain (ChainDiff nbRollback suffix) =
  summarize suffixToRollBack > summarize suffix
 where
  suffixToRollBack = AF.anchorNewest nbRollback curChain

Just thinking how to move direct mentions of Peras closer to the call sites.

forall b0 b1 b2.
( HasHeader b0
, HasHeader b1
, HasHeader b2
, HeaderHash b0 ~ HeaderHash b1
, HeaderHash b0 ~ HeaderHash b2
) =>
PerasWeightSnapshot b0 ->
-- | The chain @C@ the diff is applied to.
AnchoredFragment b1 ->
ChainDiff b2 ->
Bool
rollbackExceedsSuffix weights curChain (ChainDiff nbRollback suffix) =
weightOf suffixToRollBack > weightOf suffix
where
suffixToRollBack = AF.anchorNewest nbRollback curChain

weightOf ::
(HasHeader b, HeaderHash b ~ HeaderHash b0) =>
AnchoredFragment b -> PerasWeight
weightOf = totalWeightOfFragment weights

{-------------------------------------------------------------------------------
Constructors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ module Ouroboros.Consensus.Fragment.ValidatedDiff
, getChainDiff
, getLedger
, new
, rollbackExceedsSuffix
, toValidatedFragment

-- * Monadic
Expand Down Expand Up @@ -96,9 +95,6 @@ toValidatedFragment ::
toValidatedFragment (UnsafeValidatedChainDiff cs l) =
VF.ValidatedFragment (Diff.getSuffix cs) l

rollbackExceedsSuffix :: HasHeader b => ValidatedChainDiff b l -> Bool
rollbackExceedsSuffix = Diff.rollbackExceedsSuffix . getChainDiff

{-------------------------------------------------------------------------------
Monadic
-------------------------------------------------------------------------------}
Expand Down
Loading
Loading