Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit d5e09e1

Browse files
Merge #27
27: Byron chain download uses better checkpoints r=avieth a=avieth Previously it would use only one checkpoint: the current tip. That's fine if there are no forks, but wouldn't work if there were. Now the checkpoints are computed in the same way as the chain sync client from ouroboros-consensus: fibonacci numbers including `0` and `k` without the duplicate `1`. I've also included some unrelated improvements to documentation, and a commit to make configuring the Byron-side easier (using cardano-sl configurations). Co-authored-by: Alexander Vieth <[email protected]>
2 parents 1686120 + e17fa24 commit d5e09e1

File tree

8 files changed

+271
-222
lines changed

8 files changed

+271
-222
lines changed

cardano-byron-proxy.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ library
3636
cardano-sl-crypto,
3737
cardano-sl-db,
3838
cardano-sl-infra,
39+
cardano-sl-util,
3940
cborg,
4041
conduit,
4142
containers,
@@ -57,6 +58,7 @@ library
5758
tagged,
5859
text,
5960
time,
61+
time-units,
6062
transformers,
6163
typed-protocols,
6264
unliftio-core,

nix/.stack.nix/cardano-byron-proxy.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
(hsPkgs.cardano-sl-crypto)
3131
(hsPkgs.cardano-sl-db)
3232
(hsPkgs.cardano-sl-infra)
33+
(hsPkgs.cardano-sl-util)
3334
(hsPkgs.cborg)
3435
(hsPkgs.conduit)
3536
(hsPkgs.containers)
@@ -51,6 +52,7 @@
5152
(hsPkgs.tagged)
5253
(hsPkgs.text)
5354
(hsPkgs.time)
55+
(hsPkgs.time-units)
5456
(hsPkgs.transformers)
5557
(hsPkgs.typed-protocols)
5658
(hsPkgs.unliftio-core)

src/exec/Byron.hs

Lines changed: 114 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ module Byron
1313

1414
import Control.Concurrent.STM (STM, atomically, check, readTVar, registerDelay, retry)
1515
import Control.Exception (IOException, catch, throwIO)
16-
import Control.Monad (forM_, when)
16+
import Control.Monad (when)
1717
import Control.Tracer (Tracer, traceWith)
1818
import qualified Data.ByteString.Lazy as Lazy (fromStrict)
19+
import Data.Foldable (foldlM)
1920
import Data.List.NonEmpty (NonEmpty)
2021
import qualified Data.List.NonEmpty as NE
22+
import Data.Maybe (mapMaybe)
2123
import qualified Data.Text.Lazy.Builder as Text
2224
import Data.Typeable (Typeable)
25+
import Data.Word (Word64)
2326
import System.Random (StdGen, getStdGen, randomR)
2427

2528
import qualified Cardano.Binary as Binary
@@ -28,126 +31,159 @@ import qualified Cardano.Chain.Slotting as Cardano
2831

2932
import qualified Pos.Binary.Class as CSL (decodeFull, serialize)
3033
import qualified Pos.Chain.Block as CSL (Block, BlockHeader (..), GenesisBlock,
31-
MainBlockHeader, headerHash)
34+
MainBlockHeader, HeaderHash, headerHash)
3235
import qualified Pos.Infra.Diffusion.Types as CSL
3336

3437
import Ouroboros.Byron.Proxy.Block (Block, ByronBlockOrEBB (..),
3538
coerceHashToLegacy, unByronHeaderOrEBB, headerHash)
3639
import Ouroboros.Byron.Proxy.Main
40+
import Ouroboros.Consensus.Block (Header)
3741
import Ouroboros.Consensus.Ledger.Byron (ByronGiven)
42+
import Ouroboros.Consensus.Protocol.Abstract (SecurityParam (maxRollbacks))
43+
import Ouroboros.Network.Block (ChainHash (..), Point, pointHash)
3844
import qualified Ouroboros.Network.AnchoredFragment as AF
3945
import qualified Ouroboros.Network.ChainFragment as CF
4046
import Ouroboros.Storage.ChainDB.API (ChainDB)
4147
import 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.
4755
download
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+
151187
recodeBlockOrFail
152188
:: Cardano.EpochSlots
153189
-> (forall x . Binary.DecoderError -> IO x)

src/exec/DB.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import qualified Ouroboros.Consensus.Ledger.Byron as Byron
2727
import Ouroboros.Consensus.Ledger.Byron.Config (ByronConfig)
2828
import Ouroboros.Consensus.Ledger.Extended (ExtLedgerState)
2929
import Ouroboros.Consensus.Protocol (NodeConfig)
30-
import Ouroboros.Consensus.Protocol.Abstract (SecurityParam (..))
30+
import Ouroboros.Consensus.Protocol.Abstract (SecurityParam (..),
31+
protocolSecurityParam)
3132
import Ouroboros.Consensus.Util.ResourceRegistry (ResourceRegistry)
3233
import qualified Ouroboros.Consensus.Util.ResourceRegistry as ResourceRegistry
3334
import Ouroboros.Storage.ChainDB.API (ChainDB)
@@ -60,12 +61,11 @@ withDB
6061
-> Tracer IO (ChainDB.TraceEvent (Block ByronConfig))
6162
-> Tracer IO Sqlite.TraceEvent
6263
-> ResourceRegistry IO
63-
-> SecurityParam
6464
-> NodeConfig (BlockProtocol (Block ByronConfig))
6565
-> ExtLedgerState (Block ByronConfig)
6666
-> (Index IO (Header (Block ByronConfig)) -> ChainDB IO (Block ByronConfig) -> IO t)
6767
-> IO t
68-
withDB dbOptions dbTracer indexTracer rr securityParam nodeConfig extLedgerState k = do
68+
withDB dbOptions dbTracer indexTracer rr nodeConfig extLedgerState k = do
6969
-- The ChainDB/Storage layer will not create a directory for us, we have
7070
-- to ensure it exists.
7171
System.Directory.createDirectoryIfMissing True (dbFilePath dbOptions)
@@ -116,7 +116,7 @@ withDB dbOptions dbTracer indexTracer rr securityParam nodeConfig extLedgerState
116116

117117
, cdbValidation = ValidateMostRecentEpoch
118118
, cdbBlocksPerFile = 21600 -- ?
119-
, cdbMemPolicy = defaultMemPolicy securityParam
119+
, cdbMemPolicy = defaultMemPolicy (protocolSecurityParam nodeConfig)
120120
, cdbDiskPolicy = ledgerDiskPolicy
121121

122122
, cdbNodeConfig = nodeConfig

0 commit comments

Comments
 (0)