Bài giảng 2: Tập lệnh xác thực cấp thấp và cấp cao
Plutus Pioneer Program - Cohort 3 January 20, 2022
Offical Video by Lars Brünjes: PPP-Cohort3-Lecture2
Google Doc version can be found HERE
Table of Contents
Chuẩn bị cho bài giảng 2
Trước khi bắt đầu bài giảng 2, trước tiên chúng ta phải cập nhật môi trường phát triển của mình. Bạn có thể sao chép và dán trực tiếp bất kỳ mã nào trong hướng dẫn này vào Terminal hoặc IDE của mình.
Đầu tiên, hãy vào thư mục plutus-pioneer-program để lấy nội dung bài giảng tuần 2. Hành hình:
~/plutus-pioneer-program$ git pull
Bây giờ bạn có thể điều hướng đến thư mục week02 hiện tại và mở tệp cabal.project:
~/plutus-pioneer-program/code/week02$ cat cabal.project
Lấy thẻ plutus-apps bên trong tệp cabal.project:
location: https://github.com/input-output-hk/plutus-apps.git
tag:6aff97d596ac9d59460aab5c65627b1c8c0a1528
Quay trở lại thư mục plutus-apps và cập nhật nó vào thẻ git hiện tại:
~/plutus-apps$ git checkout main
~/plutus-apps$ git pull
~/plutus-apps$ git checkout 6aff97d596ac9d59460aab5c65627b1c8c0a1528
Bây giờ bạn đã được cập nhật và có thể chạy nix-shell trong thư mục này. Chạy nix-shell:
~/plutus-apps$ nix-shell
Quay trở lại thư mục week02 để bắt đầu chạy các lệnh cabal:
[nix-shell:~/plutus-pioneer-program/code/week02]$ cabal update
[nix-shell:~/plutus-pioneer-program/code/week02]$ cabal build
[nix-shell:~/plutus-pioneer-program/code/week02]$ cabal repl
Nếu thành công, bây giờ bạn đã sẵn sàng để bắt đầu bài giảng:
Ok, 9 modules loaded.
Prelude week02.Burn >
Low Level Untyped Validation Scripts
Bài giảng này sẽ tập trung vào mã on-chain của tập lệnh plutus. Có ba phần dữ liệu mà tập lệnh Plutus nhận được:
1. `datum` trong UTxO
2. `redeemer` đến từ đầu vào và xác thực
3. `context` của giao dịch được xác thực từ I/O của nó
Ba phần dữ liệu này cần được biểu diễn bằng một kiểu dữ liệu Haskell. Nhìn vào triển khai cấp thấp, cùng một loại dữ liệu sẽ được sử dụng cho cả ba phần dữ liệu. Trong phần tiếp theo, chúng ta sẽ xem xét xác thực cấp cao sẽ xem xét các loại dữ liệu tùy chỉnh cho mốc thời gian và người đổi quà. Xác thực cấp cao sẽ phải trả giá bằng hiệu suất.
Nhìn vào dữ liệu cho một redeemer
:
data Data
A generic "data" type.
The main constructor Constr represents a datatype value in sum-of-products form: Constr i args represents a use of the ith constructor along with its arguments.
The other constructors are various primitives.
Constructors
Constr Integer [Data]
Map [(Data, Data)]
List [Data]
I Integer
B ByteString
Bây giờ chúng ta có thể sử dụng Terminal để có được một số trải nghiệm thực tế. Đầu tiên, hãy nhập PlutusTx
Prelude week02.Burn > import PlutusTx
Bây giờ chúng ta có thể lấy thông tin về dữ liệu bằng lệnh:
Prelude PlutusTx week02.Burn > :i Data
Output:
type Data :: *
data Data
= Constr Integer [Data]
| Map [(Data, Data)]
| List [Data]
| I Integer
| B bytestring
Example :
Prelude PlutusTx week02.Burn > I 42
Output:
I 42
Prelude PlutusTx week02.Burn > :t I 42
Output:
I 42 :: Data
Bây giờ chúng ta có thể sử dụng tiện ích mở rộng này (-XOverloadedStrings) để sử dụng các chuỗi ký tự cho các kiểu string-like. Một ví dụ là kiểu Byte string. Execute:
Prelude PlutusTx week02.Burn > :set -XOverloadedStrings
Example using the B constructor:
Prelude PlutusTx week02.Burn > B "Haskell"
Output:
B "Haskell"
Prelude PlutusTx week02.Burn > :t B "Haskell"
Output:
B "Haskell" :: Data
Example using Map:
Prelude PlutusTx week02.Burn >
:t Map [(I 42, B "Haskell"), (List [I 0], I 1000)]
Output:
Map [(I 42, B "Haskell"), (List [I 0], I 1000)] :: Data
Với kiến thức này, giờ đây chúng ta có thể tạo trình xác thực đầu tiên của mình. Chúng tôi sẽ sử dụng tệp Gift.hs có trong thư mục week02.
Nhìn vào phần xác thực của Gift.hs:
{-# INLINABLE mkValidator #-}
mkValidator :: BuiltinData -> BuiltinData -> BuiltinData -> ()
mkValidator _ _ _ = ()
validator :: Validator
validator = mkValidatorScript $$(PlutusTx.compile [|| mkValidator ||])
valHash :: Ledger.ValidatorHash
valHash = Scripts.validatorHash validator
scrAddress :: Ledger.Address
scrAddress = scriptAddress validator
Đây là chức năng xác thực cơ bản nhất. Tệp được gọi là quà tặng vì nếu bất kỳ ai gửi tiền đến địa chỉ tập lệnh này, thì bất kỳ ai khác cũng có thể sử dụng đầu ra đó để sử dụng.
Trước tiên, chúng tôi xem xét mkValidatorScript:
validator :: Validator
validator = mkValidatorScript $$(PlutusTx.compile [|| mkValidator ||])
Chúng tôi có một hàm haskell có logic, trong đó (||) dấu ngoặc vuông Oxford chuyển đổi hàm đó thành biểu diễn cú pháp của hàm đó. Trình biên dịch lấy biểu diễn đó và biến nó thành một hàm lõi plutus tương ứng. Sau đó, ($$) lấy lõi Plutus đó và ghép nó vào mã nguồn. Kết quả đó là những gì sau đó biến thành trình xác thực.
Trường hợp mkValidatorScript là:
mkValidatorScript :: CompiledCode (BuiltinData -> BuiltinData -> BuiltinData -> ()) -> Validator
Để tận dụng điều này, chúng ta cần thêm một hàm Pragma để tạo ra hàm xác thực:
{-# INLINABLE mkValidator #-}
Bây giờ chúng ta có thể tải tệp này:
Prelude PlutusTx week02.Burn > :l src/Week02/Gift.hs
Output:
Ok, one module loaded.
Đảm bảo cả PlutusTx và Ledger.Scripts đều được imported:
Prelude week02.Gift > import PlutusTx
Prelude PlutusTx week02.Burn > import Ledger.Scripts
Type validator:
Prelude PlutusTx Ledger.Scripts week02.Gift > validator
Output:
Validator { <script> }
Trình xác thực và tập lệnh ở đâu:
newtype Validator
Validator is a wrapper around Scripts which are used as validators in transaction outputs.
Constructors
Validator
getValidator :: Script
newtype Script
A script on the chain. This is an opaque type as far as the chain is concerned.
Constructors
Script
unScript :: Program DeBruijn DefaultUni DefaultFun ()
Bây giờ chúng ta có thể chạy trình xác thực unScript $ getValidator:
Prelude PlutusTx Ledger.Scripts week02.Gift >
unScript $ getValidator validator
Output:
Program () (Version () 1 0 0) (Apply () (Apply () (LamAbs () (DeBruijn {dbnIndex = 0}) (LamAbs () (DeBruijn {dbnIndex = 0}) (LamAbs () (DeBruijn {dbnIndex = 0}) (LamAbs () (DeBruijn {dbnIndex = 0}) (LamAbs () (DeBruijn {dbnIndex = 0}) (Var () (DeBruijn {dbnIndex = 5}))))))) (Delay () (LamAbs () (DeBruijn {dbnIndex = 0}) (Var () (DeBruijn {dbnIndex = 1}))))) (LamAbs () (DeBruijn {dbnIndex = 0}) (Var () (DeBruijn {dbnIndex = 1}))))
Đây là tập lệnh cốt lõi của plutus trong biểu diễn này. Chúng tôi đã biên dịch chức năng mkValidator của mình và biến nó thành Plutus Core.
Hai phần quan trọng khác của trình xác thực là các hàm validatorHash và ScriptAddress.
valHash :: Ledger.ValidatorHash
valHash = Scripts.validatorHash validator
scrAddress :: Ledger.Address
scrAddress = scriptAddress validator
Trong đó valHash lưu trữ hàm băm của trình xác thực và scrAddress lưu trữ địa chỉ của tập lệnh.
Thí dụ:
Prelude PlutusTx Ledger.Scripts week02.Gift > valHash
Output:
67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656
Prelude PlutusTx Ledger.Scripts week02.Gift > scrAddress
Output:
Address {addressCredential = ScriptCredential 67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656, addressStakingCredential = Nothing}
Bây giờ chúng ta có thể kiểm tra điều này trong Plutus Playground.
Để bắt đầu với Plutus Playground, chúng ta cần có hai Terminal đang chạy, cả hai đều nằm trong nix-shell.
Hãy bắt đầu với terminal 1. Đi tới thư mục plutus-apps và chạy nix-shell trước:
Terminal 1
. /home/user/.nix-profile/etc/profile.d/nix.sh
~/plutus-apps$ nix-shell
Tiếp theo, chúng tôi đi đến thư mục plutus-playground-server và chạy:
Terminal 1
cd ~/plutus-apps/plutus-playground-server
[nix-shell:~/plutus-apps/plutus-playground-server]$ plutus-playground-server
Nếu thành công, bạn sẽ thấy đầu ra:
Terminal 1
Interpreter Ready
Hãy bắt đầu với terminal 2. Đi tới thư mục plutus-apps và chạy nix-shell trước:
Terminal 2
. /home/user/.nix-profile/etc/profile.d/nix.sh
~/plutus-apps$ nix-shell
Tiếp theo, chúng tôi đi đến thư mục plutus-playground-client và chạy:
Terminal 2
cd ~/plutus-apps/plutus-playground-clien
[nix-shell:~/plutus-apps/plutus-playground-client]$ npm run start
Nếu thành công, bạn sẽ thấy đầu ra:
Terminal 2
[wdm]: Compiled successfully.
or
[wdm]: Compiled with warnings.
Giữ cả hai Terminal mở và giờ đây chúng ta có thể truy cập Plutus Playground từ trình duyệt.
Mở trình duyệt và truy cập địa chỉ:
https://localhost:8009
Bạn sẽ nhận được một cảnh báo phàn nàn về việc đây là một trang web nguy hiểm, dù sao hãy bỏ qua thông báo để nhấp qua.
Giờ đây, bạn có thể biên dịch và chạy thành công hợp đồng quà tặng bằng cách sao chép/dán nó vào Plutus Playground và sử dụng hai nút ở góc trên cùng bên phải: “Biên dịch” và “Mô phỏng”
Thiết lập ví của chúng ta sẽ giống như sau:
Genesis Slot 0 looks like:
Slot 1, TX 0:
Slot 1, TX 1:
Slot 2, TX 0:
Final Balances:
Bây giờ chúng ta xem tệp Burn.hs nơi mkValidator trông giống như:
mkValidator :: BuiltinData -> BuiltinData -> BuiltinData -> ()
mkValidator _ _ _ = traceError "BURNT!"
Tải tệp và kiểm tra lỗi:
Prelude PlutusTx week02.Gift > :l src/Week02/Burn.hs
Output:
Ok, one module loaded.
Giờ đây, bạn có thể biên dịch và chạy thành công hợp đồng ghi đĩa bằng cách sao chép/dán nó vào Plutus Playground và sử dụng hai nút ở góc trên cùng bên phải: “Biên dịch” và “Mô phỏng”:
Đánh giá các ví có cấu hình tương tự gift.hs:
Genesis Slot 0 looks like:
Slot 1, TX 0:
Slot 1, TX 1:
Final Balances:
As expected, the grab did not work. No transactions can ever use those outputs as inputs.
Contract instance stopped with error: "WalletError (ValidationError (ScriptFailure (EvaluationError [\"BURNT!\"] \"CekEvaluationFailure\")))" ]
High Level Typed Validation Scripts
Bây giờ chúng ta sẽ xem xét một số ví dụ về các tập lệnh xác thực được nhập ở mức độ cao. Chúng ta có thể bắt đầu bằng cách xem Typed.hs:
Prelude PlutusTx week02.Burn > :l src/Week02/Typed.hs
Output:
Ok, one module loaded.
Hàm mkValidator bên trong Typed.hs có dạng:
mkValidator :: () -> Integer -> ScriptContext -> Bool
mkValidator _ r _ = traceIfFalse "wrong redeemer" $ r == 42
Redeemer này sẽ kiểm tra xem số nguyên có phải là 42 hay không, nếu không, nó sẽ trả về giá trị False, xuất ra “Redeemer False”. Sau đó, chúng tôi đã sửa đổi chức năng biên dịch:
data Typed
instance Scripts.ValidatorTypes Typed where
type instance DatumType Typed = ()
type instance RedeemerType Typed = Integer
typedValidator :: Scripts.TypedValidator Typed
typedValidator = Scripts.mkTypedValidator @Typed
$$(PlutusTx.compile [|| mkValidator ||])
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @() @Integer
Đầu tiên chúng ta khai báo DatumType là kiểu unit() và RedeemerType là một Integer. Sau đó, chúng tôi thêm chức năng bọc để có thể dịch các loại mạnh từ phiên bản cấp thấp. Sau đó, nó được khai báo ở vị trí, rằng mốc thời gian và người mua lại lần lượt là loại () và Số nguyên.
Bây giờ chúng ta có thể xem một ví dụ thực tế trong Plutus Playground. Trước tiên, hãy kiểm tra để đảm bảo không có lỗi trong tệp isData.hs.
Prelude PlutusTx week02.Typed > :l src/Week02/isData.hs
Output:
Ok, one module loaded.
Nhìn vào mã xác thực trên chuỗi: on-chain
{-# INLINABLE mkValidator #-}
mkValidator :: () -> MySillyRedeemer -> ScriptContext -> Bool
mkValidator _ (MySillyRedeemer r) _ = traceIfFalse "wrong redeemer" $ r == 42
data Typed
instance Scripts.ValidatorTypes Typed where
type instance DatumType Typed = ()
type instance RedeemerType Typed = MySillyRedeemer
typedValidator :: Scripts.TypedValidator Typed
typedValidator = Scripts.mkTypedValidator @Typed
$$(PlutusTx.compile [|| mkValidator ||])
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @() @MySillyRedeemer
validator :: Validator
validator = Scripts.validatorScript typedValidator
valHash :: Ledger.ValidatorHash
valHash = Scripts.validatorHash typedValidator
scrAddress :: Ledger.Address
scrAddress = scriptAddress validator
Giờ đây, bạn có thể biên dịch và chạy thành công hợp đồng isData bằng cách sao chép/dán nó vào Plutus Playground và sử dụng hai nút ở góc trên cùng bên phải: “Biên dịch” và “Mô phỏng”:
Trường hợp thử nghiệm đầu tiên của chúng tôi sẽ sử dụng giá trị lấy là 100. Điều này có thể sẽ thất bại và tiền sẽ không được chuyển.
Results:
Đúng như dự đoán, vụ chộp lấy đã không xảy ra.
Trường hợp thử nghiệm thứ hai của chúng tôi sẽ sử dụng giá trị là 42. Điều này sẽ vượt qua xác thực.
Results:
Đúng như dự đoán, việc lấy đã thành công và tiền đã được chuyển..
Homework Part 1
-- This should validate if and only if the two Booleans in the redeemer are equal!
mkValidator :: () -> (Bool, Bool) -> ScriptContext -> Bool
mkValidator _ _ _ = True -- FIX ME!
Mục tiêu của bài tập về nhà phần 1 là vượt qua mkValidator chỉ khi hai phép toán luận trong trình chuộc bằng nhau. Trước tiên, chúng ta cần chuyển các tham số chính xác vào mkValidator. Nó chấp nhận một loại đơn vị (), theo sau là hai boolean mà chúng ta có thể gọi là b và c tương ứng.
mkValidator :: () -> (Bool, Bool) -> ScriptContext -> Bool
mkValidator () (b, c) _ = traceIfFalse "wrong redeemer" $ b == c
Tiếp theo, chúng tôi kiểm tra xem b và c có giá trị bằng nhau không; nếu không thì ném thông báo "wrong Redeemer". Sau đó, chúng ta cần khai báo các kiểu dữ liệu cho cả tham số đơn vị và boolean.
data Typed
instance Scripts.ValidatorTypes Typed where
type instance DatumType Typed = ()
type instance RedeemerType Typed = (Bool, Bool)
Tiếp theo, chúng tôi viết mã biên dịch cho tập lệnh xác thực cấp cao, gói gọn cả loại đơn vị và giá trị boolean.
typedValidator :: Scripts.TypedValidator Typed
typedValidator = Scripts.mkTypedValidator @Typed
$$(PlutusTx.compile [|| mkValidator ||])
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @() @(Bool, Bool)
Cuối cùng, chúng tôi viết mã cho validator, valHash, and srcAddress.
validator :: Validator
validator = Scripts.validatorScript typedValidator
valHash :: Ledger.ValidatorHash
valHash = Scripts.validatorHash typedValidator
scrAddress :: Ledger.Address
scrAddress = scriptAddress validator
Mã trên chuỗi cuối cùng sẽ giống như:
{-# INLINABLE mkValidator #-}
-- This should validate if and only if the two Booleans in the redeemer are equal!
mkValidator :: () -> (Bool, Bool) -> ScriptContext -> Bool
mkValidator () (b, c) _ = traceIfFalse "wrong redeemer" $ b == c
data Typed
instance Scripts.ValidatorTypes Typed where
type instance DatumType Typed = ()
type instance RedeemerType Typed = (Bool, Bool)
typedValidator :: Scripts.TypedValidator Typed
typedValidator = Scripts.mkTypedValidator @Typed
$$(PlutusTx.compile [|| mkValidator ||])
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @() @(Bool, Bool)
validator :: Validator
validator = Scripts.validatorScript typedValidator
valHash :: Ledger.ValidatorHash
valHash = Scripts.validatorHash typedValidator
scrAddress :: Ledger.Address
scrAddress = scriptAddress validator
Kiểm tra mã trong Plutus Playground:
Results:
Như mong đợi, quá trình xác thực được thông qua khi cả hai phép toán luận đều có giá trị bằng nhau.
Homework Part 2
Mục tiêu của bài tập về nhà phần 2 giống với mục tiêu của phần, ngoại trừ việc sử dụng các loại dữ liệu tùy chỉnh cho redeemer:
data MyRedeemer = MyRedeemer
{ flag1 :: Bool
, flag2 :: Bool
} deriving (Generic, FromJSON, ToJSON, ToSchema)
Logic giống nhau, ngoại trừ bây giờ chúng ta sẽ sử dụng MyRedeemer để chuyển cả hai cờ dưới dạng booleans.
mkValidator :: () -> MyRedeemer -> ScriptContext -> Bool
mkValidator () (MyRedeemer b c) _ = traceIfFalse "wrong redeemer" $ b == c
Chúng tôi thay đổi mã và thay đổi dữ liệu đã nhập từ boolean thành MyRedeemer:
data Typed
instance Scripts.ValidatorTypes Typed where
type instance DatumType Typed = ()
type instance RedeemerType Typed = MyRedeemer
Thay đổi tương tự bên trong trình bao bọc biên dịch:
typedValidator :: Scripts.TypedValidator Typed
typedValidator = Scripts.mkTypedValidator @Typed
$$(PlutusTx.compile [|| mkValidator ||])
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @() @MyRedeemer
Mã trên chuỗi cuối cùng sẽ giống như:
data MyRedeemer = MyRedeemer
{ flag1 :: Bool
, flag2 :: Bool
} deriving (Generic, FromJSON, ToJSON, ToSchema)
PlutusTx.unstableMakeIsData ''MyRedeemer
{-# INLINABLE mkValidator #-}
-- This should validate if and only if the two Booleans in the redeemer are equal!
mkValidator :: () -> MyRedeemer -> ScriptContext -> Bool
mkValidator () (MyRedeemer b c) _ = traceIfFalse "wrong redeemer" $ b == c
data Typed
instance Scripts.ValidatorTypes Typed where
type instance DatumType Typed = ()
type instance RedeemerType Typed = MyRedeemer
typedValidator :: Scripts.TypedValidator Typed
typedValidator = Scripts.mkTypedValidator @Typed
$$(PlutusTx.compile [|| mkValidator ||])
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @() @MyRedeemer
validator :: Validator
validator = Scripts.validatorScript typedValidator
valHash :: Ledger.ValidatorHash
valHash = Scripts.validatorHash typedValidator
scrAddress :: Ledger.Address
scrAddress = scriptAddress validator
Kiểm tra mã trong Plutus Playground:
Results:
Như mong đợi, quá trình xác thực được thông qua khi cả hai phép toán luận đều có giá trị bằng nhau