Skip to content

Commit b07a86e

Browse files
committed
Ensure that compared fragments always intersect
This is needed for weighted chain comparisons.
1 parent 2098537 commit b07a86e

File tree

3 files changed

+50
-17
lines changed

3 files changed

+50
-17
lines changed

ouroboros-network-api/src/Ouroboros/Network/BlockFetch/ConsensusInterface.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ data ChainComparison header =
201201
-- This is used as part of selecting which chains to prioritise for
202202
-- downloading block bodies.
203203
--
204+
-- PRECONDITION: The two fragments must intersect.
205+
--
204206
compareCandidateChains :: HasCallStack
205207
=> AnchoredFragment header
206208
-> AnchoredFragment header

ouroboros-network/src/Ouroboros/Network/BlockFetch/Decision.hs

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import Data.Set qualified as Set
3333
import Data.Function (on)
3434
import Data.Hashable
3535
import Data.List as List (foldl', groupBy, sortBy, transpose)
36-
import Data.Maybe (mapMaybe)
36+
import Data.Maybe (fromMaybe, mapMaybe)
3737
import Data.Set (Set)
3838

3939
import Control.Exception (assert)
@@ -473,8 +473,19 @@ empty fetch range, but this is ok since we never request empty ranges.
473473
--
474474
-- A 'ChainSuffix' must be non-empty, as an empty suffix, i.e. the candidate
475475
-- chain is equal to the current chain, would not be a plausible candidate.
476-
newtype ChainSuffix header =
477-
ChainSuffix { getChainSuffix :: AnchoredFragment header }
476+
--
477+
-- Additionally, we store the full candidate (with the same anchor as our
478+
-- current chain), as this is needed for comparing different candidates via
479+
-- 'compareCandidateChains'.
480+
data ChainSuffix header = ChainSuffix {
481+
-- | The suffix of the candidate after the intersection with the current
482+
-- chain.
483+
getChainSuffix :: !(AnchoredFragment header),
484+
-- | The full candidate, characterized by having the same tip as
485+
-- 'getChainSuffix' and the same anchor as our current chain. In particular,
486+
-- 'getChainSuffix' is a suffix of 'getFullCandidate'.
487+
getFullCandidate :: !(AnchoredFragment header)
488+
}
478489

479490
{-
480491
We define the /chain suffix/ as the suffix of the candidate chain up until (but
@@ -511,25 +522,31 @@ interested in this candidate at all.
511522
-- current chain.
512523
--
513524
chainForkSuffix
514-
:: (HasHeader header, HasHeader block,
515-
HeaderHash header ~ HeaderHash block)
516-
=> AnchoredFragment block -- ^ Current chain.
517-
-> AnchoredFragment header -- ^ Candidate chain
525+
:: HasHeader header
526+
=> AnchoredFragment header
527+
-> AnchoredFragment header
518528
-> Maybe (ChainSuffix header)
519529
chainForkSuffix current candidate =
520530
case AF.intersect current candidate of
521531
Nothing -> Nothing
522-
Just (_, _, _, candidateSuffix) ->
532+
Just (currentChainPrefix, _, _, candidateSuffix) ->
523533
-- If the suffix is empty, it means the candidate chain was equal to
524534
-- the current chain and didn't fork off. Such a candidate chain is
525535
-- not a plausible candidate, so it must have been filtered out.
526536
assert (not (AF.null candidateSuffix)) $
527-
Just (ChainSuffix candidateSuffix)
537+
Just ChainSuffix {
538+
getChainSuffix = candidateSuffix,
539+
getFullCandidate = fullCandidate
540+
}
541+
where
542+
fullCandidate =
543+
fromMaybe (error "invariant violation of AF.intersect") $
544+
AF.join currentChainPrefix candidateSuffix
545+
528546

529547
selectForkSuffixes
530-
:: (HasHeader header, HasHeader block,
531-
HeaderHash header ~ HeaderHash block)
532-
=> AnchoredFragment block
548+
:: HasHeader header
549+
=> AnchoredFragment header
533550
-> [(FetchDecision (AnchoredFragment header), peerinfo)]
534551
-> [(FetchDecision (ChainSuffix header), peerinfo)]
535552
selectForkSuffixes current chains =
@@ -743,7 +760,11 @@ prioritisePeerChains FetchModeDeadline salt compareCandidateChains blockFetchSiz
743760
(equatingPair
744761
-- compare on probability band first, then preferred chain
745762
(==)
746-
(equateCandidateChains `on` getChainSuffix)
763+
-- Precondition of 'compareCandidateChains' (used by
764+
-- 'equateCandidateChains') is fulfilled as all
765+
-- 'getFullCandidate's intersect pairwise (due to having the
766+
-- same anchor as our current chain).
767+
(equateCandidateChains `on` getFullCandidate)
747768
`on`
748769
(\(band, chain, _fragments) -> (band, chain)))))
749770
. sortBy (descendingOrder
@@ -752,7 +773,10 @@ prioritisePeerChains FetchModeDeadline salt compareCandidateChains blockFetchSiz
752773
(comparingPair
753774
-- compare on probability band first, then preferred chain
754775
compare
755-
(compareCandidateChains `on` getChainSuffix)
776+
-- Precondition of 'compareCandidateChains' is fulfilled as
777+
-- all 'getFullCandidate's intersect pairwise (due to
778+
-- having the same anchor as our current chain).
779+
(compareCandidateChains `on` getFullCandidate)
756780
`on`
757781
(\(band, chain, _fragments) -> (band, chain))))))
758782
. map annotateProbabilityBand
@@ -776,7 +800,7 @@ prioritisePeerChains FetchModeDeadline salt compareCandidateChains blockFetchSiz
776800
| EQ <- compareCandidateChains chain1 chain2 = True
777801
| otherwise = False
778802

