Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

Commit 1151cc2

Browse files
committed
[WIP] first bits for header validation
1 parent e5a9226 commit 1151cc2

File tree

2 files changed

+60
-6
lines changed

2 files changed

+60
-6
lines changed

src/Chainweb/BlockHeader/Validation.hs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ instance Show ValidationFailure where
315315
InvalidFeatureFlags -> "The block has an invalid feature flag value"
316316
InvalidBraiding -> "The block is not braided correctly into the chainweb"
317317
InvalidAdjacentVersion -> "An adjancent parent has a chainweb version that does not match the version of the validated header"
318+
IncorrectForkNumber -> "The block has an incorrect fork number"
319+
InvalidForkVotes -> "The block has an invalid fork vote count"
318320

319321
-- | An enumeration of possible validation failures for a block header.
320322
--
@@ -374,13 +376,29 @@ data ValidationFailureType
374376
| InvalidAdjacentVersion
375377
-- ^ An adjacent parent has chainweb version that does not match the
376378
-- version of the validated header.
379+
| IncorrectForkNumber
380+
-- ^ The block has an incorrect fork number. At the beginning of a fork
381+
-- epoch the fork number is the fork number of the parent plus one if
382+
-- the fork vote count of the parent is at least 2/3 of the total number
383+
-- of blocks in a fork epoch. Otherwise the number must be equal to the
384+
-- fork number of the parent. Note, that the fork number is determined
385+
-- deterministically from the parent block. It increases monotonically
386+
-- at most once per fork epoch.
387+
| InvalidForkVotes
388+
-- ^ The block has an invalid fork vote count. At the beginning of an
389+
-- fork epoch the fork vote count must be zero. Otherwise, the fork vote
390+
-- count must be equal to the fork vote of the parent block or one more
391+
-- than that. The fork vote count increases monotonically within an fork
392+
-- epoch in steps of zero or one. The step size at each block is
393+
-- non-deterministic. It is reset to zero at the beginning of a new fork
394+
-- epoch.
377395
deriving (Show, Eq, Ord)
378396

379397
instance Exception ValidationFailure
380398

381399
-- | The list of validation failures that are definite and independent of any
382400
-- external context. A block for which validation fails with one of these
383-
-- failures must be dicarded.
401+
-- failures must be discarded.
384402
--
385403
-- No node on the chainweb-web network should propgate blocks with these
386404
-- failures. If a block is received that causes a definite validation failures
@@ -401,6 +419,8 @@ definiteValidationFailures =
401419
, IncorrectGenesisParent
402420
, IncorrectGenesisTarget
403421
, IncorrectPayloadHash
422+
, IncorrectForkNumber
423+
, InvalidForkVotes
404424
]
405425

406426
-- | Predicate that checks whether a validation failure is definite.
@@ -631,6 +651,7 @@ validateIntrinsic t b = concat
631651
, [ BlockInTheFuture | not (prop_block_current t b)]
632652
, [ InvalidFeatureFlags | not (prop_block_featureFlags b)]
633653
, [ AdjacentChainMismatch | not (prop_block_adjacent_chainIds b) ]
654+
, [ InvalidForkVotes | not (prop_block_forkVotesReset b) ]
634655
]
635656

636657
-- | Validate properties of a block with respect to a given parent.
@@ -653,6 +674,8 @@ validateInductiveChainStep s = concat
653674
, [ VersionMismatch | not (prop_block_chainwebVersion s) ]
654675
, [ IncorrectWeight | not (prop_block_weight s) ]
655676
, [ ChainMismatch | not (prop_block_chainId s) ]
677+
, [ InvalidForkVotes | not (prop_block_forkVotesIncrement s) ]
678+
, [ IncorrectForkNumber | not (prop_block_forkNumber s)
656679
]
657680

658681
validateInductiveWebStep
@@ -718,6 +741,11 @@ prop_block_adjacent_chainIds b
718741
| isGenesisBlockHeader b = _chainGraph b
719742
| otherwise = chainGraphAt (_chainwebVersion b) (view blockHeight b - 1)
720743

