什么是 Forge?
猶如 Ruby on Rails 是一套構(gòu)建 Web 應(yīng)?的框架,F(xiàn)orge 是一套構(gòu)建區(qū)塊鏈 dApps 的框架。區(qū)塊鏈可以簡單地理解成數(shù)據(jù)庫,公開可驗(yàn)證的去中心化數(shù)據(jù)庫。
一個(gè)傳統(tǒng)的應(yīng)用把數(shù)據(jù)儲(chǔ)存在數(shù)據(jù)庫里,一個(gè) dApp 去中心化應(yīng)用把數(shù)據(jù)放在區(qū)塊鏈之中。
構(gòu)建?個(gè) dapp 比較于傳統(tǒng)的應(yīng)?要復(fù)雜許多,P2P,共識算法,網(wǎng)絡(luò)協(xié)議等?系列底層的架構(gòu)要先搭好,然后才寫用戶邏輯來實(shí)現(xiàn)業(yè)務(wù)需求。Forge 作為?個(gè)構(gòu)建基于區(qū)塊鏈的 dApp 框架,將大量的工作已經(jīng)做好,并且提供了一套接口供應(yīng)?程序調(diào)用。所以對于一名應(yīng)用程序的開發(fā)者,只需關(guān)心自己業(yè)務(wù)邏輯,F(xiàn)orge 會(huì)將數(shù)據(jù)保存在區(qū)塊鏈中供應(yīng)?程序使?。
區(qū)塊鏈?zhǔn)鞘裁矗?/p>
Forge 中有一些概念是源于區(qū)塊鏈的,而很多開發(fā)者對于區(qū)塊鏈并不是很熟悉,這里簡單介紹一下一些最基本的概念,以助于之后開發(fā)的理解,
區(qū)塊鏈就是一條由區(qū)塊組成的鏈,它其實(shí)是一種數(shù)據(jù)結(jié)構(gòu)。?的樣子有點(diǎn)像 Linked List 鏈表。用鏈表可以存些簡單的數(shù)據(jù)如 1、2、3,那區(qū)塊鏈中存儲(chǔ)的數(shù)據(jù)是什么呢?答案是 Transaction
Transaction 是什么?
transaction 交易,簡稱為 tx,是存儲(chǔ)在每個(gè)區(qū)塊中的數(shù)據(jù)。
一個(gè)區(qū)塊由區(qū)塊頭和內(nèi)容組成,頭中保存了塊高、上個(gè)塊的哈希等信息,而內(nèi)容則是一個(gè)個(gè)的 tx。為什么區(qū)塊中的數(shù)據(jù)叫做 transaction 交易呢?因?yàn)槭澜缟系谝粋€(gè)區(qū)塊鏈項(xiàng)目比特幣中,每一區(qū)塊中存的就是一筆筆的比特幣交易記錄,所以后續(xù)的各種區(qū)塊鏈項(xiàng)目都用交易即 transaction 來作為區(qū)塊鏈中的數(shù)據(jù)。
Forge 中的概念
當(dāng)我們要做一個(gè)有用的應(yīng)用程序時(shí),通常會(huì)涉及到用戶,用戶會(huì)創(chuàng)建一些資產(chǎn),并且將這些資產(chǎn)進(jìn)行交易等行為。Forge 將這些行為抽象為兩個(gè)基本的概念:
?account 賬號
?asset 資產(chǎn)
Account
Account 就是傳統(tǒng)應(yīng)用中賬戶的概念,只不過在傳統(tǒng)應(yīng)用中,一個(gè)用戶賬號是用用戶名和密碼來創(chuàng)建的;而在區(qū)塊鏈的世界中,用戶賬號是由鏈包地址和私鑰來創(chuàng)建的。
為什么不用用戶名/密碼來創(chuàng)建用戶賬戶呢?因?yàn)樵趨^(qū)塊鏈的世界中,其實(shí)是沒有一個(gè)用戶登陸的概念的。我們知道,在傳統(tǒng)的應(yīng)用中,用戶登陸成功后可以進(jìn)行一些操作。比如說轉(zhuǎn)賬,發(fā)微博等。在比特幣中,用戶之間是如何在不登陸賬戶的情況下進(jìn)行轉(zhuǎn)賬的呢?答案是通過數(shù)字簽名,即將轉(zhuǎn)賬交易用比特錢包的私鑰進(jìn)行簽名后發(fā)到區(qū)塊鏈上;之后這個(gè)簽名的交易經(jīng)由別人驗(yàn)證后就算是有效的了,這樣一筆轉(zhuǎn)賬的交易就算是完成了。所以錢包的概念也是比特幣引進(jìn)的。
Asset
Asset 資產(chǎn)則用來表示任何東西,可以是一篇文章,一張圖片,一張地圖或是一個(gè)證書。資產(chǎn)可以由某個(gè)用戶創(chuàng)建,或者應(yīng)用程序來創(chuàng)建,一旦創(chuàng)建后,可以用來進(jìn)行交易、使用等行為。具體是做什么取決于應(yīng)用程序。
Forge 中的 Transaction
前面說到,比特幣中有且僅有的一種 Transaction 就是轉(zhuǎn)賬,F(xiàn)orge 作為一個(gè)全功能框架,原生支持十幾種 Transaction,包括創(chuàng)建賬號、創(chuàng)建資產(chǎn)、轉(zhuǎn)賬、交換等。每一次事件的發(fā)生,都等價(jià)成一個(gè)個(gè)的 Transaction 發(fā)布到鏈上。
所以說若開發(fā)者想在區(qū)塊鏈上做開發(fā),歸根結(jié)底就是通過 Forge 在區(qū)塊鏈上發(fā)布一個(gè)個(gè)的 Transaction。
我們知道,當(dāng) Forge 啟動(dòng)之后,便是一個(gè)單獨(dú)的操作系統(tǒng)進(jìn)程,開發(fā)者開發(fā)的應(yīng)用程序如何與 Forge 交互來告之其應(yīng)當(dāng)發(fā)什么 Transaction 呢?Forge 提供了兩種方式,GraphQL 和 gRPC。
如何與 Forge 交互?
Forge 本身提供了兩種與其交互的形式:
?GraphQL
?gRPC
這可能與我們平時(shí)調(diào)用某個(gè)服務(wù)器提供的 API 不太一樣。我們平日接觸的 API 調(diào)用大都是通過 JSON 發(fā)送一些 HTTP 請求訪問某個(gè) API 來獲取一些資源,為什么 Forge 沒有用 JSON API 呢?
原因很簡單,效率。關(guān)于 GraphQL 和 gRPC 的優(yōu)點(diǎn),這里不再展開,不過會(huì)簡單介紹一下這二種技術(shù)。
GraphQL 怎么用?
GraphQL 是 Facebook 開源的一項(xiàng)技術(shù),皆在幫助用戶更高效快捷地從服務(wù)器獲取資源,
GraphQL 在網(wǎng)絡(luò)的應(yīng)用層面用的是 HTTP/1.1 或 HTTP/2 協(xié)議的 POST 請求,服務(wù)器接收從客戶端發(fā)來的 Query 請求,經(jīng)過處理后返回一個(gè) JSON 的結(jié)果。
客戶端能發(fā)送的請求分三類:
?Query:用來讀取資源
?Mutation:用來創(chuàng)建、改變資源
?Subscription:用來訂閱事件
在 Forge 中,Query 一般用來作查詢鏈上的數(shù)據(jù);Mutation 一般用來作向鏈發(fā)送 Transaction;Subscription 用來訂閱鏈上發(fā)生的事件。
gRPC 怎么用?
gRPC 是 Google 出的一套 RPC 框架,簡單來說:gRPC = protobuf + HTTP/2
Protocol Buffer 簡稱 Protobuf,也是 Google 自家出的一種序列化/反序列化標(biāo)準(zhǔn)。是比 XML,JSON 更加高效的序列化方式。它是通過預(yù)先定義好一個(gè) .proto 文件,記錄了要傳輸?shù)男畔⒍加心男┳侄我约八鼈兊木幪枺笮蛄谢臅r(shí)候只對字段的值進(jìn)行編碼,以達(dá)到節(jié)省空間的目的,使用方法如下:
1.用戶定義要傳輸?shù)男畔⒂心男┳侄危瑢懙揭粋€(gè) .proto 文件中,然后用官方或社區(qū)提供的你要用的語言的插件將其編譯成 .cpp 或 .ex 或 .py 文件中。
2.在你的程序中,用剛才生成出來的模塊提供的序列化函數(shù),將一個(gè)數(shù)據(jù)對象轉(zhuǎn)化成二進(jìn)制以便在網(wǎng)絡(luò)中進(jìn)行傳輸,接受方用反序列化函數(shù)得到的二進(jìn)制轉(zhuǎn)化回?cái)?shù)據(jù)對象。
用 Protobuf 進(jìn)行的對數(shù)據(jù)的序列化能很大程度上節(jié)省空間,這樣傳輸在網(wǎng)絡(luò)上的數(shù)據(jù)變少了,請求就更高效了。但是需要付出的代價(jià)就是
1.首先要有服務(wù)端定義的 .proto 文件
2.你要用的語言要有 protoc(官方提供的 protobuf 的編譯器)的插件。
Forge 所有用到的 proto 文件都在 ArcBlock/forge-abi[1] 倉庫中;Google 官方支持 C++、C#、Go、Python 的插件,其他的語言要到社區(qū)中去找了。
那么,gRPC 是啥呢?看圖說話:
?首先服務(wù)器端定義好一套請求/響應(yīng)的.proto 文件
?客戶端把要發(fā)的請求通過 protobuf 序列化成二進(jìn)制后,通過 HTTP/2 協(xié)議發(fā)給服務(wù)器
?服務(wù)器收到請求,處理之,然后再以 protobuf 序列化的二進(jìn)制發(fā)回響應(yīng)——客戶端收到響應(yīng)后,反序列化拿到結(jié)果
之所以用 HTTP/2 協(xié)議而不再用 HTTP/1.1 是為了能夠更高效地傳輸數(shù)據(jù)。同時(shí),需要用一個(gè)官方提供的或是社區(qū)提供的 gRPC 的庫來使用 gRPC。
GraphQL 還是 gRPC?
Forge 提供了 GraphQL 和 gRPC 兩種方式來與其交互,那么到底用哪個(gè)好呢?GraphQL 上手簡單,只需要用一個(gè) HTTP 客戶端和一個(gè) JSON 的原就能收發(fā)數(shù)據(jù)了,而 gRPC 上手復(fù)雜,需要了解 protobuf,并用一個(gè) gRPC 才能收發(fā)數(shù)據(jù)。我們推薦用 gRPC,雖然看起來上手難點(diǎn),但是其使用起來更靈活;而 GraphQL 上手簡單,更適合一些簡單的查詢。
Forge 中如何發(fā)送 transaction?
前面講了若開發(fā)者想在區(qū)塊鏈上做開發(fā),歸根到底就是通過 Forge 在區(qū)塊鏈上發(fā)布一個(gè)一個(gè)的 transaction。又講了 Forge 提供 GraphQL 和 gRPC 的方式來交互。接下來就講一下如何在 Forge 中通過 gRPC 中發(fā)送 transaction。
怎么樣,發(fā)送的流程簡單吧!就是把 Forge 中定義的 transaction 通過 gRPC 發(fā)給 Forge,之后 Forge 會(huì)返回一個(gè)哈希作為結(jié)果。
好的,那么接下來,我們就來看一下 Forge 中定義的 transaction 長什么樣。
Forge 中對于 transaction 的定義可以在 arcblock/forge-abi/lib/protobuf/type.proto 下面找到。
message Transaction {
string from = 1; # 這個(gè)tx是誰發(fā)的,即錢包地址
uint64 nonce = 2; # nonce 用來防止重?cái)彻簦看涡枰f增發(fā)送
string chain_id = 3; # tx發(fā)送至的鏈的id
bytes pk = 4; # 發(fā)tx的錢包的公鑰
bytes signature = 13; # 發(fā)tx的錢包的簽名
repeated multisig signatures = 14; # 多方簽名
google.protobuf.Any itx = 15; # inner transaction ,這個(gè)tx具體是干啥的
}
我們需要做的事情就是構(gòu)造出來這個(gè) transaction 后,將其發(fā)送給 Forge,接下來我們會(huì)用一個(gè)具體的例子來演示如何在鏈上創(chuàng)建一個(gè)錢包賬號。
Forge 中的錢包
創(chuàng)建錢包分 2 步,
1.在本地創(chuàng)建一個(gè)錢包
2.把這個(gè)錢包申明(decleare)到鏈上去,這樣就算完成了用戶賬號的創(chuàng)建。
所以說了這么久,錢包究竟是什么東西呢?錢包其實(shí)就是一個(gè)存儲(chǔ)了公鑰/私鑰/地址的一個(gè)數(shù)據(jù)結(jié)構(gòu),被定義于 protobuf 中,
message WalletInfo {
bytes sk = 2; # 私鑰
bytes pk = 3; # 公鑰
string address = 4; # DID地址
}
我們的錢包是一個(gè)支持 DID 規(guī)范的錢包,里面有 3 個(gè)選項(xiàng)可選
— role—type 角色 — key—type 私鑰算法 — hash-type 哈希算法
message WalletType{
keyType key = 1;
HashType hash = 2;
EncodingType address = 3;
RoleType role = 4;
}
這里的細(xì)節(jié)請參考 arcblock/abt-did-spec 里面關(guān)于創(chuàng)建 DID 的文檔
以下的參考代碼內(nèi)為 Elixir 代碼,用的是我們已經(jīng)開源的 Forge-elixir-sdk 的庫
wallet_type = ForgeAbi.WalletType.new(role: :role_account, key: :ed25519, hash: :sha3)
wallet = ForgeSdk.Wallet.util.create(wallet_type)
%ForgeABi.WalletInfo{
address: “z1mwolwq.。..” # DID 地址,里面包含了私鑰類型,哈希算法及角色
pk: 《《85,199, 。..》》 # 公鑰,32字節(jié)
sk: 《《19,21,248,。..》》 # 私鑰,我們用的ed25519,私鑰地址包括了公鑰,共64字節(jié)。
}
好的,這樣我們創(chuàng)建的錢包已是在本地創(chuàng)建的,還是要把它申明到鏈上去,
還記得之前說的,要在鏈上搞事情就得需要發(fā)一個(gè) transaction。
message Transaction{
string from = 1;
uint64 nonce = 2;
string chain_id = 3;
bytes pk = 4 ;
bytes signature = 13;
repeated Mulitisig signatures = 14;
google.protobuf.Any itx = 15
}
還剩下 signature, signatures 和 itx 未填, signaures 是多方簽名,我們這一步還用不到,不用管它,在看簽名之前我們先來看一下 itx。
Forge 中的 itx 是什么?
itx 是 inner transaction 的縮寫,都已經(jīng)有了 tx,為啥還要有 itx 呢?
做個(gè)比喻,這個(gè)就像寫信一樣,每封信都有標(biāo)題,抬頭,征文,日期,和簽名等,但是不同的信的征文內(nèi)容是不同的。tx 就是信的模版,包括寄信人,標(biāo)題,簽名;而 itx 則是信的正文,代表了具體內(nèi)容。Forge 支持了十幾種 tx,也就是說,有十幾種 itx。我們要做的將剛創(chuàng)建的錢包申明上的鏈的 itx 叫做 declare
message DeclareTx{
string moniker = 1 ; #表示這個(gè)錢包賬戶的別名
。..。
}
這里忽視了其他一些用不上的字段。那么如何將這個(gè) declare tx 創(chuàng)建成一個(gè) itx 呢?我們再來看一下 transaction 中定義的 itx 類型
google.protobuf.Any itx = 15;
它的類型是 google.protobuf.Any, 這個(gè)是 google 提供的一種類型,如它的名字一樣,是專門給任意類型用的一種通用的類型,它的定義如下
message Any{
string type_url = 1;
bytes value = 2;
}
既然是任意類型,那只用 value 來表示不就好了嗎?type_url 是個(gè)什么鬼?這個(gè)其實(shí)是給應(yīng)用程序用的,告訴它這個(gè)任意類型到底是個(gè)什么類型。google 設(shè)計(jì)的本意是這個(gè) type_url 是一個(gè) url, 但是我們并不需要它是一個(gè) url,可以是任何字符串。
Forge 中定義的 type_url 長這樣
fg:t:declare # forge縮寫:type:itx類型
declare = ForgeAbi.DeclareTx.new(moniker: “jonsnow”)
value = ForgeAbi.DeclareTx.encede(declare)
itx = Google.Proto.Any.new(type_url: “fg:t:declare”, value: value)
%Google.Proto.Any{type_url: “fg:t:declare”, value: “\n\ajonsnow”} # 這個(gè)就是用 protobuf 編碼的 declare itx
好,現(xiàn)在再看一下我們的 tx
message Transaction {
string from = 1; # wallet.address
uint64 nonce = 2; # 1
string chain_id =3; # forge
bytes pk = 4; # wallet.pk
bytes signature = 13;
repeated Multisig signatures = 14;
google.protobuf.Any itx = 15;
}
現(xiàn)在就差最后一步,簽名了。
Forge 中如何給 tx 簽名?
Forge 中的錢包支持兩種橢圓曲線數(shù)字簽名算法,ed25519 和 secp256k1。所謂的數(shù)字簽名就是用錢包的私鑰對 tx 的哈希做一個(gè)簽名,之后別人可以用其公鑰進(jìn)行驗(yàn)證。
signature = sign(data, sk)
# data 為 tx 序列化后的二進(jìn)制哈希
# sk 這里是錢包的私鑰
hash = mcrypto.hash(%Sha3{}, ForgeAbi.Transaction.encode(tx))
sig = Mcrypto.sign!(%Ed25519{}, hash, wallet.sk)
tx = %{tx | signature: sig}
至此,我們的 tx 終于算是構(gòu)造完成并且簽好名了!接下來只需要把這個(gè) tx 發(fā)送給 Forge 啦!
如何向 Forge 發(fā)送 tx?
因?yàn)槲覀冇?gRPC 與 Forge 進(jìn)行交互,所以我們只需要使用一個(gè) gRPC 提供的發(fā)送 tx 的服務(wù)就行了,這個(gè)服務(wù)在 Forge 中叫 send_tx,定義在 arcblock/forge-abi/lib/protobuf/service.proto 中。進(jìn)行這項(xiàng)操作需要參考你所用的語言的 gRPC 的庫的文檔,在 Elixir 中,這樣做
Forgesdk.send_tx(tx: tx)
“48c265bb.。..”
之后返回的哈希即是這個(gè) tx 在鏈上的哈希嘍!用這個(gè)哈希就可以在鏈上查到其狀態(tài)了。當(dāng)我們把 tx 發(fā)送請給 Forge 后,F(xiàn)orge 會(huì)做一系列的檢查,包括發(fā)送 tx 的錢包地址是否有效,簽名是否有效等。之后 Forge 會(huì)把這個(gè) tx 發(fā)送給下層的共識引擎,并且廣播到 p2p 網(wǎng)絡(luò)中,最后會(huì)被打包到新的區(qū)塊中,這樣子我們發(fā)的 tx 相當(dāng)于成功上鏈啦!當(dāng)然上鏈并不代表這個(gè) tx 就是成功了的,還需要檢查這個(gè) tx 的狀態(tài)才行哦。
Forge 中常用的 tx
方才我們學(xué)習(xí)了如何構(gòu)建并簽名一個(gè) declare tx, 并且成功將其發(fā)送給 Forge,這樣我們就成功地在 Forge 上創(chuàng)建了一個(gè)錢包賬戶,接下來我們來看一下,F(xiàn)orge 中有那些常用的 tx。
假設(shè)有如下場景
用戶 a 創(chuàng)建了一個(gè)賬戶后,簽到一次得到一些 token,之后創(chuàng)建了一個(gè)資產(chǎn)(游戲地圖), 并將這個(gè)資產(chǎn)免費(fèi)轉(zhuǎn)讓了另一用戶 b,之后用戶 a 用一些 token 向用戶 b 購買了該資產(chǎn),完成了一次交換。
declare 之前我們已經(jīng)看過了,接下來看 poke。
poke tx
poke 就是戳一下,作用是簽到領(lǐng)取 25 個(gè) token,一天只能領(lǐng)取一次。我們知道,發(fā)送 tx 時(shí),tx 的結(jié)構(gòu)都是一樣的,不同的僅僅是 itx 的內(nèi)容及簽名。我們再來看一下 tx 的結(jié)構(gòu)。
message Transaction{
string from = 1; # wallet.address
uint64 nonce = 2; # 0 《- 注意對于poke來說nonce要用0
string chain_id = 3; # Forge
bytes pk = 4; # wallet.pk
bytes signature = 13;
repeated Multisig signatures = 14;
google.protobuf.Any itx = 15; # itx 《- 改用poke tx
}
poke tx 的定義如下
message PokeTx {
string data = 1; # 簽到的日期,用當(dāng)天
string address = 2; # 向哪個(gè)錢包地址簽到,這個(gè)是固定的地址,“zzzzz.。”(36 個(gè) z)
}
poke = ForgeAbi.PokeTx.new(data:“2019-05-28”, address:“zzzzzzz.。.”)
value = ForgeAbi.PokeTx.encode(poke)
itx = Google.proto.Any.new(type_url: “fg:t:poke”, value: value)
%Google.Proto.Any{type_url: “fg:t:poke”, value: 《《10,10,50,。..》》}
然后把這個(gè) itx 塞到上面的 tx 中,簽名之后,發(fā)到鏈上吧!
ForgeSdk.send_tx(tx: tx)
“66313AFB.。..”
成功以后去鏈上查詢一下,此時(shí)我們的 jonsnow 賬號就多了 25 個(gè) token 啦!好的,現(xiàn)在我們的錢包創(chuàng)建了,并且有了 25 個(gè) token,接下來看看如何創(chuàng)建一個(gè)資產(chǎn)。
create_asset tx
asset 表示資產(chǎn),可以代表任何可交易的物體,這里我們用游戲地圖舉例子,先看看 create_asset 的定義
message CreateAssetTx{
string moniker = 1; # 這個(gè)資產(chǎn)的別名
google.protobuf.Any data= 2;
bool readonly = 3;
bool transferable = 4; # 是否可轉(zhuǎn)讓
uint32 ttl = 5;
string parent = 6;
string address = 7; # 資產(chǎn)地址
}
這里定義了 7 個(gè)字段,我們只關(guān)心其中 4 個(gè),其余的可以不管。
map = %Google.Protobuf.Any{value: “this is my map”}
asset = ForgeAbi.CreateAssetTx.new(transferable: true, moniker: “map1”, data: map)
接下來還有 asset 中的地址為空,我們需要自己將它算出來。Forge 中的所有東西的 id 都是支持 DID 標(biāo)準(zhǔn),對于 asset 的地址,也是一個(gè) DID。那么 asset 地址怎么算呢?
hash = Mcrypto.hash(%SHA3{}, ForgeAbi.createAssetTx.encode(itx)) # 之后的步驟請參考abt-did-spec文檔中的步驟,這里算出的哈希作為第5步的輸入。并且在選role-type時(shí)要選asset。
地址算好后填到上面的 asset 中
value = ForgeAbi.CreateAssetTx.encode(asset)
itx = Google.Proto.Any.new(type_url: “fg:t:create-asset”, value: value)
%Google.Proto.Any{type_url: “fg:t:create_asset”, value:《《10.4.109.。..》》}
接下來的步驟就是流水線作業(yè),將:tx 塞入 tx 中,簽名,發(fā)送 成功后,一個(gè) asset 就創(chuàng)建好了!里面的內(nèi)容放的就是“this is my map”。 ok, 接下來我們要把該資產(chǎn)轉(zhuǎn)移給另一個(gè)賬戶,這會(huì)用到 transfer tx
transfer tx
轉(zhuǎn)讓 transfer 是一個(gè)單方面的用戶行為。用戶可以向用戶 b 轉(zhuǎn)錢或者轉(zhuǎn)資產(chǎn),所以我們需要先創(chuàng)建第二個(gè)錢包
wallet_type = ForgeAbi.WalletType.new(role: :role_account, key: :ed25519, hash: :sha3)
wallet2 = ForgeSdk.Wallet.Util.create(wallet_type)
之后用 declare tx 將其聲明到鏈上去,這里就不再詳寫了。
接下來看 transfer tx 的定義
message TransferTx {
string to = 1; # 目標(biāo)錢包地址
BigUint value = 2; # 給多少錢
repeated string assets = 3; # 有哪些資產(chǎn)
}
我們這里只轉(zhuǎn)讓一個(gè)剛才創(chuàng)建的地圖資產(chǎn),只需要 asset 地址即可。
map1 = “ejdqnc.。.”
transfer = ForgeAbi.TransferTx.new(to: wallet2.address, assets: [map1])
value = ForgeAbi.TransferTx.encode(transfer)
itx = Google.Proto.Any.new(type_url: “fg:t:transfer”, value: value)
%Googel.Proto.Any{type_url: “fg:t:transfer”, value:《《10,35,122,。..》》}
之后老套路,itx 放入 tx 中,簽名,發(fā)送上鏈 成功之后,本來屬于用戶 A 的資產(chǎn)現(xiàn)在就屬于用戶 B 了!最后來看一下 exchange tx。
exchange tx
之前所有講過的 tx 都只需要一個(gè)簽名,而 exchange tx 則需要兩個(gè)簽名,因?yàn)槭墙粨Q資產(chǎn)所以需要交換的雙方都同意才行。
看一下 exchange tx 的定義
message Exchange {
string to = 1; # 與哪個(gè)地址交換
ExchangeInfo sender = 2; # 發(fā)送人信息
Exchangeinfo receiver = 3; # 接受人信息
}
message Exchangeinfo {
BigUint value = 1; # 交換的金額
repeated string asets = 2; # 交換的資產(chǎn)
}
message BigUint{
bytes value = 1; # 因?yàn)榻痤~是大整數(shù),所以我們用bytes來表示
}
構(gòu)建一下 itx
exchange = ForgeAbi.ExchangeTx.new(
to: wallet2.address,
sender: ForgeAbi.Exchangeinfo.new(value: ForgeAbi.token.to.uint(2)),
receiver: ForgeAbi.ExchangeInfo.new(assets: [map1]))
value = ForgeAbi.ExchangeTx.encode(exchange)
itx = Google.Proto.Any.new(type_url: “fg:t:exchange”, value: value)
接下倆老套路,itx 放進(jìn) tx,簽名 至此,我們的 tx 還差最后一步,也是我們之前一直沒用過的 Multisig 多方簽名
message Transaction{
string from = 1; # walle.address
uint64 nonce = 2; # 1
string chain_id = 3; # Forge
bytes pk = 4; # wallet.pk
bytes signature = 13; # signature
repeated Multisig signatures = 14;
google.protobuf.Any itx = 15; # itx
}
看下 multisig 的定義
message Multisig{
string signer = 1; # 用戶B的地址
bytes pk = 2; # 用戶B的公鑰
bytes signature = 3; # 用戶B的簽名
}
這個(gè) multisig 該如何構(gòu)建呢?很簡單。將用戶 B 的地址和公鑰填入,再塞進(jìn) tx 中,然后用戶 B 簽名就行啦!
mulitisig = ForgeAbi.Multisig.new(signer: wallet2.address, pk: wallet2.pk) # 創(chuàng)建一個(gè)mulitisig的map
tx = %{tx | signstures: [multisig]} # 將其放入tx的signatures字段中,注意現(xiàn)在這個(gè)mulitisig的簽名還是空哦
signature = Forgesdk.Wallet.Util.sign?。╳allet2, ForgeAbi.Transaction.encode(tx)) # 將這個(gè)tx讓用戶B簽名
multisig = %{multisig | signature: signature} # 簽好之后把簽名設(shè)入multisig的map中
tx = %{tx | signatures: [multisig]} # 最后將簽名的multisig放入tx中
至此,我們的 tx 就被用戶 A 和用戶 B 都簽名了,可以發(fā)送的鏈上去了!成功后,資產(chǎn)被轉(zhuǎn)移到 A 的名下,A 支付給 b 兩個(gè) token,交換成功!
整個(gè)流程的圖示
文章來源:ArcBlock區(qū)塊基石?
評論
查看更多