高 云 嚴(yán) 悍
(南京理工大學(xué) 南京 210095)
Hyperledger Fabric 是由IBM 主導(dǎo)的面向企業(yè)客戶的區(qū)塊鏈開源項(xiàng)目[1]。Fabric區(qū)塊鏈軟件結(jié)構(gòu)復(fù)雜,在應(yīng)用項(xiàng)目開發(fā)時,使用了分布式系統(tǒng)、Docker 容器、Go 語言、Node.js 語言、Nosql 等多種計(jì)算機(jī)技術(shù)[2]。在軟件設(shè)計(jì)階段既需考慮前端的用戶接入、非區(qū)塊鏈信息處理等問題,又要保證區(qū)塊鏈網(wǎng)絡(luò)邏輯的正確性。同時,F(xiàn)abric 區(qū)塊鏈軟件前后端的連接問題也需要復(fù)雜的編碼來實(shí)現(xiàn)。
基于以上問題,本文對傳統(tǒng)的Fabric 軟件開發(fā)架構(gòu)進(jìn)行了優(yōu)化,設(shè)計(jì)開發(fā)了Fabric 中間件作為連接前后端的介質(zhì)。中間件實(shí)現(xiàn)了一般場景下區(qū)塊鏈軟件所需的多種通用功能,具有可復(fù)用性。在簡化軟件架構(gòu)的同時避免了重復(fù)編程,降低了Fabric區(qū)塊鏈軟件應(yīng)用的開發(fā)成本和難度。
從廣義上講,中間件是在操作系統(tǒng)功能范圍外為應(yīng)用提供服務(wù)的多用途軟件[3]。Fabric系統(tǒng)的中間件位于前端客戶服務(wù)器與底層智能合約(chain?code)[4]之間,它接收前端服務(wù)器發(fā)送的請求,然后將請求進(jìn)行處理后傳遞給鏈代碼進(jìn)行區(qū)塊鏈操作。Fabric 中間件起到了重要的連接作用,中間件的加入,使前端開發(fā)可以更多地注重用戶需求而無需過多考慮復(fù)雜的區(qū)塊鏈問題,后臺區(qū)塊鏈底層開發(fā)者也只需保證鏈代碼的穩(wěn)定運(yùn)行。有效減少了系統(tǒng)各層的交互難度,提高了軟件開發(fā)的靈活性。Fabric 中間件在區(qū)塊鏈系統(tǒng)中的位置如圖1 所示。Fabric 中間件使用Node.js 語言進(jìn)行開發(fā),匹配Fabric 原生的Node SDK,主要調(diào)用“fabirc-cli?ent”和“fabric-ca-client”SDK 包,并進(jìn)一步擴(kuò)展和優(yōu)化了官方SDK 的功能。主要實(shí)現(xiàn)的功能包括:Fabric 區(qū)塊鏈中區(qū)塊交易的查詢、用戶連接以及鏈代碼函數(shù)的調(diào)用。
圖1 Fabric中間件在區(qū)塊鏈系統(tǒng)中的位置示意圖
包含中間件的區(qū)塊鏈軟件開發(fā)架構(gòu)分為四個層次,由上至下分別為客戶端層、應(yīng)用服務(wù)器層、中間件層、鏈代碼層。
客戶端層與應(yīng)用服務(wù)器層的交互原理與非區(qū)塊鏈的應(yīng)用程序相同,并不涉及Fabric 的相關(guān)技術(shù)原理,需根據(jù)實(shí)際的生產(chǎn)應(yīng)用場景運(yùn)用相應(yīng)的計(jì)算機(jī)技術(shù)進(jìn)行開發(fā)[5]。為了適配中間件和Fabric 網(wǎng)絡(luò),建議服務(wù)器層編程使用Node.js 語言,對于區(qū)塊鏈的Web 應(yīng)用軟件開發(fā),使用基于Node 的Express框架進(jìn)行編程[6]。應(yīng)用服務(wù)器層將用戶指令數(shù)據(jù)解析為相應(yīng)中間件函數(shù)的參數(shù),從而調(diào)用中間件函數(shù)連接區(qū)塊鏈系統(tǒng)。中間件層(Node.js 語言編程)通過其中的query 和invoke 函數(shù)進(jìn)行基本鏈碼調(diào)用連接到鏈代碼。鏈代碼層依據(jù)Fabric 區(qū)塊鏈交易原理編寫各種鏈碼函數(shù)(Go 語言編程),處理區(qū)塊鏈上的區(qū)塊、交易以及鍵值狀態(tài)事務(wù)[7]。
Fabric 區(qū)塊鏈網(wǎng)絡(luò)的數(shù)據(jù)結(jié)構(gòu)在總體上看是由一個個區(qū)塊串連而成的鏈條[8]。信息存儲的結(jié)構(gòu)分為三層:每一個區(qū)塊中存儲了多筆交易,每一筆交易又保存了一次或多次鍵值狀態(tài)(KVS)的變更操作??傮w存儲結(jié)構(gòu)如圖2所示。
圖2 Fabric區(qū)塊鏈信息存儲結(jié)構(gòu)示意圖
區(qū)塊信息的查詢借助了channel包中區(qū)塊查詢類的方法。相關(guān)中間件函數(shù)實(shí)現(xiàn)的功能包括創(chuàng)世區(qū)塊詳細(xì)信息查詢和當(dāng)前網(wǎng)絡(luò)所有區(qū)塊的遍歷。
1)創(chuàng)世區(qū)塊配置信息查詢
Channel.query Block 方法提供了按指定編號查詢區(qū)塊信息的功能。該函數(shù)的語法為<async>que?ry Block(block Number,target,use Admin,skip De?code)。針對創(chuàng)世區(qū)塊的查找,將“blockNumber”置為0,其他可選參數(shù)使用默認(rèn)值,即query Block(0)。函數(shù)執(zhí)行后返回一個Block對象,存儲了創(chuàng)世區(qū)塊的所有信息。查詢創(chuàng)世區(qū)塊配置主要是查詢order 的配置信息,order 配置可以反映區(qū)塊鏈網(wǎng)絡(luò)主要配置信息。通過對Block 對象的結(jié)構(gòu)分析,得出order配置信息的存儲位置:
ordervalues=block.data.data [0]. payload.data.config.channel_group.groups.Orderer.values。
由ordervalues.max_message_count 可以查看每個區(qū)塊的規(guī)定存儲的最大交易數(shù)。
2)區(qū)塊的遍歷
對Fabric 網(wǎng)絡(luò)中當(dāng)前所有的區(qū)塊進(jìn)行遍歷,首先需要確認(rèn)網(wǎng)絡(luò)中區(qū)塊的數(shù)量。channel.query Info方法可以實(shí)現(xiàn)區(qū)塊鏈網(wǎng)絡(luò)整體狀態(tài)信息的查看。該 函 數(shù) 語 法 為<async>query Info(target,useAd?min)。使target 等于當(dāng)前peer,use Admin 等于true,執(zhí)行該函數(shù)。返回值是一個Blockchain Info 對象,該對象存放了當(dāng)前區(qū)塊的一些信息,具有三個屬性,包括height(當(dāng)前區(qū)塊高度)、current Block Hash(當(dāng)前區(qū)塊哈希值)、previous Block Hash(前驅(qū)區(qū)塊哈希值)。經(jīng)驗(yàn)得知,height 對象下的low 屬性表示當(dāng)前區(qū)塊的序號,由于當(dāng)前區(qū)塊就是最新的區(qū)塊,因此height.low也可表示網(wǎng)絡(luò)中區(qū)塊的數(shù)量。
獲取到區(qū)塊數(shù)量后,用for 語句循環(huán)調(diào)用chan?nel.query Block 函數(shù)即可實(shí)現(xiàn)區(qū)塊信息的遍歷。根據(jù)實(shí)際需要,總結(jié)出每個Block 對象需要顯示輸出的重要區(qū)塊數(shù)據(jù),包括:區(qū)塊編號、前驅(qū)區(qū)塊哈希值、該區(qū)塊交易數(shù)量(可以統(tǒng)計(jì)出整個區(qū)塊鏈網(wǎng)絡(luò)的交易數(shù)量)、交易驗(yàn)證碼(數(shù)組類型,長度為交易數(shù)量,其中的元素為0則表示交易有效,不為0表示交易無效,由此可統(tǒng)計(jì)全網(wǎng)的有效交易數(shù)量)。
Fabric 區(qū)塊鏈中幾乎所有針對交易的查詢都需要調(diào)用channel.query Transaction 方法,該方法的功能為返回指定交易id 對應(yīng)的交易主體。該函數(shù)的返回值為一個Processed Transaction 對象,包含以下兩個屬性:validation Code(number類型)用編號表示交易的有效性;transaction Envelope(對象類型)存儲了該交易的所有信息。
基于query Transaction方法,設(shè)計(jì)編寫出通過交易id 查詢交易信息的中間件函數(shù)showtx(tx_id),參數(shù)”tx_id”為待查詢的交易id,功能是在Linux 終端顯示出交易的詳細(xì)信息。該函數(shù)為中間件交易查詢功能的基本函數(shù)。
query Transaction 函數(shù)返回的Processed Transac?tion對象中,交易信息主要存放在其子對象transac?tion Envelope 內(nèi)。該對象的payload 屬性(payload 也是對象結(jié)構(gòu))存儲了交易的頭部數(shù)據(jù)(payload.head?er,類型為Header 對象)和交易體數(shù)據(jù)(payload.da?ta,類型為Transaction 對象)。Transaction 對象保存了該交易的主體部分,讀寫集是其中最主要的部分。讀寫集保存了該交易完成后鍵值的更新情況。其中讀集包含鍵名和提交版本號信息,寫集包含了鍵名與鍵值。因此,對區(qū)塊鏈資產(chǎn)鍵值信息修改后,其值保存在該交易的寫集中。代碼清單1位獲取讀寫集的核心Node.js代碼。
代碼清單1獲取讀寫集如下。
var ns_rwset=
processed Transaction.transaction Envelope.
payload.data.actions[0].payload.action.
proposal_response_payload.extension.
results.ns_rwset;
ns_rwset.forEach((elem)=>{
var rwset=elem.rwset;
var rset=rwset.reads;
基于上述的基礎(chǔ)交易查詢功能以及交易實(shí)體內(nèi)部存儲結(jié)構(gòu)的分析,可以在中間件中對交易的查詢功能進(jìn)行擴(kuò)展。一方面,以交易版本為輸入?yún)?shù)查詢當(dāng)前交易版本所屬的交易;另一方面,通過區(qū)塊遍歷與用戶實(shí)體證書,查詢當(dāng)前用戶創(chuàng)建的所有交易。
1)根據(jù)交易版本查詢交易信息
實(shí)現(xiàn)此功能的中間件函數(shù)showtx ByVer設(shè)計(jì)為兩個形參,分別表示交易版本的兩個屬性。第1 個參數(shù)為區(qū)塊編號(設(shè)為blocknum);第2個參數(shù)是為交易編號(設(shè)為txnum),交易編號為0,代表區(qū)塊中第一個交易。由于配置交易的交易id為空值,該函數(shù)只能查詢背書交易。函數(shù)實(shí)現(xiàn)原理:首先進(jìn)行參數(shù)合法性檢驗(yàn),確保blocknum 為正值且在全網(wǎng)區(qū)塊的范圍之內(nèi),在此前提下調(diào)用queryBlock 函數(shù)定位到指定區(qū)塊,再檢驗(yàn)交易編號的合法性,保證待查詢的交易編號不會溢出;第二步,根據(jù)Block對象的結(jié)構(gòu),獲取交易編號為txnum 的交易的id;最后調(diào)用上述中間件函數(shù)showtx(),成功顯示交易信息。
2)查詢當(dāng)前用戶創(chuàng)建的所有交易。
設(shè)計(jì)中間件函數(shù)mytx()實(shí)現(xiàn)當(dāng)前用戶所有交易的查詢。函數(shù)無參,功能為顯示當(dāng)前連接區(qū)塊鏈網(wǎng)絡(luò)的用戶創(chuàng)建的交易信息。函數(shù)實(shí)現(xiàn)原理:根據(jù)User 對象的結(jié)構(gòu)獲取到用戶證書ucert(存儲位置:user。_identity。_certificate);然后依次遍歷區(qū)塊鏈網(wǎng)絡(luò)中的每個區(qū)塊,對于每個區(qū)塊,再遍歷所有的交易,根據(jù)Block 對象結(jié)構(gòu)獲取到交易中標(biāo)記的用戶證書信息bucert;如果ucert等與bucert,表明該交易的創(chuàng)建者是當(dāng)前用戶,則取得該交易的id 后,調(diào)用中間件函數(shù)showtx(),顯示出該交易的信息。
該部分實(shí)現(xiàn)了區(qū)塊鏈網(wǎng)絡(luò)啟動過程中,用戶同時加入網(wǎng)絡(luò)的功能。方法設(shè)計(jì)的準(zhǔn)備階段,根據(jù)Fabic 網(wǎng)絡(luò)架構(gòu)分析,總結(jié)出待配置的網(wǎng)絡(luò)及用戶參數(shù)。參數(shù)名稱及其含義描述如表1所示。
用戶必須在Fabric-CA 完成注冊與登記,在key-value store 中保存了自身的用戶語境和密鑰后,才允許加入網(wǎng)絡(luò)。中間件用戶連接與網(wǎng)絡(luò)啟動功能實(shí)現(xiàn)流程分為三個階段,分別為用戶狀態(tài)加密庫的配置階段、自定義參數(shù)賦值階段、用戶網(wǎng)絡(luò)連接階段。
1)用戶狀態(tài)加密庫的配置階段
通過用戶語境證書創(chuàng)建并配置狀態(tài)庫(State S?tore)、加密鍵庫(Crypto Key Store)以及用戶加密套件(Crypto Suite)[9]。State Store用于持久保存用戶狀態(tài)信息,用戶連接到網(wǎng)絡(luò)后的證書密鑰會存儲到State Store中,從而避免了每次創(chuàng)建交易都重復(fù)傳遞狀態(tài)信息;Crypto Key Store 在系統(tǒng)使用非默認(rèn)key-value-store 時創(chuàng)建新的用戶密鑰庫;Crypto Su?ite 包含了用于執(zhí)行數(shù)字簽名、加密、解密、安全散列等操作的一套密碼學(xué)算法[8]。具體步驟:根據(jù)應(yīng)用程序用戶密鑰庫的路徑配置hks;使用hks 作為參數(shù)調(diào)用client.new Default Key Value Store 函數(shù)創(chuàng)建KeyValue Store 的實(shí)例,返回值(賦值給變量kvs)用于配置State Store;使用hks 作為參數(shù)調(diào)用client.new Crypto Key Store 函 數(shù) 創(chuàng) 建Crypto Key Store 的 實(shí)例;使用kvs 作為參數(shù)調(diào)用client.set State Store,完成State Store 的創(chuàng)建和配置;調(diào)用client.new Crypto Suite函數(shù),創(chuàng)建一個用戶加密套件的實(shí)例,返回值(賦值給變量crysu)用于配置Crypto Key Store;調(diào)用crysu.set Crypto Key Store 函 數(shù),完 成Crypto Key Store 的 配置;調(diào)用client.set Crypto Suite函數(shù),完成用戶加密套件的配置。該階段各函數(shù)創(chuàng)建配置用戶庫的原理如圖3所示。
表1 網(wǎng)絡(luò)及用戶參數(shù)配置信息
圖3 各函數(shù)創(chuàng)建配置用戶庫原理圖
2)自定義參數(shù)賦值階段
需要配置的參數(shù)中部分需要根據(jù)用戶實(shí)際需要進(jìn)行自定義配置。該類型的參數(shù)主要用于設(shè)置組件的名稱及地值,包括:userid,指定連接網(wǎng)絡(luò)的用戶名,如上所述,用戶應(yīng)已在CA 注冊登記,并且密鑰庫中已產(chǎn)生證書和密鑰;chaincode_id,指定安裝到區(qū)塊鏈網(wǎng)絡(luò)的鏈碼名稱;channel_id,指定加入的通道名稱;ca_id,指定Fabric CA 服務(wù)器的名稱;ca_url,指定CA 服務(wù)器所在地址,支持http 協(xié)議,用于創(chuàng)建Fabric CA 實(shí)例;peer_url,指定與用戶直接連接的peer 節(jié)點(diǎn)地址,使用grpc 協(xié)議,可以是一個地址,也可以是一個地址數(shù)組;order_url,指定用戶連接的order節(jié)點(diǎn)的地址,使用grpc協(xié)議。
3)用戶網(wǎng)絡(luò)連接階段
用戶與區(qū)塊鏈網(wǎng)絡(luò)的連接,實(shí)際上是用戶依次與peer 和order 的連接過程。首先進(jìn)行peer 節(jié)點(diǎn)的連接,對于單個peer,調(diào)用client.new Peer 方法通過peer_url 獲取到peer 對象實(shí)體,然后調(diào)用channel.add Peer方法,將完成連接的peer加入通道,對于連接多個peer 的用戶,使用for 循環(huán)對每個peer 值進(jìn)行地址獲取和通道加入。order 節(jié)點(diǎn)的連接同理,調(diào)用client.new Order 方法從order_url 湖區(qū)到order對象,并調(diào)用channel.add Order 方法將order 加入通道。
根據(jù)以上三個階段的實(shí)現(xiàn)原理,開發(fā)中間件的用戶連接函數(shù),已登記的用戶可以借助中間件來進(jìn)行Fabric網(wǎng)絡(luò)的連接。
鏈碼函數(shù)分為三類,即初始化類(init)、查詢類(query)以及交易類(invoke)[10]。初始化類函數(shù)在Fabric 區(qū)塊鏈網(wǎng)絡(luò)啟動時自動執(zhí)行,因此中間件主要調(diào)用鏈碼的查詢與交易函數(shù)。實(shí)現(xiàn)query類鏈碼函數(shù)基本調(diào)用功能的中間件函數(shù)命名為query;實(shí)現(xiàn)invoke 類鏈碼函數(shù)基本調(diào)用功能的中間件函數(shù)命名為invoke。
query 函數(shù)所需的形參數(shù)量不固定,形參分為兩部分:第一部分為一個字符串類型的參數(shù),表示所需調(diào)用的鏈碼函數(shù)名;第二部分為若干個字符串類型參數(shù),表示被調(diào)用的鏈碼函數(shù)的形參,實(shí)際使用時該部分的參數(shù)數(shù)量需要與鏈碼函數(shù)參數(shù)的數(shù)量相等。函數(shù)語法為query(fcn,...args)函數(shù)返回其調(diào)用的查詢類鏈碼函數(shù)的相應(yīng)查詢結(jié)果。
中間件實(shí)現(xiàn)該功能所使用的核心Node SDK 函數(shù)是channel 包的queryBy Chaincode 函數(shù)。該函數(shù)提供了Node 程序與鏈代碼查詢類函數(shù)交互的接口[11]。返回值類型是一個字節(jié)數(shù)組的數(shù)組,查詢結(jié)果保存在第一個字節(jié)數(shù)組成員中,因此需要先取出第一個數(shù)組成員(該成員是一個字節(jié)數(shù)組),然后對其進(jìn)行類型轉(zhuǎn)換操作才可得到相應(yīng)結(jié)果。
中間件函數(shù)query 的實(shí)現(xiàn)流程:驗(yàn)證參數(shù)的合法性,確保fcn 不為空;指定peer 對象作為Chain?code Query Request對象targets屬性的值;調(diào)用client.new Transaction ID()函數(shù)獲取一個交易id(Transac?tion ID對象,并非字符串類型),作為transient Map屬性的值;定義一個Chaincode Query Request對象類型的靜態(tài)變量request,將上述屬性傳入變量中;以re?quest為參數(shù)調(diào)用queryBy Chaincode函數(shù),將返回值賦值給query_res 變量;將query_res[0]轉(zhuǎn)換為字符串類型并返回。
調(diào)用查詢類鏈碼函數(shù)在原理上包含中間件與peer 節(jié)點(diǎn)兩次通信。第一次通信由中間件提交查詢類調(diào)用請求到peer節(jié)點(diǎn),peer節(jié)點(diǎn)根據(jù)對應(yīng)鏈碼函數(shù)進(jìn)行背書與查詢;第二次通信由peer節(jié)點(diǎn)將背書與查詢結(jié)果返回給中間件。通信結(jié)構(gòu)如圖4 所示。
圖4 調(diào)用查詢類鏈碼函數(shù)的組件通信結(jié)構(gòu)圖
中間件函數(shù)invoke 語法為invoke(fcn,...args),形參“fcn”表示需要調(diào)用的交易類鏈碼函數(shù)名,“..args”表示若干個與鏈碼函數(shù)參數(shù)相匹配的參數(shù)值。函數(shù)功能即調(diào)用相應(yīng)鏈碼函數(shù)實(shí)現(xiàn)區(qū)塊鏈鍵值數(shù)據(jù)的修改并上鏈。
invoke 調(diào)用交易類鏈碼函數(shù)進(jìn)行交易時,必須符合Fabric 區(qū)塊鏈網(wǎng)絡(luò)交易規(guī)范[12]。除了中間件與peer 節(jié)點(diǎn)的通信外,還需要實(shí)現(xiàn)中間件與order節(jié)點(diǎn)的通信。實(shí)現(xiàn)流程可以總結(jié)為三個階段,包括交易和網(wǎng)絡(luò)合法性檢驗(yàn)階段、交易背書階段、排序服務(wù)階段。
1)交易和網(wǎng)絡(luò)合法性檢驗(yàn)階段
除了檢驗(yàn)實(shí)參合法性之外,由于交易需要同時連接peer 和order 節(jié)點(diǎn),所以在驗(yàn)證階段需要order對象已經(jīng)加入網(wǎng)絡(luò)。
2)交易背書階段
根據(jù)Fabric 交易原理,在此階段,用戶通過客戶端操作中間件提交創(chuàng)建交易,然后將提案發(fā)送給背書peer 節(jié)點(diǎn)[13]。背書peer 會對該提案進(jìn)行簽名等驗(yàn)證,并進(jìn)行模擬交易和背書(根據(jù)鏈碼的背書策略響應(yīng)鏈碼的背書請求)。最后peer背書后將結(jié)果返回到中間件。
發(fā)送提案到peer,peer 進(jìn)行驗(yàn)證背書然后將結(jié)果返回的一系列操作,其核心方法為<async>send?Transaction Proposal(request,timeout)。形參“time?out”用來指定交易超時的等待時限,默認(rèn)使用peer節(jié)點(diǎn)或世界狀態(tài)設(shè)置的超時時限?!皉equest”類型是一個Chaincode Invoke Request 對象,該對象定義了交易操作所需的各類參數(shù),相當(dāng)于Fabric 交易原理中由客戶端創(chuàng)建并發(fā)送的提案對象。如果背書完成,peer 會把背書結(jié)果的消息返回給中間件,該消息就是send Transaction Proposal 的返回值。返回值是一個Proposal Response Object類型的對象,其中包含所有參與背書節(jié)點(diǎn)的背書結(jié)果以及交易最初的提案。該對象的屬性結(jié)構(gòu)如表2所示。
表2 roposal Response Object對象屬性結(jié)構(gòu)
通過實(shí)驗(yàn)發(fā)現(xiàn),Proposal Response Object 對象還額外包含一個名為index:2 的屬性,該屬性的類型為Header對象,保存了交易的頭文件信息。
獲取到peer 的消息后,還需對消息進(jìn)行驗(yàn)證,從而檢查背書是否成功。主要驗(yàn)證內(nèi)容為驗(yàn)證Proposal Response Object 對象的index:0 屬性是否存在,且其中表示狀態(tài)的子屬性是否標(biāo)記為成功(sta?tus==200 表示背書成功)。index:0 是一個數(shù)組,每個數(shù)組成員都代表一個背書peer的背書消息,只有所有peer的背書消息屬性都存在且被標(biāo)記為成功,背書才算成功。
invoke 函數(shù)該階段的編碼實(shí)現(xiàn)流程為:指定Peer 對象實(shí)體作為Chaincode Invoke Request 對象targets 屬性的值;調(diào)用client.new Transaction ID()函數(shù)獲取一個交易id對象,作為tx_id屬性的值;定義一個Chaincode Invoke Request 對象類型的變量re?quest,將上述屬性傳入變量中;以request 為參數(shù)調(diào)用send Transaction Proposal函數(shù),進(jìn)行中間件與背書peer 節(jié)點(diǎn)的通信,將返回值賦值給results 變量;驗(yàn)證背書交易是否成功,循環(huán)遍歷所有peer 背書消息,確保所有背書消息都為成功的交易,核心實(shí)現(xiàn)原理如代碼清單2所示(Node.js語言)。
代碼清單2 驗(yàn)證背書交易是否成功
var proposal Responses=results[0];
let endsuccess=true;
for(let ind in targets){
if(?。╬roposal Responses&&
proposal Responses[ind]。response&&
proposal Responses[ind]。response.status==200)){
end success=false;
break;
} }
if(end success){console.log(“背書成功”)
}else{ console.log(“背書失敗”)
3)排序服務(wù)階段
在Fabric 交易原理的排序階段,客戶端將由背書結(jié)果與起始提案組成的交易提案發(fā)送給order節(jié)點(diǎn)[14]。order 對多個交易進(jìn)行排序服務(wù)打包成區(qū)塊,將結(jié)果區(qū)塊發(fā)布給committing peer 節(jié)點(diǎn),這些peer按背書策略進(jìn)行驗(yàn)證。驗(yàn)證成功后,該交易在區(qū)塊上被標(biāo)記為有效(valid)[15]。當(dāng)一個區(qū)塊中的所有交易都完成驗(yàn)證之后,無論交易是否有效,區(qū)塊被廣播給全網(wǎng)所有peer節(jié)點(diǎn),由peer更新其托管的分類帳信息。
中間件開發(fā)過程中,實(shí)現(xiàn)上述流程的核心Node SDK 函 數(shù)<async>send Transaction(request,timeout)。函數(shù)形參“request”類型是一個Transac?tion Request 對象,該對象即是發(fā)送給order 的交易提案實(shí)體。函數(shù)返回一個包含http 響應(yīng)代碼的Promise,用于告知中間件排序服務(wù)是否成功,當(dāng)該響應(yīng)代碼的值為200 時,表示排序服務(wù)成功,交易完成且成功上鏈(但不能判定交易的有效性)。
invoke 函數(shù)排序服務(wù)階段的編碼實(shí)現(xiàn)流程:聲明對象類型變量request 作為send Transaction 函數(shù)的實(shí)參;獲取背書階段返回值的index:0屬性,加入request 作為proposal Responses 屬性;獲取index:1,加入request作為proposal屬性;以request為實(shí)參調(diào)用send Transaction 函數(shù),完成排序服務(wù);驗(yàn)證返回值的status是否為200,顯示交易結(jié)果。
圖5 調(diào)用交易類鏈碼函數(shù)的組件通信結(jié)構(gòu)圖
調(diào)用交易類鏈碼函數(shù)原理上依次包括了中間件與peer 的兩次通信,以及中間件與order 節(jié)點(diǎn)一次通信。通信結(jié)構(gòu)如圖5所示。
針對Fabric 區(qū)塊鏈軟件應(yīng)用開發(fā)架構(gòu)的復(fù)雜性問題,本文設(shè)計(jì)研究了Fabric 中間件結(jié)構(gòu)。中間件中各個模塊的函數(shù)分擔(dān)了傳統(tǒng)服務(wù)器的部分職能。其中的區(qū)塊查詢功能模塊實(shí)現(xiàn)了初始區(qū)塊信息獲取、網(wǎng)絡(luò)中所有區(qū)塊遍歷查詢。交易查詢模塊實(shí)現(xiàn)了按多種屬性查找指定交易的功能。用戶連接模塊允許用戶通過簡單的方式與區(qū)塊鏈網(wǎng)絡(luò)進(jìn)行連接,從而執(zhí)行各種區(qū)塊鏈操作。鏈碼函數(shù)調(diào)用模塊實(shí)現(xiàn)了中間件與底層智能合約的連接,通過調(diào)用查詢類與交易類鏈碼函數(shù),完成區(qū)塊鏈中鍵值狀態(tài)的獲取與更新。
中間件的引入使區(qū)塊鏈軟件應(yīng)用各層職能更加明確,前后端開發(fā)任務(wù)更加具體,有效降低了區(qū)塊鏈軟件的開發(fā)難度。同時,中間件具有高度的可復(fù)用性,一般生產(chǎn)場景下的區(qū)塊鏈應(yīng)用均可引入該結(jié)構(gòu),在開發(fā)中避免了重復(fù)編程,降低了Fabric 區(qū)塊鏈應(yīng)用的研發(fā)成本。