萬(wàn)字長(zhǎng)文,教你用go開(kāi)發(fā)區(qū)塊鏈應(yīng)用
大概 2 年前,碰巧學(xué)習(xí)區(qū)塊鏈(Hyperledger Fabric),便寫(xiě)了一個(gè)入門(mén)級(jí)的項(xiàng)目放在 GitHub 上,公眾號(hào)有不少讀者是通過(guò)這個(gè)項(xiàng)目關(guān)注到我的,也經(jīng)常問(wèn)我,有沒(méi)有區(qū)塊鏈這方面的學(xué)習(xí)資料,有沒(méi)有這個(gè)項(xiàng)目的詳細(xì)講解,如何搭建一個(gè)區(qū)塊鏈網(wǎng)絡(luò),林林總總。
對(duì)于這些問(wèn)題,我每次的回復(fù)都一樣,學(xué)習(xí)資料我倒是沒(méi)有,但是 官方文檔[1] 就是最好的資料了。
不過(guò)今天,我想還是通過(guò)這篇文章來(lái)記錄一下我對(duì)之前區(qū)塊鏈學(xué)習(xí)的一次總結(jié)吧。
對(duì)了,這個(gè)項(xiàng)目的地址是:https://github.com/togettoyou/fabric-realty[2] ,有幫助的話(huà)點(diǎn)個(gè) star 


