• -------------------------------------------------------------
  • ====================================

06-去中心化架构介绍&Fabcar项目详解

区块链 dewbay 4年前 (2020-01-06) 1724次浏览 已收录 0个评论 扫描二维码

在刚刚接触到区块链的时候,我对智能合约,区块链网络,区块链应用三者之间的关系,一直很不清楚,没有一个很宏观的轮廓,不知道他们究竟是怎样协同工作的。后来,在慢慢的摸索中,开始阅读 HyperLedger 的官方文档,以及几个简单的区块链应用系统的介绍。渐渐明白了它们之间的关系。所以,我决定在介绍 ChainCode 的开发之前,先来简单的把一个区块链应用的基本组成部分。总的来说,我们的学习策略是,由全面的肤浅片面的深刻

接下来的文章,将按照如下的结构来展开:

06-去中心化架构介绍&Fabcar项目详解
全文结构

一、传统应用系统与简单区块链应用系统的对比

  • 传统应用系统的组成与架构

在我们日常的一个应用系统大体上是 B/S 架构或者 C/S 架构,移动端或者前端来实现用户的操作界面,后台有应用服务器和数据库服务器来进行数据和事务逻辑的处理。B/S 架构的系统组成如下图所示:

06-去中心化架构介绍&Fabcar项目详解
B/S 架构示意图

在实际的业务逻辑开发中,传统的是使用分层架构来实现用户,事务与数据之间的关系。经典三次架构图如下图所示:

06-去中心化架构介绍&Fabcar项目详解
三层架构图

1:数据访问层:主要是对非原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据库的操作,而不是数据,具体为业务逻辑层或表示层提供数据服务。

2:业务逻辑层:主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,如果说数据层是积木,那逻辑层就是对这些积木的搭建。

3:界面层:主要表示 WEB 方式,也可以表示成 WINFORM 方式,WEB 方式也可以表现成:aspx,如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。

  • 区块链应用系统的组成与架构

在 HyperLedger 的区块链应用系统中,从组成部分来说,和传统的应用区别不大。用来和用户交互的依然是浏览器或者客户端或者移动端。不同的是,在这里,我们是把数据存储到区块链网络中,而不是存在数据库服务器。下面是一个简单弹珠管理系统的组成部分示意图:

06-去中心化架构介绍&Fabcar项目详解
弹珠管理系统-系统组成部分

在上图所示的弹珠管理系统中。我们在浏览器端操作弹珠的管理。前端调用后台应用程序,而后台应用程序通过调用运行在区块链网络中的智能合约,来实现对数据的存储与更改等。所以,如果我们要开发一个区块链应用,需要编写前端程序后端程序,还要编写智能合约部署 Fabric 区块链网络等。

而在区块链应用系统中采用的系统架构,并非传统的分层架构,而是微服务架构。它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。在微服务架构中,是按照业务,而不是技术来划分组织。下图展示了 Fabric 网络的基本架构:

06-去中心化架构介绍&Fabcar项目详解
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.在通道上实例化该链码;实例化会启动链码容器。

出现如下界面,表示测试网络成功运行:

06-去中心化架构介绍&Fabcar项目详解
网络成功运行界面

上面的步骤已经成功运行了区块链网络,接下来就要着手应用开发了。这里,我们首先来介绍一下用 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 

运行成功后界面如下:

06-去中心化架构介绍&Fabcar项目详解

会在 fabcar 目录下生成一个存放 key 的文件夹:hfc-key-store

  • 4-运行 registerUser.js
node registerUser.js

成功运行后界面如下:

06-去中心化架构介绍&Fabcar项目详解

  • 5-运行 query.js

现在我们可以运行 JavaScript 程序。运行query.js 程序,返回账本上所有汽车列表。程序中预先加载了一个queryAllCars函数,用于查询所有车辆,因此我们可以简单地运行程序

node query.js

运行成功后,界面如下:

06-去中心化架构介绍&Fabcar项目详解

  • 6.关闭网络

在本地开发测试过程中,当关闭一个项目是,一定要记得清除所有的允许容器。否则的话,未关闭的容器,可能会占用端口号,给下一个项目的运行带来干扰。

进入上一级目录,basic-network,运行关闭网络的脚本

./teardown.sh

三、详解应用程序与网络交互过程

简介

通过前面的工作,我们有了简单的网络以及一些代码,现在看看他们是怎么一起工作的。

应用程序使用APIs来调用智能合约(即“链码”)而 API 可通过软件开发工具包(SDK)访问。

在本练习中,我们将使用Hyperledger Fabric Node SDK,除此以外,Fabric 还提供了 Java SDK 和 CLI 用于开发应用程序。

链码的工作流程

我们首先来介绍一下链码的工作流程:

06-去中心化架构介绍&Fabcar项目详解
链码工作流程

我们知道,在 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 接口,特别是InitInvoke函数接口。所以我们首先为我们的 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.Successshim.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.PutStateChaincodeStubInterface.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

从上面可知,我们可以调用下面的函数- initLedgerqueryCarqueryAllCarscreateCarchangeCarOwner。让我们仔细看看queryAllCars函数是如何与账本进行交互的。

该函数调用 shim 接口函数GetStateByRange来返回参数在startKeyendKey间的账本数据。这两个键值分别定义为CAR0CAR999。因此,我们理论上可以创建 1,000 辆汽车(假设 Keys 都被正确使用),queryAllCars函数将会显示出每一辆汽车的信息。

下图演示了一个应用程序如何在链码中调用不同的功能。

06-去中心化架构介绍&Fabcar项目详解
应用程序调用 ChainCode 示意图

我们可以看到我们用过的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 接口函数,这里不再累赘。具体步骤可以参考:

Fabric 中文档-编写第一个应用

4.总结

这篇文章,主要是介绍了 ChainCode 的编写,以及如何使用 JavaScript 代码来调用智能合约。将官方的 fabcar 运行过程介绍了一下。因为在官方中文文档中,有一些步骤,并不完全正确。这里参考了一些网上其他人的博客。

至此,ChainCode 的编写基本学习总结完了。后面将继续片面的深刻学习 Fabric。

参考文章:

《区块链原理、设计、应用》-杨保华-13.2.3 链码基本工作原理

Fabric 中文档-编写第一个应用

 

转自知乎 苏小乐 :https://www.zhihu.com/people/shan-de-ding-zhu/activities


露水湾 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:06-去中心化架构介绍&Fabcar项目详解
喜欢 (1)
[]
分享 (0)
关于作者:
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址