官方原文地址 Writing Your First Application
若是對fabric網絡的基本運行機制不熟悉的話,請看這裏。javascript
注意:本教程是對fabric應用以及如何使用智能合約的簡單介紹,對fabric應用及智能合約的詳細介紹請看應用開發部分和商業票據教程。html
本教程將介紹一些示例程序以助於理解fabric應用是如何工做的。這些應用和所使用的智能合約被稱爲FabCar。它們是理解Hyperledger Fabric blockchain的很好的起點。你將會學習如何編寫一個應用和智能合約來查詢或更新帳本,以及如何使用CA生成區塊鏈應用程序交互所須要的X.509證書。java
咱們會使用SDK(詳細介紹在這裏)來調用智能合約,該合約使用智能合約SDK查詢和更新帳本(詳細介紹在這裏)。node
1. 設置開發環境:應用程序須要一個網絡來進行交互,所以咱們將獲得一個智能合約和應用程序所須要的基本網絡。c++
2. 學習智能合約示例——FabCar:此合約是用JavaScript編寫的。咱們會查看該合約,理解其中的交易,以及它是如何被應用程序用來查看和更新帳本的。git
3. 使用FabCar開發一個示例程序:該程序會使用FabCar智能合約來查詢和更新帳本中的汽車資產(car assets)。咱們將深刻了解應用程序代碼及其建立的交易,包括查詢汽車、查詢一系列汽車以及建立一輛新車。github
3、示例下載學習docker
3.一、代碼下載typescript
cd $GOPATH/src/github.com/hyperledger/ git clone https://github.com/hyperledger/fabric-samples.git cd fabric-samples/fabcar
此代碼放到與fabric並行目錄下,沒特殊要求shell
3.二、在fabcar下會有以下文件
javascript javascript-low-level startFabric.sh typescript
3.三、關閉曾經建立過的網絡
注意:此部分須要你進入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 進行配置。
3.四、啓動網絡
注意:此部分須要你進入fabcar子目錄
在fabcar目錄下運行./startFabric.sh javascript
實際測試增長了javascript會啓動失敗,
如遇到:
ERROR: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
到上面這個問題,能夠參考這裏解決
再次運行,獲得下面的結果:dsafdsfsfsdfsdfdsfdsfsd
這代表已經成功啓動了示例網絡,而且fabcar智能合約也被成功的安裝和實例化了。接下來進行應用程序的安裝。
3.五、安裝應用程序
注意:此部分須要你進入fabcar/javascript子目錄
運行下面的命令安裝應用程序所需的fabric依賴(在此以前要保證已經安裝了npm):
npm install
3.六、註冊admin用戶
注意:如下兩個部分涉及與證書頒發機構的通訊。當運行程序時,經過打開新的終端shell並運行docker logs -f ca.example.com,您可能會發現流式傳輸CA日誌很是有用。
當咱們建立網絡時,一個名爲admin的管理用戶被建立爲證書頒發機構(CA)的註冊器。咱們的第一步是使用enroll.js程序爲管理員生成私鑰、公鑰和X.509證書。此過程使用證書籤名請求(CSR)(私鑰和公鑰首先在本地生成,而後將公鑰發送到CA,CA返回編碼的證書供應用程序使用。而後,這三個憑證存儲在錢包中,容許咱們充當CA的管理員。)
接下來註冊admin用戶(此命令將會把CA管理員的憑證存儲在wallet目錄中):
node enrollAdmin.js
3.七、註冊user用戶
上一步已經註冊了管理員用戶,錢包中有了管理員的憑證,如今能夠註冊一個新用戶(user1)用於查詢和更新分類帳:
node registerUser.js
如今咱們就有了兩個獨立用戶admin和user1的憑證了,後面的應用程序會用到這些憑證。
3.八、查詢帳本
區塊鏈網絡中的每個節點都有一個帳本的副本,應用程序能夠經過調用智能合約查詢帳本,該智能合約查詢帳本的最新值(世界狀態)並將其返回給應用程序。世界狀態是一組鍵值對,應用程序能夠查詢單個鍵或多個鍵。
如今首先運行query.js程序獲取帳本上全部車輛的信息,此程序使用第二個身份(user1)來獲取帳本:
node query.js
查看query.js
/* * SPDX-License-Identifier: Apache-2.0 */ 'use strict'; const { FileSystemWallet, Gateway } = require('fabric-network'); const fs = require('fs'); const path = require('path'); const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json'); const ccpJSON = fs.readFileSync(ccpPath, 'utf8'); const ccp = JSON.parse(ccpJSON); async function main() { try { // Create a new file system based wallet for managing identities. const walletPath = path.join(process.cwd(), 'wallet'); const wallet = new FileSystemWallet(walletPath); console.log(`Wallet path: ${walletPath}`); // Check to see if we've already enrolled the user. const userExists = await wallet.exists('user1'); if (!userExists) { console.log('An identity for the user "user1" does not exist in the wallet'); console.log('Run the registerUser.js application before retrying'); return; } // Create a new gateway for connecting to our peer node. const gateway = new Gateway(); await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } }); // Get the network (channel) our contract is deployed to. const network = await gateway.getNetwork('mychannel'); // Get the contract from the network. const contract = network.getContract('fabcar'); // Evaluate the specified transaction. // queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4') // queryAllCars transaction - requires no arguments, ex: ('queryAllCars') const result = await contract.evaluateTransaction('queryAllCars'); console.log(`Transaction has been evaluated, result is: ${result.toString()}`); } catch (error) { console.error(`Failed to evaluate transaction: ${error}`); process.exit(1); } } main();
程序開始部分首先引用了fabric-network模塊的FileSystemWallet
和Gateway兩個關鍵類。這兩個類用來定位user1的錢包身份,以及鏈接網絡:
const { FileSystemWallet, Gateway } = require('fabric-network');
應用程序使用一個網關來鏈接網絡:
const gateway = new Gateway(); await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
接下來嘗試修改query.js,使其只查詢CAR4:
//const result = await contract.evaluateTransaction('queryAllCars'); const result = await contract.evaluateTransaction('queryCar', 'CAR4');
具體鏈碼能夠查看:fabric-samples/chaincode查找,核心代碼
/* * SPDX-License-Identifier: Apache-2.0 */ 'use strict'; const { Contract } = require('fabric-contract-api'); class FabCar extends Contract { async initLedger(ctx) { console.info('============= START : Initialize Ledger ==========='); const cars = [ { color: 'blue', make: 'Toyota', model: 'Prius', owner: 'Tomoko', }, { color: 'red', make: 'Ford', model: 'Mustang', owner: 'Brad', }, { color: 'green', make: 'Hyundai', model: 'Tucson', owner: 'Jin Soo', }, { color: 'yellow', make: 'Volkswagen', model: 'Passat', owner: 'Max', }, { color: 'black', make: 'Tesla', model: 'S', owner: 'Adriana', }, { color: 'purple', make: 'Peugeot', model: '205', owner: 'Michel', }, { color: 'white', make: 'Chery', model: 'S22L', owner: 'Aarav', }, { color: 'violet', make: 'Fiat', model: 'Punto', owner: 'Pari', }, { color: 'indigo', make: 'Tata', model: 'Nano', owner: 'Valeria', }, { color: 'brown', make: 'Holden', model: 'Barina', owner: 'Shotaro', }, ]; for (let i = 0; i < cars.length; i++) { cars[i].docType = 'car'; await ctx.stub.putState('CAR' + i, Buffer.from(JSON.stringify(cars[i]))); console.info('Added <--> ', cars[i]); } console.info('============= END : Initialize Ledger ==========='); } async queryCar(ctx, carNumber) { const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state if (!carAsBytes || carAsBytes.length === 0) { throw new Error(`${carNumber} does not exist`); } console.log(carAsBytes.toString()); return carAsBytes.toString(); } async createCar(ctx, carNumber, make, model, color, owner) { console.info('============= START : Create Car ==========='); const car = { color, docType: 'car', make, model, owner, }; await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car))); console.info('============= END : Create Car ==========='); } async queryAllCars(ctx) { const startKey = 'CAR0'; const endKey = 'CAR999'; const iterator = await ctx.stub.getStateByRange(startKey, endKey); const allResults = []; while (true) { const res = await iterator.next(); if (res.value && res.value.value.toString()) { console.log(res.value.value.toString('utf8')); const Key = res.value.key; let Record; try { Record = JSON.parse(res.value.value.toString('utf8')); } catch (err) { console.log(err); Record = res.value.value.toString('utf8'); } allResults.push({ Key, Record }); } if (res.done) { console.log('end of data'); await iterator.close(); console.info(allResults); return JSON.stringify(allResults); } } } async changeCarOwner(ctx, carNumber, newOwner) { console.info('============= START : changeCarOwner ==========='); const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state if (!carAsBytes || carAsBytes.length === 0) { throw new Error(`${carNumber} does not exist`); } const car = JSON.parse(carAsBytes.toString()); car.owner = newOwner; await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car))); console.info('============= END : changeCarOwner ==========='); } } module.exports = FabCar;
再次運行query.js:
$ node query-car4.js
Wallet path: /Users/lihongxu6/work/mygo/src/github.com/hyperledger/fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is: {"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
如今,大概已經對查詢交易有了大概的瞭解,接下來看看如何更新帳本。
咱們能夠作不少方面的更新操做,但先從建立一輛新車開始吧。
從應用程序的角度來看,更新帳本是很簡單的。應用先提交一個交易到區塊鏈網絡,而後當這個交易被證實有效後,應用會收到一個交易成功的通知。這個過程涉及到共識機制:
上圖顯示了更新帳本所涉及到的主要組件。區塊鏈網絡包含多個peer,每一個peer都維護一份帳本副本,而且選擇性的維護一個智能合約副本,除此以外,網絡還包括一個排序服務。排序服務可以建立一個包含鏈接到此網絡的不一樣應用所提交的通過排序的交易的區塊。
咱們使用invoke.js程序來實現建立一輛新車的更新操做:如下是在invoke中的一段代碼:
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
運行
$ node invoke.js Wallet path: /Users/lihongxu6/work/mygo/src/github.com/hyperledger/fabric-samples/fabcar/javascript/wallet 2019-04-03T10:26:48.661Z - info: [TransactionEventHandler]: _strategySuccess: strategy success for transaction "5a9563b1397339de4de191aa3c6e52371033811b9aa3d3435bc429b2afabe54f" Transaction has been submitted
注意一下,invoke.js程序與區塊鏈交互使用的是submitTransaction API而不是evaluateTransaction。
submitTransaction比evaluateTransaction複雜的多。SDK會將submitTransaction提案發送給每個須要的組織中的peer,而不是隻和單個peer交互。全部這些接收提案的peer將會執行提案要求執行的智能合約生成交易響應,並對交易響應進行簽名後發回給SDK。SDK將收集到的全部通過簽名的交易響應合併到一個新的交易中,而後將其發送給orderer。orderer未來自各個應用的交易進行收集和排序後,將這些交易放到區塊中。而後將這個新區塊(每一個交易都是通過證實合法的)分發給網絡中的全部peer節點。最後,通知SDK將控制權交回給應用程序。
上一段很重要,涉及到不少工做,而這些工做都是由submitTransaction完成的。應用程序、智能合約、peers、ordering service協同工做來保證帳本的一致性的過程叫作共識過程,共識機制的詳細介紹看這裏。
經過已下查看便可【注意鏈碼調用方法】
node query.js
查看結果中會有新加入的記錄
如今,假設Tom很是慷慨,想把Honda Accord車送給Dave,爲了實現這個過程,須要修改invoke.js程序,
// await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom'); await contract.submitTransaction('changeCarOwner', 'CAR12', 'Dave');
再次執行invoke.js程序(因爲網絡緣由,可能會因爲鏈接超時而報錯,多運行幾回便可):
$ node invoke.js Wallet path: /Users/lihongxu6/work/mygo/src/github.com/hyperledger/fabric-samples/fabcar/javascript/wallet 2019-04-03T10:35:21.125Z - info: [TransactionEventHandler]: _strategySuccess: strategy success for transaction "d64761e0880c70b4578b53e467e05cfa8696cfb2791bce5d3062bc1196894359" Transaction has been submitted
再次執行query.js查看owner值是否爲Dave:
能夠看到,全部者確實變爲了Dave。
至此,就成功完成了fabric1.4官方文檔中的writing your first application部分。這一部分比較簡單,更深刻的請看下面:
As we said in the introduction, we have a whole section on Developing Applications that includes in-depth information on smart contracts, process and data design, a tutorial using a more in-depth Commercial Paper tutorial and a large amount of other material relating to the development of applications.
如下是1.1版本的示例,chaincode在根目錄下
三、開啓網絡配置[注意docker版本17.06,在1.12.6版本沒有-e命令]
./startFabric.sh
四、查詢一個帳本
安裝node
yum install gcc-c++
npm install
查詢[注意配置query.js中的ip地址]
node query.js
展現以下數據
Create a client and set the wallet location Set wallet path, and associate user PeerAdmin with application Check user is enrolled, and set a query URL in the network Make query Assigning transaction_id: f2f45cb045d6290d199e1b2d4eb3b60b1e9cafeff8d09e2b7683dd8578492be7 returned from query 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"}}]
五、分析query.js
1》初始化參數,包含了用戶ID,信道,鏈碼,網絡鏈接入口
var options = { wallet_path: path.join(__dirname, './creds'), user_id: 'PeerAdmin', channel_id: 'mychannel', chaincode_id: 'fabcar', network_url: 'grpc://localhost:7051', };
2》查詢代碼
var transaction_id = client.newTransactionID(); // queryCar - requires 1 argument, ex: args: ['CAR4'], // queryAllCars - requires no arguments , ex: args: [''], const request = { chaincodeId: options.chaincode_id, txId: transaction_id, fcn: 'queryAllCars', args: [''] }; return channel.queryByChaincode(request);
這裏設置了鏈碼ID,交易ID,以及調用的鏈碼的方法fcn,方法參數args等
3》鏈碼:/chaincode/fabcar/目錄下fabcar.go
此文件匹配上文的鏈碼ID,包含了以下方法:initLedger
, queryCar
,queryAllCars
, createCar
and changeCarOwner
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { startKey := "CAR0" endKey := "CAR999" resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
此處就是查詢範圍內的數據。
查看全部方法
六、測試
cp query.js query1.js
vim query1.js
修改內部訪問鏈碼方法
const request = { chaincodeId: options.chaincode_id, txId: transaction_id, fcn: 'queryCar', args: ['CAR4'] };
執行:node query1.js
Create a client and set the wallet location Set wallet path, and associate user PeerAdmin with application Check user is enrolled, and set a query URL in the network Make query Assigning transaction_id: ca88dc3b60f4df009a709f2f5ee5ad3b54f43d03a7e0b931042e2797f70c795d returned from query Query result count = 1 Response is {"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
七、更新帳本數據
使用fabcar目錄下的invoke.js
修改,中的 fcn,以及args等參數
var request = { targets: targets, chaincodeId: options.chaincode_id, fcn: '', args: [''], chainId: options.channel_id, txId: tx_id
以下
var request = { targets: targets, chaincodeId: options.chaincode_id, fcn: 'createCar', args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'], chainId: options.channel_id, txId: tx_id
執行命令
node invoke.js
成功後會有
The transaction has been committed on peer localhost:7053
執行
cp query1.js query2.js vim query2.js
將query2.js中查詢條件參數,變爲CAR10便可
Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}
ok,能夠繼續調試其餘方法。