Skip to content

Commit 198c3b6

Browse files
committed
Improve Blockfrost integration
Some fixes for the blockfrost tests to pass
1 parent 09ee2ba commit 198c3b6

File tree

8 files changed

+67
-15
lines changed

8 files changed

+67
-15
lines changed

hydra-cluster/exe/hydra-cluster/Main.hs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ module Main where
44

55
import Hydra.Prelude
66

7-
import CardanoNode (findRunningCardanoNode, waitForFullySynchronized, withCardanoNodeDevnet, withCardanoNodeOnKnownNetwork)
7+
import CardanoNode (
8+
findFileStartingAtDirectory,
9+
findRunningCardanoNode,
10+
waitForFullySynchronized,
11+
withCardanoNodeDevnet,
12+
withCardanoNodeOnKnownNetwork,
13+
)
814
import Hydra.Cardano.Api (TxId, serialiseToRawBytesHexText)
915
import Hydra.Chain.Backend (ChainBackend, blockfrostProjectPath)
1016
import Hydra.Chain.Blockfrost (BlockfrostBackend (..))
@@ -39,7 +45,8 @@ run options =
3945
publishOrReuseHydraScripts tracer backend
4046
>>= singlePartyHeadFullLifeCycle tracer workDir backend
4147
else do
42-
let backend = BlockfrostBackend $ BlockfrostOptions{projectPath = blockfrostProjectPath}
48+
bfProjectPath <- findFileStartingAtDirectory 3 blockfrostProjectPath
49+
let backend = BlockfrostBackend $ BlockfrostOptions{projectPath = bfProjectPath}
4350
publishOrReuseHydraScripts tracer backend
4451
>>= singlePartyHeadFullLifeCycle tracer workDir backend
4552
Nothing -> do

