Skip to content

Commit 5e64ad8

Browse files
committed
Construct a TimeHandle using blockfrost
Signed-off-by: Sasha Bogicevic <[email protected]>
1 parent d287e5d commit 5e64ad8

File tree

8 files changed

+122
-23
lines changed

8 files changed

+122
-23
lines changed

hydra-cardano-api/src/Cardano/Api/UTxO.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ filter fn = UTxO . Map.filter fn . toMap
7676
inputSet :: UTxO' out -> Set TxIn
7777
inputSet = Map.keysSet . toMap
7878

79+
squash :: [UTxO' out] -> UTxO' out
80+
squash = foldMap (<> mempty)
81+
7982
-- | Get a human-readable pretty text representation of a UTxO.
8083
render :: (TxIn, TxOut ctx era) -> Text
8184
render (k, TxOut _ (txOutValueToValue -> v) _ _) =

hydra-node/hydra-node.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ library
5858
Hydra.Chain
5959
Hydra.Chain.Blockfrost
6060
Hydra.Chain.Blockfrost.Client
61+
Hydra.Chain.Blockfrost.TimeHandle
6162
Hydra.Chain.CardanoClient
6263
Hydra.Chain.Direct
6364
Hydra.Chain.Direct.Handlers

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import Hydra.Cardano.Api (
1515
import Hydra.Chain.Blockfrost.Client (
1616
mkEraHistory,
1717
queryGenesis,
18+
queryScriptRegistry,
1819
queryTip,
1920
queryUTxO,
2021
runBlockfrostM,
@@ -24,18 +25,40 @@ import Hydra.Chain.Blockfrost.Client (
2425
import Hydra.Chain.Direct.Handlers (
2526
DirectChainLog (..),
2627
)
27-
import Hydra.Chain.Direct.Wallet (
28-
TinyWallet (..),
29-
WalletInfoOnChain (..),
30-
newTinyWallet,
31-
)
28+
import Hydra.Chain.Direct.State (ChainContext (..))
29+
import Hydra.Chain.Direct.Wallet (TinyWallet, WalletInfoOnChain (..), newTinyWallet)
3230
import Hydra.Logging (Tracer)
3331
import Hydra.Node.Util (
3432
readKeyPair,
3533
)
3634
import Hydra.Options (BlockfrostChainConfig (..))
35+
import Hydra.Tx (Party)
3736
import Ouroboros.Consensus.HardFork.History qualified as Consensus
3837

38+
-- | Build the 'ChainContext' from a 'ChainConfig' and additional information.
39+
loadChainContext ::
40+
BlockfrostChainConfig ->
41+
-- | Hydra party of our hydra node.
42+
Party ->
43+
-- | The current running era we can use to query the node
44+
IO ChainContext
45+
loadChainContext config party = do
46+
(vk, _) <- readKeyPair cardanoSigningKey
47+
(scriptRegistry, networkId) <- queryScriptRegistry projectPath hydraScriptsTxId
48+
pure $
49+
ChainContext
50+
{ networkId
51+
, ownVerificationKey = vk
52+
, ownParty = party
53+
, scriptRegistry
54+
}
55+
where
56+
BlockfrostChainConfig
57+
{ projectPath
58+
, hydraScriptsTxId
59+
, cardanoSigningKey
60+
} = config
61+
3962
mkTinyWallet ::
4063
Tracer IO DirectChainLog ->
4164
BlockfrostChainConfig ->

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

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Data.Time.Clock.POSIX
1414
import Hydra.Cardano.Api hiding (LedgerState, fromNetworkMagic)
1515

1616
import Cardano.Api.UTxO qualified as UTxO
17+
import Cardano.Crypto.Hash (hashToTextAsHex)
1718
import Cardano.Ledger.Api.PParams
1819
import Cardano.Ledger.BaseTypes (EpochInterval (..), EpochSize (..), NonNegativeInterval, UnitInterval, boundRational, unsafeNonZero)
1920
import Cardano.Ledger.Binary.Version (mkVersion)
@@ -42,7 +43,7 @@ import Data.Text qualified as T
4243
import Hydra.Cardano.Api.Prelude (StakePoolKey, fromNetworkMagic)
4344
import Hydra.Chain.CardanoClient (QueryPoint (..))
4445
import Hydra.Chain.ScriptRegistry (buildScriptPublishingTxs)
45-
import Hydra.Tx (txId)
46+
import Hydra.Tx (ScriptRegistry, newScriptRegistry, txId)
4647
import Money qualified
4748
import Ouroboros.Consensus.Block (GenesisWindow (..))
4849
import Ouroboros.Consensus.HardFork.History (Bound (..), EraEnd (..), EraParams (..), EraSummary (..), SafeZone (..), Summary (..), mkInterpreter)
@@ -63,6 +64,34 @@ runBlockfrostM prj action = do
6364
Left err -> throwIO (BlockfrostError $ show err)
6465
Right val -> pure val
6566

67+
-- | Query for 'TxIn's in the search for outputs containing all the reference
68+
-- scripts of the 'ScriptRegistry'.
69+
--
70+
-- This is implemented by repeated querying until we have all necessary
71+
-- reference scripts as we do only know the transaction id, not the indices.
72+
--
73+
-- Can throw at least 'NewScriptRegistryException' on failure.
74+
queryScriptRegistry ::
75+
(MonadIO m, MonadThrow m) =>
76+
FilePath ->
77+
[TxId] ->
78+
m (ScriptRegistry, NetworkId)
79+
queryScriptRegistry projectPath txIds = do
80+
prj <- liftIO $ Blockfrost.projectFromFile projectPath
81+
runBlockfrostM prj $ do
82+
Blockfrost.Genesis
83+
{ _genesisNetworkMagic
84+
, _genesisSystemStart
85+
} <-
86+
queryGenesis
87+
let networkId = toCardanoNetworkId _genesisNetworkMagic
88+
utxoList <- forM candidates $ \candidateTxIn -> queryUTxOByTxIn networkId candidateTxIn
89+
case newScriptRegistry $ UTxO.squash utxoList of
90+
Left e -> liftIO $ throwIO e
91+
Right sr -> pure (sr, networkId)
92+
where
93+
candidates = map (\txid -> TxIn txid (TxIx 0)) txIds
94+
6695
publishHydraScripts ::
6796
-- | The path where the Blockfrost project token hash is stored.
6897
FilePath ->
@@ -114,13 +143,13 @@ toCardanoUTxO :: [Blockfrost.AddressUtxo] -> AddressInEra -> UTxO' (TxOut CtxUTx
114143
toCardanoUTxO utxos addr = UTxO.fromPairs (toEntry <$> utxos)
115144
where
116145
toEntry :: Blockfrost.AddressUtxo -> (TxIn, TxOut CtxUTxO)
117-
toEntry utxo = (toCardanoTxIn utxo, toCardanoTxOut utxo addr)
146+
toEntry utxo = (toCardanoTxIn (Blockfrost._addressUtxoTxHash utxo) (Blockfrost._addressUtxoOutputIndex utxo), toCardanoTxOut utxo addr)
118147

119-
toCardanoTxIn :: Blockfrost.AddressUtxo -> TxIn
120-
toCardanoTxIn Blockfrost.AddressUtxo{_addressUtxoTxHash = Blockfrost.TxHash{unTxHash}, _addressUtxoOutputIndex} =
148+
toCardanoTxIn :: Blockfrost.TxHash -> Integer -> TxIn
149+
toCardanoTxIn Blockfrost.TxHash{unTxHash} i =
121150
case deserialiseFromRawBytesHex AsTxId (encodeUtf8 unTxHash) of
122151
Left err -> error (show err)
123-
Right txid -> TxIn txid (TxIx (fromIntegral _addressUtxoOutputIndex))
152+
Right txid -> TxIn txid (TxIx (fromIntegral i))
124153

125154
-- REVIEW! TxOutDatumNone and ReferenceScriptNone
126155
toCardanoTxOut :: Blockfrost.AddressUtxo -> AddressInEra -> TxOut CtxUTxO
@@ -361,6 +390,56 @@ mkEraHistory = do
361390
-- Wallet API --
362391
----------------
363392

393+
-- | Query the Blockfrost API to get the 'UTxO' for 'TxIn' and convert to cardano 'UTxO'.
394+
queryUTxOByTxIn :: NetworkId -> TxIn -> BlockfrostClientT IO UTxO
395+
queryUTxOByTxIn networkId txIn = do
396+
bfUTxO <- Blockfrost.getTxUtxos (Blockfrost.TxHash $ hashToTextAsHex txHash)
397+
fromBFUtxo bfUTxO
398+
where
399+
fromBFUtxo Blockfrost.TransactionUtxos{_transactionUtxosOutputs} = do
400+
utxoList <- mapM toCardanoUTxO' _transactionUtxosOutputs
401+
pure $ UTxO.squash utxoList
402+
403+
toCardanoUTxO' output@Blockfrost.UtxoOutput{_utxoOutputReferenceScriptHash} = do
404+
case _utxoOutputReferenceScriptHash of
405+
-- NOTE: We don't care about outputs without reference scripts
406+
Nothing -> pure mempty
407+
Just scriptHash -> do
408+
Blockfrost.ScriptCBOR{_scriptCborCbor} <- Blockfrost.getScriptCBOR scriptHash
409+
case _scriptCborCbor of
410+
Nothing -> liftIO $ throwIO $ BlockfrostError "Failed to get script CBOR."
411+
Just fullScriptCBOR -> do
412+
case decodeBase16 fullScriptCBOR of
413+
Left decodeErr -> liftIO $ throwIO . DecodeError $ "Bad Base16 PlutusScript CBOR: " <> decodeErr
414+
Right bytes ->
415+
case deserialiseFromCBOR (proxyToAsType (Proxy @PlutusScript)) bytes of
416+
Left err -> liftIO $ throwIO $ BlockfrostError $ "Failed to decode script: " <> T.pack (show err)
417+
Right plutusScript -> do
418+
let o = toCardanoTxOut' output plutusScript
419+
pure $ UTxO.singleton (txIn, o)
420+
421+
toCardanoTxOut' Blockfrost.UtxoOutput{_utxoOutputAddress, _utxoOutputAmount, _utxoOutputDataHash, _utxoOutputInlineDatum, _utxoOutputReferenceScriptHash} plutusScript =
422+
let datum =
423+
case _utxoOutputInlineDatum of
424+
Nothing ->
425+
case _utxoOutputDataHash of
426+
Nothing -> TxOutDatumNone
427+
Just datumHash -> TxOutDatumHash (fromString $ T.unpack $ Blockfrost.unDatumHash datumHash)
428+
Just (Blockfrost.InlineDatum (Blockfrost.ScriptDatumCBOR cborDatum)) ->
429+
case deserialiseFromCBOR (proxyToAsType (Proxy @HashableScriptData)) (encodeUtf8 cborDatum) of
430+
Left _ -> TxOutDatumNone
431+
Right hashableScriptData -> TxOutDatumInline hashableScriptData
432+
in TxOut (scriptAddr plutusScript) (toCardanoValue _utxoOutputAmount) datum (mkScriptRef plutusScript)
433+
434+
scriptAddr script =
435+
makeShelleyAddressInEra
436+
shelleyBasedEra
437+
networkId
438+
(PaymentCredentialByScript $ hashScript $ PlutusScript script)
439+
NoStakeAddress
440+
441+
TxIn (TxId txHash) _ = txIn
442+
364443
-- | Query the Blockfrost API for address UTxO and convert to cardano 'UTxO'.
365444
queryUTxO :: SigningKey PaymentKey -> NetworkId -> BlockfrostClientT IO UTxO
366445
queryUTxO sk networkId = do

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

Lines changed: 0 additions & 5 deletions
This file was deleted.

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,6 @@ import Hydra.Chain.Direct.State (
8080
ChainStateAt (..),
8181
)
8282
import Hydra.Chain.Direct.TimeHandle (queryTimeHandle)
83-
import Hydra.Chain.Direct.Util (
84-
readKeyPair,
85-
)
8683
import Hydra.Chain.Direct.Wallet (
8784
TinyWallet (..),
8885
WalletInfoOnChain (..),

hydra-node/src/Hydra/Chain/Direct/Wallet.hs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import Hydra.Prelude
88

99
import Cardano.Api.UTxO (UTxO)
1010
import Cardano.Ledger.Address qualified as Ledger
11-
import Cardano.Ledger.Alonzo.Plutus.Context (EraPlutusContext, ContextError, EraPlutusContext)
12-
import Cardano.Crypto.Hash.Class
11+
import Cardano.Ledger.Alonzo.Plutus.Context (ContextError, EraPlutusContext)
1312
import Cardano.Ledger.Alonzo.Scripts (
1413
AlonzoEraScript (..),
1514
AsIx (..),
@@ -101,8 +100,8 @@ import Hydra.Chain.CardanoClient (QueryPoint (..))
101100
import Hydra.Ledger.Cardano ()
102101
import Hydra.Logging (Tracer, traceWith)
103102

104-
type Address = Ledger.Addr StandardCrypto
105-
type TxIn = Ledger.TxIn StandardCrypto
103+
type Address = Ledger.Addr
104+
type TxIn = Ledger.TxIn
106105
type TxOut = Ledger.TxOut LedgerEra
107106

108107
-- | A 'TinyWallet' is a small abstraction of a wallet with basic UTXO
@@ -236,7 +235,7 @@ data ErrCoverFee
236235
| ErrUnknownInput {input :: TxIn}
237236
| ErrScriptExecutionFailed {redeemerPointer :: Text, scriptFailure :: Text}
238237
| ErrTranslationError (ContextError LedgerEra)
239-
| ErrConwayUpgradeError (TxUpgradeError Conway)
238+
| ErrConwayUpgradeError (TxUpgradeError ConwayEra)
240239
deriving stock (Show)
241240

242241
data ChangeError = ChangeError {inputBalance :: Coin, outputBalance :: Coin}

hydra-node/src/Hydra/Options.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,8 @@ data BlockfrostChainConfig = BlockfrostChainConfig
407407
-- ^ Path to the blockfrost project file
408408
, cardanoSigningKey :: FilePath
409409
-- ^ Path to the cardano signing key of the internal wallet.
410+
, hydraScriptsTxId :: [TxId]
411+
-- ^ Identifier of transaction holding the hydra scripts to use.
410412
}
411413
deriving stock (Eq, Show, Generic)
412414

0 commit comments

Comments
 (0)