Skip to content

Commit 7449d2f

Browse files
Amend hashForkNo and document fork number (#22)
# Description This PR fixes `hashForkNo` as previous behavior uniquely identified branches by fork number and the new parameterized implementation did not. This seems relevant because otherwise `BlockTreeBranch`es forking from the same block in the trunk would end up with the same fork number. As it turns out, these _fork numbers_ are not related to the order in which forking takes place. Related documentation was added. This seem to be related to an apparent mismatch. On the one hand, the `Test.Util.TestBlock.TestHash` documentation states that multiple branches can steem from a single node in a tree and _suggests_ that the fork number indexes branches in an orderly way. On the other hand, the implementation of `GenesisTest` generation in `Test.Consensus.Genesis.Setup.GenChains` assigns fork numbers to branches in an arbitrary way.
1 parent 8dbfbed commit 7449d2f

File tree

2 files changed

+44
-37
lines changed

2 files changed

+44
-37
lines changed

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/Genesis/Setup/GenChains.hs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ genHonestChainSchema = do
6868

6969
-- | Random generator for one alternative chain schema forking off a given
7070
-- honest chain schema. The alternative chain schema is returned as the pair of
71-
-- a slot number on the honest chain schema and a list of active slots.
71+
-- a block number (representing the common prefix block count) on the honest
72+
-- chain schema and a list of active slots.
7273
--
73-
-- REVIEW: Use 'SlotNo' instead of 'Int'?
74+
-- REVIEW: Use 'BlockNo' instead of 'Int'?
7475
genAlternativeChainSchema :: (H.HonestRecipe, H.ChainSchema base hon) -> QC.Gen (Int, [S])
7576
genAlternativeChainSchema (testRecipeH, arHonest) =
7677
unsafeMapSuchThatJust $ do
@@ -153,6 +154,11 @@ genChainsWithExtraHonestPeers genNumExtraHonest genNumForks = do
153154
-- those values for individual tests?
154155
-- Also, we might want to generate these randomly.
155156
gtCSJParams = CSJParams $ fromIntegral scg,
157+
-- The generated alternative chains (branches added to the @goodChain@)
158+
-- can end up having the same /common prefix count/, meaning they fork
159+
-- at the same block. Note that the assigned fork number has no relation
160+
-- with the order in which the branching happens, rather it is just a
161+
-- means to tag branches.
156162
gtBlockTree = List.foldl' (flip BT.addBranch') (BT.mkTrunk goodChain) $ zipWith (genAdversarialFragment goodBlocks) [1..] alternativeChainSchemas,
157163
gtExtraHonestPeers,
158164
gtSchedule = ()
@@ -170,6 +176,9 @@ genChainsWithExtraHonestPeers genNumExtraHonest genNumForks = do
170176
mkTestFragment =
171177
AF.fromNewestFirst AF.AnchorGenesis
172178

179+
-- Cons new blocks acoording to the given active block schema
180+
-- and mark the first with the corresponding fork number; the
181+
-- next blocks get a zero fork number.
173182
mkTestBlocks :: [blk] -> [S] -> Int -> [blk]
174183
mkTestBlocks pre active forkNo =
175184
fst (List.foldl' folder ([], 0) active)

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/PeerSimulator/StateDiagram.hs

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,15 @@ import Control.Monad.State.Strict (State, gets, modify', runState,
3131
state)
3232
import Control.Tracer (Tracer (Tracer), debugTracer, traceWith)
3333
import Data.Bifunctor (first)
34-
import Data.Bool (bool)
3534
import Data.Foldable as Foldable (foldl', foldr')
3635
import Data.List (intersperse, mapAccumL, sort, transpose)
3736
import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty, (<|))
3837
import qualified Data.List.NonEmpty as NonEmpty
3938
import Data.Map (Map)
40-
import qualified Data.Map as M
4139
import Data.Map.Strict ((!?))
4240
import qualified Data.Map.Strict as Map
4341
import Data.Maybe (fromMaybe, mapMaybe)
44-
import Data.Monoid (Any (..), Sum (..))
45-
import qualified Data.Set as S
42+
import Data.Monoid (First (..))
4643
import Data.String (IsString (fromString))
4744
import Data.Vector (Vector)
4845
import qualified Data.Vector as Vector
@@ -61,7 +58,7 @@ import Ouroboros.Network.AnchoredFragment (anchor, anchorToSlotNo)
6158
import qualified Ouroboros.Network.AnchoredFragment as AF
6259
import Ouroboros.Network.Block (HeaderHash)
6360
import Test.Consensus.BlockTree (BlockTree (btBranches, btTrunk),
64-
BlockTreeBranch (btbPrefix, btbSuffix), deforestBlockTree,
61+
BlockTreeBranch (btbSuffix), deforestBlockTree,
6562
prettyBlockTree)
6663
import Test.Consensus.PointSchedule.NodeState (NodeState (..),
6764
genesisNodeState)
@@ -359,41 +356,42 @@ initSlots lastSlot (Range l u) blocks =
359356
mkSlot num capacity =
360357
Slot {num = At num, capacity, aspects = []}
361358