hydra-cluster/src/CardanoNode.hs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Data.Aeson (Value (String), (.=))
1212
import Data.Aeson qualified as Aeson
1313
import Data.Aeson.Lens (atKey, key, _Number)
1414
import Data.Fixed (Centi)
15+
import Data.Text (pack)
1516
import Data.Text qualified as Text
1617
import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
1718
import Hydra.Cardano.Api (
@@ -30,9 +31,17 @@ import Hydra.Cluster.Fixture (KnownNetwork (..), toNetworkId)
3031
import Hydra.Cluster.Util (readConfigFile)
3132
import Hydra.Options (BlockfrostOptions (..), DirectOptions (..))
3233
import Network.HTTP.Simple (getResponseBody, httpBS, parseRequestThrow)
33-
import System.Directory (createDirectoryIfMissing, doesFileExist, removeFile)
34+
import System.Directory (
35+
createDirectoryIfMissing,
36+
doesFileExist,
37+
getCurrentDirectory,
38+
removeFile,
39+
)
3440
import System.Exit (ExitCode (..))
35-
import System.FilePath (takeDirectory, (</>))
41+
import System.FilePath (
42+
takeDirectory,
43+
(</>),
44+
)
3645
import System.Posix (ownerReadMode, setFileMode)
3746
import System.Process (
3847
CreateProcess (..),
@@ -169,9 +178,34 @@ withBlockfrostBackend ::
169178
withBlockfrostBackend _tracer stateDirectory action = do
170179
args <- setupCardanoDevnet stateDirectory
171180
shelleyGenesis <- readFileBS >=> unsafeDecodeJson $ stateDirectory </> nodeShelleyGenesisFile args
172-
let backend = BlockfrostBackend $ BlockfrostOptions{projectPath = Backend.blockfrostProjectPath}
181+
bfProjectPath <- findFileStartingAtDirectory 3 Backend.blockfrostProjectPath
182+
let backend = BlockfrostBackend $ BlockfrostOptions{projectPath = bfProjectPath}
173183
action (getShelleyGenesisBlockTime shelleyGenesis) backend
174184

185+
-- | Find the given file in the current directory or its parents.
186+
--
187+
-- This function starts from the current working directory and checks if the specified file exists there.
188+
-- If not found, it recursively checks the parent directories up to the given maximum depth.
189+
findFileStartingAtDirectory :: Int -> FilePath -> IO FilePath
190+
findFileStartingAtDirectory maxDepth fileName = do
191+
cwd <- getCurrentDirectory
192+
findInDir maxDepth cwd
193+
where
194+
findInDir :: Int -> FilePath -> IO FilePath
195+
findInDir depth dir = do
196+
let path = dir </> fileName
197+
exists <- doesFileExist path
198+
if exists
199+
then pure path
200+
else
201+
if depth <= 0
202+
then error $ "Could not locate the Blockfrost project file at or above: " <> pack dir
203+
else do
204+
let parent = ".." </> takeDirectory dir
205+
if parent == dir
206+
then error "Reached root directory without finding the Blockfrost project file."
207+
else findInDir (depth - 1) parent
208+
175209
withBackend ::
176210
forall a.
177211
Tracer IO NodeLog ->

hydra-cluster/src/Hydra/Cluster/Faucet.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ createOutputAtAddress ::
208208
IO (TxIn, TxOut CtxUTxO)
209209
createOutputAtAddress networkId backend atAddress datum val = do
210210
(faucetVk, faucetSk) <- keysFor Faucet
211-
utxo <- findFaucetUTxO networkId backend 0
211+
utxo <- findFaucetUTxO networkId backend (selectLovelace val)
212212
let collateralTxIns = mempty
213213
let output = TxOut atAddress val datum ReferenceScriptNone
214214
buildTransaction backend (mkVkAddress networkId faucetVk) utxo collateralTxIns [output] >>= \case

hydra-cluster/src/Hydra/Cluster/Scenarios.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -477,20 +477,20 @@ singlePartyHeadFullLifeCycle tracer workDir backend hydraScriptsTxId =
477477
utxoToCommit <- seedFromFaucet backend walletVk (lovelaceToValue amount) (contramap FromFaucet tracer)
478478
requestCommitTx n1 utxoToCommit <&> signTx walletSk >>= Backend.submitTransaction backend
479479

480-
waitFor hydraTracer (10 * blockTime) [n1] $
480+
waitFor hydraTracer (50 * blockTime) [n1] $
481481
output "HeadIsOpen" ["utxo" .= toJSON utxoToCommit, "headId" .= headId]
482482
-- Close head
483483
send n1 $ input "Close" []
484-
deadline <- waitMatch (10 * blockTime) n1 $ \v -> do
484+
deadline <- waitMatch (50 * blockTime) n1 $ \v -> do
485485
guard $ v ^? key "tag" == Just "HeadIsClosed"
486486
guard $ v ^? key "headId" == Just (toJSON headId)
487487
v ^? key "contestationDeadline" . _JSON
488488
remainingTime <- diffUTCTime deadline <$> getCurrentTime
489-
waitFor hydraTracer (remainingTime + 3 * blockTime) [n1] $
489+
waitFor hydraTracer (remainingTime + 10 * blockTime) [n1] $
490490
output "ReadyToFanout" ["headId" .= headId]
491491
send n1 $ input "Fanout" []
492492

493-
waitForAllMatch (10 * blockTime) [n1] $ checkFanout headId utxoToCommit
493+
waitForAllMatch (50 * blockTime) [n1] $ checkFanout headId utxoToCommit
494494
traceRemainingFunds Alice
495495
traceRemainingFunds AliceFunds
496496
where

hydra-cluster/test/Test/BlockfrostChainSpec.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Cardano.Api.UTxO qualified as UTxO
99
import Control.Concurrent.STM (takeTMVar)
1010
import Control.Concurrent.STM.TMVar (putTMVar)
1111
import Control.Exception (IOException)
12+
import Data.Time (secondsToNominalDiffTime)
1213
import Hydra.Chain (
1314
Chain (Chain, draftCommitTx, postTx),
1415
ChainEvent (..),
@@ -91,7 +92,7 @@ spec = around (onlyWithBlockfrostProjectFile . showLogsOnFailure "BlockfrostChai
9192
participants <- loadParticipants [Alice]
9293
let headParameters = HeadParameters blockfrostcperiod [alice]
9394
postTx $ InitTx{participants, headParameters}
94-
(headId, headSeed) <- observesInTimeSatisfying' aliceChain 500 $ hasInitTxWith headParameters participants
95+
(headId, headSeed) <- observesInTimeSatisfying' aliceChain (secondsToNominalDiffTime $ fromIntegral Blockfrost.queryTimeout) $ hasInitTxWith headParameters participants
9596

9697
let blueprintTx = txSpendingUTxO someUTxO
9798
externalCommit' backend aliceChain [aliceExternalSk] headId someUTxO blueprintTx
@@ -134,7 +135,7 @@ spec = around (onlyWithBlockfrostProjectFile . showLogsOnFailure "BlockfrostChai
134135
let expectedUTxO =
135136
(Snapshot.utxo snapshot <> fromMaybe mempty (Snapshot.utxoToCommit snapshot))
136137
`withoutUTxO` fromMaybe mempty (Snapshot.utxoToDecommit snapshot)
137-
observesInTimeSatisfying' aliceChain 500 $ \case
138+
observesInTimeSatisfying' aliceChain (secondsToNominalDiffTime $ fromIntegral Blockfrost.queryTimeout) $ \case
138139
OnFanoutTx{headId = headId', fanoutUTxO}
139140
| headId' == headId ->
140141
if UTxO.containsOutputs fanoutUTxO expectedUTxO

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Hydra.Options (ChainBackendOptions)
1111
import Hydra.Tx (ScriptRegistry)
1212

1313
blockfrostProjectPath :: FilePath
14-
blockfrostProjectPath = "./blockfrost-project.txt"
14+
blockfrostProjectPath = "blockfrost-project.txt"
1515

1616
class ChainBackend a where
1717
queryGenesisParameters :: (MonadIO m, MonadThrow m) => a -> m (GenesisParameters ShelleyEra)

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,13 @@ submitTransaction tx = Blockfrost.submitTx $ Blockfrost.CBORString $ fromStrict
378378
-- Queries --
379379
----------------
380380

381+
-- NOTE: Is this value good enough for all cardano networks?
382+
queryTimeout :: Int
383+
queryTimeout = 10
384+
385+
retryTimeout :: Int
386+
retryTimeout = 300
387+
381388
queryEraHistory :: BlockfrostClientT IO EraHistory
382389
queryEraHistory = do
383390
eras' <- Blockfrost.getNetworkEras
@@ -417,7 +424,7 @@ queryEraHistory = do
417424
-- | Query the Blockfrost API to get the 'UTxO' for 'TxIn' and convert to cardano 'UTxO'.
418425
-- FIXME: make blockfrost wait times configurable.
419426
queryUTxOByTxIn :: NetworkId -> [TxIn] -> BlockfrostClientT IO UTxO
420-
queryUTxOByTxIn networkId = foldMapM (\(TxIn txid _) -> go (300 :: Int) (serialiseToRawBytesHexText txid))
427+
queryUTxOByTxIn networkId = foldMapM (\(TxIn txid _) -> go retryTimeout (serialiseToRawBytesHexText txid))
421428
where
422429
go 0 txHash = liftIO $ throwIO $ BlockfrostError $ FailedUTxOForHash txHash
423430
go n txHash = do
@@ -450,6 +457,9 @@ queryScript scriptHashTxt = do
450457
-- fact this is a single address query always.
451458
queryUTxO :: NetworkId -> [Address ShelleyAddr] -> BlockfrostClientT IO UTxO
452459
queryUTxO networkId addresses = do
460+
-- NOTE: We can't know at the time of doing a query if the information on specific address UTxO is _fresh_ or not
461+
-- so we try to wait for sufficient period of time and hope for best.
462+
liftIO $ threadDelay $ fromIntegral queryTimeout
453463
let address' = Blockfrost.Address . serialiseAddress $ List.head addresses
454464
utxoWithAddresses <- Blockfrost.getAddressUtxos address'
455465

@@ -525,7 +535,7 @@ awaitTransaction :: Tx -> VerificationKey PaymentKey -> BlockfrostClientT IO UTx
525535
awaitTransaction tx vk = do
526536
Blockfrost.Genesis{_genesisNetworkMagic} <- queryGenesisParameters
527537
let networkId = toCardanoNetworkId _genesisNetworkMagic
528-
awaitUTxO networkId [makeShelleyAddress networkId (PaymentCredentialByKey $ verificationKeyHash vk) NoStakeAddress] (getTxId $ getTxBody tx) 300
538+
awaitUTxO networkId [makeShelleyAddress networkId (PaymentCredentialByKey $ verificationKeyHash vk) NoStakeAddress] (getTxId $ getTxBody tx) retryTimeout
529539

530540
-- | Await for specific UTxO at address - the one that is produced by the given 'TxId'.
531541
awaitUTxO ::

nohup.out

Whitespace-only changes.

0 commit comments

Comments
 (0)