@@ -43,7 +43,7 @@ import Data.List.NonEmpty (NonEmpty)
4343import qualified Data.List.NonEmpty as NE
4444import Data.Map.Strict (Map )
4545import qualified Data.Map.Strict as Map
46- import Data.Maybe (mapMaybe , maybeToList )
46+ import Data.Maybe (maybeToList )
4747import Data.Maybe.Strict (StrictMaybe )
4848import Data.Word (Word64 )
4949import Ouroboros.Consensus.Block
@@ -255,16 +255,41 @@ sharedCandidatePrefix curChain candidates =
255255 immutableTip = AF. anchorPoint curChain
256256
257257 splitAfterImmutableTip (peer, frag) =
258- (,) peer . snd <$> AF. splitAfterPoint frag immutableTip
258+ case AF. splitAfterPoint frag immutableTip of
259+ -- When there is no intersection, we assume the candidate fragment is
260+ -- empty and anchored at the immutable tip.
261+ -- See Note [CSJ truncates the candidate fragments].
262+ Nothing -> (peer, AF. takeOldest 0 curChain)
263+ Just (_, suffix) -> (peer, suffix)
259264
260265 immutableTipSuffixes =
261- -- If a ChainSync client's candidate forks off before the
262- -- immutable tip, then this transaction is currently winning an
263- -- innocuous race versus the thread that will fatally raise
264- -- 'InvalidIntersection' within that ChainSync client, so it's
265- -- sound to pre-emptively discard their candidate from this
266- -- 'Map' via 'mapMaybe'.
267- mapMaybe splitAfterImmutableTip candidates
266+ map splitAfterImmutableTip candidates
267+
268+ -- Note [CSJ truncates the candidate fragments]
269+ -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
270+ --
271+ -- Before CSJ, only rollback could cause truncation of a candidate fragment.
272+ -- Truncation is a serious business to GDD because the LoE might have allowed
273+ -- the selection to advance, based on the tips of the candidate fragments.
274+ --
275+ -- Truncating a candidate fragment risks moving the LoE back, which could be
276+ -- earlier than the anchor of the latest selection. When rollbacks where the
277+ -- only mechanism to truncate, it was fine to ignore candidate fragments that
278+ -- don't intersect with the current selection. This could only happen if the
279+ -- peer is rolling back more than k blocks, which is dishonest behavior.
280+ --
281+ -- With CSJ, however, the candidate fragments can recede without a rollback.
282+ -- A former objector might be asked to jump back when it becomes a jumper again.
283+ -- The jump point might still be a descendent of the immutable tip. But by the
284+ -- time the jump is accepted, the immutable tip might have advanced, and the
285+ -- candidate fragment of the otherwise honest peer might be ignored by GDD.
286+ --
287+ -- Therefore, at the moment, when there is no intersection with the current
288+ -- selection, the GDD assumes that the candidate fragment is empty and anchored
289+ -- at the immutable tip. It is the job of the ChainSync client to update the
290+ -- candidate fragment so it intersects with the selection or to disconnect the
291+ -- peer if no such fragment can be established.
292+ --
268293
269294data DensityBounds blk =
270295 DensityBounds {
0 commit comments