預(yù)警:為了照顧到更多讀者,本篇盡量從新手的視角出發(fā),可能會(huì)有很多特別基礎(chǔ)的內(nèi)容,對(duì)于已經(jīng)懂的部分,選擇跳過(guò)即可。
再次預(yù)警:文章內(nèi)容有點(diǎn)長(zhǎng),請(qǐng)耐心看,最好跟著一起動(dòng)手實(shí)踐,如果中途發(fā)現(xiàn)了錯(cuò)誤之處,歡迎告知我。
技術(shù)棧
首先,以下這些我提到的技術(shù)要求你事先稍微學(xué)習(xí)掌握一下:
1、yaml 文件的編寫(xiě)
需要注意一下幾個(gè)規(guī)則:
大小寫(xiě)敏感 使用縮進(jìn)表示層級(jí)關(guān)系 縮進(jìn)不允許使用 tab,只允許空格 縮進(jìn)的空格數(shù)不重要,只要相同層級(jí)的元素左對(duì)齊即可 #表示注釋&用來(lái)建立錨點(diǎn),<<表示合并到當(dāng)前數(shù)據(jù),*用來(lái)引用錨點(diǎn)
2、Docker 和 Docker Compose
Docker 是一個(gè)開(kāi)源的應(yīng)用容器引擎,可以將應(yīng)用以及所需要的環(huán)境一起打包到一個(gè)輕量級(jí)、可移植的容器中,從而可以快速交付軟件。
Docker Compose 是用來(lái)定義和運(yùn)行多容器的工具??梢酝ㄟ^(guò) yaml 文件來(lái)配置應(yīng)用程序需要的所有服務(wù)。說(shuō)白了,就是批量管理 Docker 容器。
后續(xù)區(qū)塊鏈的節(jié)點(diǎn)以及應(yīng)用程序的部署我們都會(huì)使用 Docker Compose 來(lái)管理。
3、 go 語(yǔ)言
我的項(xiàng)目包括本篇文章的示例都是使用 go 語(yǔ)言開(kāi)發(fā)的,雖然 fabric 也提供了 Java,nodejs,python 等語(yǔ)言的 SDK ,但個(gè)人還是比較推薦 go 語(yǔ)言,畢竟 fabric 自身也是 go 實(shí)現(xiàn)的。
題外話(huà):以上這些技能除了在 fabric 區(qū)塊鏈體系中需掌握,在如今火熱的云原生技術(shù)下也一樣是基礎(chǔ)。
區(qū)塊鏈基礎(chǔ)知識(shí)
1、什么是區(qū)塊
Block ,每個(gè)區(qū)塊記錄著上一個(gè)區(qū)塊的 hash 值、本區(qū)塊中的交易集合、本區(qū)塊的 hash 等基礎(chǔ)數(shù)據(jù)。由于每個(gè)區(qū)塊都有上一區(qū)塊的 hash 值,區(qū)塊間由這個(gè)值兩兩串聯(lián),形成了區(qū)塊鏈。
2、什么是區(qū)塊鏈
Blockchain ,最早起源于比特幣的底層技術(shù),并在其后不斷演進(jìn)發(fā)展。
區(qū)塊鏈本質(zhì)上就是一個(gè)多方共享的分布式賬本技術(shù),用來(lái)記錄網(wǎng)絡(luò)上發(fā)生的所有交易。
而其中去中心化的概念,是因?yàn)橘~本信息會(huì)被復(fù)制到許多網(wǎng)絡(luò)參與者中,每個(gè)參與者都在協(xié)作維護(hù)賬本,不像傳統(tǒng)應(yīng)用的數(shù)據(jù)被中心管理著。
另外信息只能以附加的方式記錄到賬本上,并使用加密技術(shù)保證一旦將交易添加到賬本就無(wú)法修改。這種不可修改的屬性簡(jiǎn)化了信息的溯源,因?yàn)閰⑴c者可以確定信息在記錄后沒(méi)有改變過(guò)。所以區(qū)塊鏈有時(shí)也被稱(chēng)為證明系統(tǒng)。
3、什么是公鏈、聯(lián)盟鏈和私鏈
區(qū)塊鏈分為公有鏈、聯(lián)盟鏈、私有鏈三種基本類(lèi)型。其中:
完全去中心化:公鏈,人人都可以參與,就像比特幣(挖礦相當(dāng)于在記賬)。主要采取工作量證明機(jī)制(POW)、權(quán)益證明機(jī)制(POS)、股份授權(quán)證明機(jī)制(DPOS)等方式。 部分去中心化:聯(lián)盟鏈,參與者是指定的。聯(lián)盟鏈可以是幾家公司共同擁有的鏈,也可能是幾個(gè)國(guó)家共同承認(rèn)的鏈。這是后續(xù)發(fā)展的趨勢(shì)。 中心化:私鏈,寫(xiě)入權(quán)限僅在一個(gè)組織手里的區(qū)塊鏈,僅對(duì)特定的團(tuán)隊(duì)、組織或者個(gè)人開(kāi)放。
4、什么是交易
Transaction ,區(qū)塊鏈接收的數(shù)據(jù)稱(chēng)之為交易。
5、什么是智能合約
Smart contract,為了支持以同樣的方式更新信息,并實(shí)現(xiàn)一整套賬本功能(交易,查詢(xún)等),區(qū)塊鏈?zhǔn)褂弥悄芎霞s來(lái)提供對(duì)賬本的受控訪問(wèn)。
智能合約不僅是在網(wǎng)絡(luò)中封裝和簡(jiǎn)化信息的關(guān)鍵機(jī)制,它還可以被編寫(xiě)成自動(dòng)執(zhí)行參與者的特定交易的合約。
例如,可以編寫(xiě)智能合約以規(guī)定運(yùn)輸物品的成本,其中運(yùn)費(fèi)根據(jù)物品到達(dá)的速度而變化。根據(jù)雙方同意并寫(xiě)入賬本的條款,當(dāng)收到物品時(shí),相應(yīng)的資金會(huì)自動(dòng)轉(zhuǎn)手。
通俗易懂點(diǎn),智能合約就是按照大家約定好的規(guī)則編寫(xiě)的業(yè)務(wù)邏輯代碼實(shí)現(xiàn),然后只能通過(guò)這些合約來(lái)操作區(qū)塊鏈網(wǎng)絡(luò)這個(gè)賬本。
6、什么是共識(shí)
保持賬本在整個(gè)網(wǎng)絡(luò)中同步的過(guò)程稱(chēng)為共識(shí)。該過(guò)程確保賬本僅在交易被相應(yīng)參與者批準(zhǔn)時(shí)才會(huì)更新,并且當(dāng)賬本更新時(shí),它們以相同的順序更新相同的交易。
Hyperledger Fabric 基礎(chǔ)知識(shí)
1、什么是 Hyperledger Fabric
Linux 基金會(huì)于 2015 年創(chuàng)建了 Hyperledger(超級(jí)賬本)項(xiàng)目,而 Hyperledger Fabric 是其中一個(gè)用 Go 語(yǔ)言實(shí)現(xiàn)的版本。
Hyperledger Fabric 網(wǎng)絡(luò)的成員只能從可信賴(lài)的成員服務(wù)提供者(MSP) 注冊(cè),也就是說(shuō) Hyperledger Fabric 搭建的區(qū)塊鏈?zhǔn)且环N聯(lián)盟鏈。
Hyperledger Fabric 的賬本包括兩個(gè)組件: 世界狀態(tài)和交易日志。并且每個(gè)參與者都擁有他們所屬的每個(gè) Hyperledger Fabric 網(wǎng)絡(luò)的賬本的副本。
世界狀態(tài):描述了在給定時(shí)間點(diǎn)的賬本的狀態(tài)。它是賬本的數(shù)據(jù)庫(kù)。默認(rèn)情況下,使用 LevelDB 鍵值存儲(chǔ)數(shù)據(jù)庫(kù),可插拔,可替換為 CouchDB 。 交易日志:記錄產(chǎn)生世界狀態(tài)中當(dāng)前值的所有交易。這是世界狀態(tài)的更新歷史。它只記錄區(qū)塊鏈網(wǎng)絡(luò)使用賬本數(shù)據(jù)庫(kù)前后的值。
總結(jié):Hyperledger Fabric 是一種賬本技術(shù),其賬本包括世界狀態(tài)數(shù)據(jù)庫(kù)和交易日志歷史記錄。
2、什么是聯(lián)盟
聯(lián)盟指參與一個(gè)基于區(qū)塊鏈的業(yè)務(wù)協(xié)作或業(yè)務(wù)交易網(wǎng)絡(luò)的所有組織的集合,一個(gè)聯(lián)盟一般包含多個(gè)組織。
一般由聯(lián)盟發(fā)起方或運(yùn)營(yíng)方創(chuàng)建 Orderer 排序節(jié)點(diǎn),并負(fù)責(zé)交易排序、區(qū)塊產(chǎn)生和達(dá)成共識(shí)。聯(lián)盟發(fā)起方或運(yùn)營(yíng)方邀請(qǐng)各個(gè)組織實(shí)例加入聯(lián)盟,進(jìn)而創(chuàng)建通道。
3、什么是組織
組織代表的是參與區(qū)塊鏈網(wǎng)絡(luò)的企業(yè)、政府機(jī)構(gòu)、團(tuán)體等實(shí)體。
一個(gè)組織實(shí)例主要包含如下節(jié)點(diǎn):
CA:區(qū)塊鏈節(jié)點(diǎn)類(lèi)型之一,全稱(chēng) Certificate Authority ,數(shù)字證書(shū)頒發(fā)機(jī)構(gòu),負(fù)責(zé)組織內(nèi)部成員的register和enroll等,為該組織的區(qū)塊鏈用戶(hù)生成和頒發(fā)數(shù)字證書(shū)。Peer:區(qū)塊鏈節(jié)點(diǎn)類(lèi)型之一,負(fù)責(zé)保存和記錄賬本數(shù)據(jù)、對(duì)交易背書(shū)、運(yùn)行智能合約等。
4、什么是節(jié)點(diǎn)
節(jié)點(diǎn)(Peers)是區(qū)塊鏈的通信實(shí)體。它只是一個(gè)邏輯功能,只要能在“信任域”中分組并與控制它們的邏輯實(shí)體相關(guān)聯(lián),就可以將不同類(lèi)型的多個(gè)節(jié)點(diǎn)運(yùn)行在同一個(gè)物理服務(wù)器上,比如用 Docker 部署。
Orderer排序服務(wù)節(jié)點(diǎn) 或 排序節(jié)點(diǎn):Orderer 是一個(gè)運(yùn)行實(shí)現(xiàn)交付擔(dān)保的通信服務(wù)節(jié)點(diǎn),例如原子性或總順序廣播。排序節(jié)點(diǎn)負(fù)責(zé)接受交易并排序(排序算法有: SOLO,KAFKA,RAFT,PBFT),最后將排序好的交易按照配置中的約定整理為區(qū)塊之后提交給記賬節(jié)點(diǎn)進(jìn)行處理。Peer節(jié)點(diǎn):Peer 是業(yè)務(wù)參與方組織在區(qū)塊鏈網(wǎng)絡(luò)中所擁有的參與共識(shí)和賬本記錄的節(jié)點(diǎn)??梢杂卸喾N角色。作為Committing Peer記賬節(jié)點(diǎn)時(shí),無(wú)需安裝鏈碼,只負(fù)責(zé)驗(yàn)證從 Orderer 發(fā)出的區(qū)塊和交易的合法性、并存儲(chǔ)賬本區(qū)塊信息。作為Endorsing Peer背書(shū)節(jié)點(diǎn)時(shí),必須安裝鏈碼,在交易時(shí)需進(jìn)行簽名背書(shū)。Anchor錨節(jié)點(diǎn):為了實(shí)現(xiàn)高可用,每個(gè)參與方組織一般包含兩個(gè)或多個(gè)Peer節(jié)點(diǎn),可以設(shè)置其中的一個(gè)為Anchor,與區(qū)塊鏈網(wǎng)絡(luò)中的其他組織進(jìn)行信息同步。客戶(hù)端節(jié)點(diǎn):客戶(hù)端扮演了代表最終用戶(hù)的實(shí)體,可以同時(shí)與
Peer和Orderer通信,創(chuàng)建并調(diào)用交易。這里客戶(hù)端可以指應(yīng)用程序、SDK、命令行等。
5、什么是通道
Hyperledger Fabric 中的通道(Channel)是兩個(gè)或兩個(gè)以上特定網(wǎng)絡(luò)成員之間通信的專(zhuān)用“子網(wǎng)”,用于進(jìn)行私有和機(jī)密的交易。
可以理解為組織間拉了個(gè)群聊,這個(gè)群聊就是通道,在里面聊天交易,一個(gè)聯(lián)盟鏈中可以有多個(gè)群聊(通道),一個(gè)組織可以加入多個(gè)群聊,每個(gè)群聊可以代表一項(xiàng)具體的業(yè)務(wù),有自身對(duì)應(yīng)的一套賬本,群聊間互不干擾,互相隔離。
6、什么是鏈碼
Hyperledger Fabric 的智能合約用鏈碼(Chaincode)編寫(xiě)。在大多數(shù)情況下,鏈碼只與賬本的數(shù)據(jù)庫(kù)即世界狀態(tài)交互,而不與交易日志交互。
鏈碼可以用多種編程語(yǔ)言實(shí)現(xiàn)。有 Go、Node.js 和 Java 鏈碼等。
搭建區(qū)塊鏈網(wǎng)絡(luò)
基礎(chǔ)知識(shí)過(guò)完,接下來(lái)就到了本篇核心的項(xiàng)目實(shí)戰(zhàn)環(huán)節(jié)。首先是搭建一個(gè)區(qū)塊鏈網(wǎng)絡(luò),只需按照下面幾個(gè)順序,一步步來(lái)就行(推薦在 Linux 或 MacOS 下操作):
1、下載 fabric 二進(jìn)制工具
以 v1.4.12 版本為例, fabric 二進(jìn)制工具的下載地址在:https://github.com/hyperledger/fabric/releases/tag/v1.4.12[3]
自行根據(jù)你的系統(tǒng)環(huán)境下載對(duì)應(yīng)的包。
其中幾個(gè)主要的工具說(shuō)明:
cryptogen:用來(lái)生成 Hyperledger Fabric 密鑰材料的工具,這個(gè)過(guò)程是靜態(tài)的。cryptogen工具通過(guò)一個(gè)包含網(wǎng)絡(luò)拓?fù)涞?crypto-config.yaml文件,為所有組織和屬于這些組織的組件生成一組證書(shū)和秘鑰。cryptogen適合用于測(cè)試開(kāi)發(fā)環(huán)境,在生產(chǎn)環(huán)境建議使用動(dòng)態(tài)的 CA 服務(wù)。configtxgen:用于創(chuàng)建和查看排序節(jié)點(diǎn)的創(chuàng)世區(qū)塊、通道配置交易等相關(guān)的工具。configtxgen使用configtx.yaml文件來(lái)定義網(wǎng)絡(luò)配置。configtxlator:fabric 中Protobuf和JSON格式轉(zhuǎn)換的工具,fabric 中任何的使用Protobuf定義的類(lèi)型,都可使用該工具進(jìn)行轉(zhuǎn)換。peer:peer 命令有 5 個(gè)不同的子命令,每個(gè)命令都可以讓指定的 peer 節(jié)點(diǎn)執(zhí)行特定的一組任務(wù)。比如,可以使用子命令peer channel讓一個(gè) peer 節(jié)點(diǎn)加入通道,或者使用peer chaincode命令把智能合約鏈碼部署到 peer 節(jié)點(diǎn)上。
2、將 fabric 二進(jìn)制工具添加到環(huán)境變量
為了后續(xù)方便使用命令,可以將第 1 步下載的工具添加到系統(tǒng)環(huán)境變量中:
$?export?PATH=${PWD}/hyperledger-fabric-linux-amd64-1.4.12/bin:$PATH
3、生成證書(shū)和秘鑰
我們將使用 cryptogen 工具生成各種加密材料( x509 證書(shū)和簽名秘鑰)。這些證書(shū)是身份的代表,在實(shí)體相互通信和交易的時(shí)候,可以對(duì)其身份進(jìn)行簽名和驗(yàn)證。
首先創(chuàng)建 crypto-config.yaml 文件,定義網(wǎng)絡(luò)拓?fù)?,為所有組織和屬于這些組織的組件(也就是節(jié)點(diǎn))生成一組證書(shū)和秘鑰,內(nèi)容如下:
#?排序節(jié)點(diǎn)的組織定義
OrdererOrgs:
??-?Name:?QQ?#?名稱(chēng)
????Domain:?qq.com?#?域名
????Specs:?#?節(jié)點(diǎn)域名:orderer.qq.com
??????-?Hostname:?orderer?#?主機(jī)名
#?peer節(jié)點(diǎn)的組織定義
PeerOrgs:
??#?Taobao-組織
??-?Name:?Taobao?#?名稱(chēng)
????Domain:?taobao.com?#?域名
????Template:?#?使用模板定義。Count?指的是該組織下組織節(jié)點(diǎn)的個(gè)數(shù)
??????Count:?2?#?節(jié)點(diǎn)域名:peer0.taobao.com?和?peer1.taobao.com
????Users:?#?組織的用戶(hù)信息。Count?指該組織中除了?Admin?之外的用戶(hù)的個(gè)數(shù)
??????Count:?1?#?用戶(hù):Admin?和?User1
??#?JD-組織
??-?Name:?JD
????Domain:?jd.com
????Template:
??????Count:?2?#?節(jié)點(diǎn)域名:peer0.jd.com?和?peer1.jd.com
????Users:
??????Count:?1?#?用戶(hù):Admin?和?User1
接著執(zhí)行 cryptogen generate 命令,生成結(jié)果將默認(rèn)保存在 crypto-config 文件夾中:
$?cryptogen?generate?--config=./crypto-config.yaml
taobao.com
jd.com
我們可以看看在 crypto-config 文件夾里生成了什么:
$?tree?crypto-config
crypto-config
├──?ordererOrganizations
│???└──?qq.com
│???????├──?ca
│???????│???├──?3e41f960bb5a3002a1e436e9079311d79cf8846c2ad2a09080ea8575e16bb5b7_sk
│???????│???└──?ca.qq.com-cert.pem
│???????├──?msp
│???????│???├──?admincerts
│???????│???│???└──[email protected]
│???????│???├──?cacerts
│???????│???│???└──?ca.qq.com-cert.pem
│???????│???└──?tlscacerts
│???????│???????└──?tlsca.qq.com-cert.pem
│???????├──?orderers
│???????│???└──?orderer.qq.com
│???????│???????├──?msp
│???????│???????│???├──?admincerts
│???????│???????│???│???└──[email protected]
│???????│???????│???├──?cacerts
│???????│???????│???│???└──?ca.qq.com-cert.pem
│???????│???????│???├──?keystore
│???????│???????│???│???└──?6bd45f78877b96cfbcd040262ee4c808bd6d894cabfed44552fb7c22d6d427d1_sk
│???????│???????│???├──?signcerts
│???????│???????│???│???└──?orderer.qq.com-cert.pem
│???????│???????│???└──?tlscacerts
│???????│???????│???????└──?tlsca.qq.com-cert.pem
│???????│???????└──?tls
│???????│???????????├──?ca.crt
│???????│???????????├──?server.crt
│???????│???????????└──?server.key
│???????├──?tlsca
│???????│???├──?bd48b5360c82ce5beeb31dea1b7e8e7918a5e7246d3f8892889fe1b2efadc1aa_sk
│???????│???└──?tlsca.qq.com-cert.pem
│???????└──?users
│???????????└──[email protected]
│???????????????├──?msp
│???????????????│???├──?admincerts
│???????????????│???│???└──[email protected]
│???????????????│???├──?cacerts
│???????????????│???│???└──?ca.qq.com-cert.pem
│???????????????│???├──?keystore
│???????????????│???│???└──?f28c1ed4c67fd438a891e420a2e53b20352bdf40907a0a8ee39095505475c99f_sk
│???????????????│???├──?signcerts
│???????????????│???│???└──[email protected]
│???????????????│???└──?tlscacerts
│???????????????│???????└──?tlsca.qq.com-cert.pem
│???????????????└──?tls
│???????????????????├──?ca.crt
│???????????????????├──?client.crt
│???????????????????└──?client.key
└──?peerOrganizations
????├──?jd.com
????│???├──?ca
????│???│???├──?5672a9717fd943d0dcd2269ea1700c10309ad49d16b849e9c6e24225deafceb5_sk
????│???│???└──?ca.jd.com-cert.pem
????│???├──?msp
????│???│???├──?admincerts
????│???│???│???└──[email protected]
????│???│???├──?cacerts
????│???│???│???└──?ca.jd.com-cert.pem
????│???│???└──?tlscacerts
????│???│???????└──?tlsca.jd.com-cert.pem
????│???├──?peers
????│???│???├──?peer0.jd.com
????│???│???│???├──?msp
????│???│???│???│???├──?admincerts
????│???│???│???│???│???└──[email protected]
????│???│???│???│???├──?cacerts
????│???│???│???│???│???└──?ca.jd.com-cert.pem
????│???│???│???│???├──?keystore
????│???│???│???│???│???└──?012700eb44d6e19becb63c944e685a18d69ea9f1120aaa45fe549236c6a90fb6_sk
????│???│???│???│???├──?signcerts
????│???│???│???│???│???└──?peer0.jd.com-cert.pem
????│???│???│???│???└──?tlscacerts
????│???│???│???│???????└──?tlsca.jd.com-cert.pem
????│???│???│???└──?tls
????│???│???│???????├──?ca.crt
????│???│???│???????├──?server.crt
????│???│???│???????└──?server.key
????│???│???└──?peer1.jd.com
????│???│???????├──?msp
????│???│???????│???├──?admincerts
????│???│???????│???│???└──[email protected]
????│???│???????│???├──?cacerts
????│???│???????│???│???└──?ca.jd.com-cert.pem
????│???│???????│???├──?keystore
????│???│???????│???│???└──?b1e81b66080705595f5e56cc8d78575b0e935b79c8f674001e46cae452a71f32_sk
????│???│???????│???├──?signcerts
????│???│???????│???│???└──?peer1.jd.com-cert.pem
????│???│???????│???└──?tlscacerts
????│???│???????│???????└──?tlsca.jd.com-cert.pem
????│???│???????└──?tls
????│???│???????????├──?ca.crt
????│???│???????????├──?server.crt
????│???│???????????└──?server.key
????│???├──?tlsca
????│???│???├──?f4c7d0b660575f383d189696480bf559f312d798eb0352c9102f8be6ecde52d6_sk
????│???│???└──?tlsca.jd.com-cert.pem
????│???└──?users
????│???????├──[email protected]
????│???????│???├──?msp
????│???????│???│???├──?admincerts
????│???????│???│???│???└──[email protected]
????│???????│???│???├──?cacerts
????│???????│???│???│???└──?ca.jd.com-cert.pem
????│???????│???│???├──?keystore
????│???????│???│???│???└──?d7f476884ff36a19aa7100c63aa30f8f378cc5ec826ca58977539e1c9c6b22df_sk
????│???????│???│???├──?signcerts
????│???????│???│???│???└──[email protected]
????│???????│???│???└──?tlscacerts
????│???????│???│???????└──?tlsca.jd.com-cert.pem
????│???????│???└──?tls
????│???????│???????├──?ca.crt
????│???????│???????├──?client.crt
????│???????│???????└──?client.key
????│???????└──[email protected]
????│???????????├──?msp
????│???????????│???├──?admincerts
????│???????????│???│???└──[email protected]
????│???????????│???├──?cacerts
????│???????????│???│???└──?ca.jd.com-cert.pem
????│???????????│???├──?keystore
????│???????????│???│???└──?e83862c8e78509f2a4362d3282214421179fa47f3d655f75cb3539d5534f7494_sk
????│???????????│???├──?signcerts
????│???????????│???│???└──[email protected]
????│???????????│???└──?tlscacerts
????│???????????│???????└──?tlsca.jd.com-cert.pem
????│???????????└──?tls
????│???????????????├──?ca.crt
????│???????????????├──?client.crt
????│???????????????└──?client.key
????└──?taobao.com
????????├──?ca
????????│???├──?4a31791b9fade54ab70496f03169707f6b9643c04d1bc734da15b0c625628865_sk
????????│???└──?ca.taobao.com-cert.pem
????????├──?msp
????????│???├──?admincerts
????????│???│???└──[email protected]
????????│???├──?cacerts
????????│???│???└──?ca.taobao.com-cert.pem
????????│???└──?tlscacerts
????????│???????└──?tlsca.taobao.com-cert.pem
????????├──?peers
????????│???├──?peer0.taobao.com
????????│???│???├──?msp
????????│???│???│???├──?admincerts
????????│???│???│???│???└──[email protected]
????????│???│???│???├──?cacerts
????????│???│???│???│???└──?ca.taobao.com-cert.pem
????????│???│???│???├──?keystore
????????│???│???│???│???└──?914648b8c4dc4783b0505a22b5c7630e424c3cf8dd54e2fe05b47dc321a4e61b_sk
????????│???│???│???├──?signcerts
????????│???│???│???│???└──?peer0.taobao.com-cert.pem
????????│???│???│???└──?tlscacerts
????????│???│???│???????└──?tlsca.taobao.com-cert.pem
????????│???│???└──?tls
????????│???│???????├──?ca.crt
????????│???│???????├──?server.crt
????????│???│???????└──?server.key
????????│???└──?peer1.taobao.com
????????│???????├──?msp
????????│???????│???├──?admincerts
????????│???????│???│???└──[email protected]
????????│???????│???├──?cacerts
????????│???????│???│???└──?ca.taobao.com-cert.pem
????????│???????│???├──?keystore
????????│???????│???│???└──?3eef8defc07afb547e94f08702a5b30807d2e2a672e3d437bfb54dd1590b0fa7_sk
????????│???????│???├──?signcerts
????????│???????│???│???└──?peer1.taobao.com-cert.pem
????????│???????│???└──?tlscacerts
????????│???????│???????└──?tlsca.taobao.com-cert.pem
????????│???????└──?tls
????????│???????????├──?ca.crt
????????│???????????├──?server.crt
????????│???????????└──?server.key
????????├──?tlsca
????????│???├──?296a941f625974153aa5ab6cf57b0933023aaa13b0e4363a7378e5c527de26a1_sk
????????│???└──?tlsca.taobao.com-cert.pem
????????└──?users
????????????├──[email protected]
????????????│???├──?msp
????????????│???│???├──?admincerts
????????????│???│???│???└──[email protected]
????????????│???│???├──?cacerts
????????????│???│???│???└──?ca.taobao.com-cert.pem
????????????│???│???├──?keystore
????????????│???│???│???└──?a2af975d659f77182b2aca318321797d281036f085dda9799ab79b6400e5e970_sk
????????????│???│???├──?signcerts
????????????│???│???│???└──[email protected]
????????????│???│???└──?tlscacerts
????????????│???│???????└──?tlsca.taobao.com-cert.pem
????????????│???└──?tls
????????????│???????├──?ca.crt
????????????│???????├──?client.crt
????????????│???????└──?client.key
????????????└──[email protected]
????????????????├──?msp
????????????????│???├──?admincerts
????????????????│???│???└──[email protected]
????????????????│???├──?cacerts
????????????????│???│???└──?ca.taobao.com-cert.pem
????????????????│???├──?keystore
????????????????│???│???└──?c65d45e1c7e1070e3f1b00bd8ac41e91d2bfaea10a769d75b9599590791ccc02_sk
????????????????│???├──?signcerts
????????????????│???│???└──[email protected]
????????????????│???└──?tlscacerts
????????????????│???????└──?tlsca.taobao.com-cert.pem
????????????????└──?tls
????????????????????├──?ca.crt
????????????????????├──?client.crt
????????????????????└──?client.key
109?directories,?101?files
總結(jié):在這個(gè)環(huán)節(jié)中,我們假設(shè) QQ 作為一個(gè)運(yùn)營(yíng)方,提供了 1 個(gè) Orderer 節(jié)點(diǎn) orderer.qq.com 來(lái)創(chuàng)建聯(lián)盟鏈的基礎(chǔ)設(shè)施, 而 Taobao 和 JD 則是作為組織成員加入到鏈中,各自提供 2 個(gè) Peer 節(jié)點(diǎn) peer0.xx.com 和 peer1.xx.com 參與工作,以及還各自創(chuàng)建了 2 個(gè)組織用戶(hù) Admin 和 User1 。然后我們使用 crypto-config.yaml 文件和 cryptogen 工具為其定義所需要的證書(shū)文件以供后續(xù)使用。
4、創(chuàng)建排序通道創(chuàng)世區(qū)塊
我們可以使用 configtx.yaml 文件和 configtxgen 工具輕松地創(chuàng)建通道的配置。configtx.yaml 文件可以以易于理解和編輯的 yaml 格式來(lái)構(gòu)建通道配置所需的信息。configtxgen 工具通過(guò)讀取 configtx.yaml 文件中的信息,將其轉(zhuǎn)成 Fabric 可以讀取的 protobuf 格式。
先來(lái)創(chuàng)建 configtx.yaml 文件,內(nèi)容如下:
#?定義組織機(jī)構(gòu)實(shí)體
Organizations:
??-?&QQ
????Name:?QQ?#?組織的名稱(chēng)
????ID:?QQMSP?#?組織的?MSPID
????MSPDir:?crypto-config/ordererOrganizations/qq.com/msp?#組織的證書(shū)相對(duì)位置(生成的crypto-config目錄)
??-?&Taobao
????Name:?Taobao
????ID:?TaobaoMSP
????MSPDir:?crypto-config/peerOrganizations/taobao.com/msp
????AnchorPeers:?#?組織錨節(jié)點(diǎn)的配置
??????-?Host:?peer0.taobao.com
????????Port:?7051
??-?&JD
????Name:?JD
????ID:?JDMSP
????MSPDir:?crypto-config/peerOrganizations/jd.com/msp
????AnchorPeers:?#?組織錨節(jié)點(diǎn)的配置
??????-?Host:?peer0.jd.com
????????Port:?7051
#?定義了排序服務(wù)的相關(guān)參數(shù),這些參數(shù)將用于創(chuàng)建創(chuàng)世區(qū)塊
Orderer:?&OrdererDefaults
??#?排序節(jié)點(diǎn)類(lèi)型用來(lái)指定要啟用的排序節(jié)點(diǎn)實(shí)現(xiàn),不同的實(shí)現(xiàn)對(duì)應(yīng)不同的共識(shí)算法
??OrdererType:?solo?#?共識(shí)機(jī)制
??Addresses:?#?Orderer?的域名(用于連接)
????-?orderer.qq.com:7050
??BatchTimeout:?2s?#?出塊時(shí)間間隔
??BatchSize:?#?用于控制每個(gè)block的信息量
????MaxMessageCount:?10?#每個(gè)區(qū)塊的消息個(gè)數(shù)
????AbsoluteMaxBytes:?99?MB?#每個(gè)區(qū)塊最大的信息大小
????PreferredMaxBytes:?512?KB?#每個(gè)區(qū)塊包含的一條信息最大長(zhǎng)度
??Organizations:
#?定義Peer組織如何與應(yīng)用程序通道交互的策略
#?默認(rèn)策略:所有Peer組織都將能夠讀取數(shù)據(jù)并將數(shù)據(jù)寫(xiě)入賬本
Application:?&ApplicationDefaults
??Organizations:
#?用來(lái)定義用于?configtxgen?工具的配置入口
#?將?Profile?參數(shù)(?TwoOrgsOrdererGenesis?或?TwoOrgsChannel?)指定為?configtxgen?工具的參數(shù)
Profiles:
??#??TwoOrgsOrdererGenesis配置文件用于創(chuàng)建系統(tǒng)通道創(chuàng)世塊
??#??該配置文件創(chuàng)建一個(gè)名為SampleConsortium的聯(lián)盟
??#??該聯(lián)盟在configtx.yaml文件中包含兩個(gè)Peer組織Taobao和JD
??TwoOrgsOrdererGenesis:
????Orderer:
??????<<:?*OrdererDefaults
??????Organizations:
????????-?*QQ
????Consortiums:
??????SampleConsortium:
????????Organizations:
??????????-?*Taobao
??????????-?*JD
??#?使用TwoOrgsChannel配置文件創(chuàng)建應(yīng)用程序通道
??TwoOrgsChannel:
????Consortium:?SampleConsortium
????Application:
??????<<:?*ApplicationDefaults
??????Organizations:
????????-?*Taobao
????????-?*JD
執(zhí)行 configtxgen 命令,并指定 Profile 為 TwoOrgsOrdererGenesis 參數(shù):
$?configtxgen?-profile?TwoOrgsOrdererGenesis?-outputBlock?./config/genesis.block?-channelID?firstchannel
排序區(qū)塊是排序服務(wù)的創(chuàng)世區(qū)塊,通過(guò)以上命令就可以預(yù)先生成創(chuàng)世區(qū)塊的 protobuf 格式的配置文件 ./config/genesis.block 了。這一步也是為后續(xù)做準(zhǔn)備用的。
5、創(chuàng)建通道配置交易
接下來(lái),我們需要繼續(xù)使用 configtxgen 根據(jù)去創(chuàng)建通道的交易配置,和第 4 步不同的是,這次需要指定 Profile 為 TwoOrgsChannel 參數(shù)。
生成通道配置事務(wù) ./config/appchannel.tx :
$?configtxgen?-profile?TwoOrgsChannel?-outputCreateChannelTx?./config/appchannel.tx?-channelID?appchannel
為 Taobao 組織定義錨節(jié)點(diǎn),生成 ./config/TaobaoAnchor.tx :
$?configtxgen?-profile?TwoOrgsChannel?-outputAnchorPeersUpdate?./config/TaobaoAnchor.tx?-channelID?appchannel?-asOrg?Taobao
為 JD 組織定義錨節(jié)點(diǎn),生成 ./config/JDAnchor.tx :
$?configtxgen?-profile?TwoOrgsChannel?-outputAnchorPeersUpdate?./config/JDAnchor.tx?-channelID?appchannel?-asOrg?JD
當(dāng)然,這一步也是為后續(xù)使用做準(zhǔn)備的。不過(guò)至此,需要準(zhǔn)備的配置都齊了。
來(lái)看看現(xiàn)在 config 文件夾都有什么:
$?tree?config
config
├──?JDAnchor.tx
├──?TaobaoAnchor.tx
├──?appchannel.tx
└──?genesis.block
0?directories,?4?files
6、創(chuàng)建并啟動(dòng)各組織的節(jié)點(diǎn)
我們說(shuō)過(guò):我們假設(shè) QQ 作為一個(gè)運(yùn)營(yíng)方,提供了 1 個(gè) Orderer 節(jié)點(diǎn) orderer.qq.com 來(lái)創(chuàng)建聯(lián)盟鏈的基礎(chǔ)設(shè)施, 而 Taobao 和 JD 則是作為組織成員加入到鏈中,各自提供 2 個(gè) Peer 節(jié)點(diǎn) peer0.xx.com 和 peer1.xx.com 參與工作。
現(xiàn)在這些組織及其節(jié)點(diǎn)所需要的配置已經(jīng)準(zhǔn)備好了。我們接下來(lái)就可以使用 Docker Compose 來(lái)模擬啟動(dòng)這些節(jié)點(diǎn)服務(wù)。
由于這些節(jié)點(diǎn)之間需要互相通信,所以我們需要將這些節(jié)點(diǎn)都放入到一個(gè) Docker 網(wǎng)絡(luò)中,以 fabric_network 為例。
docker-compose.yaml 的內(nèi)容如下:
version:?'2.1'
volumes:
??orderer.qq.com:
??peer0.taobao.com:
??peer1.taobao.com:
??peer0.jd.com:
??peer1.jd.com:
networks:
??fabric_network:
????name:?fabric_network
services:
??#?排序服務(wù)節(jié)點(diǎn)
??orderer.qq.com:
????container_name:?orderer.qq.com
????image:?hyperledger/fabric-orderer:1.4.12
????environment:
??????-?GODEBUG=netdns=go
??????-?ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
??????-?ORDERER_GENERAL_GENESISMETHOD=file
??????-?ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/config/genesis.block?#?注入創(chuàng)世區(qū)塊
??????-?ORDERER_GENERAL_LOCALMSPID=QQMSP
??????-?ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/orderer/msp?#?證書(shū)相關(guān)
????command:?orderer
????ports:
??????-?"7050:7050"
????volumes:?#?掛載由cryptogen和configtxgen生成的證書(shū)文件以及創(chuàng)世區(qū)塊
??????-?./config/genesis.block:/etc/hyperledger/config/genesis.block
??????-?./crypto-config/ordererOrganizations/qq.com/orderers/orderer.qq.com/:/etc/hyperledger/orderer
??????-?orderer.qq.com:/var/hyperledger/production/orderer
????networks:
??????-?fabric_network
??#??Taobao?組織?peer0?節(jié)點(diǎn)
??peer0.taobao.com:
????extends:
??????file:?docker-compose-base.yaml
??????service:?peer-base
????container_name:?peer0.taobao.com
????environment:
??????-?CORE_PEER_ID=peer0.taobao.com
??????-?CORE_PEER_LOCALMSPID=TaobaoMSP
??????-?CORE_PEER_ADDRESS=peer0.taobao.com:7051
????ports:
??????-?"7051:7051"?#?grpc服務(wù)端口
??????-?"7053:7053"?#?eventhub端口
????volumes:
??????-?./crypto-config/peerOrganizations/taobao.com/peers/peer0.taobao.com:/etc/hyperledger/peer
??????-?peer0.taobao.com:/var/hyperledger/production
????depends_on:
??????-?orderer.qq.com
??#??Taobao?組織?peer1?節(jié)點(diǎn)
??peer1.taobao.com:
????extends:
??????file:?docker-compose-base.yaml
??????service:?peer-base
????container_name:?peer1.taobao.com
????environment:
??????-?CORE_PEER_ID=peer1.taobao.com
??????-?CORE_PEER_LOCALMSPID=TaobaoMSP
??????-?CORE_PEER_ADDRESS=peer1.taobao.com:7051
????ports:
??????-?"17051:7051"
??????-?"17053:7053"
????volumes:
??????-?./crypto-config/peerOrganizations/taobao.com/peers/peer1.taobao.com:/etc/hyperledger/peer
??????-?peer1.taobao.com:/var/hyperledger/production
????depends_on:
??????-?orderer.qq.com
??#??JD?組織?peer0?節(jié)點(diǎn)
??peer0.jd.com:
????extends:
??????file:?docker-compose-base.yaml
??????service:?peer-base
????container_name:?peer0.jd.com
????environment:
??????-?CORE_PEER_ID=peer0.jd.com
??????-?CORE_PEER_LOCALMSPID=JDMSP
??????-?CORE_PEER_ADDRESS=peer0.jd.com:7051
????ports:
??????-?"27051:7051"
??????-?"27053:7053"
????volumes:
??????-?./crypto-config/peerOrganizations/jd.com/peers/peer0.jd.com:/etc/hyperledger/peer
??????-?peer0.jd.com:/var/hyperledger/production
????depends_on:
??????-?orderer.qq.com
??#??JD?組織?peer1?節(jié)點(diǎn)
??peer1.jd.com:
????extends:
??????file:?docker-compose-base.yaml
??????service:?peer-base
????container_name:?peer1.jd.com
????environment:
??????-?CORE_PEER_ID=peer1.jd.com
??????-?CORE_PEER_LOCALMSPID=JDMSP
??????-?CORE_PEER_ADDRESS=peer1.jd.com:7051
????ports:
??????-?"37051:7051"
??????-?"37053:7053"
????volumes:
??????-?./crypto-config/peerOrganizations/jd.com/peers/peer1.jd.com:/etc/hyperledger/peer
??????-?peer1.jd.com:/var/hyperledger/production
????depends_on:
??????-?orderer.qq.com
??#?客戶(hù)端節(jié)點(diǎn)
??cli:
????container_name:?cli
????image:?hyperledger/fabric-tools:1.4.12
????tty:?true
????environment:
??????#?go?環(huán)境設(shè)置
??????-?GO111MODULE=auto
??????-?GOPROXY=https://goproxy.cn
??????-?CORE_PEER_ID=cli
????command:?/bin/bash
????volumes:
??????-?./config:/etc/hyperledger/config
??????-?./crypto-config/peerOrganizations/taobao.com/:/etc/hyperledger/peer/taobao.com
??????-?./crypto-config/peerOrganizations/jd.com/:/etc/hyperledger/peer/jd.com
??????-?./../chaincode:/opt/gopath/src/chaincode?#?鏈碼路徑注入
????networks:
??????-?fabric_network
????depends_on:
??????-?orderer.qq.com
??????-?peer0.taobao.com
??????-?peer1.taobao.com
??????-?peer0.jd.com
??????-?peer1.jd.com
為了方便,這里我還定義了一個(gè) docker-compose-base.yaml 作為 Peer 節(jié)點(diǎn)的公共模板,內(nèi)容如下:
version:?'2.1'
services:
??peer-base:?#?peer的公共服務(wù)
????image:?hyperledger/fabric-peer:1.4.12
????environment:
??????-?GODEBUG=netdns=go
??????-?CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
??????-?CORE_LOGGING_PEER=info
??????-?CORE_CHAINCODE_LOGGING_LEVEL=INFO
??????-?CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/msp?#?msp證書(shū)(節(jié)點(diǎn)證書(shū))
??????-?CORE_LEDGER_STATE_STATEDATABASE=goleveldb?#?狀態(tài)數(shù)據(jù)庫(kù)的存儲(chǔ)引擎(or?CouchDB)
??????-?CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_network?#?docker?網(wǎng)絡(luò)
????volumes:
??????-?/var/run/docker.sock:/host/var/run/docker.sock
????working_dir:?/opt/gopath/src/github.com/hyperledger/fabric
????command:?peer?node?start
????networks:
??????-?fabric_network
注意觀察,在 volumes 配置項(xiàng)中,我們將 config 和 crypto-config 內(nèi)的配置文件都掛載到相對(duì)應(yīng)的節(jié)點(diǎn)中了。并且在 peer 的公共服務(wù)中,我們還掛載了 /var/run/docker.sock 文件,有了該文件,在容器內(nèi)就可以向其發(fā)送 http 請(qǐng)求和 Docker Daemon 通信,通俗理解,就是有了它,就可以在容器內(nèi)操作宿主機(jī)的 Docker 了,比如在容器內(nèi)控制 Docker 再啟動(dòng)一個(gè)容器出來(lái)。而這,就是為了后面可以部署智能合約(節(jié)點(diǎn)部署鏈碼其實(shí)就是啟動(dòng)一個(gè)鏈碼容器)。
現(xiàn)在繼續(xù)將這些節(jié)點(diǎn)服務(wù)啟動(dòng)起來(lái):
$?docker-compose?up?-d
Creating?network?"fabric_network"?with?the?default?driver
Creating?volume?"network_orderer.qq.com"?with?default?driver
Creating?volume?"network_peer0.taobao.com"?with?default?driver
Creating?volume?"network_peer1.taobao.com"?with?default?driver
Creating?volume?"network_peer0.jd.com"?with?default?driver
Creating?volume?"network_peer1.jd.com"?with?default?driver
Creating?orderer.qq.com?...?done
Creating?peer1.taobao.com?...?done
Creating?peer0.jd.com?????...?done
Creating?peer1.jd.com?????...?done
Creating?peer0.taobao.com?...?done
Creating?cli??????????????...?done
哦對(duì)了,除了必須的節(jié)點(diǎn)服務(wù),我還啟動(dòng)了一個(gè) cli 服務(wù),來(lái)自 hyperledger/fabric-tools 鏡像,這個(gè)其實(shí)就是集成了前面第 1 步提到的 fabric 工具的容器,我們接下來(lái)的命令執(zhí)行就使用這個(gè)容器內(nèi)的工具來(lái)完成了,你也可以繼續(xù)使用自己下載的二進(jìn)制工具,只是個(gè)人覺(jué)得環(huán)境配置起來(lái)會(huì)比較麻煩。
7、為 cli 服務(wù)配置環(huán)境
接下來(lái)我們要使用 cli 服務(wù)來(lái)執(zhí)行 peer 命令,所以要為其先配置一下環(huán)境變量,使用四個(gè)不同的變量 TaobaoPeer0Cli、TaobaoPeer1Cli、JDPeer0Cli、JDPeer1Cli ,代表 cli 服務(wù)代表著不同的節(jié)點(diǎn):
$?TaobaoPeer0Cli="CORE_PEER_ADDRESS=peer0.taobao.com:7051?CORE_PEER_LOCALMSPID=TaobaoMSP?CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/taobao.com/users/[email protected]/msp"
$?TaobaoPeer1Cli="CORE_PEER_ADDRESS=peer1.taobao.com:7051?CORE_PEER_LOCALMSPID=TaobaoMSP?CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/taobao.com/users/[email protected]/msp"
$?JDPeer0Cli="CORE_PEER_ADDRESS=peer0.jd.com:7051?CORE_PEER_LOCALMSPID=JDMSP?CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/jd.com/users/[email protected]/msp"
$?JDPeer1Cli="CORE_PEER_ADDRESS=peer1.jd.com:7051?CORE_PEER_LOCALMSPID=JDMSP?CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/jd.com/users/[email protected]/msp"
8、開(kāi)始創(chuàng)建通道
通道主要用于實(shí)現(xiàn)區(qū)塊鏈網(wǎng)絡(luò)中業(yè)務(wù)的隔離。一個(gè)聯(lián)盟中可以有多個(gè)通道,每個(gè)通道可代表一項(xiàng)業(yè)務(wù),并且對(duì)應(yīng)一套賬本。通道內(nèi)的成員為業(yè)務(wù)參與方(即聯(lián)盟內(nèi)的組織),一個(gè)組織可以加入多個(gè)通道。
我們現(xiàn)在有請(qǐng) Taobao 組織的 peer0 節(jié)點(diǎn)來(lái)創(chuàng)建一個(gè)通道 appchannel :
$?docker?exec?cli?bash?-c?"$TaobaoPeer0Cli?peer?channel?create?-o?orderer.qq.com:7050?-c?appchannel?-f?/etc/hyperledger/config/appchannel.tx"
通道就相當(dāng)于“群聊”, Taobao 組織的 peer0 節(jié)點(diǎn)創(chuàng)建了一個(gè)名稱(chēng)為 appchannel 的“群聊”。
9、將所有節(jié)點(diǎn)加入通道
將所有的節(jié)點(diǎn)都加入到通道 appchannel 中(正常是按需加入):
$?docker?exec?cli?bash?-c?"$TaobaoPeer0Cli?peer?channel?join?-b?appchannel.block"
$?docker?exec?cli?bash?-c?"$TaobaoPeer1Cli?peer?channel?join?-b?appchannel.block"
$?docker?exec?cli?bash?-c?"$JDPeer0Cli?peer?channel?join?-b?appchannel.block"
$?docker?exec?cli?bash?-c?"$JDPeer1Cli?peer?channel?join?-b?appchannel.block"
這時(shí)相當(dāng)于大家都加入到了 appchannel “群聊”中,之后大家都可以在里面“聊天”了。
10、更新錨節(jié)點(diǎn)
錨節(jié)點(diǎn)是必需的。普通節(jié)點(diǎn)只能發(fā)現(xiàn)本組織下的其它節(jié)點(diǎn),而錨節(jié)點(diǎn)可以跨組織服務(wù)發(fā)現(xiàn)到其它組織下的節(jié)點(diǎn),建議每個(gè)組織都選擇至少一個(gè)錨節(jié)點(diǎn)。
利用之前準(zhǔn)備好的配置文件,向通道更新錨節(jié)點(diǎn):
$?docker?exec?cli?bash?-c?"$TaobaoPeer0Cli?peer?channel?update?-o?orderer.qq.com:7050?-c?appchannel?-f?/etc/hyperledger/config/TaobaoAnchor.tx"
$?docker?exec?cli?bash?-c?"$JDPeer0Cli?peer?channel?update?-o?orderer.qq.com:7050?-c?appchannel?-f?/etc/hyperledger/config/JDAnchor.tx"
這樣,Taobao 和 JD 組織間的節(jié)點(diǎn)就都可以互相發(fā)現(xiàn)了。
到這里,我們的區(qū)塊鏈網(wǎng)絡(luò)基本已經(jīng)搭建好了,但是還差最關(guān)鍵的智能合約。一個(gè)沒(méi)有智能合約的通道是沒(méi)有靈魂的,啥事都做不了。
編寫(xiě)智能合約
fabric 的智能合約稱(chēng)為鏈碼,編寫(xiě)智能合約也就是編寫(xiě)鏈碼。
鏈碼其實(shí)很簡(jiǎn)單,可以由 Go 、 node.js 、或者 Java 編寫(xiě),其實(shí)只是實(shí)現(xiàn)一些預(yù)定義的接口。
以 Go 為例,創(chuàng)建一個(gè) main.go 文件:
package?main
import?(
?"fmt"
?"github.com/hyperledger/fabric/core/chaincode/shim"
?pb?"github.com/hyperledger/fabric/protos/peer"
)
type?MyChaincode?struct?{
}
//?Init?初始化時(shí)會(huì)執(zhí)行該方法
func?(c?*MyChaincode)?Init(stub?shim.ChaincodeStubInterface)?pb.Response?{
?fmt.Println("鏈碼初始化")
?return?shim.Success(nil)
}
//?Invoke?智能合約的功能函數(shù)定義
func?(c?*MyChaincode)?Invoke(stub?shim.ChaincodeStubInterface)?pb.Response?{
?funcName,?args?:=?stub.GetFunctionAndParameters()
?switch?funcName?{
?default:
??return?shim.Error(fmt.Sprintf("沒(méi)有該功能:?%s",?funcName))
?}
}
func?main()?{
?err?:=?shim.Start(new(MyChaincode))
?if?err?!=?nil?{
??panic(err)
?}
}
我們定義的 MyChaincode 結(jié)構(gòu)體實(shí)現(xiàn)了 shim.Chaincode 接口:
//?Chaincode?interface?must?be?implemented?by?all?chaincodes.?The?fabric?runs
//?the?transactions?by?calling?these?functions?as?specified.
type?Chaincode?interface?{
?//?Init?is?called?during?Instantiate?transaction?after?the?chaincode?container
?//?has?been?established?for?the?first?time,?allowing?the?chaincode?to
?//?initialize?its?internal?data
?Init(stub?ChaincodeStubInterface)?pb.Response
?//?Invoke?is?called?to?update?or?query?the?ledger?in?a?proposal?transaction.
?//?Updated?state?variables?are?not?committed?to?the?ledger?until?the
?//?transaction?is?committed.
?Invoke(stub?ChaincodeStubInterface)?pb.Response
}
然后在啟動(dòng)入口 main 函數(shù)中調(diào)用 shim.Start(new(MyChaincode)) 就完成了鏈碼的啟動(dòng),沒(méi)錯(cuò),就是這么簡(jiǎn)單。
我們知道鏈碼其實(shí)就是用來(lái)處理區(qū)塊鏈網(wǎng)絡(luò)中的成員一致同意的業(yè)務(wù)邏輯。比如 Taobao 和 JD 規(guī)定了一個(gè)規(guī)則,將其編寫(xiě)成鏈碼,后面雙方就只能遵循這個(gè)規(guī)則了,因?yàn)殒湸a到時(shí)候即部署在你的節(jié)點(diǎn),也會(huì)部署在我的節(jié)點(diǎn)上,你偷偷改了邏輯,我的節(jié)點(diǎn)不會(huì)認(rèn)可你的,這也正是區(qū)塊鏈的作用之一。
鏈碼的功能定義在 Invoke 方法中。
一個(gè)簡(jiǎn)易的示例如下:
package?main
import?(
?"encoding/json"
?"errors"
?"fmt"
?"strconv"
?"github.com/hyperledger/fabric/core/chaincode/shim"
?pb?"github.com/hyperledger/fabric/protos/peer"
)
type?MyChaincode?struct?{
}
//?Init?初始化時(shí)會(huì)執(zhí)行該方法
func?(c?*MyChaincode)?Init(stub?shim.ChaincodeStubInterface)?pb.Response?{
?fmt.Println("鏈碼初始化")
?//?假設(shè)A有1000元,以復(fù)合主鍵?userA?的形式寫(xiě)入賬本
?err?:=?WriteLedger(stub,?map[string]interface{}{"name":?"A",?"balance":?1000},?"user",?[]string{"A"})
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?//?假設(shè)B有1000元,以復(fù)合主鍵?userB?的形式寫(xiě)入賬本
?err?=?WriteLedger(stub,?map[string]interface{}{"name":?"B",?"balance":?1000},?"user",?[]string{"B"})
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?return?shim.Success(nil)
}
//?Invoke?智能合約的功能函數(shù)定義
func?(c?*MyChaincode)?Invoke(stub?shim.ChaincodeStubInterface)?pb.Response?{
?funcName,?args?:=?stub.GetFunctionAndParameters()
?switch?funcName?{
?case?"query":
??return?query(stub,?args)
?case?"transfer":
??return?transfer(stub,?args)
?default:
??return?shim.Error(fmt.Sprintf("沒(méi)有該功能:?%s",?funcName))
?}
}
func?query(stub?shim.ChaincodeStubInterface,?args?[]string)?pb.Response?{
?//?如果?args?為空,則表示查詢(xún)所有?user
?results,?err?:=?ReadLedger(stub,?"user",?args)
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?var?users?[]map[string]interface{}
?for?_,?result?:=?range?results?{
??var?user?map[string]interface{}
??if?err?=?json.Unmarshal(result,?&user);?err?!=?nil?{
???return?shim.Error(err.Error())
??}
??users?=?append(users,?user)
?}
?usersByte,?err?:=?json.Marshal(&users)
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?return?shim.Success(usersByte)
}
func?transfer(stub?shim.ChaincodeStubInterface,?args?[]string)?pb.Response?{
?//?驗(yàn)證參數(shù)
?if?len(args)?!=?3?{
??return?shim.Error("參數(shù)個(gè)數(shù)不滿(mǎn)足")
?}
?from?:=?args[0]
?to?:=?args[1]
?money,?err?:=?strconv.ParseFloat(args[2],?64)
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?//?從賬本查詢(xún)?from?用戶(hù)
?fromResults,?err?:=?ReadLedger(stub,?"user",?[]string{from})
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?if?len(fromResults)?!=?1?{
??return?shim.Error("沒(méi)有該用戶(hù)?"?+?from)
?}
?var?fromUser?map[string]interface{}
?if?err?=?json.Unmarshal(fromResults[0],?&fromUser);?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?//?從賬本查詢(xún)?to?用戶(hù)
?toResults,?err?:=?ReadLedger(stub,?"user",?[]string{to})
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?if?len(toResults)?!=?1?{
??return?shim.Error("沒(méi)有該用戶(hù)?"?+?to)
?}
?var?toUser?map[string]interface{}
?if?err?=?json.Unmarshal(toResults[0],?&toUser);?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?//?from?用戶(hù)扣除余額
?if?money?>?fromUser["balance"].(float64)?{
??return?shim.Error("余額不足")
?}
?fromUser["balance"]?=?fromUser["balance"].(float64)?-?money
?//?to?用戶(hù)增加余額
?toUser["balance"]?=?toUser["balance"].(float64)?+?money
?//?寫(xiě)回賬本
?err?=?WriteLedger(stub,?fromUser,?"user",?[]string{from})
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?err?=?WriteLedger(stub,?toUser,?"user",?[]string{to})
?if?err?!=?nil?{
??return?shim.Error(err.Error())
?}
?return?shim.Success([]byte("ok"))
}
func?main()?{
?err?:=?shim.Start(new(MyChaincode))
?if?err?!=?nil?{
??panic(err)
?}
}
//?WriteLedger?寫(xiě)入賬本
//?obj?為要寫(xiě)入的數(shù)據(jù)
//?objectType和keys?共同組成復(fù)合主鍵
func?WriteLedger(stub?shim.ChaincodeStubInterface,?obj?interface{},?objectType?string,?keys?[]string)?error?{
?//創(chuàng)建復(fù)合主鍵
?var?key?string
?if?val,?err?:=?stub.CreateCompositeKey(objectType,?keys);?err?!=?nil?{
??return?errors.New(fmt.Sprintf("%s-創(chuàng)建復(fù)合主鍵出錯(cuò)?%s",?objectType,?err.Error()))
?}?else?{
??key?=?val
?}
?bytes,?err?:=?json.Marshal(obj)
?if?err?!=?nil?{
??return?err
?}
?//寫(xiě)入?yún)^(qū)塊鏈賬本
?if?err?:=?stub.PutState(key,?bytes);?err?!=?nil?{
??return?errors.New(fmt.Sprintf("%s-寫(xiě)入?yún)^(qū)塊鏈賬本出錯(cuò):?%s",?objectType,?err.Error()))
?}
?return?nil
}
//?ReadLedger?根據(jù)復(fù)合主鍵查詢(xún)賬本數(shù)據(jù)(適合獲取全部或指定的數(shù)據(jù))
//?objectType和keys?共同組成復(fù)合主鍵
func?ReadLedger(stub?shim.ChaincodeStubInterface,?objectType?string,?keys?[]string)?(results?[][]byte,?err?error)?{
?//?通過(guò)主鍵從區(qū)塊鏈查找相關(guān)的數(shù)據(jù),相當(dāng)于對(duì)主鍵的模糊查詢(xún)
?resultIterator,?err?:=?stub.GetStateByPartialCompositeKey(objectType,?keys)
?if?err?!=?nil?{
??return?nil,?errors.New(fmt.Sprintf("%s-獲取全部數(shù)據(jù)出錯(cuò):?%s",?objectType,?err))
?}
?defer?resultIterator.Close()
?//檢查返回的數(shù)據(jù)是否為空,不為空則遍歷數(shù)據(jù),否則返回空數(shù)組
?for?resultIterator.HasNext()?{
??val,?err?:=?resultIterator.Next()
??if?err?!=?nil?{
???return?nil,?errors.New(fmt.Sprintf("%s-返回的數(shù)據(jù)出錯(cuò):?%s",?objectType,?err))
??}
??results?=?append(results,?val.GetValue())
?}
?return?results,?nil
}
在這段鏈碼中,初始化的時(shí)候我們假設(shè)有用戶(hù) A 和 B ,并且都各自有 1000 元余額,我們?cè)?Invoke 方法中為其定義了兩個(gè)功能函數(shù) query 和 transfer 。
其中 query 函數(shù)可以查詢(xún) A 和 B 或指定用戶(hù)的余額信息, transfer 函數(shù)可以通過(guò)傳入轉(zhuǎn)賬人,被轉(zhuǎn)賬人,金額,三個(gè)參數(shù)來(lái)實(shí)現(xiàn)轉(zhuǎn)賬功能。例如 {"Args":["transfer","A","B","100.0"]} 代表 A 向 B 轉(zhuǎn)賬 100 元。
部署鏈碼
我們將剛剛編寫(xiě)的智能合約也就是鏈碼安裝到區(qū)塊鏈網(wǎng)絡(luò)中,同樣是借助 cli 服務(wù),我們?cè)?Taobao 組織的 peer0 節(jié)點(diǎn)和 JD 組織的 peer0 節(jié)點(diǎn)上都安裝上鏈碼:
$?docker?exec?cli?bash?-c?"$TaobaoPeer0Cli?peer?chaincode?install?-n?fabric-realty?-v?1.0.0?-l?golang?-p?chaincode"
$?docker?exec?cli?bash?-c?"$JDPeer0Cli?peer?chaincode?install?-n?fabric-realty?-v?1.0.0?-l?golang?-p?chaincode"
其中 -n 參數(shù)是鏈碼名稱(chēng),可以自己隨便設(shè)置,-v 是鏈碼版本號(hào),-p 是鏈碼的目錄(我們已經(jīng)將鏈碼掛載到 cli 容器中了,在 /opt/gopath/src/ 目錄下)
鏈碼安裝后,還需要實(shí)例化后才可以使用,只需要在任意一個(gè)節(jié)點(diǎn)實(shí)例化就可以了,以 Taobao 組織的 peer0 節(jié)點(diǎn)為例:
$?docker?exec?cli?bash?-c?"$TaobaoPeer0Cli?peer?chaincode?instantiate?-o?orderer.qq.com:7050?-C?appchannel?-n?fabric-realty?-l?golang?-v?1.0.0?-c?'{\"Args\":[\"init\"]}'?-P?\"AND?('TaobaoMSP.member','JDMSP.member')\""
實(shí)例化鏈碼主要就是傳入 {"Args":["init"]} 參數(shù),此時(shí)會(huì)調(diào)用我們編寫(xiě)的 func (c *MyChaincode) Init 方法,進(jìn)行鏈碼的初始化。其中 -P 參數(shù)用于指定鏈碼的背書(shū)策略,AND ('TaobaoMSP.member','JDMSP.member') 代表鏈碼的寫(xiě)入操作需要同時(shí)得到 Taobao和 JD 組織成員的背書(shū)才允許通過(guò)。AND 也可以替換成 OR,代表任意一組織成員背書(shū)即可,更多具體用法,可以去看官方文檔。
鏈碼實(shí)例化成功之后就會(huì)啟動(dòng)鏈碼容器,而啟動(dòng)的方法,就是我們之前提過(guò)的 peer 節(jié)點(diǎn)服務(wù)掛載了 /var/run/docker.sock 文件。
查看啟動(dòng)的鏈碼容器:
$?docker?ps?-a?|?awk?'($2?~?/dev-peer.*fabric-realty.*/)?{print?$2}'
dev-peer0.taobao.com-fabric-realty-1.0.0-4f127a0415dd835529133a69b480ce24581dd5ddcaf18426ecc1d3dfb02b4670
因?yàn)槲覀兪褂?Taobao 組織的 peer0 節(jié)點(diǎn)實(shí)例化鏈碼,所以此時(shí)還只有這個(gè)節(jié)點(diǎn)的鏈碼容器啟動(dòng)起來(lái)了。
我們可以試著使用 cli 服務(wù)去調(diào)用鏈碼:
$?docker?exec?cli?bash?-c?"$TaobaoPeer0Cli?peer?chaincode?invoke?-C?appchannel?-n?fabric-realty?-c?'{\"Args\":[\"query\"]}'"
2022-03-22?21:13:40.152?UTC?[chaincodeCmd]?InitCmdFactory?->?INFO?001?Retrieved?channel?(appchannel)?orderer?endpoint:?orderer.qq.com:7050
2022-03-22?21:13:40.157?UTC?[chaincodeCmd]?chaincodeInvokeOrQuery?->?INFO?002?Chaincode?invoke?successful.?result:?status:200?payload:"[{\
"balance\":1000,\"name\":\"A\"},{\"balance\":1000,\"name\":\"B\"}]"
當(dāng)然,使用JD組織的節(jié)點(diǎn)也是可以的:
$?docker?exec?cli?bash?-c?"$JDPeer0Cli?peer?chaincode?invoke?-C?appchannel?-n?fabric-realty?-c?'{\"Args\":[\"query\"]}'"
2022-03-22?21:14:45.397?UTC?[chaincodeCmd]?InitCmdFactory?->?INFO?001?Retrieved?channel?(appchannel)?orderer?endpoint:?orderer.qq.com:7050
2022-03-22?21:14:45.402?UTC?[chaincodeCmd]?chaincodeInvokeOrQuery?->?INFO?002?Chaincode?invoke?successful.?result:?status:200?payload:"[{\
"balance\":1000,\"name\":\"A\"},{\"balance\":1000,\"name\":\"B\"}]"
此時(shí),因?yàn)槲覀儾樵?xún)了 JD 組織的 peer0 節(jié)點(diǎn)上的鏈碼,所以對(duì)應(yīng)的鏈碼容器也會(huì)啟動(dòng)起來(lái)了,再次查看啟動(dòng)的鏈碼容器:
$?docker?ps?-a?|?awk?'($2?~?/dev-peer.*fabric-realty.*/)?{print?$2}'
dev-peer0.jd.com-fabric-realty-1.0.0-5c5e915cdcd47324151383f9619a0ff9a33283d969555e6029aa256cc389ebc9
dev-peer0.taobao.com-fabric-realty-1.0.0-4f127a0415dd835529133a69b480ce24581dd5ddcaf18426ecc1d3dfb02b4670
現(xiàn)在,我們的智能合約就成功部署到區(qū)塊鏈網(wǎng)絡(luò)的通道中了。
編寫(xiě)應(yīng)用程序
在部署鏈碼之后,我們是使用 cli 服務(wù)去調(diào)用的,但這種方式一般只是作為驗(yàn)證使用,更多情況下,應(yīng)該是我們自己編寫(xiě)應(yīng)用程序集成 fabric 提供的 SDK 去調(diào)用。
Go 語(yǔ)言可以使用官方的 github.com/hyperledger/fabric-sdk-go 庫(kù)。
這個(gè) SDK 使用起來(lái)也很簡(jiǎn)單。
第一步調(diào)用其 New 方法創(chuàng)建一個(gè) FabricSDK 實(shí)例,后續(xù)使用這個(gè)實(shí)例就可以調(diào)用操作合約的方法了。
//?New?根據(jù)提供的一組選項(xiàng)初始化?SDK
//?ConfigOptions?提供應(yīng)用程序配置
func?New(configProvider?core.ConfigProvider,?opts?...Option)?(*FabricSDK,?error)?{
?pkgSuite?:=?defPkgSuite{}
?return?fromPkgSuite(configProvider,?&pkgSuite,?opts...)
}
其中 configProvider 可以從 Reader(實(shí)現(xiàn)了io.Reader接口的實(shí)例) 、 File(文件) 或 Raw([]byte) 獲取。我們選擇最簡(jiǎn)單的文件方式。
創(chuàng)建一個(gè) config.yaml ,配置如下:
version:?1.0.0
#?GO?SDK?客戶(hù)端配置
client:
??#?客戶(hù)端所屬的組織,必須是organizations定義的組織
??organization:?JD
??#?日志級(jí)別
??logging:
????level:?info
??#?MSP證書(shū)的根路徑
??cryptoconfig:
????path:?/network/crypto-config
#?通道定義
channels:
??appchannel:
????orderers:
??????-?orderer.qq.com
????peers:
??????peer0.jd.com:
????????endorsingPeer:?true
????????chaincodeQuery:?true
????????ledgerQuery:?true
????????eventSource:?true
??????peer1.jd.com:
????????endorsingPeer:?true
????????chaincodeQuery:?true
????????ledgerQuery:?true
????????eventSource:?true
#?組織配置
organizations:
??JD:
????mspid:?"JDMSP"
????cryptoPath:?peerOrganizations/jd.com/users/{username}@jd.com/msp
????peers:
??????-?peer0.jd.com
??????-?peer1.jd.com
#?orderer節(jié)點(diǎn)列表
orderers:
??orderer.qq.com:
????url:?orderer.qq.com:7050
????#?傳遞給gRPC客戶(hù)端構(gòu)造函數(shù)
????grpcOptions:
??????ssl-target-name-override:?orderer.qq.com
??????keep-alive-time:?0s
??????keep-alive-timeout:?20s
??????keep-alive-permit:?false
??????fail-fast:?false
??????allow-insecure:?true
#?peers節(jié)點(diǎn)列表
peers:
??#?peer節(jié)點(diǎn)定義,可以定義多個(gè)
??peer0.jd.com:
????#?URL用于發(fā)送背書(shū)和查詢(xún)請(qǐng)求
????url:?peer0.jd.com:7051
????#?傳遞給gRPC客戶(hù)端構(gòu)造函數(shù)
????grpcOptions:
??????ssl-target-name-override:?peer0.jd.com
??????keep-alive-time:?0s
??????keep-alive-timeout:?20s
??????keep-alive-permit:?false
??????fail-fast:?false
??????allow-insecure:?true
??peer1.jd.com:
????url:?peer1.jd.com:7051
????grpcOptions:
??????ssl-target-name-override:?peer1.jd.com
??????keep-alive-time:?0s
??????keep-alive-timeout:?20s
??????keep-alive-permit:?false
??????fail-fast:?false
??????allow-insecure:?true
??peer0.taobao.com:
????url:?peer0.taobao.com:7051
????grpcOptions:
??????ssl-target-name-override:?peer0.taobao.com
??????keep-alive-time:?0s
??????keep-alive-timeout:?20s
??????keep-alive-permit:?false
??????fail-fast:?false
??????allow-insecure:?true
??peer1.taobao.com:
????url:?peer1.taobao.com:7051
????grpcOptions:
??????ssl-target-name-override:?peer1.taobao.com
??????keep-alive-time:?0s
??????keep-alive-timeout:?20s
??????keep-alive-permit:?false
??????fail-fast:?false
??????allow-insecure:?true
我們假定是 JD 組織來(lái)編寫(xiě)這個(gè)應(yīng)用程序,該配置主要就是用于驗(yàn)證 JD 組織及其節(jié)點(diǎn)的身份。
其中組織配置中 {username} 為動(dòng)態(tài)傳遞, MSP 證書(shū)的根路徑我們后續(xù)會(huì)掛載進(jìn)去。
現(xiàn)在開(kāi)始編寫(xiě)代碼,我們先來(lái)實(shí)例化 SDK ,創(chuàng)建 sdk.go:
package?main
import?(
?"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
?"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
?"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
)
//?配置信息
var?(
?sdk???????????*fabsdk.FabricSDK??????????????????????????????//?Fabric?SDK
?channelName???=?"appchannel"?????????????????????????????????//?通道名稱(chēng)
?username??????=?"Admin"??????????????????????????????????????//?用戶(hù)
?chainCodeName?=?"fabric-realty"??????????????????????????????//?鏈碼名稱(chēng)
?endpoints?????=?[]string{"peer0.jd.com",?"peer0.taobao.com"}?//?要發(fā)送交易的節(jié)點(diǎn)
)
//?init?初始化
func?init()?{
?var?err?error
?//?通過(guò)配置文件初始化SDK
?sdk,?err?=?fabsdk.New(config.FromFile("config.yaml"))
?if?err?!=?nil?{
??panic(err)
?}
}
//?ChannelExecute?區(qū)塊鏈交互
func?ChannelExecute(fcn?string,?args?[][]byte)?(channel.Response,?error)?{
?//?創(chuàng)建客戶(hù)端,表明在通道的身份
?ctx?:=?sdk.ChannelContext(channelName,?fabsdk.WithUser(username))
?cli,?err?:=?channel.New(ctx)
?if?err?!=?nil?{
??return?channel.Response{},?err
?}
?//?對(duì)區(qū)塊鏈賬本的寫(xiě)操作(調(diào)用了鏈碼的invoke)
?resp,?err?:=?cli.Execute(channel.Request{
??ChaincodeID:?chainCodeName,
??Fcn:?????????fcn,
??Args:????????args,
?},?channel.WithTargetEndpoints(endpoints...))
?if?err?!=?nil?{
??return?channel.Response{},?err
?}
?//返回鏈碼執(zhí)行后的結(jié)果
?return?resp,?nil
}
//?ChannelQuery?區(qū)塊鏈查詢(xún)
func?ChannelQuery(fcn?string,?args?[][]byte)?(channel.Response,?error)?{
?//?創(chuàng)建客戶(hù)端,表明在通道的身份
?ctx?:=?sdk.ChannelContext(channelName,?fabsdk.WithUser(username))
?cli,?err?:=?channel.New(ctx)
?if?err?!=?nil?{
??return?channel.Response{},?err
?}
?//?對(duì)區(qū)塊鏈賬本查詢(xún)的操作(調(diào)用了鏈碼的invoke),只返回結(jié)果
?resp,?err?:=?cli.Query(channel.Request{
??ChaincodeID:?chainCodeName,
??Fcn:?????????fcn,
??Args:????????args,
?},?channel.WithTargetEndpoints(endpoints...))
?if?err?!=?nil?{
??return?channel.Response{},?err
?}
?//返回鏈碼執(zhí)行后的結(jié)果
?return?resp,?nil
}
在這段代碼中,我們將使用 Admin 的身份去調(diào)用合約,并將每次的交易同時(shí)發(fā)送給 peer0.jd.com 和 peer0.taobao.com 節(jié)點(diǎn)進(jìn)行背書(shū),這是因?yàn)槲覀冊(cè)趯?shí)例化鏈碼的時(shí)候指定了背書(shū)策略為 AND ('TaobaoMSP.member','JDMSP.member') ,代表交易需要同時(shí)得到 Taobao和 JD 組織成員的背書(shū)才允許通過(guò)。每次寫(xiě)入賬本時(shí),會(huì)驗(yàn)證這兩個(gè)節(jié)點(diǎn)的數(shù)據(jù)一致性,只有當(dāng)這兩個(gè)節(jié)點(diǎn)的數(shù)據(jù)一致時(shí),交易才算最終成功。
繼續(xù)編寫(xiě) main.go ,我們使用 gin 來(lái)創(chuàng)建一個(gè) http 服務(wù):
package?main
import?(
?"bytes"
?"encoding/json"
?"github.com/gin-gonic/gin"
)
func?main()?{
?g?:=?gin.Default()
?g.GET("/query",?func(c?*gin.Context)?{
??args?:=?make([][]byte,?0)
??user?:=?c.Query("user")
??if?user?!=?""?{
???args?=?append(args,?[]byte(user))
??}
??//?調(diào)用鏈碼的query函數(shù)
??resp,?err?:=?ChannelQuery("query",?args)
??if?err?!=?nil?{
???c.AbortWithStatusJSON(500,?gin.H{"err":?err.Error()})
???return
??}
??var?data?[]map[string]interface{}
??if?err?=?json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(),?&data);?err?!=?nil?{
???c.AbortWithStatusJSON(500,?gin.H{"err":?err.Error()})
???return
??}
??c.JSON(200,?data)
?})
?g.POST("/transfer",?func(c?*gin.Context)?{
??from?:=?c.Query("from")
??to?:=?c.Query("to")
??money?:=?c.Query("money")
??if?from?==?""?||?to?==?""?||?money?==?""?{
???c.AbortWithStatusJSON(400,?gin.H{"err":?"參數(shù)不能為空"})
???return
??}
??args?:=?make([][]byte,?0)
??args?=?append(args,?[]byte(from),?[]byte(to),?[]byte(money))
??//?調(diào)用鏈碼的transfer函數(shù)
??resp,?err?:=?ChannelExecute("transfer",?args)
??if?err?!=?nil?{
???c.AbortWithStatusJSON(500,?gin.H{"err":?err.Error()})
???return
??}
??c.JSON(200,?gin.H{"msg":?string(resp.Payload)})
?})
?g.Run("0.0.0.0:8000")
}
在 main 函數(shù)中,我們創(chuàng)建了兩個(gè)接口 GET /query 和 POST /transfer ,其中 /query 接口調(diào)用鏈碼的 query 函數(shù)功能實(shí)現(xiàn)查詢(xún)用戶(hù)余額,/transfer 接口調(diào)用鏈碼的 transfer 函數(shù)功能實(shí)現(xiàn)轉(zhuǎn)賬功能。
我們將繼續(xù)使用 Docker 部署該應(yīng)用程序,這樣的好處是可以和區(qū)塊鏈網(wǎng)絡(luò)處于同一網(wǎng)絡(luò)下,方便調(diào)用節(jié)點(diǎn),當(dāng)然你也可以更改 config.yaml 文件去調(diào)用暴露在宿主機(jī)的節(jié)點(diǎn)端口也是可以的,首先編寫(xiě) Dockerfile 文件:
FROM?golang:1.14?AS?app
ENV?GO111MODULE=on
ENV?GOPROXY?https://goproxy.cn,direct
WORKDIR?/root/togettoyou
COPY?.?.
RUN?CGO_ENABLED=0?go?build?-v?-o?"app"?.
FROM?scratch
WORKDIR?/root/togettoyou/
COPY?--from=app?/root/togettoyou/app?./
COPY?--from=app?/root/togettoyou/config.yaml?./
ENTRYPOINT?["./app"]
docker-compose.yml 文件:
version:?'2.1'
networks:
??fabric_network:
????external:
??????name:?fabric_network
services:
??app:
????build:?.
????image:?app:latest
????ports:
??????-?"8000:8000"
????volumes:
??????-?./../network/crypto-config:/network/crypto-config?#?掛載搭建區(qū)塊鏈網(wǎng)絡(luò)時(shí)生成的crypto-config文件夾
????networks:
??????-?fabric_network
其中掛載的 crypto-config 文件夾就是之前搭建區(qū)塊鏈網(wǎng)絡(luò)時(shí)生成的。
編譯部署應(yīng)用程序:
$?docker-compose?build
$?docker-compose?up
調(diào)用應(yīng)用程序的接口:
$?curl?"http://localhost:8000/query"
[{"balance":1000,"name":"A"},{"balance":1000,"name":"B"}]
$?curl?"http://localhost:8000/query?user=A"
[{"balance":1000,"name":"A"}]
$?curl?"http://localhost:8000/query?user=B"
[{"balance":1000,"name":"B"}]
$?curl?-X?POST?"http://localhost:8000/transfer?from=A&to=B&money=500"
{"msg":"ok"}
$?curl?"http://localhost:8000/query"
[{"balance":500,"name":"A"},{"balance":1500,"name":"B"}]
到這里,我們就已經(jīng)完整地實(shí)現(xiàn)了一個(gè)區(qū)塊鏈應(yīng)用了。你也可以繼續(xù)為這個(gè)區(qū)塊鏈應(yīng)用實(shí)現(xiàn)前端頁(yè)面。流程呢,和傳統(tǒng)前后端分離架構(gòu)也沒(méi)什么區(qū)別。
最后
關(guān)于對(duì) fabric 的了解程度,我已經(jīng)盡可能地毫無(wú)保留了,但是對(duì)于真正想要進(jìn)入?yún)^(qū)塊鏈這一領(lǐng)域的讀者來(lái)講,fabric 技術(shù)只是區(qū)塊鏈中的冰山一角,更多的還需要你們自己去探索。
而為什么我沒(méi)有選擇繼續(xù)往區(qū)塊鏈這一領(lǐng)域發(fā)展,理由很簡(jiǎn)單,因?yàn)閭€(gè)人比較喜歡云原生方向。
參考資料
官方文檔: https://hyperledger-fabric.readthedocs.io/zh_CN/release-2.2/
[2]項(xiàng)目地址: https://github.com/togettoyou/fabric-realty
[3]fabric v1.4.12 二進(jìn)制工具: https://github.com/hyperledger/fabric/releases/tag/v1.4.12
推薦閱讀
