在刚刚接触到区块链的时候,我对智能合约,区块链网络,区块链应用三者之间的关系,一直很不清楚,没有一个很宏观的轮廓,不知道他们究竟是怎样协同工作的。后来,在慢慢的摸索中,开始阅读 HyperLedger 的官方文档,以及几个简单的区块链应用系统的介绍。渐渐明白了它们之间的关系。所以,我决定在介绍 ChainCode 的开发之前,先来简单的把一个区块链应用的基本组成部分。总的来说,我们的学习策略是,由全面的肤浅到片面的深刻。
接下来的文章,将按照如下的结构来展开:
一、传统应用系统与简单区块链应用系统的对比
- 传统应用系统的组成与架构
在我们日常的一个应用系统大体上是 B/S 架构或者 C/S 架构,移动端或者前端来实现用户的操作界面,后台有应用服务器和数据库服务器来进行数据和事务逻辑的处理。B/S 架构的系统组成如下图所示:
在实际的业务逻辑开发中,传统的是使用分层架构来实现用户,事务与数据之间的关系。经典三次架构图如下图所示:
1:数据访问层:主要是对非原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据库的操作,而不是数据,具体为业务逻辑层或表示层提供数据服务。
2:业务逻辑层:主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,如果说数据层是积木,那逻辑层就是对这些积木的搭建。
3:界面层:主要表示 WEB 方式,也可以表示成 WINFORM 方式,WEB 方式也可以表现成:aspx,如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。
- 区块链应用系统的组成与架构
在 HyperLedger 的区块链应用系统中,从组成部分来说,和传统的应用区别不大。用来和用户交互的依然是浏览器或者客户端或者移动端。不同的是,在这里,我们是把数据存储到区块链网络中,而不是存在数据库服务器。下面是一个简单弹珠管理系统的组成部分示意图:
在上图所示的弹珠管理系统中。我们在浏览器端操作弹珠的管理。前端调用后台应用程序,而后台应用程序通过调用运行在区块链网络中的智能合约,来实现对数据的存储与更改等。所以,如果我们要开发一个区块链应用,需要编写前端程序,后端程序,还要编写智能合约,部署 Fabric 区块链网络等。
而在区块链应用系统中采用的系统架构,并非传统的分层架构,而是微服务架构。它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。在微服务架构中,是按照业务,而不是技术来划分组织。下图展示了 Fabric 网络的基本架构:
二、FabCar 的区块链应用实战
简介
在接下来的部分,我们将运行并解读 HyperLedger 官方提供的一个小的例子 FabCar。这个例子,很好的展示了后台程序对 ChainCode 的调用,并来操作区块链网络账本的。这一部分,主要是参考:HyperLedger 中文文档-编写第一个应用
区块链网络应用程序需要提供给用户查询账本(包含特定记录)以及更新账本(添加记录)的功能。我们的应用程序基于 Javascript,通过 Node.js SDK 与(账本所在的)网络进行交互。这一部分将通过三步来编写第一个应用程序。
- 1. 启动一个 Hyperledger Fabric 区块链测试网络。 在我们的网络中,我们需要一些最基本的组件来查询和更新账本。这些组件 —— peer 节点、ordering 节点以及证书管理 —— 是我们网络的基础。而 CLI 容器则用来发送一些管理命令。
- 2. 学习应用程序中所用到的智能合约例子的参数。 智能合约包含的各种功能让我们可以用多种方式和账本进行交互。如,我们可以读取整体的数据或者某一部分详尽的数据。
- 3. 开发能够查询以及更新记录的应用程序。 我们提供两个程序例子 —— 一个用于查询账本,另一个用户更新账本。我们的程序将使用 SDK APIs 来和网络进行交互,并最终调用这些功能。
完成这一部分实战后,我们应该会基本了解一个使用 Hyperledger Fabric Node.js SDK 并带有智能合约的应用程序,是如何与 Hyperledger Fabric 网络中的账本进行交互的。
环境准备
- 安装 go,下载 Fabric 源码以及下载好 docker 镜像。这一部分,在《1-HyperLedger 实战-快速搭建一个 Fabric1.0 环境》这篇文章里有详细的介绍。
- 安装 node.因为这文是利用 nodeSDK 来进行开发的,所以,我们需要安装 node。具体命令如下:
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs
注意!Fabric Node SDK 支持的 Node 版本是 v6,不支持最新的 v8 版本。
安装完成后我们可以使用以下两个命令来查看安装的 Node 版本和 npm 版本。
node –v
npm -v
实战步骤
- 1-下载测试网络
这里我们进行测试的代码,是官方托管在 GitHub 上的,下载并进入 fabcar 子目录。
git clone https://github.com/hyperledger/fabric-samples.git
cd fabric-samples/fabcar
这个子目录 – fabcar
– 包含运行示例程序的脚本以及程序代码。在该目录运行ls
命令,您应该会看到以下内容:
enrollAdmin.js node_modules query.js startFabric.sh
invoke.js package.json registerUser.js
现在调用 startFabric.sh 来启动网络。
./startFabric.sh
这个命令主要做了如下工作:
1.启动 peer 节点、Ordering 节点,证书颁发机构,CLI 容器等。
2.创建一个通道,并将 peer 加入该通道
3.将智能合约(即链码)安装到 peer 节点的文件系统上。
4.在通道上实例化该链码;实例化会启动链码容器。
出现如下界面,表示测试网络成功运行:
上面的步骤已经成功运行了区块链网络,接下来就要着手应用开发了。这里,我们首先来介绍一下用 NodeSDK 来开发调用智能合约的基本步骤:
- 2-编写 package.json 并下载依赖模块:
fabcar/package.json 中的内容如下:
{
"name": "fabcar",
"version": "1.0.0",
"description": "Hyperledger Fabric Car Sample Application",
"main": "fabcar.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"fabric-ca-client": "~1.1.0",
"fabric-client": "~1.1.0",
"grpc": "^1.6.0"
},
"author": "Anthony O'Dowd",
"license": "Apache-2.0",
"keywords": [
"Hyperledger",
"Fabric",
"Car",
"Sample",
"Application"
]
}
我们就可以运行 npm install 命令来下载所有相关的依赖模块,但是由于 npm 服务器在国外,所以下载可能会很慢,感谢淘宝为我们提供了国内的 npm 镜像,使得安装 npm 模块快很多。运行的命令是:
npm install --registry=https://registry.npm.taobao.org
运行完毕后我们查看一下 fabcar 目录,可以看到多了一个node_modules文件夹。这里就是使用刚才的命令下载下来的所有依赖包。
- 3-运行 enrollAdmin.js
运行下列命令:
node enrollAdmin.js
运行成功后界面如下:
会在 fabcar 目录下生成一个存放 key 的文件夹:hfc-key-store
- 4-运行 registerUser.js
node registerUser.js
成功运行后界面如下:
- 5-运行 query.js
现在我们可以运行 JavaScript 程序。运行query.js
程序,返回账本上所有汽车列表。程序中预先加载了一个queryAllCars
函数,用于查询所有车辆,因此我们可以简单地运行程序
node query.js
运行成功后,界面如下:
- 6.关闭网络
在本地开发测试过程中,当关闭一个项目是,一定要记得清除所有的允许容器。否则的话,未关闭的容器,可能会占用端口号,给下一个项目的运行带来干扰。
进入上一级目录,basic-network,运行关闭网络的脚本
./teardown.sh
三、详解应用程序与网络交互过程
简介
通过前面的工作,我们有了简单的网络以及一些代码,现在看看他们是怎么一起工作的。
应用程序使用APIs来调用智能合约(即“链码”)而 API 可通过软件开发工具包(SDK)访问。
在本练习中,我们将使用Hyperledger Fabric Node SDK,除此以外,Fabric 还提供了 Java SDK 和 CLI 用于开发应用程序。
链码的工作流程
我们首先来介绍一下链码的工作流程:
我们知道,在 Fabric 中,链码运行在节点上的沙盒(Docker 容器)中,被调用时的基本工作流程如上图所示。
- 首先,用户通过客户端(SDK 或 CLI),向 Fabric 的背书节点(endorser)发出调用链码的交易提案(proposal)。
- 然后,节点对提案进行包括 ACL 权限检查在内的各种检验,通过后则创建模拟执行这一交易的环境。
- 接着,背书节点和链码容器之间通过 gRPC 消息来交互,模拟执行交易并给出背书结论。
- 最后,客户端收到足够的背书节点的支持后,便可以将这笔交易发送给排序节点(orderer)进行排序,并最终写入区块链。
链码容器的shim 层是节点与链码交互的中间层。当链码的代码逻辑需要读写账本时,链码会通过 shim 层发送相应操作类型的 ChaincodeMessage 给节点,节点本地操作账本后返回响应消息。
在上面的流程中,出现了一个重要的部分,shim 层。这里需要再次强调一下,链码容器的 shim 层是节点与链码交互的中间层。
ChainCode 的编写简介
每个 chaincode 程序都必须实现 chaincode 接口 ,接口中的方法会在响应传来的交易时被调用。
Init
(初始化)方法会在 chaincode 接收到instantiate
(实例化)或者upgrade
(升级)交易时被调用,进而使得 chaincode 顺利执行必要的初始化操作,包括初始化应用的状态。Invoke
(调用)方法会在响应invoke
(调用)交易时被调用以执行交易。
ChainCode 编写的步骤
1.引入相关包
首先, 我们先进行准备工作。对于每一个 chaincode,它都会实现预定义的chaincode 接口,特别是Init
和Invoke
函数接口。所以我们首先为我们的 chaincode 引入必要的依赖。这里的 shim 层是节点与链码交互的中间层。如下图所示:
package main
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
)
2.初始化 Chaincode
接下来,我们将实现Init
函数。
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
}
值得留意的是 chaincode 升级同样会调用该函数。当我们编写的 chaincode 会升级现有 chaincode 时,需要确保适当修正 Init 函数。特别地,如果没有“迁移”操作或其他需要在升级中初始化的东西,那么就提供一个空的“Init”方法。我们这里仅仅提供了一个简单的 Init 方法。
3.编写 Chaincode 接口函数
首先,添加Invoke
函数签名。
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
}
我们需要调用ChaincodeStubInterface
来获取参数。我们将调用ChaincodeStubInterface并以键值为参数传入。如果一切正常,那么我们会收到表明初始化成功的 peer.Response 返回对象。Invoke
函数所需的传入参数正是应用想要调用的 chaincode 的名称。在我们的应用里面,我们有几个简单的功能函数:queryCar,initLedger,createCar,queryAllCars,changeCarOwner 等。
下面,我们将使这几个函数名正式生效,并调用这些 chaincode 应用函数,经由shim.Success
或shim.Error
函数返回一个合理的响应。这两个shim
成员函数可以将响应序列化为 gRPC protobuf 消息
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
function, args := APIstub.GetFunctionAndParameters()
if function == "queryCar" {
return s.queryCar(APIstub, args)
} else if function == "initLedger" {
return s.initLedger(APIstub)
} else if function == "createCar" {
return s.createCar(APIstub, args)
} else if function == "queryAllCars" {
return s.queryAllCars(APIstub)
} else if function == "changeCarOwner" {
return s.changeCarOwner(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}
4.编写 Chaincode 的调用函数
如上文所述,我们的 chaincode 应用实现了五个函数,并可以被Invoke
函数调用。下面我们就来真正实现这些函数。注意,就像上文一样,我们调用 chaincode shim API 中的ChaincodeStubInterface.PutState和ChaincodeStubInterface.GetState函数来访问账本。
func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
carAsBytes, _ := APIstub.GetState(args[0])
return shim.Success(carAsBytes)
}
func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
cars := []Car{
Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
}
i := 0
for i < len(cars) {
fmt.Println("i is ", i)
carAsBytes, _ := json.Marshal(cars[i])
APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
fmt.Println("Added", cars[i])
i = i + 1
}
return shim.Success(nil)
}
func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 5 {
return shim.Error("Incorrect number of arguments. Expecting 5")
}
var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}
carAsBytes, _ := json.Marshal(car)
APIstub.PutState(args[0], carAsBytes)
return shim.Success(nil)
}
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {
startKey := "CAR0"
endKey := "CAR999"
resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()
// buffer is a JSON array containing QueryResults
var buffer bytes.Buffer
buffer.WriteString("[")
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"")
buffer.WriteString(", \"Record\":")
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("- queryAllCars:\n%s\n", buffer.String())
return shim.Success(buffer.Bytes())
}
func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
carAsBytes, _ := APIstub.GetState(args[0])
car := Car{}
json.Unmarshal(carAsBytes, &car)
car.Owner = args[1]
carAsBytes, _ = json.Marshal(car)
APIstub.PutState(args[0], carAsBytes)
return shim.Success(nil)
}
func main() {
// Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}
这里可以看到,在最后关头我们写了 main 函数,它将调用shim.Start 函数,main 函数的作用,是在容器里启动 chaincode。
5.整合全部代码
将上面的代码整合在一起如下:
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* The sample smart contract for documentation topic:
* Writing Your First Blockchain Application
*/
package main
/* Imports
* 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
* 2 specific Hyperledger Fabric specific libraries for Smart Contracts
*/
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
)
// Define the Smart Contract structure
type SmartContract struct {
}
// Define the car structure, with 4 properties. Structure tags are used by encoding/json library
type Car struct {
Make string `json:"make"`
Model string `json:"model"`
Colour string `json:"colour"`
Owner string `json:"owner"`
}
/*
* The Init method is called when the Smart Contract "fabcar" is instantiated by the blockchain network
* Best practice is to have any Ledger initialization in separate function -- see initLedger()
*/
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
}
/*
* The Invoke method is called as a result of an application request to run the Smart Contract "fabcar"
* The calling application program has also specified the particular smart contract function to be called, with arguments
*/
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
// Retrieve the requested Smart Contract function and arguments
function, args := APIstub.GetFunctionAndParameters()
// Route to the appropriate handler function to interact with the ledger appropriately
if function == "queryCar" {
return s.queryCar(APIstub, args)
} else if function == "initLedger" {
return s.initLedger(APIstub)
} else if function == "createCar" {
return s.createCar(APIstub, args)
} else if function == "queryAllCars" {
return s.queryAllCars(APIstub)
} else if function == "changeCarOwner" {
return s.changeCarOwner(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}
func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
carAsBytes, _ := APIstub.GetState(args[0])
return shim.Success(carAsBytes)
}
func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
cars := []Car{
Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
}
i := 0
for i < len(cars) {
fmt.Println("i is ", i)
carAsBytes, _ := json.Marshal(cars[i])
APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
fmt.Println("Added", cars[i])
i = i + 1
}
return shim.Success(nil)
}
func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 5 {
return shim.Error("Incorrect number of arguments. Expecting 5")
}
var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}
carAsBytes, _ := json.Marshal(car)
APIstub.PutState(args[0], carAsBytes)
return shim.Success(nil)
}
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {
startKey := "CAR0"
endKey := "CAR999"
resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()
// buffer is a JSON array containing QueryResults
var buffer bytes.Buffer
buffer.WriteString("[")
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"")
buffer.WriteString(", \"Record\":")
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("- queryAllCars:\n%s\n", buffer.String())
return shim.Success(buffer.Bytes())
}
func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
carAsBytes, _ := APIstub.GetState(args[0])
car := Car{}
json.Unmarshal(carAsBytes, &car)
car.Owner = args[1]
carAsBytes, _ = json.Marshal(car)
APIstub.PutState(args[0], carAsBytes)
return shim.Success(nil)
}
// The main function is only relevant in unit test mode. Only included here for completeness.
func main() {
// Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}
query.js 的源码分析
回到我们实战部分的第五个步骤。运行 node query.js,系统给我们是十辆车的记录。现在让我们来看看代码内容。使用编辑器(例如 atom 或 visual studio)打开query.js
程序。
以下是构建查询的代码块:
const request = {
chaincodeId: 'fabcar',
fcn: 'queryAllCars',
args: []
};
我们将chaincode_id
变量赋值为fabcar
– 这让我们定位到这个特定的链码 – 然后调用该链码中定义的queryAllCars
函数。
在前面,我们发出node query.js
命令时,会调用了特定函数来查询账本。但是,这不是我们能够使用的唯一功能。
在 fabric-sample 目录下,上文我们编写的 ChainCode 的代码其实是学习在chaincode
子目录中的fabcar.go
。
从上面可知,我们可以调用下面的函数- initLedger
、queryCar
、queryAllCars
、createCar
和changeCarOwner
。让我们仔细看看queryAllCars
函数是如何与账本进行交互的。
该函数调用 shim 接口函数GetStateByRange
来返回参数在startKey
和endKey
间的账本数据。这两个键值分别定义为CAR0
和CAR999
。因此,我们理论上可以创建 1,000 辆汽车(假设 Keys 都被正确使用),queryAllCars
函数将会显示出每一辆汽车的信息。
下图演示了一个应用程序如何在链码中调用不同的功能。
我们可以看到我们用过的queryAllCars
函数,还有一个叫做createCar
,这个函数可以让我们更新账本,并最终在链上增加一个新区块。但首先,让我们做另外一个查询。
现在我们返回query.js
程序并编辑请求构造函数以查询特定的车辆。为达此目的,我们将函数queryAllCars
更改为queryCar
并将特定的“Key” 传递给 args 参数。在这里,我们使用CAR4
。 所以我们编辑后的query.js
程序现在应该包含以下内容:
const request = {
chaincodeId: options.chaincode_id,
txId: transaction_id,
fcn: 'queryCar',
args: ['CAR4']
}
保存程序并返回fabcar
目录。现在再次运行程序:
node query.js
您应该看到以下内容:
{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
按照上面的步骤,我们可以继续调用别的 slim 接口函数,这里不再累赘。具体步骤可以参考:
4.总结
这篇文章,主要是介绍了 ChainCode 的编写,以及如何使用 JavaScript 代码来调用智能合约。将官方的 fabcar 运行过程介绍了一下。因为在官方中文文档中,有一些步骤,并不完全正确。这里参考了一些网上其他人的博客。
至此,ChainCode 的编写基本学习总结完了。后面将继续片面的深刻学习 Fabric。
参考文章:
《区块链原理、设计、应用》-杨保华-13.2.3 链码基本工作原理
转自知乎 苏小乐 :https://www.zhihu.com/people/shan-de-ding-zhu/activities