779-
chainHeadPoint (_,ChainSuffix c,_) = AF.headPoint c
803+
chainHeadPoint (_,ChainSuffix {getChainSuffix = c},_) = AF.headPoint c
780804

781805
prioritisePeerChains FetchModeBulkSync salt compareCandidateChains blockFetchSize =
782806
map (\(decision, peer) ->
@@ -785,7 +809,11 @@ prioritisePeerChains FetchModeBulkSync salt compareCandidateChains blockFetchSiz
785809
(comparingRight
786810
(comparingPair
787811
-- compare on preferred chain first, then duration
788-
(compareCandidateChains `on` getChainSuffix)
812+
--
813+
-- Precondition of 'compareCandidateChains' is fulfilled as
814+
-- all 'getFullCandidate's intersect pairwise (due to having
815+
-- the same anchor as our current chain).
816+
(compareCandidateChains `on` getFullCandidate)
789817
compare
790818
`on`
791819
(\(duration, chain, _fragments) -> (chain, duration)))))

ouroboros-network/src/Ouroboros/Network/BlockFetch/Decision/Genesis.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,13 +462,16 @@ selectTheCandidate
462462
case inRace of
463463
[] -> pure Nothing
464464
_ : _ -> do
465+
-- Precondition of 'compareCandidateChains' is fulfilled as all
466+
-- 'getFullCandidate's intersect pairwise (due to having the same
467+
-- anchor as our current chain).
465468
let maxChainOn f c0 c1 = case compareCandidateChains (f c0) (f c1) of
466469
LT -> c1
467470
_ -> c0
468471
-- maximumBy yields the last element in case of a tie while we
469472
-- prefer the first one
470473
chainSfx = fst $
471-
List.foldl1' (maxChainOn (getChainSuffix . fst)) inRace
474+
List.foldl1' (maxChainOn (getFullCandidate . fst)) inRace
472475
pure $ Just (chainSfx, inRace)
473476

474477
-- | Given _the_ candidate fragment to sync from, and a list of peers (with

0 commit comments

Comments
 (0)