1、本篇背景
前面已經對鏈碼開發作了比較詳細的介紹,并且對官方提供的 fabcar
鏈碼進行了解讀,本篇將介紹如何使用 Node.js SDK
與區塊鏈網絡中的鏈碼進行交互。
本篇內容基本來自官方 Hyperledger Fabric 文檔中的 Writing Your First Application
章節,對文檔進行翻譯,原文網址如下:
http://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html
主要根據谷歌來翻譯的,并稍微做一些修改。由于水平有限,最終的翻譯質量不太好,歡迎大家拍磚。
2、編寫你的第一個應用
在本節中,我們將查看一些示例程序,以了解Fabric應用程序的工作方式。這些應用程序(以及他們使用的智能合約) - 統稱為 fabcar
--提供了Fabric功能的廣泛演示。值得注意的是,我們將展示與證書頒發機構交互并生成注冊證書的過程,之后我們將利用這些身份來查詢和更新賬本。
我們將通過三個主要步驟:
1.建立一個開發環境。我們的應用程序需要一個網絡來進行交互,因此我們將下載一個簡化為注冊/登記,查詢和更新所需的組件:
2.學習我們的應用將使用的示例智能合約的參數。我們的智能合約包含各種功能,使我們能夠以不同的方式與賬本進行交互。 我們將進入并檢查該智能合約,以了解我們的應用程序將使用的功能。
3.開發應用程序以便能夠查詢和更新Fabric記錄。 我們將自己進入應用程序代碼(我們的應用程序已經使用JavaScript編寫),并手動操作變量以運行不同類型的查詢和更新。
完成本教程后,您應該基本了解如何將應用程序與智能合約一起編程,以便與Fabric網絡上的賬本(即節點peer)進行交互。
3、設置您的開發環境
如果您已經完成 建立您的第一個網絡,您應該設置好您的開發環境,并下載好 fabric-samples
以及附帶的工件。要運行本教程,您現在需要做的是移除您擁有的任何現有網絡,您可以通過執行以下操作來完成此操作:
./byfn.sh down
如果您沒有開發環境以及網絡和應用程序的附帶工件,請訪問 先決條件 頁面,并確保您的計算機上安裝了必要的依賴項。
接下來,如果您尚未這樣做,請訪問安裝示例,二進制文件和Docker鏡像 頁面并按照提供的說明進行操作。
克隆 fabric-samples
庫后,返回到本教程,并下載最新穩定版的Fabric鏡像和可用的工具。
此時應該安裝好了一切。進入到 fabric-samples
庫中的 fabcar
子目錄,并查看內部內容:
cd fabric-samples/fabcar && ls
你應該看到以下內容:
enrollAdmin.js invoke.js package.json registerUser.js
hfc-key-store node_modules query.js startFabric.sh
在開始之前,我們還需要做一點準備工作。運行以下命令來殺死當前運行或者活躍的容器:
docker rm -f $(docker ps -aq)
清除所有緩存網絡:
# Press 'y' when prompted by the command
# 命令提示時請輸入'y'
docker network prune
最后,如果您已經完成了本教程,您還需要刪除 fabcar
智能合約的底層鏈碼鏡像。如果您是第一次瀏覽此內容的用戶,那么您的系統上不會有此鏈接代碼鏡像:
docker rmi dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba
3.1 安裝客戶端并啟動網絡
提示:
以下說明需要您在克隆在本地的fabric-samples
庫中的fabcar
子目錄中。在本教程接下來的部分,請確保在此子目錄的根目錄下。
運行以下命令為應用程序安裝Fabric依賴庫。我們關注 fabric-ca-client
,它將允許我們的應用程序與CA服務器進行通信并檢索身份資料,以及使用 fabric-client
,它允許我們加載身份資料并與節點交互并訂閱服務。
npm install
通過使用 startFabric.sh
shell腳本啟動您的網絡。該命令將啟動我們的各種Fabric工具,并啟動用Golang編寫的鏈碼的智能合約容器:
./startFabric.sh
您還可以選擇運行本教程,以針對使用Node.js編寫的鏈碼。如果您想追求這種方式,請改為執行以下命令:
./startFabric.sh node
提示:
請注意,Node.js鏈代碼方案大約需要90秒才能完成或者更久;該腳本并未掛起,反而增加的時間是在構建鏈碼鏡像時安裝了fabric-shim的結果。
好吧,現在你已經有了一個示例網絡和一些代碼,讓我們來看看不同部分如何組合在一起。
4、應用程序如何與網絡進行交互
要更深入地了解我們 fabcar
網絡中的組件(以及它們如何部署)以及應用程序如何與更細粒度級別的組件進行交互,請參閱了解Fabcar網絡。
開發者更感興趣的是應用程序之間作了什么 - 以及查看代碼本身以了解應用程序是如何構建的。目前,更需要了解的事情是,應用程序使用軟件開發工具包(SDK)訪問允許查詢和更新賬本的API。
5、注冊管理員用戶
提示:
以下兩節涉及與證書頒發機構的通信。在運行即將推出的程序時,您可能會發現流式傳輸CA日志很有用。
要流式處理您的CA日志,拆分您的終端或打開一個新的shell并發出以下命令:
docker logs -f ca.example.com
現在返回到您的終端 fabcar
內容。。。
當我們啟動我們的網絡時,管理員用戶 - admin
- 已在我們的認證中心注冊。現在我們需要向CA服務器發送一個注冊呼叫,并為該用戶檢索注冊證書(eCert)。我們不會在這里詳細介紹注冊的詳細信息,但可以說SDK和擴展我們的應用程序需要此證書才能形成管理員的用戶對象。然后我們將使用這個管理對象來注冊并注冊一個新用戶。將管理員注冊呼叫發送到CA服務器:
node enrollAdmin.js
該程序將調用證書簽名請求(CSR),并最終將eCert和密鑰材料輸出到此項目的根目錄中新創建的文件夾- hfc-key-store
中。然后,我們的應用程序將在他們需要為我們的各種用戶創建或加載身份對象時查找此位置。
6、注冊普通用戶user1
使用我們新生成的管理員eCert,我們現在將再次與CA服務器進行通信,以注冊和注冊新用戶。這個user - user1 - 將是我們在查詢和更新賬本時使用的身份。這里需要注意的一點是,為我們的新用戶發布注冊和注冊呼叫的管理員身份(即,此用戶正在扮演注冊員的角色)。發送注冊并為 user1
注冊呼叫:
node registerUser.js
注:終端顯示如下
wenzildeiMac:fabcar wenzil$ node registerUser.js
Store path:/Users/wenzil/Desktop/study/fabric-samples/fabcar/hfc-key-store
Successfully loaded admin from persistence
Successfully registered user1 - secret:PBpPiXbnokEz
Successfully enrolled member user "user1"
User1 was successfully registered and enrolled and is ready to intreact with the fabric network
與管理員注冊類似,此程序調用CSR并將密鑰和eCert輸出到 hfc-key-store
子目錄中。所以現在我們擁有兩個獨立用戶的身份資料 - admin
和 user1
。 與賬本交互的時間。。。
7、查詢賬本
查詢是指如何從賬本中讀取數據。這些數據存儲為一系列鍵值對,您可以查詢單個鍵,多個鍵的值,或者 - 如果賬本是使用JSON等豐富的數據存儲格式編寫的,則可以對其執行復雜的搜索(例如,查找包含特定關鍵字的所有資產)。
這是查詢如何工作的表示形式:
首先,讓我們運行我們的 query.js
程序返回賬本上所有汽車的列表。我們將使用我們的第二個身份 - user1 - 作為此應用程序的簽名實體。我們程序中的以下行將user1指定為簽名者:
fabric_client.getUserContext('user1', true);
回想一下,user1
注冊資料已經被放入我們的 hfc-key-store
子目錄,所以我們只需要告訴我們的應用程序來獲取這個身份。通過定義用戶對象,我們現在可以繼續從賬本中讀取數據。查詢所有汽車的函數 queryAllCars
在應用程序中預加載,所以我們可以簡單地按照原樣運行程序:
node query.js
它應該返回像這樣的東西:
Successfully loaded user1 from persistence
Query has completed, checking results
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輛車。 由Adriana擁有的黑色特斯拉Model S,由Brad擁有的紅色Ford Mustang,由Pari擁有的紫色Fiat Punto等等。賬本是基于關鍵值的,在我們的實施中,關鍵是 CAR0
到 CAR9
。這一點將變得特別重要。
讓我們仔細看看這個程序。 使用編輯器(例如atom或visual studio)并打開 query.js
。
應用程序的初始部分定義了某些變量,例如通道名稱,證書存儲位置和網絡端點。在我們的示例應用程序中,這些變量已被內置,但在真實應用程序中,這些變量必須由應用程序開發者指定。
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://localhost:7051');
channel.addPeer(peer);
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;
這是我們構建查詢的代碼塊:
// queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'],
// queryAllCars chaincode function - requires no arguments , ex: args: [''],
const request = {
//targets : --- letting this default to the peers assigned to the channel
chaincodeId: 'fabcar',
fcn: 'queryAllCars',
args: ['']
};
當應用程序運行時,它調用節點peer上的 fabcar
鏈碼,在其中運行 queryAllCars
函數,并且不傳遞任何參數。
要查看我們智能合約中的可用功能,進入到 fabric-samples
根目錄下的 chaincode/fabcar/go
子目錄,然后在您的編輯器打開 fabcar.go
。
提示:
這些相同的功能在fabcar
鏈碼的Node.js版本中定義。
你會看到我們有以下功能可供調用:initLedger
, queryCar
,queryAllCars
,createCar
和 changeCarOwner
。
讓我們仔細看看 queryAllCars
函數,看看它如何與賬本交互。
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {
startKey := "CAR0"
endKey := "CAR999"
resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
這定義了queryAllCars的范圍。CAR0和CAR999之間的每輛車 - 總共1,000輛汽車,假設每個鑰匙都被正確標記 - 將由查詢返回。
以下是應用程序如何在鏈碼中調用不同功能的表示形式。每個功能必須根據鏈碼shim接口中的可用API進行編碼,這反過來又允許智能合同容器與對等賬本正確對接。
我們可以看到我們的 queryAllCars
函數以及一個叫做 createCar
的函數,它允許我們更新賬本并最終在鏈中添加一個新塊。
但首先,返回 query.js
程序并編輯構造函數請求以查詢CAR4。我們通過將 query.js
中的函數從 queryAllCars
更改為 queryCar
并將CAR4作為特定鍵傳遞來實現此目的。
query.js
程序現在應該如下所示:
const request = {
//targets : --- letting this default to the peers assigned to the channel
chaincodeId: 'fabcar',
fcn: 'queryCar',
args: ['CAR4']
};
保存程序并返回到您的 fabcar
目錄。 現在再次運行該程序:
node query.js
您將看到如下內容:
{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
如果你回頭看看我們之前查詢過每輛車的結果,可以看到 CAR4
是Adriana的黑色特斯拉模型S,這是在這里返回的結果。
使用 queryCar
函數,我們可以查詢任何關鍵字(例如 CAR0
)并獲取與該車相對應的任何品牌,型號,顏色和所有者。
很好。此時,您應該熟悉智能合約中的基本查詢功能以及查詢程序中的少量參數。 時間更新賬本。。。
8、更新賬本
現在我們已經完成了幾個賬本查詢并添加了一些代碼,我們已經準備好更新賬本。有很多潛在的更新我們可以做,但我們先創建一輛新車。
下面我們可以看到這個過程如何工作。提案更新,通過認可,然后返回到應用程序,然后將其發送到訂單并寫入每個節點peer賬戶:
我們對賬本的第一次更新將是創造一輛新車。我們有一個單獨的Javascript程序 - invoke.js - 我們將用它來進行更新。與查詢一樣,使用編輯器打開程序并進入到構建我們的調用的代碼塊:
// createCar chaincode function - requires 5 args, ex: args: ['CAR12', 'Honda', 'Accord', 'Black', 'Tom'],
// changeCarOwner chaincode function - requires 2 args , ex: args: ['CAR10', 'Barry'],
// must send the proposal to endorsing peers
var request = {
//targets: let default to the peer assigned to the client
chaincodeId: 'fabcar',
fcn: '',
args: [''],
chainId: 'mychannel',
txId: tx_id
};
您會看到我們可以調用兩個函數之一 - createCar
或 changeCarOwner
。首先,讓我們創建一個紅色雪佛蘭Volt并將其交給名為Nick的所有者。我們在賬本上使用 CAR9
,因此我們將在此使用 CAR10
作為識別密鑰。編輯代碼如下:
var request = {
//targets: let default to the peer assigned to the client
chaincodeId: 'fabcar',
fcn: 'createCar',
args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'],
chainId: 'mychannel',
txId: tx_id
};
保存并運行程序:
node invoke.js
終端中會有一些關于 ProposalResponse
和Promise的輸出。 然而,我們所關心的只是這個信息:
The transaction has been committed on peer localhost:7053
要查看此事務已寫入,請返回 query.js
并將參數從 CAR4
更改為 CAR10
。
換句話說,把這里:
const request = {
//targets : --- letting this default to the peers assigned to the channel
chaincodeId: 'fabcar',
fcn: 'queryCar',
args: ['CAR4']
};
改為:
const request = {
//targets : --- letting this default to the peers assigned to the channel
chaincodeId: 'fabcar',
fcn: 'queryCar',
args: ['CAR10']
};
再次保存,然后查詢:
node query.js
這應該返回這個:
Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}
恭喜!您已經創建了一輛車!
所以,現在我們已經做到了,讓我們說Nick很慷慨,他想把他的雪佛蘭Volt交給一個名叫Dave的人。
要做到這一點,請返回到 invoke.js
并將函數從 createCar
更改為 changeCarOwner
并輸入如下所示的參數:
var request = {
//targets: let default to the peer assigned to the client
chaincodeId: 'fabcar',
fcn: 'changeCarOwner',
args: ['CAR10', 'Dave'],
chainId: 'mychannel',
txId: tx_id
};
第一個參數 - CAR10
- 表明將改變車主的汽車。第二個參數 - Dave
- 表明為汽車的新主人。
再次保存并執行該程序:
node invoke.js
現在,讓我們再次查詢賬本并確保Dave現在與 CAR10
鍵相關聯:
node query.js
它應該返回這個結果:
Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Dave"}
CAR10
的所有權從Dave變成了Dave。
提示:
在現實世界的應用程序中,鏈碼可能會有一些訪問控制邏輯。例如,只有特定的授權用戶可以創建新車,并且只有車主才可以將車輛轉移給其他人。
9、總結
現在,我們已經完成了一些查詢和一些更新,您應該對應用程序如何與網絡進行交互有一個很好的理解。您已經了解了智能合約,API和SDK在查詢和更新中扮演的角色的基本知識,您應該了解如何使用不同類型的應用程序來執行其他業務任務和操作。
在隨后的文檔中,我們將學習如何實際編寫智能合約,以及如何利用這些更低級別的應用程序功能中的一些功能(特別是與身份和會員服務有關的功能)。
10、額外的資源
Hyperledger Fabric Node SDK repo是很好的資源,里面有更深入的文檔和示例代碼。 您還可以在Hyperledger Rocket Chat上咨詢Fabric社區和組件專家。
由于水平有限,翻譯質量不太好,歡迎大家拍磚。
對應官網連接地址如下:
Hyperledger Fabric - Writing Your First Application
PS:剛入坑的小白,很多不懂,還請各位大佬多賜教,謝謝!