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