Plutus教程:#6 如何写Plutus交易

By George Z. September 6, 2021

6. 如何写 Plutus 交易

本教程概述了什么是 Plutus 事务以及如何编写它。 这是按以下顺序完成的:

  1. 在链码上写你的 Plutus。
  2. 将 Plutus on chain code 序列化为文本信封格式(cardano-cli 需要这种格式)。
  3. 使用随附的 Plutus 脚本创建您的交易。
  4. 提交交易以执行 Plutus 脚本。

6.1 什么是 Plutus 交易

交易是包含输入和输出的一段数据,从 Alonzo 时代开始,它们还可以包含 Plutus 脚本。 输入是来自先前交易 (UTxO) 的未花费输出。 一旦 UTxO 被用作交易中的输入,它就会被花费并且永远无法再次使用。 输出由地址(公钥或公钥哈希)和值(由 ADA 金额和可选的附加本机代币金额组成)指定。 这个流程图让我们更好地了解交易的组成部分在技术层面上是什么:

figure-4

figure-4.png

简而言之,输入包含对先前交易引入的 UTXO 的引用,输出是本次交易将产生的新 UTXO。此外,如果我们考虑一下,这允许我们更改智能合约的状态,因为新数据可以包含在生成的输出中。定义 Plutus Tx 是什么也很重要。 Plutus Tx 是 Haskell 程序的特殊分隔部分的名称,用于将合约应用程序的链上部分编译到 Plutus Core(此编译后的代码然后用于验证交易,因此称为“Tx”) .生成的 Plutus Core 表达式可以是交易数据的一部分,也可以是存储在账本上的数据。这些代码段需要在区块链上进行特殊处理,称为 Plutus 脚本。

6.1.1. 为什么

从 Plutus 开发人员的角度来看,通过使用事务,我们可以控制 Plutus 脚本的执行流程。因此,交易也可以被认为是用于与智能合约交互的消息。理解交易是掌握智能合约开发的关键概念。

6.1.2. 什么时候

交易应该由钱包在评估链外代码时创建。现在,我们必须使用 cardano-cli 组装交易并将编译后的 Plutus 脚本放入其中。不过,在后期阶段,这将由用户的钱包软件自动执行。交易一旦提交,将被验证,因此 Plutus 代码将由验证器节点评估。如果脚本评估成功,交易将被视为有效。如果没有,交易将被拒绝。

6.1.3. 设置环境

如果您已经设置好 Haskell 开发环境,请跳过本节,否则,我们将设置一个合适的环境来使用 Nix 编译 plutus 脚本,或者您可以按照本指南进行操作。我们将使用 Nix 来提供 Haskell 和 Cabal,但如果您愿意,您也可以依靠 ghcup 工具来管理这些依赖项。但是,我们不会涵盖这一点。您可以参考官方 ghcup 站点以获取有关该方法的说明。Nix 是一个了不起的工具,除其他外,它允许我们创建隔离的环境,我们可以在其中嵌入应用程序所需的所有依赖项。这些依赖项甚至可以是系统级依赖项。因此,我们可以创建一个隔离的环境来确保应用程序能够运行,因为所有必需的依赖项都可用。通过推荐的多用户安装在任何 Linux 发行版、MacOS 或 Windows(通过 WSL)上安装 Nix。简而言之,您需要在终端上运行它:

