@@ -13,13 +13,16 @@ module Byron
1313
1414import Control.Concurrent.STM (STM , atomically , check , readTVar , registerDelay , retry )
1515import Control.Exception (IOException , catch , throwIO )
16- import Control.Monad (forM_ , when )
16+ import Control.Monad (when )
1717import Control.Tracer (Tracer , traceWith )
1818import qualified Data.ByteString.Lazy as Lazy (fromStrict )
19+ import Data.Foldable (foldlM )
1920import Data.List.NonEmpty (NonEmpty )
2021import qualified Data.List.NonEmpty as NE
22+ import Data.Maybe (mapMaybe )
2123import qualified Data.Text.Lazy.Builder as Text
2224import Data.Typeable (Typeable )
25+ import Data.Word (Word64 )
2326import System.Random (StdGen , getStdGen , randomR )
2427
2528import qualified Cardano.Binary as Binary
@@ -28,126 +31,159 @@ import qualified Cardano.Chain.Slotting as Cardano
2831
2932import qualified Pos.Binary.Class as CSL (decodeFull , serialize )
3033import qualified Pos.Chain.Block as CSL (Block , BlockHeader (.. ), GenesisBlock ,
31- MainBlockHeader , headerHash )
34+ MainBlockHeader , HeaderHash , headerHash )
3235import qualified Pos.Infra.Diffusion.Types as CSL
3336
3437import Ouroboros.Byron.Proxy.Block (Block , ByronBlockOrEBB (.. ),
3538 coerceHashToLegacy , unByronHeaderOrEBB , headerHash )
3639import Ouroboros.Byron.Proxy.Main
40+ import Ouroboros.Consensus.Block (Header )
3741import Ouroboros.Consensus.Ledger.Byron (ByronGiven )
42+ import Ouroboros.Consensus.Protocol.Abstract (SecurityParam (maxRollbacks ))
43+ import Ouroboros.Network.Block (ChainHash (.. ), Point , pointHash )
3844import qualified Ouroboros.Network.AnchoredFragment as AF
3945import qualified Ouroboros.Network.ChainFragment as CF
4046import Ouroboros.Storage.ChainDB.API (ChainDB )
4147import qualified Ouroboros.Storage.ChainDB.API as ChainDB
4248
4349-- | Download the best available chain from Byron peers and write to the
44- -- database, over and over again.
50+ -- database, over and over again. It will download the best chain from its
51+ -- Byron peers regardless of whether it has a better one in the database.
4552--
46- -- No exception handling is done.
53+ -- The ByronGiven and Typeable constraints are needed in order to use
54+ -- AF.selectPoints, that's all.
4755download
48- :: Tracer IO Text. Builder
56+ :: forall cfg void .
57+ ( ByronGiven , Typeable cfg )
58+ => Tracer IO Text. Builder
4959 -> CSL. GenesisBlock -- ^ For use as checkpoint when DB is empty. Also will
5060 -- be put into an empty DB.
5161 -- Sadly, old Byron net API doesn't give any meaning to an
5262 -- empty checkpoint set; it'll just fall over.
5363 -> Cardano. EpochSlots
64+ -> SecurityParam
5465 -> ChainDB IO (Block cfg )
5566 -> ByronProxy
56- -> (CSL. Block -> Block cfg -> IO () )
57- -> IO x
58- download tracer genesisBlock epochSlots db bp k = getStdGen >>= mainLoop Nothing
67+ -> IO void
68+ download tracer genesisBlock epochSlots securityParam db bp = do
69+ gen <- getStdGen
70+ mTip <- ChainDB. getTipHeader db
71+ tipHash <- case mTip of
72+ Nothing -> do
73+ traceWith tracer " Seeding database with genesis"
74+ genesisBlock' :: Block cfg <- recodeBlockOrFail epochSlots throwIO (Left genesisBlock)
75+ ChainDB. addBlock db genesisBlock'
76+ pure $ CSL. headerHash genesisBlock
77+ Just header -> pure $ coerceHashToLegacy (headerHash header)
78+ mainLoop gen tipHash
5979
6080 where
6181
82+ -- The BestTip always gives the longest chain seen so far by Byron. All we
83+ -- need to do here is wait until it actually changes, then try to download.
84+ -- For checkpoints, we just need to choose some good ones up to k blocks
85+ -- back, and everything should work out fine. NB: the checkpoints will only
86+ -- be on the main chain.
87+ -- getCurrentChain will give exactly what we need.
6288 waitForNext
63- :: Maybe (BestTip CSL. BlockHeader )
64- -> STM (Either (BestTip CSL. BlockHeader ) Atom )
65- waitForNext mBt = do
66- mBt' <- bestTip bp
67- if mBt == mBt'
68- -- If recvAtom retries then the whole STM will retry and we'll check again
69- -- for the best tip to have changed.
70- then fmap Right (recvAtom bp)
71- else case mBt' of
72- Nothing -> retry
73- Just bt -> pure (Left bt)
74-
75- mainLoop :: Maybe (BestTip CSL. BlockHeader ) -> StdGen -> IO x
76- mainLoop mBt rndGen = do
89+ :: CSL. HeaderHash
90+ -> STM (BestTip CSL. BlockHeader )
91+ waitForNext lastDownloadedHash = do
92+ mBt <- bestTip bp
93+ case mBt of
94+ -- Haven't seen any tips from Byron peers.
95+ Nothing -> retry
96+ Just bt ->
97+ if thisHash == lastDownloadedHash
98+ then retry
99+ else pure bt
100+ where
101+ thisHash = CSL. headerHash (btTip bt)
102+
103+ mainLoop :: StdGen -> CSL. HeaderHash -> IO void
104+ mainLoop rndGen tipHash = do
77105 -- Wait until the best tip has changed from the last one we saw. That can
78106 -- mean the header changed and/or the list of peers who announced it
79107 -- changed.
80- next <- atomically $ waitForNext mBt
81- case next of
82- -- TODO we don't get to know from where it was received. Problem? Maybe
83- -- not.
84- Right atom -> do
85- traceWith tracer $ mconcat
86- [ " Got atom: "
87- , Text. fromString (show atom)
88- ]
89- mainLoop mBt rndGen
90- Left bt -> do
91- mTip <- ChainDB. getTipHeader db
92- tipHash <- case mTip of
93- -- If the DB is empty, we use the genesis hash as our tip, but also
94- -- we need to put the genesis block into the database, because the
95- -- Byron peer _will not serve it to us_!
96- Nothing -> do
97- traceWith tracer " Seeding database with genesis"
98- genesisBlock' :: Block cfg <- recodeBlockOrFail epochSlots throwIO (Left genesisBlock)
99- ChainDB. addBlock db genesisBlock'
100- pure $ CSL. headerHash genesisBlock
101- Just header -> pure $ coerceHashToLegacy (headerHash header)
102- -- Pick a peer from the list of announcers at random and download
103- -- the chain.
104- let (peer, rndGen') = pickRandom rndGen (btPeers bt)
105- remoteTipHash = CSL. headerHash (btTip bt)
106- traceWith tracer $ mconcat
107- [ " Attempting to download chain with hash "
108- , Text. fromString (show remoteTipHash)
109- , " from "
110- , Text. fromString (show peer)
111- ]
112- -- Try to download the chain, but do not die in case of IOExceptions.
113- _ <- downloadChain
114- bp
115- peer
116- remoteTipHash
117- [tipHash]
118- streamer
119- `catch`
120- exceptionHandler
121- mainLoop (Just bt) rndGen'
122-
123- -- If it ends at an EBB, the EBB will _not_ be written. The tip will be the
124- -- parent of the EBB.
125- -- This should be OK.
126- streamer :: CSL. StreamBlocks CSL. Block IO ()
127- streamer = CSL. StreamBlocks
108+ bt <- atomically $ waitForNext tipHash
109+ -- Pick a peer from the list of announcers at random and download
110+ -- the chain.
111+ let (peer, rndGen') = pickRandom rndGen (btPeers bt)
112+ chain <- atomically $ ChainDB. getCurrentChain db
113+ traceWith tracer $ mconcat
114+ [ " Attempting to download chain with hash "
115+ , Text. fromString (show tipHash)
116+ , " from "
117+ , Text. fromString (show peer)
118+ ]
119+ -- Try to download the chain, but do not die in case of IOExceptions.
120+ -- The hash of the last downloaded block is returned, so that on the next
121+ -- recursive call, that chain won't be downloaded again. If there's an
122+ -- exception, or if batch downloaded was used, this hash may not be the
123+ -- hash of the tip of the chain that was to be downloaded.
124+ tipHash' <- downloadChain
125+ bp
126+ peer
127+ (CSL. headerHash (btTip bt))
128+ (checkpoints chain)
129+ (streamer tipHash)
130+ `catch`
131+ exceptionHandler tipHash
132+ mainLoop rndGen' tipHash'
133+
134+ checkpoints
135+ :: AF. AnchoredFragment (Header (Block cfg ))
136+ -> [CSL. HeaderHash ]
137+ checkpoints = mapMaybe pointToHash . AF. selectPoints (fmap fromIntegral offsets)
138+
139+ pointToHash :: Point (Header (Block cfg )) -> Maybe CSL. HeaderHash
140+ pointToHash pnt = case pointHash pnt of
141+ GenesisHash -> Nothing
142+ BlockHash hash -> Just $ coerceHashToLegacy hash
143+
144+ -- Offsets for selectPoints. Defined in the same way as for the Shelley
145+ -- chain sync client: fibonacci numbers including 0 and k.
146+ offsets :: [Word64 ]
147+ offsets = 0 : foldr includeK ([] {- this is never forced -} ) (tail fibs)
148+
149+ includeK :: Word64 -> [Word64 ] -> [Word64 ]
150+ includeK w ws | w >= k = [k]
151+ | otherwise = w : ws
152+
153+ fibs :: [Word64 ]
154+ fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
155+
156+ streamer :: CSL. HeaderHash -> CSL. StreamBlocks CSL. Block IO CSL. HeaderHash
157+ streamer tipHash = CSL. StreamBlocks
128158 { CSL. streamBlocksMore = \ blocks -> do
129159 -- List comes in newest-to-oldest order.
130160 let orderedBlocks = NE. toList (NE. reverse blocks)
131161 -- The blocks are legacy CSL blocks. To put them into the DB, we must
132162 -- convert them to new cardano-ledger blocks. That's done by
133163 -- encoding and decoding.
134- forM_ orderedBlocks $ \ blk -> do
135- blk' <- recodeBlockOrFail epochSlots throwIO blk
136- ChainDB. addBlock db blk'
137- k blk blk'
138- pure streamer
139- , CSL. streamBlocksDone = pure ()
164+ tipHash' <- foldlM commitBlock tipHash orderedBlocks
165+ pure (streamer tipHash')
166+ , CSL. streamBlocksDone = pure tipHash
140167 }
141168
169+ commitBlock :: CSL. HeaderHash -> CSL. Block -> IO CSL. HeaderHash
170+ commitBlock _ blk = do
171+ blk' <- recodeBlockOrFail epochSlots throwIO blk
172+ ChainDB. addBlock db blk'
173+ pure $ CSL. headerHash blk
174+
142175 -- No need to trace it; cardano-sl libraries will do that.
143- exceptionHandler :: IOException -> IO ( Maybe () )
144- exceptionHandler _ = pure Nothing
176+ exceptionHandler :: CSL. HeaderHash -> IOException -> IO CSL. HeaderHash
177+ exceptionHandler h _ = pure h
145178
146179 pickRandom :: StdGen -> NonEmpty t -> (t , StdGen )
147180 pickRandom rndGen ne =
148181 let (idx, rndGen') = randomR (0 , NE. length ne - 1 ) rndGen
149182 in (ne NE. !! idx, rndGen')
150183
184+ k :: Word64
185+ k = maxRollbacks securityParam
186+
151187recodeBlockOrFail
152188 :: Cardano. EpochSlots
153189 -> (forall x . Binary. DecoderError -> IO x )
0 commit comments