744+
prop_block_forkVotesReset :: BlockHeader -> Bool
745+
prop_block_forkVotesReset b
746+
| isForkEpochStartBlockHeader b = view blockForkVotes b == 0
747+
| otherwise = True
748+
721749
-- -------------------------------------------------------------------------- --
722750
-- Inductive BlockHeader Properties
723751
-- -------------------------------------------------------------------------- --
@@ -745,6 +773,14 @@ prop_block_chainId :: ChainStep -> Bool
745773
prop_block_chainId (ChainStep (ParentHeader p) b)
746774
= view blockChainId p == view blockChainId b
747775

776+
prop_block_forkVotesIncrement :: ChainStep -> Bool
777+
prop_block_forkVotesIncrement (ChainStep (ParentHeader p) b)
778+
| isForkEpochStartBlockHeader b = votes == 0
779+
| otherwise = fv == parentVotes || votes == parentVotes + 1
780+
where
781+
votes = view blockForkVotes b
782+
parentVotes = view blockForkVotes p
783+
748784
-- -------------------------------------------------------------------------- --
749785
-- Multi chain inductive properties
750786

src/Chainweb/ForkState.hs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{-# LANGUAGE ImportQualifiedPost #-}
22

33
-- |
4-
-- Module: Chainweb.ForkNumber
4+
-- Module: Chainweb.ForkState
55
-- Copyright: Copyright © 2025 Kadena LLC.
66
-- License: MIT
77
-- Maintainer: Lars Kuhtz <lars@kadena.io>
@@ -46,9 +46,12 @@ module Chainweb.ForkState
4646
-- epochs between the activation of two successive forks.
4747
--
4848
-- Starting with chainweb-node version 2.33, a node must accept blocks that have
49-
-- either the fork number of the block height of the block or the respetive fork
50-
-- number plus one. It must reject all other blocks. The fork number at the time
51-
-- that chainweb node version 2.33 is released is zero.
49+
-- a fork number equal to the current fork number and a fork vote count that is
50+
-- either the fork vote count of the previous block or that number plus one. It
51+
-- must reject all other blocks. The fork number at the time that chainweb node
52+
-- version 2.33 is released is zero. That version does not increase the vote
53+
-- count, so the vote count will be zero as long as no other version is
54+
-- released that proposes a fork.
5255
--
5356
newtype ForkState = ForkState { _forkState :: Word64 }
5457
deriving (Eq, Ord, Generic)
@@ -87,6 +90,7 @@ forkVotes = forkState . forkVotes'
8790

8891
forkEpochLength :: Natural
8992
forkEpochLength = 120 * 120 -- 5 days
93+
forkEpochLength = 120 * 120
9094

9195
isForkEpoch :: BlockHeader -> Bool
9296
isForkEpoch hdr = mod (view blockHeight hdr) forkEpochLength == 0
@@ -100,7 +104,7 @@ getForkNumberVotes = view signaledNumber . view forkState
100104
validateForkNumber :: Parent BlockHeader -> BlockHeader -> Bool
101105
validateForkNumber (Parent parent) hdr
102106
| isForkEpoch hdr =
103-
if view getForkNumberVotes parent >= (forkEpochLength * 2 `div 3)
107+
if view getForkNumberVotes parent >= ((forkEpochLength * 2) `div` 3)
104108
then
105109
view getForkNumber hdr == view getForkNumber parent + 1
106110
&& view getForkNumberVotes hdr <= 1
@@ -111,3 +115,17 @@ validateForkNumber (Parent parent) hdr
111115
view getForkNumber hdr == view getForkNumber (parent hdr)
112116
&& view getForkNumberVotes hdr <= view getForkNumberVotes (parent hdr) + 1
113117

118+
-- -------------------------------------------------------------------------- --
119+
-- Details
120+
--
121+
-- Votes:
122+
--
123+
-- * Voting is done per chain over a fork epoch mines a DA epoch.
124+
-- * The last epoch in a fork epoch no voting is done. Instead the votes are
125+
-- aggregated over all chains, by computing the global average:
126+
--
127+
-- * Use a quantized average consensus algorithm to compute the average vote.
128+
-- * Use 1000 quantization levels.
129+
-- * In each step use integer division with biased rounding.
130+
-- * At the end roude to nearest integer using bankers rounding.
131+
--

0 commit comments

Comments
 (0)