什么是 Forge?
猶如 Ruby on Rails 是一套構(gòu)建 Web 應(yīng)?的框架,F(xiàn)orge 是一套構(gòu)建區(qū)塊鏈 dApps 的框架。區(qū)塊鏈可以簡(jiǎn)單地理解成數(shù)據(jù)庫(kù),公開可驗(yàn)證的去中心化數(shù)據(jù)庫(kù)。
一個(gè)傳統(tǒng)的應(yīng)用把數(shù)據(jù)儲(chǔ)存在數(shù)據(jù)庫(kù)里,一個(gè) dApp 去中心化應(yīng)用把數(shù)據(jù)放在區(qū)塊鏈之中。
構(gòu)建?個(gè) dapp 比較于傳統(tǒng)的應(yīng)?要復(fù)雜許多,P2P,共識(shí)算法,網(wǎng)絡(luò)協(xié)議等?系列底層的架構(gòu)要先搭好,然后才寫用戶邏輯來(lái)實(shí)現(xiàn)業(yè)務(wù)需求。Forge 作為?個(gè)構(gòu)建基于區(qū)塊鏈的 dApp 框架,將大量的工作已經(jīng)做好,并且提供了一套接口供應(yīng)?程序調(diào)用。所以對(duì)于一名應(yīng)用程序的開發(fā)者,只需關(guān)心自己業(yè)務(wù)邏輯,F(xiàn)orge 會(huì)將數(shù)據(jù)保存在區(qū)塊鏈中供應(yīng)?程序使?。
區(qū)塊鏈?zhǔn)鞘裁矗?/p>
Forge 中有一些概念是源于區(qū)塊鏈的,而很多開發(fā)者對(duì)于區(qū)塊鏈并不是很熟悉,這里簡(jiǎn)單介紹一下一些最基本的概念,以助于之后開發(fā)的理解,
區(qū)塊鏈就是一條由區(qū)塊組成的鏈,它其實(shí)是一種數(shù)據(jù)結(jié)構(gòu)。?的樣子有點(diǎn)像 Linked List 鏈表。用鏈表可以存些簡(jiǎn)單的數(shù)據(jù)如 1、2、3,那區(qū)塊鏈中存儲(chǔ)的數(shù)據(jù)是什么呢?答案是 Transaction
Transaction 是什么?
transaction 交易,簡(jiǎn)稱為 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 來(lái)作為區(qū)塊鏈中的數(shù)據(jù)。
Forge 中的概念
當(dāng)我們要做一個(gè)有用的應(yīng)用程序時(shí),通常會(huì)涉及到用戶,用戶會(huì)創(chuàng)建一些資產(chǎn),并且將這些資產(chǎn)進(jìn)行交易等行為。Forge 將這些行為抽象為兩個(gè)基本的概念:
?account 賬號(hào)
?asset 資產(chǎn)
Account
Account 就是傳統(tǒng)應(yīng)用中賬戶的概念,只不過在傳統(tǒng)應(yīng)用中,一個(gè)用戶賬號(hào)是用用戶名和密碼來(lái)創(chuàng)建的;而在區(qū)塊鏈的世界中,用戶賬號(hào)是由鏈包地址和私鑰來(lái)創(chuàng)建的。
為什么不用用戶名/密碼來(lái)創(chuàng)建用戶賬戶呢?因?yàn)樵趨^(qū)塊鏈的世界中,其實(shí)是沒有一個(gè)用戶登陸的概念的。我們知道,在傳統(tǒng)的應(yīng)用中,用戶登陸成功后可以進(jìn)行一些操作。比如說(shuō)轉(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)則用來(lái)表示任何東西,可以是一篇文章,一張圖片,一張地圖或是一個(gè)證書。資產(chǎn)可以由某個(gè)用戶創(chuàng)建,或者應(yīng)用程序來(lái)創(chuàng)建,一旦創(chuàng)建后,可以用來(lái)進(jìn)行交易、使用等行為。具體是做什么取決于應(yīng)用程序。
Forge 中的 Transaction
前面說(shuō)到,比特幣中有且僅有的一種 Transaction 就是轉(zhuǎn)賬,F(xiàn)orge 作為一個(gè)全功能框架,原生支持十幾種 Transaction,包括創(chuàng)建賬號(hào)、創(chuàng)建資產(chǎn)、轉(zhuǎn)賬、交換等。每一次事件的發(fā)生,都等價(jià)成一個(gè)個(gè)的 Transaction 發(fā)布到鏈上。
所以說(shuō)若開發(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 交互來(lái)告之其應(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 請(qǐng)求訪問某個(gè) API 來(lái)獲取一些資源,為什么 Forge 沒有用 JSON API 呢?
原因很簡(jiǎn)單,效率。關(guān)于 GraphQL 和 gRPC 的優(yōu)點(diǎn),這里不再展開,不過會(huì)簡(jiǎn)單介紹一下這二種技術(shù)。
GraphQL 怎么用?
GraphQL 是 Facebook 開源的一項(xiàng)技術(shù),皆在幫助用戶更高效快捷地從服務(wù)器獲取資源,
GraphQL 在網(wǎng)絡(luò)的應(yīng)用層面用的是 HTTP/1.1 或 HTTP/2 協(xié)議的 POST 請(qǐng)求,服務(wù)器接收從客戶端發(fā)來(lái)的 Query 請(qǐng)求,經(jīng)過處理后返回一個(gè) JSON 的結(jié)果。
客戶端能發(fā)送的請(qǐng)求分三類:
?Query:用來(lái)讀取資源
?Mutation:用來(lái)創(chuàng)建、改變資源
?Subscription:用來(lái)訂閱事件
在 Forge 中,Query 一般用來(lái)作查詢鏈上的數(shù)據(jù);Mutation 一般用來(lái)作向鏈發(fā)送 Transaction;Subscription 用來(lái)訂閱鏈上發(fā)生的事件。
gRPC 怎么用?
gRPC 是 Google 出的一套 RPC 框架,簡(jiǎn)單來(lái)說(shuō):gRPC = protobuf + HTTP/2
Protocol Buffer 簡(jiǎn)稱 Protobuf,也是 Google 自家出的一種序列化/反序列化標(biāo)準(zhǔn)。是比 XML,JSON 更加高效的序列化方式。它是通過預(yù)先定義好一個(gè) .proto 文件,記錄了要傳輸?shù)男畔⒍加心男┳侄我约八鼈兊木幪?hào),之后序列化的時(shí)候只對(duì)字段的值進(jìn)行編碼,以達(dá)到節(jié)省空間的目的,使用方法如下:
1.用戶定義要傳輸?shù)男畔⒂心男┳侄危瑢懙揭粋€(gè) .proto 文件中,然后用官方或社區(qū)提供的你要用的語(yǔ)言的插件將其編譯成 .cpp 或 .ex 或 .py 文件中。
2.在你的程序中,用剛才生成出來(lái)的模塊提供的序列化函數(shù),將一個(gè)數(shù)據(jù)對(duì)象轉(zhuǎn)化成二進(jìn)制以便在網(wǎng)絡(luò)中進(jìn)行傳輸,接受方用反序列化函數(shù)得到的二進(jìn)制轉(zhuǎn)化回?cái)?shù)據(jù)對(duì)象。
用 Protobuf 進(jìn)行的對(duì)數(shù)據(jù)的序列化能很大程度上節(jié)省空間,這樣傳輸在網(wǎng)絡(luò)上的數(shù)據(jù)變少了,請(qǐng)求就更高效了。但是需要付出的代價(jià)就是
1.首先要有服務(wù)端定義的 .proto 文件
2.你要用的語(yǔ)言要有 protoc(官方提供的 protobuf 的編譯器)的插件。
Forge 所有用到的 proto 文件都在 ArcBlock/forge-abi[1] 倉(cāng)庫(kù)中;Google 官方支持 C++、C#、Go、Python 的插件,其他的語(yǔ)言要到社區(qū)中去找了。
那么,gRPC 是啥呢?看圖說(shuō)話:
?首先服務(wù)器端定義好一套請(qǐng)求/響應(yīng)的.proto 文件
?客戶端把要發(fā)的請(qǐng)求通過 protobuf 序列化成二進(jìn)制后,通過 HTTP/2 協(xié)議發(fā)給服務(wù)器
?服務(wù)器收到請(qǐng)求,處理之,然后再以 protobuf 序列化的二進(jìn)制發(fā)回響應(yīng)——客戶端收到響應(yīng)后,反序列化拿到結(jié)果
之所以用 HTTP/2 協(xié)議而不再用 HTTP/1.1 是為了能夠更高效地傳輸數(shù)據(jù)。同時(shí),需要用一個(gè)官方提供的或是社區(qū)提供的 gRPC 的庫(kù)來(lái)使用 gRPC。
GraphQL 還是 gRPC?
Forge 提供了 GraphQL 和 gRPC 兩種方式來(lái)與其交互,那么到底用哪個(gè)好呢?GraphQL 上手簡(jiǎn)單,只需要用一個(gè) HTTP 客戶端和一個(gè) JSON 的原就能收發(fā)數(shù)據(jù)了,而 gRPC 上手復(fù)雜,需要了解 protobuf,并用一個(gè) gRPC 才能收發(fā)數(shù)據(jù)。我們推薦用 gRPC,雖然看起來(lái)上手難點(diǎn),但是其使用起來(lái)更靈活;而 GraphQL 上手簡(jiǎn)單,更適合一些簡(jiǎn)單的查詢。
Forge 中如何發(fā)送 transaction?
前面講了若開發(fā)者想在區(qū)塊鏈上做開發(fā),歸根到底就是通過 Forge 在區(qū)塊鏈上發(fā)布一個(gè)一個(gè)的 transaction。又講了 Forge 提供 GraphQL 和 gRPC 的方式來(lái)交互。接下來(lái)就講一下如何在 Forge 中通過 gRPC 中發(fā)送 transaction。
怎么樣,發(fā)送的流程簡(jiǎn)單吧!就是把 Forge 中定義的 transaction 通過 gRPC 發(fā)給 Forge,之后 Forge 會(huì)返回一個(gè)哈希作為結(jié)果。
好的,那么接下來(lái),我們就來(lái)看一下 Forge 中定義的 transaction 長(zhǎng)什么樣。
Forge 中對(duì)于 transaction 的定義可以在 arcblock/forge-abi/lib/protobuf/type.proto 下面找到。
message Transaction {
string from = 1; # 這個(gè)tx是誰(shuí)發(fā)的,即錢包地址
uint64 nonce = 2; # nonce 用來(lái)防止重?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)造出來(lái)這個(gè) transaction 后,將其發(fā)送給 Forge,接下來(lái)我們會(huì)用一個(gè)具體的例子來(lái)演示如何在鏈上創(chuàng)建一個(gè)錢包賬號(hào)。
Forge 中的錢包
創(chuàng)建錢包分 2 步,
1.在本地創(chuàng)建一個(gè)錢包
2.把這個(gè)錢包申明(decleare)到鏈上去,這樣就算完成了用戶賬號(hào)的創(chuàng)建。
所以說(shuō)了這么久,錢包究竟是什么東西呢?錢包其實(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é)請(qǐng)參考 arcblock/abt-did-spec 里面關(guān)于創(chuàng)建 DID 的文檔
以下的參考代碼內(nèi)為 Elixir 代碼,用的是我們已經(jīng)開源的 Forge-elixir-sdk 的庫(kù)
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)建的,還是要把它申明到鏈上去,
還記得之前說(shuō)的,要在鏈上搞事情就得需要發(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 是多方簽名,我們這一步還用不到,不用管它,在看簽名之前我們先來(lái)看一下 itx。
Forge 中的 itx 是什么?
itx 是 inner transaction 的縮寫,都已經(jīng)有了 tx,為啥還要有 itx 呢?
做個(gè)比喻,這個(gè)就像寫信一樣,每封信都有標(biāo)題,抬頭,征文,日期,和簽名等,但是不同的信的征文內(nèi)容是不同的。tx 就是信的模版,包括寄信人,標(biāo)題,簽名;而 itx 則是信的正文,代表了具體內(nèi)容。Forge 支持了十幾種 tx,也就是說(shuō),有十幾種 itx。我們要做的將剛創(chuàng)建的錢包申明上的鏈的 itx 叫做 declare
message DeclareTx{
string moniker = 1 ; #表示這個(gè)錢包賬戶的別名
。..。
}
這里忽視了其他一些用不上的字段。那么如何將這個(gè) declare tx 創(chuàng)建成一個(gè) itx 呢?我們?cè)賮?lái)看一下 transaction 中定義的 itx 類型
google.protobuf.Any itx = 15;
它的類型是 google.protobuf.Any, 這個(gè)是 google 提供的一種類型,如它的名字一樣,是專門給任意類型用的一種通用的類型,它的定義如下
message Any{
string type_url = 1;
bytes value = 2;
}
既然是任意類型,那只用 value 來(lái)表示不就好了嗎?type_url 是個(gè)什么鬼?這個(gè)其實(shí)是給應(yīng)用程序用的,告訴它這個(gè)任意類型到底是個(gè)什么類型。google 設(shè)計(jì)的本意是這個(gè) type_url 是一個(gè) url, 但是我們并不需要它是一個(gè) url,可以是任何字符串。
Forge 中定義的 type_url 長(zhǎng)這樣
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ù)字簽名就是用錢包的私鑰對(duì) 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)造完成并且簽好名了!接下來(lái)只需要把這個(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)操作需要參考你所用的語(yǔ)言的 gRPC 的庫(kù)的文檔,在 Elixir 中,這樣做
Forgesdk.send_tx(tx: tx)
“48c265bb.。..”
之后返回的哈希即是這個(gè) tx 在鏈上的哈希嘍!用這個(gè)哈希就可以在鏈上查到其狀態(tài)了。當(dāng)我們把 tx 發(fā)送請(qǐng)給 Forge 后,F(xiàn)orge 會(huì)做一系列的檢查,包括發(fā)送 tx 的錢包地址是否有效,簽名是否有效等。之后 Forge 會(huì)把這個(gè) tx 發(fā)送給下層的共識(shí)引擎,并且廣播到 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è)錢包賬戶,接下來(lái)我們來(lái)看一下,F(xiàn)orge 中有那些常用的 tx。
假設(shè)有如下場(chǎng)景
用戶 a 創(chuàng)建了一個(gè)賬戶后,簽到一次得到一些 token,之后創(chuàng)建了一個(gè)資產(chǎn)(游戲地圖), 并將這個(gè)資產(chǎn)免費(fèi)轉(zhuǎn)讓了另一用戶 b,之后用戶 a 用一些 token 向用戶 b 購(gòu)買了該資產(chǎn),完成了一次交換。
declare 之前我們已經(jīng)看過了,接下來(lái)看 poke。
poke tx
poke 就是戳一下,作用是簽到領(lǐng)取 25 個(gè) token,一天只能領(lǐng)取一次。我們知道,發(fā)送 tx 時(shí),tx 的結(jié)構(gòu)都是一樣的,不同的僅僅是 itx 的內(nèi)容及簽名。我們?cè)賮?lái)看一下 tx 的結(jié)構(gòu)。
message Transaction{
string from = 1; # wallet.address
uint64 nonce = 2; # 0 《- 注意對(duì)于poke來(lái)說(shuō)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 賬號(hào)就多了 25 個(gè) token 啦!好的,現(xiàn)在我們的錢包創(chuàng)建了,并且有了 25 個(gè) token,接下來(lái)看看如何創(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)
接下來(lái)還有 asset 中的地址為空,我們需要自己將它算出來(lái)。Forge 中的所有東西的 id 都是支持 DID 標(biāo)準(zhǔn),對(duì)于 asset 的地址,也是一個(gè) DID。那么 asset 地址怎么算呢?
hash = Mcrypto.hash(%SHA3{}, ForgeAbi.createAssetTx.encode(itx)) # 之后的步驟請(qǐng)參考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.。..》》}
接下來(lái)的步驟就是流水線作業(yè),將:tx 塞入 tx 中,簽名,發(fā)送 成功后,一個(gè) asset 就創(chuàng)建好了!里面的內(nèi)容放的就是“this is my map”。 ok, 接下來(lái)我們要把該資產(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 將其聲明到鏈上去,這里就不再詳寫了。
接下來(lái)看 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ā)送上鏈 成功之后,本來(lái)屬于用戶 A 的資產(chǎn)現(xiàn)在就屬于用戶 B 了!最后來(lái)看一下 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來(lái)表示
}
構(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)建呢?很簡(jiǎn)單。將用戶 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!(wallet2, 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è)流程的圖示
文章來(lái)源:ArcBlock區(qū)塊基石?
評(píng)論
查看更多