@@ -51,19 +51,23 @@ import NoThunks.Class (unsafeNoThunks)
5151import Ouroboros.Consensus.Block
5252import Ouroboros.Consensus.Config
5353import Ouroboros.Consensus.Forecast
54+ import Ouroboros.Consensus.HardFork.History (PastHorizonException (PastHorizon ))
5455import Ouroboros.Consensus.HeaderStateHistory
5556 (HeaderStateHistory (.. ), validateHeader )
5657import qualified Ouroboros.Consensus.HeaderStateHistory as HeaderStateHistory
5758import Ouroboros.Consensus.HeaderValidation hiding (validateHeader )
59+ import Ouroboros.Consensus.Ledger.Basics (LedgerState )
5860import Ouroboros.Consensus.Ledger.Extended
5961import Ouroboros.Consensus.Ledger.SupportsProtocol
62+ import qualified Ouroboros.Consensus.MiniProtocol.ChainSync.Client.InFutureCheck as InFutureCheck
6063import Ouroboros.Consensus.Node.NetworkProtocolVersion
6164import Ouroboros.Consensus.Protocol.Abstract
6265import Ouroboros.Consensus.Storage.ChainDB (ChainDB ,
6366 InvalidBlockReason )
6467import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB
6568import Ouroboros.Consensus.Util
6669import Ouroboros.Consensus.Util.Assert (assertWithMsg )
70+ import qualified Ouroboros.Consensus.Util.EarlyExit as EarlyExit
6771import Ouroboros.Consensus.Util.IOLike
6872import Ouroboros.Consensus.Util.STM (Fingerprint , Watcher (.. ),
6973 WithFingerprint (.. ), withWatcher )
@@ -426,13 +430,20 @@ chainSyncClient
426430 => MkPipelineDecision
427431 -> Tracer m (TraceChainSyncClientEvent blk )
428432 -> TopLevelConfig blk
433+ -> InFutureCheck. HeaderInFutureCheck m blk
429434 -> ChainDbView m blk
430435 -> NodeToNodeVersion
431436 -> ControlMessageSTM m
432437 -> HeaderMetricsTracer m
433438 -> StrictTVar m (AnchoredFragment (Header blk ))
434439 -> Consensus ChainSyncClientPipelined blk m
435440chainSyncClient mkPipelineDecision0 tracer cfg
441+ InFutureCheck. HeaderInFutureCheck
442+ { handleHeaderArrival
443+ , judgeHeaderArrival
444+ , proxyArrival = Proxy :: Proxy arrival
445+ , recordHeaderArrival
446+ }
436447 ChainDbView
437448 { getCurrentChain
438449 , getHeaderStateHistory
@@ -706,104 +717,151 @@ chainSyncClient mkPipelineDecision0 tracer cfg
706717 (ClientPipelinedStIdle n )
707718 rollForward mkPipelineDecision n hdr theirTip
708719 = Stateful $ \ kis -> traceException $ do
709- now <- getMonotonicTime
710- let hdrPoint = headerPoint hdr
711-
712- isInvalidBlock <- atomically $ forgetFingerprint <$> getIsInvalidBlock
713- let disconnectWhenInvalid = \ case
714- GenesisHash -> pure ()
715- BlockHash hash ->
720+ arrival <- recordHeaderArrival hdr
721+ now <- getMonotonicTime
722+ let hdrPoint = headerPoint hdr
723+ slotNo = blockSlot hdr
724+
725+ do
726+ let scrutinee =
727+ case isPipeliningEnabled version of
728+ NotReceivingTentativeBlocks -> BlockHash (headerHash hdr)
729+ -- Disconnect if the parent block of `hdr` is known to be invalid.
730+ ReceivingTentativeBlocks -> headerPrevHash hdr
731+ case scrutinee of
732+ GenesisHash -> return ()
733+ BlockHash hash -> do
734+ -- If the peer is sending headers quickly, the
735+ -- @invalidBlockWatcher@ might miss one. So this call is a
736+ -- lightweight supplement. Note that neither check /must/ be 100%
737+ -- reliable.
738+ isInvalidBlock <- atomically $ forgetFingerprint <$> getIsInvalidBlock
716739 whenJust (isInvalidBlock hash) $ \ reason ->
717740 disconnect $ InvalidBlock hdrPoint hash reason
718- disconnectWhenInvalid $
719- case isPipeliningEnabled version of
720- -- Disconnect if the parent block of `hdr` is known to be invalid.
721- ReceivingTentativeBlocks -> headerPrevHash hdr
722- NotReceivingTentativeBlocks -> BlockHash (headerHash hdr)
723-
724- -- Get the ledger view required to validate the header
725- -- NOTE: This will block if we are too far behind.
726- intersectCheck <- atomically $ do
727- -- Before obtaining a 'LedgerView', we must find the most recent
728- -- intersection with the current chain. Note that this is cheap when
729- -- the chain and candidate haven't changed.
741+
742+ mLedgerView <- EarlyExit. withEarlyExit $ do
743+ Intersects kis2 lst <- checkArrivalTime kis arrival
744+ Intersects kis3 ledgerView <- case projectLedgerView slotNo lst of
745+ Just ledgerView -> pure $ Intersects kis2 ledgerView
746+ Nothing -> readLedgerState kis2 (projectLedgerView slotNo)
747+ pure $ Intersects kis3 ledgerView
748+
749+ case mLedgerView of
750+
751+ Nothing -> do
752+ -- The above computation exited early, which means our chain (tip)
753+ -- has changed and it no longer intersects with the candidate
754+ -- fragment, so we have to find a new intersection. But first drain
755+ -- the pipe.
756+ continueWithState ()
757+ $ drainThePipe n
758+ $ findIntersection NoMoreIntersection
759+
760+ Just (Intersects kis' ledgerView) -> do
761+ -- Our chain still intersects with the candidate fragment and we
762+ -- have obtained a 'LedgerView' that we can use to validate @hdr@.
763+ let KnownIntersectionState {
764+ ourFrag
765+ , theirFrag
766+ , theirHeaderStateHistory
767+ , mostRecentIntersection
768+ } = kis'
769+
770+ -- Validate header
771+ theirHeaderStateHistory' <-
772+ case runExcept $ validateHeader cfg ledgerView hdr theirHeaderStateHistory of
773+ Right theirHeaderStateHistory' -> return theirHeaderStateHistory'
774+ Left vErr ->
775+ disconnect $
776+ HeaderError hdrPoint vErr (ourTipFromChain ourFrag) theirTip
777+
778+ let theirFrag' = theirFrag :> hdr
779+ -- Advance the most recent intersection if we have the same
780+ -- header on our fragment too. This is cheaper than recomputing
781+ -- the intersection from scratch.
782+ mostRecentIntersection'
783+ | Just ourSuccessor <-
784+ AF. successorBlock (castPoint mostRecentIntersection) ourFrag
785+ , headerHash ourSuccessor == headerHash hdr
786+ = headerPoint hdr
787+ | otherwise
788+ = mostRecentIntersection
789+ kis'' = assertKnownIntersectionInvariants (configConsensus cfg) $
790+ KnownIntersectionState {
791+ theirFrag = theirFrag'
792+ , theirHeaderStateHistory = theirHeaderStateHistory'
793+ , ourFrag = ourFrag
794+ , mostRecentIntersection = mostRecentIntersection'
795+ }
796+ atomically $ writeTVar varCandidate theirFrag'
797+ atomically $ traceWith headerMetricsTracer (slotNo, now)
798+
799+ continueWithState kis'' $ nextStep mkPipelineDecision n theirTip
800+
801+ -- Used in 'rollForward': determines whether the header is from the future,
802+ -- and handle that fact if so. Also return the ledger state used for the
803+ -- determination.
804+ --
805+ -- Relies on 'readLedgerState'.
806+ checkArrivalTime :: KnownIntersectionState blk
807+ -> arrival
808+ -> EarlyExit. WithEarlyExit m (Intersects blk (LedgerState blk ))
809+ checkArrivalTime kis arrival = do
810+ Intersects kis' (lst, judgment) <- readLedgerState kis $ \ lst ->
811+ case runExcept $ judgeHeaderArrival (configLedger cfg) lst arrival of
812+ Left PastHorizon {} -> Nothing
813+ Right judgment -> Just (lst, judgment)
814+
815+ -- For example, throw an exception if the header is from the far
816+ -- future.
817+ EarlyExit. lift $ handleHeaderArrival judgment >>= \ case
818+ Just exn -> disconnect (InFutureHeaderExceedsClockSkew exn)
819+ Nothing -> return $ Intersects kis' lst
820+
821+ -- Used in 'rollForward': block until the the ledger state at the
822+ -- intersection with the local selection returns 'Just'.
823+ --
824+ -- Exits early if the intersection no longer exists.
825+ readLedgerState :: KnownIntersectionState blk
826+ -> (LedgerState blk -> Maybe a )
827+ -> EarlyExit. WithEarlyExit m (Intersects blk a )
828+ readLedgerState kis prj = join $ EarlyExit. lift $ atomically $ do
829+ -- We must first find the most recent intersection with the current
830+ -- chain. Note that this is cheap when the chain and candidate haven't
831+ -- changed.
730832 mKis' <- intersectsWithCurrentChain kis
731833 case mKis' of
732- Nothing -> return NoLongerIntersects
834+ Nothing -> return EarlyExit. exitEarly
733835 Just kis'@ KnownIntersectionState { mostRecentIntersection } -> do
734- -- We're calling 'ledgerViewForecastAt' in the same STM transaction
735- -- as 'intersectsWithCurrentChain'. This guarantees the former's
736- -- precondition: the intersection is within the last @k@ blocks of
737- -- the current chain.
738- forecast <-
836+ lst <-
739837 maybe
740838 (error $
741839 " intersection not within last k blocks: " <> show mostRecentIntersection)
742- (ledgerViewForecastAt (configLedger cfg) . ledgerState)
840+ ledgerState
743841 <$> getPastLedger mostRecentIntersection
744842
745- case runExcept $ forecastFor forecast (blockSlot hdr) of
746- -- The header is too far ahead of the intersection point with our
747- -- current chain. We have to wait until our chain and the
748- -- intersection have advanced far enough. This will wait on
749- -- changes to the current chain via the call to
750- -- 'intersectsWithCurrentChain' before it.
751- Left OutsideForecastRange {} ->
752- retry
753- Right ledgerView ->
754- return $ Intersects kis' ledgerView
755-
756- case intersectCheck of
757- NoLongerIntersects ->
758- -- Our chain (tip) has changed and it no longer intersects with the
759- -- candidate fragment, so we have to find a new intersection, but
760- -- first drain the pipe.
761- continueWithState ()
762- $ drainThePipe n
763- $ findIntersection NoMoreIntersection
764-
765- Intersects kis' ledgerView -> do
766- -- Our chain still intersects with the candidate fragment and we
767- -- have obtained a 'LedgerView' that we can use to validate @hdr@.
768-
769- let KnownIntersectionState {
770- ourFrag
771- , theirFrag
772- , theirHeaderStateHistory
773- , mostRecentIntersection
774- } = kis'
775-
776- -- Validate header
777- theirHeaderStateHistory' <-
778- case runExcept $ validateHeader cfg ledgerView hdr theirHeaderStateHistory of
779- Right theirHeaderStateHistory' -> return theirHeaderStateHistory'
780- Left vErr ->
781- disconnect $
782- HeaderError hdrPoint vErr (ourTipFromChain ourFrag) theirTip
783-
784- let theirFrag' = theirFrag :> hdr
785- -- Advance the most recent intersection if we have the same header
786- -- on our fragment too. This is cheaper than recomputing the
787- -- intersection from scratch.
788- mostRecentIntersection'
789- | Just ourSuccessor <-
790- AF. successorBlock (castPoint mostRecentIntersection) ourFrag
791- , headerHash ourSuccessor == headerHash hdr
792- = headerPoint hdr
793- | otherwise
794- = mostRecentIntersection
795- kis'' = assertKnownIntersectionInvariants (configConsensus cfg) $
796- KnownIntersectionState {
797- theirFrag = theirFrag'
798- , theirHeaderStateHistory = theirHeaderStateHistory'
799- , ourFrag = ourFrag
800- , mostRecentIntersection = mostRecentIntersection'
801- }
802- atomically $ writeTVar varCandidate theirFrag'
803- let slotNo = blockSlot hdr
804- atomically $ traceWith headerMetricsTracer (slotNo, now)
805-
806- continueWithState kis'' $ nextStep mkPipelineDecision n theirTip
843+ case prj lst of
844+ Nothing -> retry
845+ Just ledgerView -> return $ return $ Intersects kis' ledgerView
846+
847+ -- Used in 'rollForward': returns 'Nothing' if the ledger state cannot
848+ -- forecast the ledger view that far into the future.
849+ projectLedgerView :: SlotNo
850+ -> LedgerState blk
851+ -> Maybe (LedgerView (BlockProtocol blk ))
852+ projectLedgerView slot lst =
853+ let forecast = ledgerViewForecastAt (configLedger cfg) lst
854+ -- TODO cache this in the KnownIntersectionState? Or even in the
855+ -- LedgerDB?
856+ in
857+ case runExcept $ forecastFor forecast slot of
858+ -- The header is too far ahead of the intersection point with our
859+ -- current chain. We have to wait until our chain and the
860+ -- intersection have advanced far enough. This will wait on
861+ -- changes to the current chain via the call to
862+ -- 'intersectsWithCurrentChain' before it.
863+ Left OutsideForecastRange {} -> Nothing
864+ Right ledgerView -> Just ledgerView
807865
808866 rollBackward :: MkPipelineDecision
809867 -> Nat n
@@ -1024,16 +1082,10 @@ invalidBlockRejector tracer version getIsInvalidBlock getCandidate =
10241082 throwIO ex
10251083
10261084-- | Auxiliary data type used as an intermediary result in 'rollForward'.
1027- data IntersectCheck blk =
1028- -- | The upstream chain no longer intersects with our current chain because
1029- -- our current chain changed in the background.
1030- NoLongerIntersects
1031- -- | The upstream chain still intersects with our chain, return the
1032- -- resulting 'KnownIntersectionState' and the 'LedgerView' corresponding to
1033- -- the header 'rollForward' received.
1034- | Intersects
1035- (KnownIntersectionState blk )
1036- (LedgerView (BlockProtocol blk ))
1085+ data Intersects blk a =
1086+ Intersects
1087+ (KnownIntersectionState blk )
1088+ a
10371089
10381090{- ------------------------------------------------------------------------------
10391091 Explicit state
@@ -1159,6 +1211,8 @@ data ChainSyncClientException =
11591211 -- different from the previous argument.
11601212 (InvalidBlockReason blk )
11611213
1214+ | InFutureHeaderExceedsClockSkew ! InFutureCheck. HeaderArrivalException
1215+
11621216deriving instance Show ChainSyncClientException
11631217
11641218instance Eq ChainSyncClientException where
@@ -1180,6 +1234,10 @@ instance Eq ChainSyncClientException where
11801234 Just Refl -> (a, b, c) == (a', b', c')
11811235 InvalidBlock {} == _ = False
11821236
1237+ InFutureHeaderExceedsClockSkew a == InFutureHeaderExceedsClockSkew a' =
1238+ a == a'
1239+ InFutureHeaderExceedsClockSkew {} == _ = False
1240+
11831241instance Exception ChainSyncClientException
11841242
11851243{- ------------------------------------------------------------------------------
0 commit comments