From d2b037d0a3b2028ac061063dc26c33413652472c Mon Sep 17 00:00:00 2001 From: Daniel Firth Date: Mon, 18 Nov 2024 11:51:33 +0000 Subject: [PATCH 1/7] Import schnorkell plutus add singlePartyUsesSchnorrkelScriptOnL2 scenario test Finalize schnorkel test case There is some refactoring in order but the test itself is fine. Update plutus source-repository-package treefmt fixup Have some ada for the min UTxO Almost there; insufficient collateral now Trying to be more clear about the UTxOs to pay for things WIP on schnorrkel; just missing script as input --- cabal.project | 22 ++++ hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 100 ++++++++++++++++++- hydra-cluster/test/Test/EndToEndSpec.hs | 6 ++ hydra-node/src/Hydra/Chain/CardanoClient.hs | 40 +++++--- hydra-plutus/src/Hydra/Contract/Dummy.hs | 18 +++- hydra-tx/src/Hydra/Ledger/Cardano/Builder.hs | 4 + hydra-tx/src/Hydra/Tx/Utils.hs | 3 +- 7 files changed, 177 insertions(+), 16 deletions(-) diff --git a/cabal.project b/cabal.project index 818f82bf657..fff10556092 100644 --- a/cabal.project +++ b/cabal.project @@ -45,3 +45,25 @@ program-options constraints: quickcheck-instances==0.3.31, data-default==0.7.1.3 + +source-repository-package + type: git + location: https://github.com/locallycompact/cardano-base + tag: 1a800a0c1392935256aebabf6c1fdefe5e8b34ae + --sha256: sha256-LXYzVyWHWBS361NR4pL/Jbilnv48z6ZozltaL2/ym2s= + subdir: + cardano-crypto-class + +source-repository-package + type: git + location: https://github.com/locallycompact/plutus + tag: b117b4460b5b5da2a599db8693b18dacd811bb91 + --sha256: sha256-GUPwIwbTOy/eTBhjOwrR+XwJsML/jmBlAf1qU6zWvd8= + subdir: + prettyprinter-configurable + plutus-core + plutus-ledger-api + plutus-tx-plugin + plutus-tx + +allow-newer: cardano-crypto-class diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index 8aaba13daf3..ced5cc55fec 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -11,11 +11,14 @@ import CardanoClient ( QueryPoint (QueryTip), RunningNode (..), buildTransaction, + buildTransactionWithBody, + queryProtocolParameters, queryTip, queryUTxOFor, submitTx, waitForUTxO, ) +import Hydra.Cardano.Api.Pretty (renderTxWithUTxO) import CardanoNode (NodeLog) import Control.Concurrent.Async (mapConcurrently_) import Control.Lens ((^..), (^?)) @@ -35,13 +38,17 @@ import Hydra.API.HTTPServer ( ) import Hydra.Cardano.Api ( Coin (..), + mkTxIn, File (File), Key (SigningKey), + KeyWitnessInCtx (KeyWitnessForSpending), PaymentKey, Tx, TxId, UTxO, addTxIns, + addTxInsCollateral, + addTxOuts, defaultTxBodyContent, getTxBody, getTxId, @@ -51,18 +58,23 @@ import Hydra.Cardano.Api ( mkScriptAddress, mkScriptDatum, mkScriptWitness, + mkTxOutAutoBalance, mkTxOutDatumHash, mkVkAddress, scriptWitnessInCtx, selectLovelace, + setTxFee, signTx, toScriptData, txOutValue, utxoFromTx, writeFileTextEnvelope, pattern BuildTxWith, + pattern KeyWitness, + pattern PlutusScriptSerialised, pattern ReferenceScriptNone, pattern ScriptWitness, + pattern TxFeeExplicit, pattern TxOut, pattern TxOutDatumNone, ) @@ -72,12 +84,12 @@ import Hydra.Cluster.Fixture (Actor (..), actorName, alice, aliceSk, aliceVk, bo import Hydra.Cluster.Mithril (MithrilLog) import Hydra.Cluster.Options (Options) import Hydra.Cluster.Util (chainConfigFor, keysFor, modifyConfig, setNetworkId) -import Hydra.Ledger.Cardano (mkSimpleTx, mkTransferTx, unsafeBuildTransaction) +import Hydra.Ledger.Cardano (changePParams, mkSimpleTx, mkTransferTx, unsafeBuildTransaction) import Hydra.Logging (Tracer, traceWith) import Hydra.Options (DirectChainConfig (..), networkId, startChainFrom) import Hydra.Tx (HeadId, IsTx (balance), Party, txId) import Hydra.Tx.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod), fromNominalDiffTime) -import Hydra.Tx.Utils (dummyValidatorScript, verificationKeyToOnChainId) +import Hydra.Tx.Utils (dummyValidatorScript, schnorrkelValidatorScript, verificationKeyToOnChainId) import HydraNode ( HydraClient (..), HydraNodeLog, @@ -381,6 +393,90 @@ singlePartyCommitsFromExternal tracer workDir node hydraScriptsTxId = where RunningNode{nodeSocket, blockTime} = node +singlePartyUsesSchnorrkelScriptOnL2 :: + Tracer IO EndToEndLog -> + FilePath -> + RunningNode -> + [TxId] -> + IO () +singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = + ( `finally` + do + returnFundsToFaucet tracer node Alice + returnFundsToFaucet tracer node AliceFunds + ) + $ do + refuelIfNeeded tracer node Alice 250_000_000 + aliceChainConfig <- chainConfigFor Alice workDir nodeSocket hydraScriptsTxId [] $ UnsafeContestationPeriod 100 + let hydraNodeId = 1 + let hydraTracer = contramap FromHydraNode tracer + withHydraNode hydraTracer aliceChainConfig workDir hydraNodeId aliceSk [] [1] $ \n1 -> do + send n1 $ input "Init" [] + headId <- waitMatch (10 * blockTime) n1 $ headIsInitializingWith (Set.fromList [alice]) + + (walletVk, walletSk) <- keysFor AliceFunds + + -- Create money on L1 + utxoToCommit <- seedFromFaucet node walletVk 100_000_000 (contramap FromFaucet tracer) + + -- Push it into L2 + requestCommitTx n1 utxoToCommit <&> signTx walletSk >>= \tx -> do + putStrLn $ renderTxWithUTxO utxoToCommit tx + submitTx node tx + + -- Check UTxO is present in L2 + waitFor hydraTracer (10 * blockTime) [n1] $ + output "HeadIsOpen" ["utxo" .= toJSON utxoToCommit, "headId" .= headId] + + pparams <- queryProtocolParameters networkId nodeSocket QueryTip + + -- Send the UTxO to a script; in preparation for running the script + let serializedScript = PlutusScriptSerialised dummyValidatorScript + -- let serializedScript = PlutusScriptSerialised schnorrkelValidatorScript + let scriptAddress = mkScriptAddress networkId serializedScript + let scriptOutput = + mkTxOutAutoBalance + pparams + scriptAddress + (lovelaceToValue 0) -- Autobalanced + (mkTxOutDatumHash ()) + ReferenceScriptNone + + Right tx <- buildTransaction networkId nodeSocket (mkVkAddress networkId walletVk) utxoToCommit [] [scriptOutput] + + let signedL2tx = signTx walletSk tx + send n1 $ input "NewTx" ["transaction" .= signedL2tx] + + putStrLn $ renderTxWithUTxO utxoToCommit signedL2tx + + waitMatch 10 n1 $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + guard $ + toJSON signedL2tx + `elem` (v ^.. key "snapshot" . key "confirmed" . values) + + -- Finally, take money from the script + let scriptWitness = + BuildTxWith $ + ScriptWitness scriptWitnessInCtx $ + mkScriptWitness serializedScript (mkScriptDatum ()) (toScriptData ()) + + -- TODO: Include the script as an input! + let body = + defaultTxBodyContent + & addTxIns [(mkTxIn signedL2tx 0, scriptWitness)] + + tx <- either (failure . show) pure =<< buildTransactionWithBody networkId nodeSocket (mkVkAddress networkId walletVk) body utxoToCommit + let signedL2tx = signTx walletSk tx + + waitMatch 10 n1 $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + guard $ + toJSON signedL2tx + `elem` (v ^.. key "snapshot" . key "confirmed" . values) + where + RunningNode{networkId, nodeSocket, blockTime} = node + singlePartyCommitsScriptBlueprint :: Tracer IO EndToEndLog -> FilePath -> diff --git a/hydra-cluster/test/Test/EndToEndSpec.hs b/hydra-cluster/test/Test/EndToEndSpec.hs index 23cff102141..0dc8f808307 100644 --- a/hydra-cluster/test/Test/EndToEndSpec.hs +++ b/hydra-cluster/test/Test/EndToEndSpec.hs @@ -67,6 +67,7 @@ import Hydra.Cluster.Scenarios ( singlePartyCommitsFromExternalTxBlueprint, singlePartyCommitsScriptBlueprint, singlePartyHeadFullLifeCycle, + singlePartyUsesSchnorrkelScriptOnL2, testPreventResumeReconfiguredPeer, threeNodesNoErrorsOnOpen, ) @@ -178,6 +179,11 @@ spec = around (showLogsOnFailure "EndToEndSpec") $ do withCardanoNodeDevnet (contramap FromCardanoNode tracer) tmpDir $ \node -> publishHydraScriptsAs node Faucet >>= singlePartyCommitsFromExternal tracer tmpDir node + it "can use a schnorrkel script on L2" $ \tracer -> do + withClusterTempDir $ \tmpDir -> do + withCardanoNodeDevnet (contramap FromCardanoNode tracer) tmpDir $ \node -> + publishHydraScriptsAs node Faucet + >>= singlePartyUsesSchnorrkelScriptOnL2 tracer tmpDir node it "can submit a signed user transaction" $ \tracer -> do withClusterTempDir $ \tmpDir -> do withCardanoNodeDevnet (contramap FromCardanoNode tracer) tmpDir $ \node -> diff --git a/hydra-node/src/Hydra/Chain/CardanoClient.hs b/hydra-node/src/Hydra/Chain/CardanoClient.hs index fd724190dde..8a3aac6c677 100644 --- a/hydra-node/src/Hydra/Chain/CardanoClient.hs +++ b/hydra-node/src/Hydra/Chain/CardanoClient.hs @@ -63,26 +63,19 @@ mkCardanoClient networkId nodeSocket = -- * Tx Construction / Submission --- | Construct a simple payment consuming some inputs and producing some --- outputs (no certificates or withdrawals involved). --- --- On success, the returned transaction is fully balanced. On error, return --- `TxBodyErrorAutoBalance`. -buildTransaction :: +buildTransactionWithBody :: -- | Current network identifier NetworkId -> -- | Filepath to the cardano-node's domain socket SocketPath -> -- | Change address to send AddressInEra -> + -- | Body + TxBodyContent BuildTx -> -- | Unspent transaction outputs to spend. UTxO -> - -- | Collateral inputs. - [TxIn] -> - -- | Outputs to create. - [TxOut CtxTx] -> IO (Either (TxBodyErrorAutoBalance Era) Tx) -buildTransaction networkId socket changeAddress utxoToSpend collateral outs = do +buildTransactionWithBody networkId socket changeAddress body utxoToSpend = do pparams <- queryProtocolParameters networkId socket QueryTip systemStart <- querySystemStart networkId socket QueryTip eraHistory <- queryEraHistory networkId socket QueryTip @@ -98,9 +91,32 @@ buildTransaction networkId socket changeAddress utxoToSpend collateral outs = do mempty mempty (UTxO.toApi utxoToSpend) - (bodyContent pparams) + body changeAddress Nothing + +-- | Construct a simple payment consuming some inputs and producing some +-- outputs (no certificates or withdrawals involved). +-- +-- On success, the returned transaction is fully balanced. On error, return +-- `TxBodyErrorAutoBalance`. +buildTransaction :: + -- | Current network identifier + NetworkId -> + -- | Filepath to the cardano-node's domain socket + SocketPath -> + -- | Change address to send + AddressInEra -> + -- | Unspent transaction outputs to spend. + UTxO -> + -- | Collateral inputs. + [TxIn] -> + -- | Outputs to create. + [TxOut CtxTx] -> + IO (Either (TxBodyErrorAutoBalance Era) Tx) +buildTransaction networkId socket changeAddress utxoToSpend collateral outs = do + pparams <- queryProtocolParameters networkId socket QueryTip + buildTransactionWithBody networkId socket changeAddress (bodyContent pparams) utxoToSpend where -- NOTE: 'makeTransactionBodyAutoBalance' overwrites this. dummyFeeForBalancing = TxFeeExplicit 0 diff --git a/hydra-plutus/src/Hydra/Contract/Dummy.hs b/hydra-plutus/src/Hydra/Contract/Dummy.hs index 2f200252ae0..d340157232b 100644 --- a/hydra-plutus/src/Hydra/Contract/Dummy.hs +++ b/hydra-plutus/src/Hydra/Contract/Dummy.hs @@ -5,12 +5,28 @@ module Hydra.Contract.Dummy where -import Hydra.Prelude +import Hydra.Cardano.Api (PlutusScriptVersion (PlutusScriptV3)) +import Hydra.Plutus.Extras (ValidatorType, scriptValidatorHash, wrapValidator) +import Hydra.Prelude hiding ((==)) import Hydra.Cardano.Api (PlutusScript, pattern PlutusScriptSerialised) import Hydra.Plutus.Extras (ValidatorType, wrapValidator) import PlutusLedgerApi.V3 (BuiltinData, ScriptContext, serialiseCompiledCode, toOpaque) import PlutusTx (CompiledCode, compile) +import PlutusTx.Builtins (schnorrkel) +import PlutusTx.Prelude (Eq (..)) + +schnorrkelValidator :: BuiltinData -> BuiltinData -> ScriptContext -> Bool +schnorrkelValidator _ _ _ = "" == schnorrkel "" + +schnorrkelValidatorScript :: SerialisedScript +schnorrkelValidatorScript = serialiseCompiledCode compiledDummyValidator + +compiledSchnorrkelValidator :: CompiledCode ValidatorType +compiledSchnorrkelValidator = + $$(PlutusTx.compile [||fakeWrap schnorrkelValidator||]) + where + wrap = wrapValidator @BuiltinData @BuiltinData dummyValidator :: BuiltinData -> BuiltinData -> ScriptContext -> Bool dummyValidator _ _ _ = True diff --git a/hydra-tx/src/Hydra/Ledger/Cardano/Builder.hs b/hydra-tx/src/Hydra/Ledger/Cardano/Builder.hs index b9f089771d9..19639e85ccd 100644 --- a/hydra-tx/src/Hydra/Ledger/Cardano/Builder.hs +++ b/hydra-tx/src/Hydra/Ledger/Cardano/Builder.hs @@ -38,6 +38,10 @@ addTxInsSpending :: [TxIn] -> TxBodyContent BuildTx -> TxBodyContent BuildTx addTxInsSpending ins = addTxIns ((,BuildTxWith $ KeyWitness KeyWitnessForSpending) <$> ins) +changePParams :: PParams (ShelleyLedgerEra Era) -> TxBodyContent BuildTx -> TxBodyContent BuildTx +changePParams pparams tx = + tx{txProtocolParams = BuildTxWith $ Just $ LedgerProtocolParameters pparams} + -- | Mint tokens with given plutus minting script and redeemer. mintTokens :: ToScriptData redeemer => PlutusScript -> redeemer -> [(AssetName, Quantity)] -> TxBodyContent BuildTx -> TxBodyContent BuildTx mintTokens script redeemer assets = addTxMintValue newTokens diff --git a/hydra-tx/src/Hydra/Tx/Utils.hs b/hydra-tx/src/Hydra/Tx/Utils.hs index bd53b730042..282a828e5c6 100644 --- a/hydra-tx/src/Hydra/Tx/Utils.hs +++ b/hydra-tx/src/Hydra/Tx/Utils.hs @@ -1,6 +1,7 @@ module Hydra.Tx.Utils ( module Hydra.Tx.Utils, dummyValidatorScript, + schnorrkelValidatorScript, ) where import Hydra.Cardano.Api @@ -13,7 +14,7 @@ import Control.Lens ((.~), (^.)) import Data.Map.Strict qualified as Map import Data.Maybe.Strict (StrictMaybe (..)) import GHC.IsList (IsList (..)) -import Hydra.Contract.Dummy (dummyValidatorScript) +import Hydra.Contract.Dummy (dummyValidatorScript, schnorrkelValidatorScript) import Hydra.Contract.Util (hydraHeadV1) import Hydra.Tx.OnChainId (OnChainId (..)) import Ouroboros.Consensus.Shelley.Eras qualified as Ledger From b56a8ed2de9edf5731b245b33fec2aac70378ca1 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Mon, 13 Jan 2025 12:24:51 +0000 Subject: [PATCH 2/7] Treefmt --- hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index ced5cc55fec..6ed908fb2e7 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -18,7 +18,6 @@ import CardanoClient ( submitTx, waitForUTxO, ) -import Hydra.Cardano.Api.Pretty (renderTxWithUTxO) import CardanoNode (NodeLog) import Control.Concurrent.Async (mapConcurrently_) import Control.Lens ((^..), (^?)) @@ -38,7 +37,6 @@ import Hydra.API.HTTPServer ( ) import Hydra.Cardano.Api ( Coin (..), - mkTxIn, File (File), Key (SigningKey), KeyWitnessInCtx (KeyWitnessForSpending), @@ -58,6 +56,7 @@ import Hydra.Cardano.Api ( mkScriptAddress, mkScriptDatum, mkScriptWitness, + mkTxIn, mkTxOutAutoBalance, mkTxOutDatumHash, mkVkAddress, @@ -78,6 +77,7 @@ import Hydra.Cardano.Api ( pattern TxOut, pattern TxOutDatumNone, ) +import Hydra.Cardano.Api.Pretty (renderTxWithUTxO) import Hydra.Cluster.Faucet (FaucetLog, createOutputAtAddress, seedFromFaucet, seedFromFaucet_) import Hydra.Cluster.Faucet qualified as Faucet import Hydra.Cluster.Fixture (Actor (..), actorName, alice, aliceSk, aliceVk, bob, bobSk, bobVk, carol, carolSk) @@ -420,9 +420,10 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = utxoToCommit <- seedFromFaucet node walletVk 100_000_000 (contramap FromFaucet tracer) -- Push it into L2 - requestCommitTx n1 utxoToCommit <&> signTx walletSk >>= \tx -> do - putStrLn $ renderTxWithUTxO utxoToCommit tx - submitTx node tx + requestCommitTx n1 utxoToCommit + <&> signTx walletSk >>= \tx -> do + putStrLn $ renderTxWithUTxO utxoToCommit tx + submitTx node tx -- Check UTxO is present in L2 waitFor hydraTracer (10 * blockTime) [n1] $ From a53793b5e196722a0540067ac08f2a24ade6bff9 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 14 Jan 2025 14:40:14 +0000 Subject: [PATCH 3/7] Still not working but getting closer --- hydra-cluster/hydra-cluster.cabal | 1 + hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 25 +++++++++++--------- hydra-plutus/src/Hydra/Contract/Dummy.hs | 4 ++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/hydra-cluster/hydra-cluster.cabal b/hydra-cluster/hydra-cluster.cabal index 770ac27c1fd..a8e7ec31dc9 100644 --- a/hydra-cluster/hydra-cluster.cabal +++ b/hydra-cluster/hydra-cluster.cabal @@ -113,6 +113,7 @@ library , typed-process , unix , websockets + , cardano-api ghc-options: -haddock diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index 6ed908fb2e7..8af22c0ca4e 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -39,7 +39,6 @@ import Hydra.Cardano.Api ( Coin (..), File (File), Key (SigningKey), - KeyWitnessInCtx (KeyWitnessForSpending), PaymentKey, Tx, TxId, @@ -69,11 +68,8 @@ import Hydra.Cardano.Api ( utxoFromTx, writeFileTextEnvelope, pattern BuildTxWith, - pattern KeyWitness, - pattern PlutusScriptSerialised, pattern ReferenceScriptNone, pattern ScriptWitness, - pattern TxFeeExplicit, pattern TxOut, pattern TxOutDatumNone, ) @@ -84,7 +80,7 @@ import Hydra.Cluster.Fixture (Actor (..), actorName, alice, aliceSk, aliceVk, bo import Hydra.Cluster.Mithril (MithrilLog) import Hydra.Cluster.Options (Options) import Hydra.Cluster.Util (chainConfigFor, keysFor, modifyConfig, setNetworkId) -import Hydra.Ledger.Cardano (changePParams, mkSimpleTx, mkTransferTx, unsafeBuildTransaction) +import Hydra.Ledger.Cardano (mkSimpleTx, mkTransferTx, unsafeBuildTransaction) import Hydra.Logging (Tracer, traceWith) import Hydra.Options (DirectChainConfig (..), networkId, startChainFrom) import Hydra.Tx (HeadId, IsTx (balance), Party, txId) @@ -422,7 +418,7 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = -- Push it into L2 requestCommitTx n1 utxoToCommit <&> signTx walletSk >>= \tx -> do - putStrLn $ renderTxWithUTxO utxoToCommit tx + -- putStrLn $ renderTxWithUTxO utxoToCommit tx submitTx node tx -- Check UTxO is present in L2 @@ -432,8 +428,9 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = pparams <- queryProtocolParameters networkId nodeSocket QueryTip -- Send the UTxO to a script; in preparation for running the script - let serializedScript = PlutusScriptSerialised dummyValidatorScript - -- let serializedScript = PlutusScriptSerialised schnorrkelValidatorScript + let serializedScript = dummyValidatorScript + -- TODO: Use this one. + -- let serializedScript = schnorrkelValidatorScript let scriptAddress = mkScriptAddress networkId serializedScript let scriptOutput = mkTxOutAutoBalance @@ -448,8 +445,6 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = let signedL2tx = signTx walletSk tx send n1 $ input "NewTx" ["transaction" .= signedL2tx] - putStrLn $ renderTxWithUTxO utxoToCommit signedL2tx - waitMatch 10 n1 $ \v -> do guard $ v ^? key "tag" == Just "SnapshotConfirmed" guard $ @@ -462,10 +457,18 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = ScriptWitness scriptWitnessInCtx $ mkScriptWitness serializedScript (mkScriptDatum ()) (toScriptData ()) + let txIn = mkTxIn signedL2tx 0 + + putStrLn "UTxO ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + putStrLn $ renderTxWithUTxO utxoToCommit tx + putStrLn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UTxO" + -- TODO: Include the script as an input! let body = defaultTxBodyContent - & addTxIns [(mkTxIn signedL2tx 0, scriptWitness)] + & addTxIns [(txIn, scriptWitness)] + + print body tx <- either (failure . show) pure =<< buildTransactionWithBody networkId nodeSocket (mkVkAddress networkId walletVk) body utxoToCommit let signedL2tx = signTx walletSk tx diff --git a/hydra-plutus/src/Hydra/Contract/Dummy.hs b/hydra-plutus/src/Hydra/Contract/Dummy.hs index d340157232b..ac7a0134fb4 100644 --- a/hydra-plutus/src/Hydra/Contract/Dummy.hs +++ b/hydra-plutus/src/Hydra/Contract/Dummy.hs @@ -19,8 +19,8 @@ import PlutusTx.Prelude (Eq (..)) schnorrkelValidator :: BuiltinData -> BuiltinData -> ScriptContext -> Bool schnorrkelValidator _ _ _ = "" == schnorrkel "" -schnorrkelValidatorScript :: SerialisedScript -schnorrkelValidatorScript = serialiseCompiledCode compiledDummyValidator +schnorrkelValidatorScript :: PlutusScript +schnorrkelValidatorScript = PlutusScriptSerialised $ serialiseCompiledCode compiledDummyValidator compiledSchnorrkelValidator :: CompiledCode ValidatorType compiledSchnorrkelValidator = From 6d43e609a53b3c0f16205e4f809920570279bff5 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 15 Jan 2025 14:39:03 +0000 Subject: [PATCH 4/7] Identified problem: autobalancing --- hydra-cluster/hydra-cluster.cabal | 1 - hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 21 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hydra-cluster/hydra-cluster.cabal b/hydra-cluster/hydra-cluster.cabal index a8e7ec31dc9..770ac27c1fd 100644 --- a/hydra-cluster/hydra-cluster.cabal +++ b/hydra-cluster/hydra-cluster.cabal @@ -113,7 +113,6 @@ library , typed-process , unix , websockets - , cardano-api ghc-options: -haddock diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index 8af22c0ca4e..66a876e7edd 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -46,6 +46,7 @@ import Hydra.Cardano.Api ( addTxIns, addTxInsCollateral, addTxOuts, + createAndValidateTransactionBody, defaultTxBodyContent, getTxBody, getTxId, @@ -418,7 +419,6 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = -- Push it into L2 requestCommitTx n1 utxoToCommit <&> signTx walletSk >>= \tx -> do - -- putStrLn $ renderTxWithUTxO utxoToCommit tx submitTx node tx -- Check UTxO is present in L2 @@ -457,22 +457,23 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = ScriptWitness scriptWitnessInCtx $ mkScriptWitness serializedScript (mkScriptDatum ()) (toScriptData ()) - let txIn = mkTxIn signedL2tx 0 - - putStrLn "UTxO ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - putStrLn $ renderTxWithUTxO utxoToCommit tx - putStrLn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UTxO" + -- Note: Bug! autobalancing breaks the script business + -- tx <- either (failure . show) pure =<< buildTransactionWithBody networkId nodeSocket (mkVkAddress networkId walletVk) body utxoToCommit - -- TODO: Include the script as an input! + let txIn = mkTxIn signedL2tx 0 let body = defaultTxBodyContent & addTxIns [(txIn, scriptWitness)] - print body - - tx <- either (failure . show) pure =<< buildTransactionWithBody networkId nodeSocket (mkVkAddress networkId walletVk) body utxoToCommit + -- Note: Fix! Use `createAndValidateTransactionBody` instead. This + -- means we _can_ construct the tx; but it doesn't submit (because it + -- isn't balanced! And it's missing collateral, etc... + txBody <- either (failure . show) pure (createAndValidateTransactionBody body) + let tx = makeSignedTransaction [] txBody let signedL2tx = signTx walletSk tx + send n1 $ input "NewTx" ["transaction" .= signedL2tx] + waitMatch 10 n1 $ \v -> do guard $ v ^? key "tag" == Just "SnapshotConfirmed" guard $ From 8c83664fb68c726c4160142d3d2146ed2bc70e87 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Mon, 20 Jan 2025 13:28:52 +0000 Subject: [PATCH 5/7] Wip --- hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 41 +++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index 66a876e7edd..1518d8a9483 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -19,6 +19,7 @@ import CardanoClient ( waitForUTxO, ) import CardanoNode (NodeLog) +import Cardano.Ledger.Core (witsTxL) import Control.Concurrent.Async (mapConcurrently_) import Control.Lens ((^..), (^?)) import Data.Aeson (Value, object, (.=)) @@ -73,6 +74,12 @@ import Hydra.Cardano.Api ( pattern ScriptWitness, pattern TxOut, pattern TxOutDatumNone, + pattern TxFeeExplicit, + pattern KeyWitness, + KeyWitnessInCtx (KeyWitnessForSpending), + txOuts', + negateValue, + setTxProtocolParams ) import Hydra.Cardano.Api.Pretty (renderTxWithUTxO) import Hydra.Cluster.Faucet (FaucetLog, createOutputAtAddress, seedFromFaucet, seedFromFaucet_) @@ -460,10 +467,20 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = -- Note: Bug! autobalancing breaks the script business -- tx <- either (failure . show) pure =<< buildTransactionWithBody networkId nodeSocket (mkVkAddress networkId walletVk) body utxoToCommit + let txIn = mkTxIn signedL2tx 0 + let remainder = mkTxIn signedL2tx 1 + + let fee = 8_000_000 + + let outAmt = foldMap txOutValue (txOuts' tx) <> negateValue (lovelaceToValue fee) let body = defaultTxBodyContent - & addTxIns [(txIn, scriptWitness)] + & addTxIns [(txIn, scriptWitness), (remainder, BuildTxWith $ KeyWitness KeyWitnessForSpending)] + & addTxInsCollateral [remainder] + & setTxFee (TxFeeExplicit fee) + & addTxOuts [TxOut (mkVkAddress networkId walletVk) outAmt TxOutDatumNone ReferenceScriptNone] + -- & setTxProtocolParams (Just pparams) -- Note: Fix! Use `createAndValidateTransactionBody` instead. This -- means we _can_ construct the tx; but it doesn't submit (because it @@ -482,6 +499,28 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = where RunningNode{networkId, nodeSocket, blockTime} = node + +-- | Compute the integrity hash of a transaction using a list of plutus languages. +-- recomputeIntegrityHash :: +-- (Ledger.AlonzoEraPParams ppera, Ledger.AlonzoEraTxWits txera, Ledger.AlonzoEraTxBody txera, EraTx txera) => +-- Ledger.PParams ppera -> +-- [Ledger.Language] -> +-- Ledger.Tx txera -> +-- Ledger.Tx txera +recomputeIntegrityHash pp languages tx = do + tx & bodyTxL . scriptIntegrityHashTxBodyL .~ integrityHash + where + integrityHash = + hashScriptIntegrity + (Set.fromList $ getLanguageView pp <$> languages) + (tx ^. witsTxL . rdmrsTxWitsL) + (tx ^. witsTxL . datsTxWitsL) + + + + + + singlePartyCommitsScriptBlueprint :: Tracer IO EndToEndLog -> FilePath -> From b665c13f338d89f50220a44ef824052e777728bc Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Mon, 20 Jan 2025 13:55:01 +0000 Subject: [PATCH 6/7] It works! --- hydra-cluster/hydra-cluster.cabal | 5 +- hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 50 +++++++++++--------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/hydra-cluster/hydra-cluster.cabal b/hydra-cluster/hydra-cluster.cabal index 770ac27c1fd..f529c1abe4f 100644 --- a/hydra-cluster/hydra-cluster.cabal +++ b/hydra-cluster/hydra-cluster.cabal @@ -85,8 +85,11 @@ library build-depends: , aeson , async - , base >=4.7 && <5 + , base >=4.7 && <5 , bytestring + , cardano-ledger-alonzo + , cardano-ledger-api + , cardano-ledger-core , cardano-slotting , containers , contra-tracer diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index 1518d8a9483..d0ba4da7a9e 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -7,6 +7,11 @@ import Hydra.Prelude import Test.Hydra.Prelude import Cardano.Api.UTxO qualified as UTxO +import Cardano.Ledger.Alonzo.Tx (hashScriptIntegrity) +import Cardano.Ledger.Api.PParams (getLanguageView) +import Cardano.Ledger.Api.Tx (bodyTxL, datsTxWitsL, rdmrsTxWitsL, witsTxL) +import Cardano.Ledger.Api.Tx.Body (scriptIntegrityHashTxBodyL) +import Cardano.Ledger.Plutus.Language (Language (PlutusV3)) import CardanoClient ( QueryPoint (QueryTip), RunningNode (..), @@ -19,9 +24,8 @@ import CardanoClient ( waitForUTxO, ) import CardanoNode (NodeLog) -import Cardano.Ledger.Core (witsTxL) import Control.Concurrent.Async (mapConcurrently_) -import Control.Lens ((^..), (^?)) +import Control.Lens ((.~), (^.), (^..), (^?)) import Data.Aeson (Value, object, (.=)) import Data.Aeson qualified as Aeson import Data.Aeson.Lens (key, values, _JSON, _String) @@ -38,9 +42,12 @@ import Hydra.API.HTTPServer ( ) import Hydra.Cardano.Api ( Coin (..), + ExecutionUnits (..), File (File), Key (SigningKey), + KeyWitnessInCtx (KeyWitnessForSpending), PaymentKey, + PlutusScriptOrReferenceInput (PScript), Tx, TxId, UTxO, @@ -49,6 +56,7 @@ import Hydra.Cardano.Api ( addTxOuts, createAndValidateTransactionBody, defaultTxBodyContent, + fromLedgerTx, getTxBody, getTxId, getVerificationKey, @@ -61,25 +69,27 @@ import Hydra.Cardano.Api ( mkTxOutAutoBalance, mkTxOutDatumHash, mkVkAddress, + negateValue, + plutusScriptVersion, + scriptLanguageInEra, scriptWitnessInCtx, selectLovelace, setTxFee, signTx, + toLedgerTx, toScriptData, txOutValue, + txOuts', utxoFromTx, writeFileTextEnvelope, pattern BuildTxWith, + pattern KeyWitness, + pattern PlutusScriptWitness, pattern ReferenceScriptNone, pattern ScriptWitness, + pattern TxFeeExplicit, pattern TxOut, pattern TxOutDatumNone, - pattern TxFeeExplicit, - pattern KeyWitness, - KeyWitnessInCtx (KeyWitnessForSpending), - txOuts', - negateValue, - setTxProtocolParams ) import Hydra.Cardano.Api.Pretty (renderTxWithUTxO) import Hydra.Cluster.Faucet (FaucetLog, createOutputAtAddress, seedFromFaucet, seedFromFaucet_) @@ -89,6 +99,7 @@ import Hydra.Cluster.Mithril (MithrilLog) import Hydra.Cluster.Options (Options) import Hydra.Cluster.Util (chainConfigFor, keysFor, modifyConfig, setNetworkId) import Hydra.Ledger.Cardano (mkSimpleTx, mkTransferTx, unsafeBuildTransaction) +import Hydra.Ledger.Cardano.Evaluate (maxTxExecutionUnits) import Hydra.Logging (Tracer, traceWith) import Hydra.Options (DirectChainConfig (..), networkId, startChainFrom) import Hydra.Tx (HeadId, IsTx (balance), Party, txId) @@ -435,9 +446,7 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = pparams <- queryProtocolParameters networkId nodeSocket QueryTip -- Send the UTxO to a script; in preparation for running the script - let serializedScript = dummyValidatorScript - -- TODO: Use this one. - -- let serializedScript = schnorrkelValidatorScript + let serializedScript = schnorrkelValidatorScript let scriptAddress = mkScriptAddress networkId serializedScript let scriptOutput = mkTxOutAutoBalance @@ -462,17 +471,19 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = let scriptWitness = BuildTxWith $ ScriptWitness scriptWitnessInCtx $ - mkScriptWitness serializedScript (mkScriptDatum ()) (toScriptData ()) + PlutusScriptWitness + serializedScript + (mkScriptDatum ()) + (toScriptData ()) + maxTxExecutionUnits -- Note: Bug! autobalancing breaks the script business -- tx <- either (failure . show) pure =<< buildTransactionWithBody networkId nodeSocket (mkVkAddress networkId walletVk) body utxoToCommit - let txIn = mkTxIn signedL2tx 0 let remainder = mkTxIn signedL2tx 1 let fee = 8_000_000 - let outAmt = foldMap txOutValue (txOuts' tx) <> negateValue (lovelaceToValue fee) let body = defaultTxBodyContent @@ -480,14 +491,15 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = & addTxInsCollateral [remainder] & setTxFee (TxFeeExplicit fee) & addTxOuts [TxOut (mkVkAddress networkId walletVk) outAmt TxOutDatumNone ReferenceScriptNone] - -- & setTxProtocolParams (Just pparams) -- Note: Fix! Use `createAndValidateTransactionBody` instead. This -- means we _can_ construct the tx; but it doesn't submit (because it -- isn't balanced! And it's missing collateral, etc... txBody <- either (failure . show) pure (createAndValidateTransactionBody body) + let tx = makeSignedTransaction [] txBody - let signedL2tx = signTx walletSk tx + tx' = fromLedgerTx $ recomputeIntegrityHash pparams [PlutusV3] (toLedgerTx tx) + let signedL2tx = signTx walletSk tx' send n1 $ input "NewTx" ["transaction" .= signedL2tx] @@ -499,7 +511,6 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = where RunningNode{networkId, nodeSocket, blockTime} = node - -- | Compute the integrity hash of a transaction using a list of plutus languages. -- recomputeIntegrityHash :: -- (Ledger.AlonzoEraPParams ppera, Ledger.AlonzoEraTxWits txera, Ledger.AlonzoEraTxBody txera, EraTx txera) => @@ -516,11 +527,6 @@ recomputeIntegrityHash pp languages tx = do (tx ^. witsTxL . rdmrsTxWitsL) (tx ^. witsTxL . datsTxWitsL) - - - - - singlePartyCommitsScriptBlueprint :: Tracer IO EndToEndLog -> FilePath -> From 4c0484116c1e37edee53a607439e04af539f0618 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 21 Jan 2025 14:19:34 +0000 Subject: [PATCH 7/7] Make sure we can close the head --- hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 40 ++++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index d0ba4da7a9e..f82d8a64cc3 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -8,9 +8,11 @@ import Test.Hydra.Prelude import Cardano.Api.UTxO qualified as UTxO import Cardano.Ledger.Alonzo.Tx (hashScriptIntegrity) -import Cardano.Ledger.Api.PParams (getLanguageView) -import Cardano.Ledger.Api.Tx (bodyTxL, datsTxWitsL, rdmrsTxWitsL, witsTxL) -import Cardano.Ledger.Api.Tx.Body (scriptIntegrityHashTxBodyL) +import Cardano.Ledger.Api.PParams (AlonzoEraPParams, PParams, getLanguageView) +import Cardano.Ledger.Api.Tx (EraTx, bodyTxL, datsTxWitsL, rdmrsTxWitsL, witsTxL) +import Cardano.Ledger.Api.Tx qualified as Ledger +import Cardano.Ledger.Api.Tx.Body (AlonzoEraTxBody, scriptIntegrityHashTxBodyL) +import Cardano.Ledger.Api.Tx.Wits (AlonzoEraTxWits) import Cardano.Ledger.Plutus.Language (Language (PlutusV3)) import CardanoClient ( QueryPoint (QueryTip), @@ -467,7 +469,7 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = toJSON signedL2tx `elem` (v ^.. key "snapshot" . key "confirmed" . values) - -- Finally, take money from the script + -- Now,, spend the money from the script let scriptWitness = BuildTxWith $ ScriptWitness scriptWitnessInCtx $ @@ -477,9 +479,6 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = (toScriptData ()) maxTxExecutionUnits - -- Note: Bug! autobalancing breaks the script business - -- tx <- either (failure . show) pure =<< buildTransactionWithBody networkId nodeSocket (mkVkAddress networkId walletVk) body utxoToCommit - let txIn = mkTxIn signedL2tx 0 let remainder = mkTxIn signedL2tx 1 @@ -492,9 +491,12 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = & setTxFee (TxFeeExplicit fee) & addTxOuts [TxOut (mkVkAddress networkId walletVk) outAmt TxOutDatumNone ReferenceScriptNone] - -- Note: Fix! Use `createAndValidateTransactionBody` instead. This - -- means we _can_ construct the tx; but it doesn't submit (because it - -- isn't balanced! And it's missing collateral, etc... + -- TODO: Instead of using `createAndValidateTransactionBody`, we + -- should be able to just construct the Tx with autobalancing via + -- `buildTransactionWithBody`. Unfortunately this is broken in the + -- version of cardano-api that we presently use; in a future upgrade + -- of that library we can try again. + -- tx' <- either (failure . show) pure =<< buildTransactionWithBody networkId nodeSocket (mkVkAddress networkId walletVk) body utxoToCommit txBody <- either (failure . show) pure (createAndValidateTransactionBody body) let tx = makeSignedTransaction [] txBody @@ -508,16 +510,22 @@ singlePartyUsesSchnorrkelScriptOnL2 tracer workDir node hydraScriptsTxId = guard $ toJSON signedL2tx `elem` (v ^.. key "snapshot" . key "confirmed" . values) + + -- And check that we can close the head successfully + send n1 $ input "Close" [] + void $ + waitMatch (10 * blockTime) n1 $ \v -> do + guard $ v ^? key "tag" == Just "HeadIsClosed" where RunningNode{networkId, nodeSocket, blockTime} = node -- | Compute the integrity hash of a transaction using a list of plutus languages. --- recomputeIntegrityHash :: --- (Ledger.AlonzoEraPParams ppera, Ledger.AlonzoEraTxWits txera, Ledger.AlonzoEraTxBody txera, EraTx txera) => --- Ledger.PParams ppera -> --- [Ledger.Language] -> --- Ledger.Tx txera -> --- Ledger.Tx txera +recomputeIntegrityHash :: + (AlonzoEraPParams ppera, AlonzoEraTxWits txera, AlonzoEraTxBody txera, EraTx txera) => + PParams ppera -> + [Language] -> + Ledger.Tx txera -> + Ledger.Tx txera recomputeIntegrityHash pp languages tx = do tx & bodyTxL . scriptIntegrityHashTxBodyL .~ integrityHash where