sh <(curl -L https://nixos.org/nix/install) --daemon

为了提高构建速度,强烈建议通过执行以下操作来设置由 IOHK 维护的二进制缓存:

sudo mkdir -p /etc/nix
cat <<EOF | sudo tee /etc/nix/nix.conf
substituters = https://cache.nixos.org https://hydra.iohk.io
trusted-public-keys = iohk.cachix.org-1:DpRUyj7h7V830dp/i6Nti+NEO2/nhblbov/8MW7Rqoo= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
EOF

在 Nix 在您现有的 shell 中工作之前,您需要关闭它们并再次打开它们。 除此之外,你应该准备好了。安装 Nix 后,注销然后重新登录,以便在您的 shell 中正确激活它。 克隆以下内容并查看最新版本的节点。 请参阅 cardano-node 发布页面以确保您使用的是最新版本。

git clone https://github.com/input-output-hk/cardano-node
cd cardano-node
git fetch --all --recurse-submodules --tags
git checkout tags/1.29.0

在我们刚刚克隆的 git 存储库的根目录中创建一个文件,并将其保存为 plutus-tutorial.nix:

{ version ? "mainnet", pkgs ? import <nixpkgs> { }}:
let
  cardano-node-repo = import ./. { };

in pkgs.mkShell {
  buildInputs = with pkgs; [
    libsodium
    cabal-install
    zlib
    haskell.compiler.ghc8104
    haskellPackages.haskell-language-server

    cardano-node-repo.scripts."${version}".node
    cardano-node-repo.cardano-cli
  ];

  CARDANO_NODE_SOCKET_PATH = "${builtins.toString ./.}/state-node-${version}/node.socket";

}

然后使用以下命令使用此文件加载带有 Nix 的 shell:

nix-shell plutus-tutorial.nix

第一次执行此操作大约需要五到十分钟,您应该会看到类似以下内容:

these paths will be fetched (445.08 MiB download, 5870.53 MiB unpacked):
/nix/store/04jc7s1006vhg3qj4fszg6bcljlyap1a-conduit-parse-0.2.1.0-doc
/nix/store/052kzx9p5fl52pk436i2jcsqkz3ni0r2-reflection-2.1.6-doc
.
.
.
/nix/store/7jq1vjy58nj8rjwa688l5x7dyzr55d9f-monad-memo-0.5.3... (34 KB left)

这将创建一个环境,其中包含“buildInputs”部分中列出的所有依赖项,其中包括 GHC 8.10.4 和 Cabal。一旦您拥有最新版本的 GHC 和 Cabal,请确保使用 GHC 8.10.2 或更高版本:

[nix-shell:~]$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.10.4

[nix-shell:~]$ cabal --version
cabal-install version 3.4.0.0
compiled using version 3.4.0.0 of the Cabal library

6.1.4. 运行 cardano-node

在 nix-shell 里面启动一个被动的 Cardano 节点,如果你还没有激活的话记得先激活 Nix 环境:

nix-shell plutus-tutorial.nix

[nix-shell:~]

cardano-node-mainnet

此时,节点将开始与网络同步,这将有助于稍后提交我们的交易。 我们现在准备开始构建 Plutus 交易。 保持节点在此 shell 中运行,并打开一个新终端以继续执行以下步骤。 请记住在这个新终端中进入 nix-shell 环境,以便您同时使用 GHC 和 Cabal。

  1. 在链码上写你的 Plutus 我们需要一个 Haskell 程序来编译我们想要的 Plutus 脚本。 在这个例子中,我们将使用 plutus-alwayssucceeds Plutus 脚本。 注意“Plutus 总是成功”脚本在本教程中仅用作示例,因为它使用非常简单的保护机制,不执行任何资金验证。 因此,我们建议您不要在主网上部署此合约。 如果你仍然想在主网上部署它,你应该使用更安全的数据。
git clone https://github.com/input-output-hk/Alonzo-testnet.git
cd Alonzo-testnet/resources/plutus-sources/plutus-alwayssucceeds

请注意,即使该程序是测试网示例的一部分,它也可以在主网上正常工作。

  1. 在链码上序列化你的 Plutus 通过构建项目,我们生成了一个编译此脚本的二进制文件。
cabal update
cabal build

执行 plutus-alwayssucceeds 项目 我们将选择一个随机数。 它将作为参数传递给 Plutus 脚本(脚本现在不使用它,但使用脚本的事务将需要它)。 第二个参数是我们想要编译的 Plutus 脚本的文件名。

cabal run plutus-alwayssucceeds -- 42 alwayssucceeds.plutus

您应该会看到如下内容:

Up to date
Writing output to: alwayssucceeds.plutus
"Log output"
[]
"Ex Budget"
ExBudget {exBudgetCPU = ExCPU 297830, exBudgetMemory = ExMemory 1100}
cat alwayssucceeds.plutus

您应该会看到如下内容:

{
    "type": "PlutusScriptV1",
    "description": "",
    "cborHex": "4e4d01000033222220051200120011"
}
  1. 使用随附的 Plutus 脚本创建您的交易 然后我们将编译 Plutus 脚本。 现在,我们需要使用包含 Plutus 脚本的 cardano-cli 项目来构建交易。确保您拥有最新的标记版本时代。
cardano-cli query tip --mainnet

您应该会看到如下内容:

{
    "epoch": 155,
    "hash": "c8ae0bb7f06743cd95c35e19c866a811b7a3f104ad362c8667b9f0a1f0907ed2",
    "slot": 36882965,
    "block": 2899736,
    "era": "Alonzo",
    "syncProgress": "100.00"
}

注意:确保“era”对应于“Alonzo”。 如果您刚刚启动了节点,您可能需要等待您的节点同步才能看到这一点。 构建交易实际上不需要节点,但将交易提交到网络很有用。

6.1.5. 生成钱包

如果您还没有,我们必须首先创建一个支付密钥对和一个钱包地址。 对于这个例子,我们需要生成两个地址,如下所示。 对于这一步,在相应的地址中生成一个支付密钥:

cardano-cli address key-gen \
--verification-key-file payment.vkey \
--signing-key-file payment.skey

cardano-cli stake-address key-gen \
--verification-key-file stake.vkey \
--signing-key-file stake.skey

cardano-cli address build \
--payment-verification-key-file payment.vkey \
--stake-verification-key-file stake.vkey \
--out-file payment.addr \
--mainnet
cat payment.addr

确保使用上述相同步骤生成一个额外的钱包,以便您可以测试这些地址之间的交易。

6.1.6. 构建并提交一个简单的(非 Plutus)交易

在这个简单的交易中,我们将资金从一个个人地址发送到另一个地址。 假设我们在 payment.addr 和 payment2.addr 文件中有这些地址,我们想从第一个地址向第二个地址发送 500 个 ADA。首先,我们需要查询付款中的 UTXO。 地址:

cardano-cli query utxo --address $(cat payment.addr) --mainnet

考虑到您的地址有余额,您应该看到如下内容:

TxHash                                                             TxIx  Amount
--------------------------------------------------------------------------------------
8c6f74370d823130847efe3d2e2e128f0e79c8e907fda692353d841dd0d6cb38   0     1000000000 lovelace + TxOutDatumHashNone

使用这些信息,我们可以建立一个交易:

cardano-cli transaction build \
--alonzo-era \
--mainnet \
--change-address $(cat payment.addr) \
--tx-in 8c6f74370d823130847efe3d2e2e128f0e79c8e907fda692353d841dd0d6cb38#0 \
--tx-out $(cat payment2.addr)+500000000 \
--out-file tx.build

在 –tx-in 参数中,我们设置了我们用作输入的 UTXO,其格式为 TxHash#TxIx。 –tx-out 参数决定了新 UTXO 的输出,其格式为地址+数量。如上图所示,我们可以有一个或多个输入和输出。接下来是签署并提交交易:

cardano-cli transaction sign \
--tx-body-file tx.build \
--mainnet \
--signing-key-file payment.skey \
--out-file tx.signed
cardano-cli transaction submit --tx-file tx.signed --mainnet
Transaction successfully submitted.

现在,如果我们查询 payment2.addr,我们将有一个包含 30,000 个 ADA 的新 UTxO:

cardano-cli query utxo --address $(cat payment2.addr) --mainnet
TxHash                                                           TxIx   Amount
--------------------------------------------------------------------------------------
d7d207438c90fe611c1a14be29974b1662f8563331bf6fba4b6569e089ffa561 1      500000000 lovelace + TxOutDatumHashNone
cardano-cli query utxo --address $(cat payment.addr) --mainnet
TxHash                                                           TxIx   Amount
--------------------------------------------------------------------------------------
d7d207438c90fe611c1a14be29974b1662f8563331bf6fba4b6569e089ffa561 0      499831815 lovelace + TxOutDatumHashNone

我们现在已经发送了一个简单的交易。

6.1.7. 交易锁定资金

锁定资金的交易与简单的交易非常相似。 但是,它有两个关键区别:我们将资金锁定到脚本地址而不是公共地址,并且我们需要为每个输出指定一个数据哈希。我们使用我们之前编译的 plutus-alwayssucceeds Plutus 验证器脚本。 无论数据和赎回者的值如何,此脚本都不会检查任何内容并且总是会成功。

{-# INLINABLE mkValidator #-}
mkValidator :: Data -> Data -> Data -> ()
mkValidator _ _ _ = ()

首先,计算脚本地址

cardano-cli address build \
--payment-script-file alwayssucceeds.plutus \
--mainnet \
--out-file script.addr

在 script.addr文件中找到地址

cat script.addr

我们不直接将数据附加到 UTXO,而是使用它的哈希。 要获取数据的哈希值,请运行以下 cardano-cli 命令:

cardano-cli transaction hash-script-data --script-data-value 42
export scriptdatumhash=7c7c0bf83e0ed45faf3976a5ee19b4ef8bd069baab4275425161ac89d492bf82

接下来,获取协议参数并将它们保存到名为 pparams.json 的文件中:

cardano-cli query protocol-parameters \
--mainnet \
--out-file pparams.json

现在,我们应该构建将 ADA 发送到我们的 plutus-alwayssucceeds 脚本的脚本地址的交易。 我们将交易写入名为 tx-script.build 的文件中:

cardano-cli transaction build \
--alonzo-era \
--mainnet \
--change-address $(cat payment.addr) \
--tx-in d7d207438c90fe611c1a14be29974b1662f8563331bf6fba4b6569e089ffa561#0 \
--tx-out $(cat script.addr)+1379280 \
--tx-out-datum-hash ${scriptdatumhash} \
--protocol-params-file pparams.json \
--out-file tx-script.build

继续使用签名密钥 payment.skey 对交易进行签名,并将这个签名的交易保存在一个文件 tx-script.signed 中:

cardano-cli transaction sign \
--tx-body-file tx-script.build \
--signing-key-file payment.skey \
--mainnet \
--out-file tx-script.signed

最后,提交交易

cardano-cli transaction submit --mainnet --tx-file tx-script.signed
Transaction successfully submitted.

我们可以查询个人地址和脚本地址:

cardano-cli query utxo --address $(cat payment.addr) --mainnet
TxHash                                                          TxIx  Amount
--------------------------------------------------------------------------------------
f5a618d579bc66e6199ae2a1ab4a73e2d8a73cba61a324c939346e9cf32bb33a 0    498284086 lovelace + TxOutDatumHashNone
cardano-cli query utxo --address $(cat script.addr) --mainnet
TxHash                                                            TxIx  Amount
--------------------------------------------------------------------------------------
f5a618d579bc66e6199ae2a1ab4a73e2d8a73cba61a324c939346e9cf32bb33a     1        1379280 lovelace + TxOutDatumHash ScriptDataInAlonzoEra "7c7c0bf83e0ed45faf3976a5ee19b4ef8bd069baab4275425161ac89d492bf82"
.
.
export plutusutxotxin=f5a618d579bc66e6199ae2a1ab4a73e2d8a73cba61a324c939346e9cf32bb33a#1

现在,我们已经向脚本发送了资金。

  1. 提交交易以执行 Plutus 脚本 要从脚本中解锁资金,我们需要赎回者。 让我们记住,无论赎回者的价值如何,只要我们提供正确的数据,这个脚本总是会成功。 所以我们可以使用任何值作为赎回者。 我们还需要一个输入作为抵押:如果交易失败,它会承担费用。 然后,我们需要一个有足够资金的 UTXO。 我们将使用payment2.addr 帐户作为示例创建一个简单的交易。它产生了两个新的 UTXO。

检查余额:

cardano-cli query utxo --address $(cat payment2.addr) --mainnet
TxHash                                                           TxIx  Amount
--------------------------------------------------------------------------------------
d7d207438c90fe611c1a14be29974b1662f8563331bf6fba4b6569e089ffa561 1     500000000 lovelace + TxOutDatumHashNone
export txCollateral="d7d207438c90fe611c1a14be29974b1662f8563331bf6fba4b6569e089ffa561#1"

构建、签署并提交新交易以解锁资金:

cardano-cli transaction build \
--alonzo-era \
--mainnet \
--tx-in ${plutusutxotxin} \
--tx-in-script-file alwayssucceeds.plutus \
--tx-in-datum-value 42 \
--tx-in-redeemer-value 42 \
--tx-in-collateral ${txCollateral} \
--change-address $(cat payment.addr) \
--protocol-params-file pparams.json \
--out-file test-alonzo.tx

如果我们使用属于脚本地址一部分的 UTXO 作为交易的输入,我们需要指定 –tx-in-script-file –tx-in datum-value –tx-in-redeemer-value 在包含该 UTXO 的 –tx-in 参数之后的 –tx-in-collateral 参数:

cardano-cli transaction sign \
--tx-body-file test-alonzo.tx \
--signing-key-file payment.skey \
--mainnet \
--out-file test-alonzo.signed
cardano-cli transaction submit --mainnet --tx-file test-alonzo.signed
Transaction successfully submitted.

现在,如果我们查询两个地址,我们可以看到我们已经解锁了资金:

cardano-cli query utxo --address $(cat payment2.addr) --mainnet
cardano-cli query utxo --address $(cat script.addr) --mainnet

至此,您已经成功提交了您的第一笔 Plutus 交易!

comments powered by Disqus