Facebook Move編程語言入門

編程語言 Facebook 腳本語言 區塊鏈 虛擬機 技術 數據結構 讓思想在刀鋒上飛揚 2019-06-26

Facebook區塊鏈項目Libra的其中一個技術亮點,就是它使用了一種稱為Move的新編程語言,那麼這種語言是怎樣的呢,今天我們就從其官方的概述資料入手,近距離了解這種新的語言。

以下內容為譯文:

Move是一種新的編程語言,它為Libra區塊鏈提供了一個安全和可編程的基礎。Libra區塊鏈中的賬戶是任意數量Move資源及Move模塊的容器。提交至Libra 區塊鏈的每個事務,都使用以 Move語言編寫的事務腳本對其邏輯進行編碼。

這個事務腳本可調用模塊聲明的過程來更新區塊鏈的全局狀態。

在本指南的第一部分內容中,我們將概括性地介紹Move語言的主要特點:

  1. Move事務腳本啟用可編程事務;
  2. Move模塊允許組合型智能合約;
  3. Move語言具有第一類資源(First Class Resource);

對於求知慾強的讀者來說,Move編程語言的技術論文包含了更多關於該語言的細節信息:在本指南的第二部分,我們將向你展示如何在Move中間代碼優化(IR)的環境下編寫自己的應用。初始的測試網並不支持自定義Move程序,但這些功能可供你在本地試用。

Facebook Move編程語言入門

(圖片來自:libra.org)

一、Move語言的主要特點

1、1 Move事務腳本啟用可編程事務

  1. 每個Libra事務都包含一個Move事務腳本,該腳本對驗證者應代表客戶端執行的邏輯進行編碼(例如,將Libra幣從Alice的賬戶移動到Bob的賬戶);
  2. 事務腳本通過調用一個或多個Move模塊的過程,與Libra區塊鏈全局存儲中發佈的Move資源進行交互;
  3. 事務腳本不會存儲在全局狀態當中,因此其它事務腳本無法調用它,這是一個一次性程序;
  4. 我們在編寫事務腳本時,提供了幾個事務腳本示例;

1、2 Move 模塊允許組合型智能合約

Move模塊定義了更新Libra區塊鏈全局狀態的規則。Move模塊與其它區塊鏈中的智能合約一樣都是解決相同的問題。模塊聲明瞭可在用戶賬戶下發布的資源類型。Libra區塊鏈中的每個賬戶都是任意數量資源和模塊的容器。

  1. 模塊聲明結構類型(包括資源,這是一種特殊的結構)以及過程;
  2. Move模塊的過程,定義了創建、訪問以及銷燬其聲明類型的規則。
  3. 模塊是可重用的。一個模塊中聲明的結構類型,可以使用另一個模塊中聲明的結構類型,並且一個模塊中聲明的過程可以調用另一個模塊中聲明的公共過程。模塊可以調用在其他Move模塊中聲明的過程。事務腳本可以調用已發佈模塊的任何公共過程。
  4. 最終,Libra用戶將能在自己的帳戶下發布模塊。

1、3 Move語言具有第一類資源

  1. Move的主要功能是定義自定義資源類型。資源類型用於編碼具有豐富可編程性的安全數字資產。
  2. 資源是語言中的普通值,它們可存儲為數據結構,作為參數傳遞給procedure,從procedure返回,等等;
  3. Move類型系統為資源提供了特殊的安全保障。Move資源不能複製、重複使用或丟棄。資源類型只能由定義該類型的模塊創建或銷燬。這些保障是由Move虛擬機通過bytecode驗證靜態地強制執行的。Move虛擬機將拒絕運行尚未通過bytecode檢驗器的代碼;
  4. Libra幣作為一種資源類型,其名稱為LibraCoin.T。LibraCoin.T在語言中沒有特殊的地位,每種資源都享有相同的保護待遇;

二、 Move語言底層

2、1 Move中間代碼優化(IR)

本節介紹如何使用Move IR 編寫事務腳本以及模塊。先提醒下讀者,這個Move IR 目前還處於早期的階段,因此並不穩定,它也是接下來會介紹的Move 源語言的前身(有關詳細信息,請參閱未來開發者體驗部分內容)。Move IR是在Move bytecode之上的一個很薄的語法層,用於測試bytecode驗證者以及虛擬機,它對開發者而言不是特別友好。Move IR足以用於編寫人類可讀的代碼,但無法直接轉換為Move bytecode。儘管Move IR還是有些粗糙,我們還是對這個Move語言感到興奮,並希望開發者們可以嘗試一下它。我們會介紹關於Move IR的重要演示代碼段,並鼓勵讀者通過在本地編譯、運行和修改示例來了解它。libra/language/README.md以及libra/language/ir_to_bytecode/README.md的說明文件解釋瞭如何執行此操作。

2、2 編寫事務腳本

