參考:http://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html
本文的目的就是基於Hyperledger Fabric network學習第一個application,blockchain上的application最基本的作用就是查詢以及更新賬本。看完本文后,將會對application(使用Node.js的fabric SDK)如何與fabric Ledger進行交互的有一個初步了解。
一.開始一個Test Network
首頁需要確保Hyberledger Fabric所需要的環境已經安裝完成。(安裝可以參考http://www.cnblogs.com/studyzy/p/6973334.html)
克隆一個fabric-samples倉庫,然后進入fabcar子目錄
git clone https://github.com/hyperledger/fabric-samples.git cd fabric-samples/fabcar
fabcar目錄下包括用於運行樣例app的腳本以及應用程序,輸入ls,能看到如下文件或文件夾
chaincode invoke.js network package.json query.js startFabric.sh
我們使用startFabric.sh啟動網絡,接下來的命令會下載與安裝Fabric docker鏡像
./startFabric.sh
上述命令主要
1.啟動了一個peer節點、一個ordering節點、一個CA以及CLI容器
2.創建了一個channel,同時把peer加入到channel中
3.在peer的文件系統上安裝並實例化了智能合約(chaincode),啟動了一個chaincode容器
4.調用了initLedger函數在channel Ledger上生成了10個car
上述操作由一個組織(organization)或者節點(peer)admin實現。startFaric.sh腳本使用CLI執行上述命令,具體參考Hyperledger Fabric Node SDK repo
使用docker ps -a命令查看剛啟動的容器。
下圖簡要的說明了,application如何與fabric network進行交互的。
application如何與network進行交互:application使用API去調用智能合約(chaincode),這些chaincode被托管在network中,同時chaincode的名稱與版本是得到認證的。例如,chaincode容器 - dev-peer0.org1.example.com-fabcar-1.0 - ,其中名稱是fabcar,版本是1.0,peer是dev-peer0.org1.example.com
二.查詢Ledger
查詢就是從Ledger上讀取數據,可以通過單鍵值或者多鍵值查詢。假如Ledger使用類似json的富數據存儲方式,那么其支持復雜查詢(例如模糊查詢)。
在fabcar目錄下,我們可以使用query.js代碼可以用於查詢在Ledger上的car詳情。
接下來運行JavaScript程序query.js,將會返回在Ledger上的car列表
node query.js
返回值為
Query result count = 1 Response is [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}}, {"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}}, {"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}}, {"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}}, {"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}}, {"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}}, {"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}}, {"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}}, {"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}}, {"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]
展示了10輛車,本例中,car1到car9是key值,record是value值。
我們來看看query.js里面有哪些內容,通過vim打開query.js
vim query.js
發現初始化的參數options包括了chaincode ID,channel名稱以及network端點
var options = { wallet_path : path.join(__dirname, './network/creds'), user_id: 'PeerAdmin', channel_id: 'mychannel', chaincode_id: 'fabcar', network_url: 'grpc://localhost:7051', }
以下是我們構建的查詢塊
const request = { chaincodeId: options.chaincode_id, txId: transaction_id, fcn: 'queryAllCars', args: [''] }
chaincode_id用於確定具體的chaincode,接下來調用在chaincode中定義的queryAllCars函數。
在我們執行node query.js時,調用了queryAllCars函數用於查詢Ledger。除了queryAllCars函數之外,還有initLedger, queryCar, queryAllCars, createCar 以及 changeCarOwner函數可以調用。
接下來,我們打開fabcar.go,看看queryAllCars函數是如何實現查詢所有car的。
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { startKey := "CAR0" endKey := "CAR999" resultsIterator, err := APIstub.GetStateByRange(startKey, endKey) }
該函數使用的fabric shim接口GetStateByRange,用於返回startKey與endKey之間的Ledger數據。這些定義分別被定義為CAR0以及CAR999,因此理論上我們可以創建1000輛車,一個queryAllCars函數即可查詢所有車輛信息。
下圖表示一個app是如何調用不同的chaincode的函數。
其中createCar用於更新Ledger,同時最終增加了一個新的block到鏈上。
回到查詢功能上,讓我們回到query.js程序,同時我們將代碼中的queryAllCars改成queryCar,同時對key值進行修改,這里key值暫時使用CAR4,query.js將會包括如下代碼:
const request = { chaincodeId: options.chaincode_id, txId: transaction_id, fcn: 'queryCar', args: ['CAR4'] }
輸入node query.js后將會查出如下結果:
{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
本次查詢只查詢一輛車,調用了queryCar函數,通過key值去查詢。
三.更新Ledger
上面我們調用chaincode的查詢函數對車輛信息進行了查詢,接下來,我們需要嘗試對Ledger進行修改。
首先application先生成一個transaction proposal。就想query的request的一樣,這里也需要生成一個request。程序接下來調用channel.SendTransactionProposal API 把transaction proposal發送到endoring peer。
接着,network(也就是endorsing peer)會返回一個proposal response,application通過這個response對transaction request進行build與sign。然后把這個處理過的request通過channel.sendTransaction API發送至ordering serivce。ordering service將會把transaction打包至到block,接着把block發送到channel中的所有peer做驗證。
最后,application使用eh.setPeerAddr API 連接到peer的事件監聽端口,同時調用eh.registerTxEvent方法並使用具體的transactionID注冊事件。該API使得application能夠追蹤到transaction的具體情況(提交成功還是失敗)。其實就是一個通知機制。
通過執行invoke.js我們創建了一個新的asset(一輛車)。打開invoke.js,我們能夠找到類似query.js中的request。
vim invoke.js
能夠看到request內容
// createCar - requires 5 args, ex: args: ['CAR11', 'Honda', 'Accord', 'Black', 'Tom'], // changeCarOwner - requires 2 args , ex: args: ['CAR10', 'Barry'], // send proposal to endorser var request = { targets: targets, chaincodeId: options.chaincode_id, fcn: '', args: [''], chainId: options.channel_id, txId: tx_id }
接下來,我們將調用createCar 或 changeCarOwner生成一個新的車。參數如下所示
var request = { targets: targets, chaincodeId: options.chaincode_id, fcn: 'createCar', args: ['CAR11’, ‘Audi’, ‘A4’, ‘Black’, ‘Xiaohu Li’], chainId: options.channel_id, txId: tx_id }
輸入node invoke.js能夠看到
peer發出了該事件通知,我們的application通過eh.registerTxEvent API收到了該通知。接下來,我們回到query.js,修改查詢key值為CAR11,然后執行代碼。
node query.js
接下來我們能夠看到如下信息:
說明我們剛創建的車輛信息已經寫入Ledger中了。