Skip to content

Commit f91f0c9

Browse files
authored
Fix Blockfrost Chain Synchronization from Genesis and Optimize API Calls (#2300)
Problem: - When starting from ChainPointAtGenesis, the node would fail to properly initialize and synchronize with the chain. Additionally, the continuous polling approach was making excessive API calls to Blockfrost. Solution: - Improved Genesis Handling: Added proper error handling for genesis block retrieval. When block 0 (genesis) is not available via Blockfrost API, the system now gracefully falls back to block 1. (Block 0 is not available in pre-prod) <img width="1084" height="460" alt="image" src="https://github.com/user-attachments/assets/d665a24f-c7b4-4eb3-ab3e-61f11326cb6e" /> - Two-Phase Synchronization: - Phase 1 - Catch-up: New catchUpToLatest function rapidly syncs from the starting point to the latest block without delays - Phase 2 - Polling: New pollForNewBlocks function polls for new blocks at intervals based on the actual blockchain's blockTime parameter - Optimized API Usage: Replaced the tight loop with threadDelay 1 with intelligent polling that respects the blockchain's slot timing, significantly reducing unnecessary API calls to Blockfrost - Better Error Handling: Added specific handling for MissingNextBlockHash exceptions during polling (expected when waiting for new blocks) --- <!-- Consider each and tick it off one way or the other --> * [X] CHANGELOG updated or not needed * [X] Documentation updated or not needed * [X] Haddocks updated or not needed * [X] No new TODOs introduced or explained herafter
2 parents ee19ffd + 916a0b0 commit f91f0c9

File tree

2 files changed

+54
-18
lines changed

2 files changed

+54
-18
lines changed

.github/workflows/tx-cost-diff.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ on:
1010
workflow_dispatch:
1111

1212
permissions:
13-
checks: write
14-
pull-requests: write
13+
checks: write-all
14+
pull-requests: write-all
15+
1516

1617
jobs:
1718
# Compute the cost difference between this branch and master.

hydra-node/src/Hydra/Chain/Blockfrost.hs

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Hydra.Chain.Blockfrost where
22

33
import Hydra.Prelude
44

5+
import Blockfrost.Client qualified as BlockfrostAPI
56
import Control.Concurrent.Class.MonadSTM (putTMVar, readTQueue, readTVarIO, takeTMVar, writeTQueue, writeTVar)
67
import Control.Exception (IOException)
78
import Control.Retry (RetryPolicyM, constantDelay, retrying)
@@ -205,20 +206,31 @@ blockfrostChainFollow ::
205206
TinyWallet m ->
206207
m ()
207208
blockfrostChainFollow tracer prj chainPoint handler wallet = do
208-
Blockfrost.Genesis{_genesisSlotLength, _genesisActiveSlotsCoefficient} <- Blockfrost.runBlockfrostM prj Blockfrost.getLedgerGenesis
209-
210-
Blockfrost.Block{_blockHash = (Blockfrost.BlockHash genesisBlockHash)} <-
211-
Blockfrost.runBlockfrostM prj (Blockfrost.getBlock (Left 0))
209+
Blockfrost.Genesis{_genesisSlotLength, _genesisActiveSlotsCoefficient} <-
210+
Blockfrost.runBlockfrostM prj Blockfrost.getLedgerGenesis
212211

213212
let blockTime :: Double = realToFrac _genesisSlotLength / realToFrac _genesisActiveSlotsCoefficient
214213

215-
let blockHash = fromChainPoint chainPoint genesisBlockHash
214+
blockHash <- case chainPoint of
215+
ChainPointAtGenesis -> do
216+
result <- liftIO $ Blockfrost.tryError $ Blockfrost.runBlockfrost prj (Blockfrost.getBlock (Left 0))
217+
case result of
218+
Right (Right (Blockfrost.Block{_blockHash = Blockfrost.BlockHash genesisBlockHash})) -> do
219+
pure $ Blockfrost.BlockHash genesisBlockHash
220+
_ -> do
221+
Blockfrost.Block{_blockHash = Blockfrost.BlockHash block1Hash} <-
222+
Blockfrost.runBlockfrostM prj (Blockfrost.getBlock (Left 1))
223+
pure $ Blockfrost.BlockHash block1Hash
224+
ChainPoint _ headerHash ->
225+
pure $ Blockfrost.BlockHash (decodeUtf8 . Base16.encode . serialiseToRawBytes $ headerHash)
216226

217227
stateTVar <- newLabelledTVarIO "blockfrost-chain-state" blockHash
218228

229+
void $ catchUpToLatest blockHash stateTVar
230+
219231
void $
220232
retrying (retryPolicy blockTime) shouldRetry $ \_ -> do
221-
loop stateTVar
233+
pollForNewBlocks blockTime stateTVar
222234
`catch` \(ex :: APIBlockfrostError) ->
223235
pure $ Left ex
224236
where
@@ -230,12 +242,40 @@ blockfrostChainFollow tracer prj chainPoint handler wallet = do
230242
retryPolicy :: Double -> RetryPolicyM m
231243
retryPolicy blockTime' = constantDelay (truncate blockTime' * 1000 * 1000)
232244

233-
loop stateTVar = do
245+
catchUpToLatest currentHash stateTVar = do
246+
latestBlock <- Blockfrost.runBlockfrostM prj BlockfrostAPI.getLatestBlock
247+
let targetHash = BlockfrostAPI._blockHash latestBlock
248+
249+
catchUpLoop currentHash targetHash stateTVar
250+
251+
catchUpLoop currentHash targetHash stateTVar = do
252+
if currentHash == targetHash
253+
then do
254+
pure currentHash
255+
else do
256+
nextBlockHash <- rollForward tracer prj handler wallet 0 currentHash
257+
atomically $ writeTVar stateTVar nextBlockHash
258+
259+
if nextBlockHash == targetHash
260+
then do
261+
pure nextBlockHash
262+
else catchUpLoop nextBlockHash targetHash stateTVar
263+
264+
pollForNewBlocks blockTime' stateTVar = do
265+
threadDelay (realToFrac blockTime')
234266
current <- readTVarIO stateTVar
235-
nextBlockHash <- rollForward tracer prj handler wallet 1 current
236-
threadDelay 1
237-
atomically $ writeTVar stateTVar nextBlockHash
238-
loop stateTVar
267+
nextBlockHash <-
268+
rollForward tracer prj handler wallet 1 current
269+
`catch` \case
270+
MissingNextBlockHash{} -> do
271+
pure current
272+
ex -> throwIO ex
273+
274+
when (nextBlockHash /= current) $
275+
atomically $
276+
writeTVar stateTVar nextBlockHash
277+
278+
pollForNewBlocks blockTime' stateTVar
239279

240280
rollForward ::
241281
(MonadIO m, MonadThrow m) =>
@@ -348,8 +388,3 @@ toTx (Blockfrost.TransactionCBOR txCbor) =
348388
case deserialiseFromCBOR (proxyToAsType (Proxy @Tx)) bytes of
349389
Left deserializeErr -> throwIO . DecodeError $ "Bad Tx CBOR: " <> show deserializeErr
350390
Right tx -> pure tx
351-
352-
fromChainPoint :: ChainPoint -> Text -> Blockfrost.BlockHash
353-
fromChainPoint chainPoint genesisBlockHash = case chainPoint of
354-
ChainPoint _ headerHash -> Blockfrost.BlockHash (decodeUtf8 . Base16.encode . serialiseToRawBytes $ headerHash)
355-
ChainPointAtGenesis -> Blockfrost.BlockHash genesisBlockHash

0 commit comments

Comments
 (0)