正如我們在Move事務腳本啟用可編程事務部分內容中所解釋的,用戶編寫事務腳本,以請求對Libra區塊鏈的全局存儲進行更新。幾乎任何事務腳本中都會出現兩個重要的構建塊:LibraAccount.T和LibraCoin.T資源類型,LibraAccount是模塊的名稱,T是該模塊聲明的資源的名稱。這是在Move中常見的命名規則。模塊聲明的“main”類型通常命名為T.當我們說一個用戶“在Libra區塊鏈上擁有一個地址為0xff的帳戶”時,我們的意思是,這個0xff地址持有LibraAccount.T資源的實例。每個非空地址都有一個LibraAccount.T資源。此資源存儲賬戶數據,如序列號、驗證密鑰和餘額。要與帳戶交互的Libra系統的任何部分,都必須通過從LibraAccount.T資源中讀取數據或調用LibraAccount模塊的過程。

賬戶餘額是LibraCoin.T的一種類型資源。正如我們在Move具有第一類資源部分內容中解釋的,這是Libra幣的一種類型。這種類型是語言中的“第一類公民”,就像其他Move資源一樣。LibraCoin.T的類型的資源可以存儲在過程變量中,在過程之間傳遞,等等。

我們鼓勵感興趣的讀者在libra/language/stdlib/modules/ directory目錄下檢查LibraAccount和LibraCoin模塊中這兩個關鍵資源的Move IR定義,

現在,讓我們看看程序員如何在一個事務腳本中與這些模塊和資源交互。

// Simple peer-peer payment example.
// Use LibraAccount module published on the blockchain at account address
// 0x0...0 (with 64 zeroes). 0x0 is shorthand that the IR pads out to
// 256 bits (64 digits) by adding leading zeroes.
import 0x0.LibraAccount;
import 0x0.LibraCoin;
main(payee: address, amount: u64) {
// The bytecode (and consequently, the IR) has typed locals. The scope of
// each local is the entire procedure. All local variable declarations must
// be at the beginning of the procedure. Declaration and initialization of
// variables are separate operations, but the bytecode verifier will prevent
// any attempt to use an uninitialized variable.
let coin: R#LibraCoin.T;
// The R# part of the type above is one of two *kind annotation* R# and V#
// (shorthand for "Resource" and "unrestricted Value"). These annotations
// must match the kind of the type declaration (e.g., does the LibraCoin
// module declare `resource T` or `struct T`?).
// Acquire a LibraCoin.T resource with value `amount` from the sender's
// account. This will fail if the sender's balance is less than `amount`.
coin = LibraAccount.withdraw_from_sender(move(amount));
// Move the LibraCoin.T resource into the account of `payee`. If there is no
// account at the address `payee`, this step will fail
LibraAccount.deposit(move(payee), move(coin));
// Every procedure must end in a `return`. The IR compiler is very literal:
// it directly translates the source it is given. It will not do fancy
// things like inserting missing `return`s.
return;
}

此事務腳本存在著一個不幸的問題:如果地址接收方沒有賬戶,它將失敗。我們將通過修改腳本來解決這個問題,為接收方創建一個賬戶(如果接收方還不具備賬戶的話)。


// A small variant of the peer-peer payment example that creates a fresh
// account if one does not already exist.import 0x0.LibraAccount;
import 0x0.LibraCoin;
main(payee: address, amount: u64) {
let coin: R#LibraCoin.T;
let account_exists: bool;
// Acquire a LibraCoin.T resource with value `amount` from the sender's
// account. This will fail if the sender's balance is less than `amount`.
coin = LibraAccount.withdraw_from_sender(move(amount));
account_exists = LibraAccount.exists(copy(payee));
if (!move(account_exists)) {
// Creates a fresh account at the address `payee` by publishing a
// LibraAccount.T resource under this address. If theres is already a
// LibraAccount.T resource under the address, this will fail.
create_account(copy(payee));
}
LibraAccount.deposit(move(payee), move(coin));
return;
}
讓我們看一個更復雜的例子。在這個例子中,我們將使用事務腳本為多個接收方進行支付(而不是單個接收方)。// Multiple payee example. This is written in a slightly verbose way to
// emphasize the ability to split a `LibraCoin.T` resource. The more concise
// way would be to use multiple calls to `LibraAccount.withdraw_from_sender`.
import 0x0.LibraAccount;
import 0x0.LibraCoin;
main(payee1: address, amount1: u64, payee2: address, amount2: u64) {
let coin1: R#LibraCoin.T;
let coin2: R#LibraCoin.T;
let total: u64;
total = move(amount1) + copy(amount2);
coin1 = LibraAccount.withdraw_from_sender(move(total));
// This mutates `coin1`, which now has value `amount1`.
// `coin2` has value `amount2`.
coin2 = LibraCoin.withdraw(&mut coin1, move(amount2));
// Perform the payments
LibraAccount.deposit(move(payee1), move(coin1));
LibraAccount.deposit(move(payee2), move(coin2));
return;
}

好了,到這裡,我們就結束了事務腳本部分的展示,有關更多例子,包括初始測試網中支持的事務腳本,請參閱

libra/language/stdlib/transaction_scripts

。2、3 編寫模塊

