原文地址:www.xuanzhangjiong.top/2019/04/16/…
作者:TopJohn
利用Hyperledger Fabric開發你的第一個區塊鏈應用
本文示例源於
fabric-samples
中的fabcar
github.com/hyperledger…
在這個例子中,我們通過一個簡單的示例程式來了解Fabric應用是如何執行的。在這個例子中使用的應用程式和智慧合約(鏈碼)統稱為FabCar
。這個例子很好地提供了一個開始用於理解Hyperledger Fabric
。在這裡,你將學會如何開發一個應用程式和智慧合約來查詢和更新賬本,如何利用CA
來生成一個應用程式需要的用於和區塊鏈互動的X.509證照。
我們使用應用程式SDk來執行智慧合約中的查詢更新賬本的操作,這些操作在智慧合約中藉助底層介面實現。
我們將通過3個步驟來進行講解:
- 搭建開發環境。我們的應用程式需要和網路互動,因此我們需要一個智慧合約和應用程式使用的基礎網路。
-
學習一個簡單的智慧合約,FabCar。我們使用JavaScript開發智慧合約。我們通過檢視智慧合約來學習應用程式如何使用智慧合約傳送交易,如何使用智慧合約來查詢和更新賬本。
-
使用FabCar開發一個簡單的應用程式。我們的應用程式會使用
FabCar
智慧合約來查詢及更新賬本上的汽車資產。我們將進入應用程式的程式碼中去了解如何建立交易,包括查詢一輛汽車的資訊,查詢一批汽車的資訊以及建立一輛汽車。
設定區塊鏈網路
注意:下面的部分需要進入你克隆到本地的
fabric-samples
倉庫的first-network
子目錄。
如果你已經學習了Building Your First Network
,你應該已經下載了fabric-samples
而且已經執行起了一個網路。在你進行本教程之前,你需要停止這個網路:
./byfn.sh down
複製程式碼
如果你之前執行過這個教程,使用下面的命令關掉所有停止或者執行的容器。注意,這將關掉所有的容器,不論是否和Fabric有關。
docker rm -f $(docker ps -aq)
docker rmi -f $(docker images | grep fabcar | awk '{print $3}')
複製程式碼
如果你沒有這個網路和應用相關的開發環境和構件,請訪問 Prerequisites頁面,確保你的機器安裝了必要的依賴。
接下來,如果你還沒有這樣做的話,請瀏覽 Install Samples, Binaries and Docker Images頁面,跟著上面的操作進行。當你克隆了fabric-samples
倉庫,下載了最新的穩定版Fabric映象和相關工具之後回到教程。
如果你使用的是Mac OS和Mojava,你需要安裝Xcode。
啟動網路
下面的部分需要進入
fabric-samples
倉庫的fabcar
子目錄。
使用startFabric.sh
來啟動你的網路。這個命令將啟動一個區塊鏈網路,這個網路由peer節點、排序節點、證照授權服務等組成。同時也將安裝和初始化javascript版本的FabCar
智慧合約,我們的應用程式將通過它來操作賬本。我們將通過本教程學習更過關於這些元件的內容。
./startFabric.sh javascript
複製程式碼
現在,我們已經執行起來了一個示例網路,還安裝和初始化了FabCar
智慧合約。為了執行我們的應用程式,我們需要安裝一些依賴,同時讓我們看一下它們是如何工作的。
安裝應用程式
注意:下邊的章節需要進入你克隆到本地的
fabric-samples
倉庫的fabcar/javascript
子目錄。 下面的命令來安裝應用程式所需的Fabric有關的依賴。大概將話費1分鐘左右的時間:
npm install
複製程式碼
這個指令用於安裝應用程式所需的依賴,這些依賴被定義在package.json
中。其中最重要的是fabric-network
類;它使得應用程式可以使用身份、錢包和連線到通道的閘道器,以及提交交易和等待通知。本教程也將使用fabric-ca-client
類來註冊使用者以及他們的授權證照,生成一個fabric-network
使用的合法的身份。
一旦npm install
執行成功,執行應用程式所需的一切就準備好了。在這個教程中,你將主要使用fabcar/javascript
目錄下的JavaScript檔案來操作應用程式。讓我們來了解一下里面有哪些檔案:
ls
複製程式碼
你將看到下列檔案:
enrollAdmin.js node_modules package.json registerUser.js
invoke.js package-lock.json query.js wallet
複製程式碼
裡面也有一些其他程式語言的檔案,比如fabcar/typescript
目錄中。當你使用過JavaScript示例之後-其實都是類似的。
如果你在使用Mac OS而且執行的是Mojava你需要[安裝Xcode](https://hyperledger-fabric.readthedocs.io/en/latest/tutorial/installxcode.html)
。
登記管理員使用者
下面的部分涉及執行和CA伺服器通訊的過程。你在執行下面的程式的時候,開啟一個終端執行
docker logs -f ca.example.com
來檢視CA的日誌,會是十分有幫助的。
當我們建立網路的時候,一個叫admin
的使用者已經被授權伺服器(CA)建立為登記員。我們第一步要做的是使用enroll.js
程式為admin
生成私鑰,公鑰和x.509證照。這個程式使用一個證照籤名請求 (CSR)--先在本地生成私鑰和公鑰,然後把公鑰傳送到CA,CA會釋出一個應用程式使用的證照。這三個憑證會儲存在錢包中,以便於我們以管理員的身份使用CA。
接下來我們會註冊和登記一個新的應用程式使用者,我們將使用這個使用者來通過應用程式和區塊鏈進行互動。
讓我們登記一個admin
使用者:
node enrollAdmin.js
複製程式碼
這個命令將CA管理員證照儲存在wallet
目錄。
註冊和登記user1
現在我們在錢包裡放了管理員的證照,我們可以登記一個新使用者--user1--用這個使用者來查詢和更新賬本:
node registerUser.js
複製程式碼
和登記管理員類似,這個程式使用了CSR來登記user1
並把它的證照儲存到admin
所在的錢包中。現在我們有了2個獨立的使用者--admin
和user1
--它們都將用於我們的應用程式。
接下來是賬本互動時間...
查詢賬本
區塊鏈網路中的每個節點都擁有一個賬本的副本,應用程式可以通過執行智慧合約查詢賬本上的最新舒徐來實現查詢賬本操作,將結果返回給應用程式。
這是一個如何查詢的簡單闡述:
應用程式使用查詢從ledger讀取資料。最常見的就是查詢當前賬本中的最新值--世界狀態。世界狀態是一個鍵值對的集合,應用程式可以根據一個鍵或者多個鍵來查詢資料。而且,當鍵值對是以JSON形式存在的時候,世界狀態可以通過配置使用資料庫(例如CouchDB)來支援富查詢。這個特性對於查詢匹配特定的鍵的值是很有幫助的,比如查詢一個人的所有汽車。
首先,讓我們使用query.js
程式來查詢賬本上的所有汽車。這個程式使用我們的第二個身份--user1
--來操作賬本。
node query.js
複製程式碼
輸出結果如下:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result 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"}}]
複製程式碼
讓我們近距離看一下這個程式。使用文字編輯器(如atom或者visual studio)開啟query.js
。
應用程式開始的時候就從fabric-network
模組引入了兩個關鍵的類FileSystemWallet
和Gateway
。這兩個類將用於定位錢包中user1
的身份,並且使用這個身份連線網路:
const { FileSystemWallet, Gateway } = require('fabric-network');
複製程式碼
應用程式使用閘道器連線網路:
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1' });
複製程式碼
這段程式碼建立了一個新的閘道器,然後通過它來讓應用程式連線網路。cpp
描述了閘道器通過wallet
中的user1
來連線網路。開啟 ../../basic-network/connection.json
來檢視cpp
是如何解析一個JSON檔案的:
const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);
複製程式碼
如果你想了解更多關於連線配置檔案的結構以及它是怎麼定義網路的,請查閱 the connection profile topic
一個網路可以被拆分成很多個通道,程式碼中下一個很重要的地方是將應用程式連線到特定的通道mychannel
上:
在這個通道中,我們可以通過fabcar
智慧合約來和賬本進行互動:
const contract = network.getContract('fabcar');
複製程式碼
在fabcar
中有許多不同的交易,我們的應用程式先使用queryAllCars
交易來查詢賬本的世界狀態:
const result = await contract.evaluateTransaction('queryAllCars');
複製程式碼
evaluateTransaction
方法呈現了一種和區塊鏈網路中的智慧合約互動的最簡單的方法。它只是根據配置檔案中的定義連線一個節點,然後向節點傳送請求,在節點內執行該請求。智慧合約查詢了節點賬本上的所有汽車,然後把結果返回給應用程式。這次互動並沒有更新賬本。
FabCar智慧合約
讓我們看一看FabCar
智慧合約裡的交易。進入fabric-samples
下的子目錄chaincode/fabcar/javascript/lib
,然後用你的編輯器開啟fabcar.js
。
看一下我們的智慧合約是如何通過Contract
類來定義的:
class FabCar extends Contract {...
複製程式碼
在這個類結構中,你將看到定義了以下交易: initLedger
,queryCar
,queryAllCars
,createCar
和changeCarOwner
。例如:
async queryCar(ctx, carNumber) {...}
async queryAllCars(ctx) {...}
複製程式碼
讓我們更進一步看一下 queryAllCars ,看一下它是怎麼和賬本互動的。
async queryAllCars(ctx) {
const startKey = 'CAR0';
const endKey = 'CAR999';
const iterator = await ctx.stub.getStateByRange(startKey, endKey);
複製程式碼
這段程式碼定義了 queryAllCars 將要從賬本獲取的汽車的範圍。從 CAR0 到 CAR999 的每一輛車 -- 一共 1000 輛車,假定每個鍵都被合適地錨定了 -- 將會作為查詢結果被返回。 程式碼中剩下的部分,通過迭代將查詢結果打包成 JSON 並返回給應用。
下邊將展示應用程式如何呼叫智慧合約中的不同交易。每一個交易都使用一組 API 比如 getStateByRange 來和賬本進行互動。瞭解更多API請閱讀detail。
你可以看到我們的queryAllCars
交易,還有另一個叫做createCar
。我們稍後將在教程中使用他們來更新賬本,和新增新的區塊。
但是在那之前,返回到query
程式,更改evaluateTransaction
的請求來查詢為CAR4
。query
程式現在如下:
const result = await contract.evaluateTransaction('queryCar', 'CAR4');
複製程式碼
儲存程式,然後返回到fabcar/javascript
目錄。現在,再次執行query
程式:
node query.js
複製程式碼
你應該會看到如下所示:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
複製程式碼
如果你檢視一下之前queryAllCars
的交易結果,你會看到CAR4
是Adriana
的黑色 Tesla model S
,也就是這裡返回的結果,是一樣的。
我們可以使用queryCar
交易來查詢任意汽車,使用它的鍵(比如CAR0
)得到車輛的製造商、型號、顏色和車主等相關資訊。
非常好。現在你應該已經瞭解了智慧合約中基礎的查詢交易,也手動修改了查詢程式中的引數。
是時候進行更新賬本了。
更新賬本
現在我們已經完成一些賬本的查詢操作,新增了一些程式碼,我們已經準備好更新賬本了。有很 的更新操作我們可以做,但是我們從建立一輛新車開始。
從一個應用程式的角度來說,更新一個賬本很簡單。應用程式向區塊鏈網路提交一個交易, 當交易被驗證和提交後,應用程式會收到一個交易成功的提醒。但是在底層,區塊鏈網路中各元件中不同的共識程式協同工作,來保證賬本的每一個更新提案都是合法的,而且有一個大家一致認可的順序。
上圖中,我們可以看到完成這項工作的主要元件。同時,多個節點中每一個節點都擁有一份賬本的副本,並可選的擁有一份智慧合約的副本,網路中也有一個排序服務。排序服務保證網路中交易的一致性;它也將連線到網路中不同的應用程式的交易以定義好的順序生成區塊。
我們對賬本的的第一個更新是建立一輛新車。我們有一個單獨的程式叫做invoke.js
,用來更新賬本。和查詢一樣,使用一個編輯器開啟程式定位到我們構建和提交交易到網路的程式碼段:
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
複製程式碼
看一下應用程式如何呼叫智慧合約的交易createCar
來建立一輛車主為Tom的黑色Honda Accord汽車。我們使用CAR12
作為這裡的鍵,這也說明了我們不必使用連續的鍵。
儲存並執行程式:
node invoke.js
複製程式碼
如果執行成功,你將看到類似輸出:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
2018-12-11T14:11:40.935Z - info: [TransactionEventHandler]: _strategySuccess: strategy success for transaction "9076cd4279a71ecf99665aed0ed3590a25bba040fa6b4dd6d010f42bb26ff5d1"
Transaction has been submitted
複製程式碼
注意inovke
程式使用的是submitTransaction
API和區塊鏈網路互動的,而不是evaluateTransaction
。
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
複製程式碼
submitTransaction
比evaluateTransaction
要複雜的多。不只是和單個節點互動,SDK將把submitTransaction
提案傳送到區塊鏈網路中每一個必要的組織的節點。每一個節點都將根據這個提案執行請求的智慧合約,並生成一個該節點簽名的交易響應並返回給SDK 。SDK將所有經過簽名的交易響應收集到一個交易中,這個交易將會被髮送到排序節點。排序節點蒐集並排序每個應用的交易,並把這些交易放入到一個交易區塊。然後排序節點將這些區塊分發到網路中的節點,每一筆交易都會在節點中進行驗證和提交。最後,SDK會後到提醒,並把控制權返回給應用程式。
submitTransaction
也會包括一個監聽器用於確保交易已經被校驗和提交到賬本里了。應用程式需要利用監聽器或者使用submitTransaction
介面,它內部已經實現了監聽器。如果沒有監聽器,你可能無法確定交易是否被排序校驗以及提交。
應用程式中的這些工作由submitTransaction
完成!應用程式、智慧合約、節點和排序服務一起工作來保證網路中賬本一致性的程式被稱為共識。
為了檢視這個被寫入賬本的交易,返回到query.js
並將引數CAR4
更改為CAR12
。
換句話說就是將:
const result = await contract.evaluateTransaction('queryCar', 'CAR4');
複製程式碼
改為:
const result = await contract.evaluateTransaction('queryCar', 'CAR12');
複製程式碼
再次儲存,然後查詢:
node query.js
複製程式碼
將返回:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
{"colour":"Black","make":"Honda","model":"Accord","owner":"Tom"}
複製程式碼
恭喜。你建立了一輛汽車並驗證了它記錄在賬本上!
現在我們已經完成了,我們假設Tom很大方,想把他的Honda Accord送給一個叫Dave的人。
為了完成這個,返回到invoke.js
然後利用輸入的引數,將智慧合約的交易從createCar
改為changeCarOwner
:
await contract.submitTransaction('changeCarOwner', 'CAR12', 'Dave');
複製程式碼
第一個引數 ---CAR12
--- 表示將要易主的車。第二個引數 ---Dave
--- 表示車的新主人。
再次儲存並執行程式:
node invoke.js
複製程式碼
現在我們來再次查詢賬本,以確定Dave和CAR12
鍵已經關聯起來了:
node query.js
複製程式碼
將返回如下結果:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
{"colour":"Black","make":"Honda","model":"Accord","owner":"Dave"}
複製程式碼
CAR12
的主人已經從Tom變成了Dave。
在實際的應用中,智慧合約有許可權控制邏輯。舉個例子,只有有許可權的使用者可以建立新車,只有車子的擁有者可以轉移車輛所屬權。
總結
現在我們已經完成了賬本的查詢和更新,你也應該比較瞭解如何通過智慧合約和區塊鏈進行互動來查詢賬本和更新賬本了。在教程中已經講解了查詢和更新的智慧合約,API和SDK,想必你對其他商業場景也有了一定的瞭解和認識。
通過FabCar
這個例子,我們可以快速學習如何基於Node SDK
開發應用程式。