359+
-- | Get the fork number of the 'BlockTreeBranch' a block is on. /Some/ fork
360+
-- numbers are generated during the creation of the test 'BlockTree' in
361+
-- 'Test.Consensus.Genesis.Setup.GenChains.genChainsWithExtraHonestPeers'.
362+
-- There, for 'TestBlock's, these fork numbers are stored in the 'TestHash'
363+
-- by the 'IssueTestBlock' operations.
364+
-- Here, new fork numbers are created so that the pretty printing machinery
365+
-- works independently of the block type; this poses no problem because the
366+
-- exact fork numbers stored in 'TestBlock's are irrelevant as long as they
367+
-- uniquely determine each 'BlockTreeBranch'.
368+
--
369+
-- POSTCONDITION: All blocks on the same branch suffix share fork number.
370+
-- POSTCONDITION: Each 'BlockTreeBranch' has a distinct fork number.
362371
hashForkNo :: AF.HasHeader blk => BlockTree blk -> HeaderHash blk -> Word64
363372
hashForkNo bt hash =
364-
let forkPoints =
365-
-- The set of forking nodes. We can count how many of these are in our
366-
-- ancestry to determine where we might have forked.
367-
S.fromList $ do
368-
btb <- btBranches bt
369-
pure $ AF.headHash $ btbPrefix btb
370-
forkFirstBlocks =
371-
-- The set of forked nodes. If any of these is in our ancestry, we are
372-
-- not on the trunk.
373-
S.fromList $ do
374-
btb <- btBranches bt
375-
pure $ either AF.anchorToHash (BlockHash . blockHash) . AF.last $ btbSuffix btb
373+
let forkFirstBlocks =
374+
-- A map assigning numbers to forked nodes. If any of these is in our
375+
-- ancestry, we are on a branch and have a fork number.
376+
Map.fromList $ do
377+
-- `btBranches` are not sorted in a meaningful way, so the fork
378+
-- numbers assigned here are meant only to distinguish them.
379+
(btb, ix) <- zip (btBranches bt) [1..]
380+
-- The first block in a branch is the /last/ (i.e. leftmost or oldest) one.
381+
-- See the documentation of `Test.Util.TestBlock.TestHash`
382+
-- in relation to this order.
383+
let firstBlockHash = either AF.anchorToHash (BlockHash . blockHash) . AF.last $ btbSuffix btb
384+
pure $ (firstBlockHash, ix)
385+
blockAncestry = foldMap AF.toNewestFirst $ Map.lookup hash $ deforestBlockTree bt
376386
in
377-
case
378-
-- Fold over each block in the ancestry. Add up how many of them are in
379-
-- forkPoints, and determine whether any are in forkFirstBlocks.
380-
flip foldMap (
381-
maybe mempty AF.toOldestFirst $
382-
M.lookup hash $ deforestBlockTree bt
383-
)
384-
$ \blk ->
385-
let h = BlockHash $ blockHash blk
386-
in ( Any $ S.member h forkFirstBlocks
387-
, Sum $ bool 0 1 $ S.member h forkPoints
388-
)
389-
390-
of
391-
(Any True, Sum x) -> x
392-
(Any False, _) -> 0
387+
-- Get the fork number of the most recent forked node in the ancestry.
388+
fromMaybe 0 $ getFirst $ flip foldMap blockAncestry $
389+
\blk -> First $ let h = BlockHash $ blockHash blk
390+
in Map.lookup h forkFirstBlocks
393391

394392
blockForkNo :: AF.HasHeader blk => BlockTree blk -> ChainHash blk -> Word64
395-
blockForkNo pxy = \case
396-
BlockHash h -> hashForkNo pxy h
393+
blockForkNo bt = \case
394+
BlockHash h -> hashForkNo bt h
397395
_ -> 0
398396

399397
initBranch :: forall blk. (GetHeader blk, AF.HasHeader blk) => BlockTree blk -> Int -> Range -> AF.AnchoredFragment blk -> BranchSlots blk

0 commit comments

Comments
 (0)