現在,我們把注意力集中到編寫自己的Move模塊上,而不僅僅是重用現有的LibraAccount和LibraCoin模塊。考慮這樣一個情況:Bob將來某個時候將在地址a創建一個帳戶,Alice想要“指定”Bob一筆資金,以便他可以在賬戶創建後將其存入自己的帳戶。但她也希望,如果Bob一直不創建一個賬戶,她就能收回這筆資金。為了解決Alice的這個問題,我們將編寫一個專用的EarmarkedLibraCoin模塊,它會:

  1. 聲明一個新的資源類型EarmarkedLibraCoin.T,它封裝了一筆Libra幣以及接收方地址;
  2. 允許Alice創建此類型資源,並在其賬戶下發布(create過程);
  3. 允許Bob聲明資源(claim_for_recipient過程);
  4. 允許任何擁有EarmarkedLibraCoin.T資源類型的人銷燬它,並獲取底層的Libra幣(unwrap過程);

// A module for earmarking a coin for a specific recipient
module EarmarkedLibraCoin {
import 0x0.LibraCoin; // A wrapper containing a Libra coin and the address of the recipient the
// coin is earmarked for.
resource T {
coin: R#LibraCoin.T,
recipient: address
}
// Create a new earmarked coin with the given `recipient`.
// Publish the coin under the transaction sender's account address.
public create(coin: R#LibraCoin.T, recipient: address) {
let t: R#Self.T;
// Construct or "pack" a new resource of type T. Only procedures of the
// `EarmarkedCoin` module can create an `EarmarkedCoin.T`.
t = T {
coin: move(coin),
recipient: move(recipient),
};
// Publish the earmarked coin under the transaction sender's account
// address. Each account can contain at most one resource of a given type;
// this call will fail if the sender already has a resource of this type.
move_to_sender(move(t));
return;
}
// Allow the transaction sender to claim a coin that was earmarked for her.
public claim_for_recipient(earmarked_coin_address: address): R#Self.T {
let t: R#Self.T;
let t_ref: &R#Self.T;
let sender: address;
// Remove the earmarked coin resource published under `earmarked_coin_address`.
// If there is resource of type T published under the address, this will fail.
t = move_from(move(earmarked_coin_address));
t_ref = &t;
// This is a builtin that returns the address of the transaction sender.
sender = get_txn_sender();
// Ensure that the transaction sender is the recipient. If this assertion
// fails, the transaction will fail and none of its effects (e.g.,
// removing the earmarked coin) will be committed. 99 is an error code
// that will be emitted in the transaction output if the assertion fails.
assert(*(&move(t_ref).recipient) == move(sender), 99);
return move(t);
}
// Allow the creator of the earmarked coin to reclaim it.
public claim_for_creator(): R#Self.T {
let t: R#Self.T;
let coin: R#LibraCoin.T;
let recipient: address;
let sender: address;
sender = get_txn_sender();
// This will fail if no resource of type T under the sender's address.
t = move_from(move(sender));
return move(t);
}
// Extract the Libra coin from its wrapper and return it to the caller.
public unwrap(t: R#Self.T): R#LibraCoin.T {
let coin: R#LibraCoin.T;
let recipient: address;
// This "unpacks" a resource type by destroying the outer resource, but
// returning its contents. Only the module that declares a resource type
// can unpack it.
T { coin, recipient } = move(t);
return move(coin);
}
}

Alice可以為Bob創建一種預先安排的幣,方法是創建一個事務腳本,調用Bob的地址a的create,以及她所擁有的LibraCoin.T。一旦地址a被創建,Bob就可以通過從a發送一個事務來領取這筆幣,這會調用claim_for_recipient,將結果傳遞給unwrap,並將返回的LibraCoin存儲在他希望的任何地方。如果Bob在創建a的過程中花費的時間太長,而Alice想要收回她的資金,那麼Alice可以使用 claim_for_creator,然後unwrap。觀察型讀者可能已經注意到,本模塊中的代碼對LibraCoin.T的內部結構不可知。它可以很容易地使用泛型編程(例如,resource T { coin: AnyResource, ... })編寫。我們目前正致力於為Move增加這種參量多態性。

2、4 未來開發者體驗

在不久的將來,Move IR將穩定下來,編譯和驗證程序將變得更加對用戶友好。此外,IR源的位置信息將被跟蹤,然後傳遞給驗證者,以使錯誤消息更容易排錯。然而,IR將繼續作為測試Move bytecode的工具。它是作為底層bytecode的一種語義透明的表示。為了允許有效的測試, IR編譯器需生成錯誤的代碼,這些代碼將被bytecode驗證者拒絕,或在編譯器的運行時失敗。

而對用戶友好的源語言則是另一種選擇,它應該拒絕編譯在管道的後續步驟中將失敗的代碼。

未來,我們將擁有更高層次的Move源語言。這種源語言將被設計成安全而容易地表達常見的Move慣用語和編程模式。由於Move bytecode是一種新語言,而Libra區塊鏈是一種新的編程環境,我們對應支持的習慣用法和模式的理解,仍在不斷髮展。目前,源語言還處於開發的早期階段,我們還沒有為它準備好發佈時間表。

發文時比特幣價格: ¥66404.08

相關推薦

推薦中...