hydra-demo/src/Hydra/Devnet.hs
2022-10-21 01:41:49 +01:00

378 lines
12 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
-- |
module Hydra.Devnet
( HydraScriptTxId
, HydraKeyInfo(..)
, SigningKey
, KeyPair(..)
, getCardanoAddress
, seedAddressFromFaucetAndWait
, publishReferenceScripts
, queryAddressUTXOs
, buildSignedHydraTx
, generateKeys
, cardanoNodePath
, hydraNodePath
, prepareDevnet
, devnetMagic
, minTxLovelace
)
where
import System.Which
import System.Directory
import System.Process
import Control.Monad
import Control.Monad.IO.Class
import Data.Aeson
import Control.Concurrent
import Data.Map (Map)
import Data.Bool
import qualified Data.Text as T
import qualified Data.Map as Map
import Data.String.Interpolate (i)
import Paths
import qualified Data.ByteString.Lazy.Char8 as BS
import Hydra.Types
import qualified Data.UUID.V4 as UUIDV4
import qualified Data.UUID as UUID
import Data.UUID (UUID)
import Data.Maybe (fromMaybe)
devnetMagic :: Int
devnetMagic = 42
prepareDevnet :: IO ()
prepareDevnet = do
output <- readCreateProcess (shell "[ -d devnet ] || ./demo/prepare-devnet.sh") ""
putStrLn output
cardanoNodePath :: FilePath
cardanoNodePath = $(staticWhich "cardano-node")
cardanoCliPath :: FilePath
cardanoCliPath = $(staticWhich "cardano-cli")
hydraNodePath :: FilePath
hydraNodePath = $(staticWhich "hydra-node")
jqPath :: FilePath
jqPath = $(staticWhich "jq")
type TxId = T.Text
type HydraScriptTxId = T.Text
type DraftTx = FilePath
type SignedTx = FilePath
devnetNetworkId :: Int
devnetNetworkId = 42
generateKeys :: (MonadIO m) => m HydraKeyInfo
generateKeys = do
basePath <- liftIO getTempPath'
HydraKeyInfo <$> generateCardanoKeys basePath <*> generateHydraKeys basePath
type SigningKey = String
type VerificationKey = String
data KeyPair = KeyPair
{ _signingKey :: SigningKey
, _verificationKey :: VerificationKey
}
deriving (Show,Read)
data HydraKeyInfo = HydraKeyInfo
{ _cardanoKeys :: KeyPair
, _hydraKeys :: KeyPair
}
deriving (Show,Read)
-- | Generate Cardano keys. Calling with an e.g. "my/keys/alice"
-- argument results in "my/keys/alice.cardano.{vk,sk}" keys being
-- written.
generateCardanoKeys :: (MonadIO m) => String -> m KeyPair
generateCardanoKeys path = do
output <- liftIO $
readCreateProcess
(proc cardanoCliPath [ "address"
, "key-gen"
, "--verification-key-file"
, [i|#{path}.cardano.vk|]
, "--signing-key-file"
, [i|#{path}.cardano.sk|]
])
""
liftIO $ putStrLn output
pure $ KeyPair [i|#{path}.cardano.sk|] [i|#{path}.cardano.vk|]
-- | Generate Hydra keys. Calling with an e.g. "my/keys/alice"
-- argument results in "my/keys/alice.hydra.{vk,sk}" keys being
-- written.
generateHydraKeys :: (MonadIO m) => String -> m KeyPair
generateHydraKeys path = do
output <- liftIO $
readCreateProcess
(proc hydraToolsPath [ "gen-hydra-key"
, "--output-file"
, [i|#{path}.hydra|]
])
""
liftIO $ putStrLn output
pure $ KeyPair [i|#{path}.hydra.sk|] [i|#{path}.hydra.vk|]
publishReferenceScripts :: (MonadIO m) => m HydraScriptTxId
publishReferenceScripts = do
liftIO . putStrLn $ "Publishing reference scripts ('νInitial' & 'νCommit')..."
fmap (T.strip . T.pack) $ liftIO $ readCreateProcess cp ""
where
cp = proc hydraNodePath [ "publish-scripts"
, "--network-id"
, show devnetNetworkId
, "--node-socket"
, "devnet/node.socket"
, "--cardano-signing-key"
, "devnet/credentials/faucet.sk"
]
waitForTxIn :: (MonadIO m) => TxIn -> m ()
waitForTxIn txin = do
liftIO . putStrLn $ "Waiting for utxo " <> show txin <> ".."
liftIO waitFn
where
waitFn = do
exists <- txInExists txin
threadDelay 10000
unless exists waitFn
txInExists :: TxIn -> IO Bool
txInExists txin = do
result <- fmap (T.strip . T.pack) $ readCreateProcess cp "" >>= readProcess jqPath (pure $ ".\"" <> asStr <> "\"")
pure $ case result of
"null" -> False
_ -> True
where
asStr = T.unpack txin
cp = (proc cardanoCliPath [ "query"
, "utxo"
, "--tx-in"
, asStr
, "--out-file"
, "/dev/stdout"
, "--testnet-magic"
, "42"
]) { env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
txInput :: Int -> TxId -> TxIn
txInput index txid = txid <> "#" <> (T.pack . show) index
-- TODO: use this in checks?
minTxLovelace :: Int
minTxLovelace = 857690
queryAddressUTXOs :: MonadIO m => Address -> m WholeUTXO
queryAddressUTXOs addr = liftIO $ do
let queryProc =
(proc cardanoCliPath [ "query"
, "utxo"
, "--address"
, addr
, "--testnet-magic"
, "42"
, "--out-file"
, "/dev/stdout"
])
{ env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
str <- readCreateProcess queryProc ""
pure $ fromMaybe mempty $ decode $ BS.pack str
getTempPath' :: IO FilePath
getTempPath' = snd <$> getTempPath
getTempPath :: IO (UUID, FilePath)
getTempPath = do
createDirectoryIfMissing True "tmp"
uid <- UUIDV4.nextRandom
pure . (uid,) . ("tmp/" <>) . UUID.toString $ uid
-- TODO(skylar): Check lovelace vs the full amount!
buildSignedHydraTx :: SigningKey -> Address -> Address -> Map TxIn Lovelace -> Lovelace -> IO String
buildSignedHydraTx signingKey fromAddr toAddr txInAmounts amount = do
let fullAmount = sum txInAmounts
txBodyPath <- snd <$> getTempPath
void $ readCreateProcess (proc cardanoCliPath
([ "transaction"
, "build-raw"
, "--babbage-era"
]
<> (concatMap (\txin -> ["--tx-in", T.unpack txin]) . Map.keys $ txInAmounts)
<>
[ "--tx-out"
, [i|#{toAddr}+#{amount}|]
, "--tx-out"
, [i|#{fromAddr}+#{fullAmount - amount}|]
, "--fee"
, "0"
, "--out-file"
, txBodyPath
]))
""
readCreateProcess
(proc cardanoCliPath
[ "transaction"
, "sign"
, "--tx-body-file"
, txBodyPath
, "--signing-key-file"
, signingKey
, "--out-file"
, "/dev/stdout"
])
""
-- { env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
-- | Convenience for getting faucet Output for seeding
getFirstTxIn :: Address -> IO TxIn
getFirstTxIn addr =
readCreateProcess cp "" >>= readProcess jqPath ["-r", "keys[0]"] >>= \a -> pure $ T.strip $ T.pack a
where
cp = (proc cardanoCliPath [ "query"
, "utxo"
, "--address"
, addr
, "--testnet-magic"
, "42"
, "--out-file"
, "/dev/stdout"
]) { env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
getCardanoAddress :: VerificationKey -> IO Address
getCardanoAddress keyPath =
readCreateProcess cp ""
where
cp = (proc cardanoCliPath [ "address"
, "build"
, "--payment-verification-key-file"
, keyPath
, "--testnet-magic"
, "42"
]) { env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
getFaucetAddress :: IO Address
getFaucetAddress = readCreateProcess cp ""
where
cp = (proc cardanoCliPath [ "address"
, "build"
, "--payment-verification-key-file"
, "devnet/credentials/faucet.vk"
, "--testnet-magic"
, "42"
]) { env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
seedAddressFromFaucetAndWait :: (MonadIO m) => Address -> Lovelace -> Bool -> m TxIn
seedAddressFromFaucetAndWait addr amount isFuel = do
txin <- liftIO $ seedAddressFromFaucet addr amount isFuel
waitForTxIn txin
pure txin
-- | Send an amount in lovelace to the named actor
seedAddressFromFaucet :: Address -> Lovelace -> Bool -> IO TxIn
seedAddressFromFaucet addr amount isFuel = do
draftTx <- buildSeedTxForAddress addr amount isFuel
signedTx <- signSeedTx' draftTx
txin <- txInput 0 <$> seedTxIdFromSignedTx signedTx
submitTx signedTx
pure txin
buildSeedTxForAddress :: Address -> Lovelace -> Bool -> IO DraftTx
buildSeedTxForAddress addr amount isFuel = do
filename <- getTempPath'
-- when (amount < minTxLovelace) $ error $ "Minmum required UTxO: Lovelace " <> show minTxLovelace
let cp faucet hash = (proc cardanoCliPath $ filter (/= "")
[ "transaction"
, "build"
, "--babbage-era"
, "--cardano-mode"
, "--change-address"
, faucet
, "--tx-in"
, hash
, "--tx-out"
, addr <> "+" <> show amount
]
<> bool [] [ "--tx-out-datum-hash", T.unpack fuelMarkerDatumHash ] isFuel
<>
[ "--out-file"
, filename
, "--testnet-magic"
, "42"
])
{ env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
faucet <- getFaucetAddress
hash <- getFirstTxIn faucet
_ <- readCreateProcess (cp faucet (T.unpack hash)) ""
pure filename
signSeedTx' :: DraftTx -> IO SignedTx
signSeedTx' draftFile = do
outFile <- getTempPath'
let cp = (proc cardanoCliPath [ "transaction"
, "sign"
, "--tx-body-file"
, draftFile
, "--signing-key-file"
, "devnet/credentials/faucet.sk"
, "--out-file"
, outFile
, "--testnet-magic"
, "42"
])
{ env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
_ <- readCreateProcess cp ""
pure outFile
seedTxIdFromSignedTx :: SignedTx -> IO TxId
seedTxIdFromSignedTx filename =
T.strip . T.pack <$> readCreateProcess cp ""
where
cp = (proc cardanoCliPath [ "transaction"
, "txid"
, "--tx-file"
, filename
]) { env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }
submitTx :: SignedTx -> IO ()
submitTx signedFile = do
_ <- readCreateProcess cp ""
pure ()
where
cp = (proc cardanoCliPath [ "transaction"
, "submit"
, "--tx-file"
, signedFile
, "--testnet-magic"
, "42"
]) { env = Just [("CARDANO_NODE_SOCKET_PATH", "devnet/node.socket")] }