@@ -51,19 +51,23 @@ import NoThunks.Class (unsafeNoThunks)
51
51
import Ouroboros.Consensus.Block
52
52
import Ouroboros.Consensus.Config
53
53
import Ouroboros.Consensus.Forecast
54
+ import Ouroboros.Consensus.HardFork.History (PastHorizonException (PastHorizon ))
54
55
import Ouroboros.Consensus.HeaderStateHistory
55
56
(HeaderStateHistory (.. ), validateHeader )
56
57
import qualified Ouroboros.Consensus.HeaderStateHistory as HeaderStateHistory
57
58
import Ouroboros.Consensus.HeaderValidation hiding (validateHeader )
59
+ import Ouroboros.Consensus.Ledger.Basics (LedgerState )
58
60
import Ouroboros.Consensus.Ledger.Extended
59
61
import Ouroboros.Consensus.Ledger.SupportsProtocol
62
+ import qualified Ouroboros.Consensus.MiniProtocol.ChainSync.Client.InFutureCheck as InFutureCheck
60
63
import Ouroboros.Consensus.Node.NetworkProtocolVersion
61
64
import Ouroboros.Consensus.Protocol.Abstract
62
65
import Ouroboros.Consensus.Storage.ChainDB (ChainDB ,
63
66
InvalidBlockReason )
64
67
import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB
65
68
import Ouroboros.Consensus.Util
66
69
import Ouroboros.Consensus.Util.Assert (assertWithMsg )
70
+ import qualified Ouroboros.Consensus.Util.EarlyExit as EarlyExit
67
71
import Ouroboros.Consensus.Util.IOLike
68
72
import Ouroboros.Consensus.Util.STM (Fingerprint , Watcher (.. ),
69
73
WithFingerprint (.. ), withWatcher )
@@ -426,13 +430,20 @@ chainSyncClient
426
430
=> MkPipelineDecision
427
431
-> Tracer m (TraceChainSyncClientEvent blk )
428
432
-> TopLevelConfig blk
433
+ -> InFutureCheck. HeaderInFutureCheck m blk
429
434
-> ChainDbView m blk
430
435
-> NodeToNodeVersion
431
436
-> ControlMessageSTM m
432
437
-> HeaderMetricsTracer m
433
438
-> StrictTVar m (AnchoredFragment (Header blk ))
434
439
-> Consensus ChainSyncClientPipelined blk m
435
440
chainSyncClient mkPipelineDecision0 tracer cfg
441
+ InFutureCheck. HeaderInFutureCheck
442
+ { handleHeaderArrival
443
+ , judgeHeaderArrival
444
+ , proxyArrival = Proxy :: Proxy arrival
445
+ , recordHeaderArrival
446
+ }
436
447
ChainDbView
437
448
{ getCurrentChain
438
449
, getHeaderStateHistory
@@ -706,104 +717,151 @@ chainSyncClient mkPipelineDecision0 tracer cfg
706
717
(ClientPipelinedStIdle n )
707
718
rollForward mkPipelineDecision n hdr theirTip
708
719
= 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
716
739
whenJust (isInvalidBlock hash) $ \ reason ->
717
740
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.
730
832
mKis' <- intersectsWithCurrentChain kis
731
833
case mKis' of
732
- Nothing -> return NoLongerIntersects
834
+ Nothing -> return EarlyExit. exitEarly
733
835
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 <-
739
837
maybe
740
838
(error $
741
839
" intersection not within last k blocks: " <> show mostRecentIntersection)
742
- (ledgerViewForecastAt (configLedger cfg) . ledgerState)
840
+ ledgerState
743
841
<$> getPastLedger mostRecentIntersection
744
842
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
807
865
808
866
rollBackward :: MkPipelineDecision
809
867
-> Nat n
@@ -1024,16 +1082,10 @@ invalidBlockRejector tracer version getIsInvalidBlock getCandidate =
1024
1082
throwIO ex
1025
1083
1026
1084
-- | 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
1037
1089
1038
1090
{- ------------------------------------------------------------------------------
1039
1091
Explicit state
@@ -1159,6 +1211,8 @@ data ChainSyncClientException =
1159
1211
-- different from the previous argument.
1160
1212
(InvalidBlockReason blk )
1161
1213
1214
+ | InFutureHeaderExceedsClockSkew ! InFutureCheck. HeaderArrivalException
1215
+
1162
1216
deriving instance Show ChainSyncClientException
1163
1217
1164
1218
instance Eq ChainSyncClientException where
@@ -1180,6 +1234,10 @@ instance Eq ChainSyncClientException where
1180
1234
Just Refl -> (a, b, c) == (a', b', c')
1181
1235
InvalidBlock {} == _ = False
1182
1236
1237
+ InFutureHeaderExceedsClockSkew a == InFutureHeaderExceedsClockSkew a' =
1238
+ a == a'
1239
+ InFutureHeaderExceedsClockSkew {} == _ = False
1240
+
1183
1241
instance Exception ChainSyncClientException
1184
1242
1185
1243
{- ------------------------------------------------------------------------------
0 commit comments