1. 萬(wàn)字長(zhǎng)文,教你用go開(kāi)發(fā)區(qū)塊鏈應(yīng)用

        共 12754字,需瀏覽 26分鐘

         ·

        2022-04-11 15:58

        大概 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)部成員的 registerenroll 等,為該組織的區(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í)與 PeerOrderer 通信,創(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 中 ProtobufJSON 格式轉(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è)施, 而 TaobaoJD 則是作為組織成員加入到鏈中,各自提供 2 個(gè) Peer 節(jié)點(diǎn) peer0.xx.compeer1.xx.com 參與工作,以及還各自創(chuàng)建了 2 個(gè)組織用戶(hù) AdminUser1 。然后我們使用 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è)施, 而 TaobaoJD 則是作為組織成員加入到鏈中,各自提供 2 個(gè) Peer 節(jié)點(diǎn) peer0.xx.compeer1.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)中,我們將 configcrypto-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"

        這樣,TaobaoJD 組織間的節(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ù)邏輯。比如 TaobaoJD 規(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ù) AB ,并且都各自有 1000 元余額,我們?cè)?Invoke 方法中為其定義了兩個(gè)功能函數(shù) querytransfer 。 其中 query 函數(shù)可以查詢(xún) AB 或指定用戶(hù)的余額信息, transfer 函數(shù)可以通過(guò)傳入轉(zhuǎn)賬人,被轉(zhuǎn)賬人,金額,三個(gè)參數(shù)來(lái)實(shí)現(xiàn)轉(zhuǎn)賬功能。例如 {"Args":["transfer","A","B","100.0"]} 代表 AB 轉(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í)得到 TaobaoJD 組織成員的背書(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.compeer0.taobao.com 節(jié)點(diǎn)進(jìn)行背書(shū),這是因?yàn)槲覀冊(cè)趯?shí)例化鏈碼的時(shí)候指定了背書(shū)策略為 AND ('TaobaoMSP.member','JDMSP.member') ,代表交易需要同時(shí)得到 TaobaoJD 組織成員的背書(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 /queryPOST /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è)人比較喜歡云原生方向。

        參考資料

        [1]

        官方文檔: 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



        推薦閱讀


        福利

        我為大家整理了一份從入門(mén)到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門(mén)看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù)?ebook?獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。

        瀏覽 38
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 亚洲乱伦色视频 | 人人干天天干 | 成人无码www免费视频男男 | 99人操 | www.黄视频 |