第1章 引言
第2章 Hyperledger Fabric v2.0的新增功能
第3章 关键概念
第4章 入门
第5章 开发应用程序
第6章 教程(上)
第6章 教程(下)
第7章 部署生产网络
第8章 操作指南
第9章 升级到最新版本
应用程序开发人员可以使用Fabric教程开始构建自己的解决方案。通过在本地计算机上部署测试网络开始使用Fabric。然后,您可以使用“将智能合约部署到通道”教程提供的步骤来部署和测试智能合约。编写第一个应用程序教程介绍了如何使用Fabric sdk提供的APIs从客户端应用程序调用智能合约。有关Fabric应用程序和智能合约如何协同工作的深入概述,您可以访问“开发应用程序”主题。
网络运营商还可以使用“将智能合约部署到通道”教程来学习如何使用Fabric链码生命周期来管理部署在正在运行的网络上的智能合约。网络运营商和应用程序开发人员都可以使用关于私有数据和CouchDB的教程来探索重要的Fabric特性。准备在生产中部署Hyperledger Fabric时,请参阅《部署生产网络指南》。
更新通道有两个教程:更新通道配置和更新通道的功能级别,而升级组件则说明如何升级节点、排序节点、SDK等组件。
最后,我们介绍了如何编写一个基本的智能合约,开发人员的链码。
注意:如果您有本文档没有解决的问题,或者遇到任何教程的问题,请访问仍然有问题吗?页面获取有关在何处找到其他帮助的提示。
最终用户通过调用智能合约与区块链账本交互。在Hyperledger Fabric中,智能合约部署在称为链码的包中。想要验证交易记录或查询账本的组织需要在其节点上安装链码。在连接到通道的节点上安装链码后,通道成员可以将链码部署到通道,并使用链码中的智能合约在通道账本上创建或更新资产。
使用称为Fabric链码生命周期的过程将链码部署到通道。Fabric链码生命周期允许多个组织在链码用于创建交易之前商定如何操作链码。例如,虽然背书策略指定哪些组织需要执行链码以验证交易,但通道成员需要使用Fabric链码生命周期来商定链码背书策略。有关如何在通道上部署和管理链码的更深入概述,请参阅Fabric链码生命周期。
您可以使用本教程学习如何使用节点生命周期链码命令将链码部署到Fabric测试网络的通道。一旦您了解了这些命令,就可以使用本教程中的步骤将自己的链码部署到测试网络,或者将链码部署到生产网络。在本教程中,您将部署编写第一个应用程序教程所使用的Fabcar链码。
注意:这些说明使用了v2.0版本中引入的Fabric链码生命周期。如果要使用以前的生命周期来安装和实例化链码,请访问Fabric文档的v1.4版本。
我们将首先部署Fabric测试网络的一个实例。在开始之前,请确保您已经安装了先决条件并安装了示例、二进制文件和Docker镜像。使用以下命令导航到fabric-samples
存储库的本地克隆中的测试网络目录:
cd fabric-samples/test-network
在本教程中,我们希望从已知的初始状态开始操作。下面的命令将杀死任何活动的或过时的docker容器,并删除以前生成的工件。
./network.sh down
然后可以使用以下命令启动测试网络:
./network.sh up createChannel
createChannel
命令使用两个通道成员Org1和Org2创建一个名为mychannel
的通道。该命令还将属于每个组织的节点连接到通道。如果网络和通道创建成功,您可以在日志中看到以下消息:
========= Channel successfully joined ===========
我们现在可以使用节点CLI通过以下步骤将Fabcar链码部署到通道:
此步骤不是必需的,但对于排除链码故障非常有用。为了监视智能合约的日志,管理员可以使用logspout
工具查看一组Docker容器的聚合输出。该工具将来自不同Docker容器的输出流收集到一个位置,这样就可以很容易地从一个窗口查看正在发生的事情。这可以帮助管理员在安装智能合约时调试问题,也可以帮助开发人员在调用智能合约时调试问题。因为有些容器纯粹是为了启动智能合约而创建的,并且只存在很短的时间,所以从网络收集所有日志是很有帮助的。
安装和配置logspoutt的脚本,monitordocker.sh
,已包含在commercial-paper
样品中的Fabric sample中。我们也将在本教程中使用相同的脚本。logspoutt工具将连续地将日志流式传输到您的终端,因此您需要使用一个新的终端窗口。打开一个新的终端并导航到test-network
目录。
cd fabric-samples/test-network
你可以运行monitordocker.sh
任何目录中的脚本。为了便于使用,我们将复制monitordocker.sh
从commercial-paper
到你的工作目录的脚本
cp ../commercial-paper/organization/digibank/configuration/cli/monitordocker.sh .
# if you're not sure where it is
find . -name monitordocker.sh
然后,可以通过运行以下命令来启动Logspout:
./monitordocker.sh net_test
您将看到类似于以下内容的输出:
Starting monitoring on all containers on the network net_basic
Unable to find image 'gliderlabs/logspout:latest' locally
latest: Pulling from gliderlabs/logspout
4fe2ade4980c: Pull complete
decca452f519: Pull complete
ad60f6b6c009: Pull complete
Digest: sha256:374e06b17b004bddc5445525796b5f7adb8234d64c5c5d663095fccafb6e4c26
Status: Downloaded newer image for gliderlabs/logspout:latest
1f99d130f15cf01706eda3e1f040496ec885036d485cb6bcc0da4a567ad84361
一开始您不会看到任何日志,但当我们部署链码时,这将发生变化。使这个终端窗口变宽而字体变小是很有帮助的。
我们需要打包链码,然后才能将其安装到我们的节点上。如果您想安装用Go、Java或JavaScript编写的智能合约,步骤是不同的。
Go
在打包链码之前,我们需要安装链码依赖项。导航到包含Fabcar链码Go版本的文件夹。
cd fabric-samples/chaincode/fabcar/go
示例使用Go模块安装链码依赖项。依赖项列在fabcar/go
目录中的go.mod
文件中。你应该花点时间检查这个文件。
$ cat go.mod
module github.com/hyperledger/fabric-samples/chaincode/fabcar/go
go 1.13
require github.com/hyperledger/fabric-contract-api-go v1.1.0
这个go.mod
文件将Fabric合约API导入智能合约包。你可以在文本编辑器中打开fabcar.go
,查看如何在智能合约开始时使用合约API定义SmartContract
类型:
// SmartContract provides functions for managing a car
type SmartContract struct {
contractapi.Contract
}
然后,智能合约类型用于为智能合约中定义的功能创建交易环境,这些功能将数据读写到区块链账本。
// CreateCar adds a new car to the world state with given details
func (s *SmartContract) CreateCar(ctx contractapi.TransactionContextInterface, carNumber string, make string, model string, colour string, owner string) error {
car := Car{
Make: make,
Model: model,
Colour: colour,
Owner: owner,
}
carAsBytes, _ := json.Marshal(car)
return ctx.GetStub().PutState(carNumber, carAsBytes)
}
通过访问API文档和智能合约处理主题,您可以了解更多关于Go合约API的信息。
要安装智能合约依赖项,请从fabcar/go
目录运行以下命令。
GO111MODULE=on go mod vendor
如果命令成功,go包将安装在vendor
文件夹中。
既然我们有了依赖项,我们就可以创建chaincode包了。导航回test-network
文件夹中的工作目录,这样我们就可以将链码与其他网络构件打包在一起。
cd ../../../test-network
您可以使用peer
CLI以所需格式创建链码包。peer
二进制文件位于fabric-samples
存储库的bin
文件夹中。使用以下命令将这些二进制文件添加到CLI路径:
export PATH=${
PWD}/../bin:${
PWD}:$PATH
您还需要将FABRIC_CFG_PATH
设置为指向fabric-samples
存储库中的core.yaml
文件:
export FABRIC_CFG_PATH=$PWD/../config/
要确认您能够使用peer
CLI,请检查二进制文件的版本。二进制文件必须是2.0.0
或更高版本才能运行本教程。
peer version
您还需要将CORE_PEER_MSPCONFIGPATH
设置为管理员或客户端用户的MSP文件夹的位置。运行下面的命令来建立Org1 admin作为我们使用的标识。
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
现在可以使用peer lifecycle chaincode package命令创建链码包:
peer lifecycle chaincode package fabcar.tar.gz --path ../chaincode/fabcar/go/ --lang golang --label fabcar_1
此命令将在当前目录中创建一个名为fabcar.tar.gz
的文件。--lang
标志用于指定链码语言,--path
标志提供智能合约代码的位置。--label
标志用于指定一个链码标签,该标签将在安装后标识链码。建议您的标签包含链码名称和版本。
现在我们已经创建了链码包,我们可以在测试网络的节点上安装链码。
JavaScript
在打包链码之前,我们需要安装链码依赖项。导航到包含Fabcar链码JavaScript版本的文件夹。
cd fabric-samples/chaincode/fabcar/javascript
依赖项列在fabcar/javascript
目录的package.json
文件中。你应该花点时间检查这个文件。您可以在下面找到Dependencies部分:
"dependencies": {
"fabric-contract-api": "^2.0.0",
"fabric-shim": "^2.0.0"
package.json
文件将Fabric contract类导入到智能合约包中。您可以在文本编辑器中打开lib/fabcar.js
以查看导入到智能合约中并用于创建FabCar类的contract类。
const {
Contract } = require('fabric-contract-api');
class FabCar extends Contract {
...
}
FabCar
类为智能合约中定义的函数提供交易环境,这些函数将数据读写到区块链账本。
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 ===========');
}
通过访问API文档和智能合约处理主题,您可以了解有关JavaScript合约API的更多信息。
要安装智能合约依赖项,请从fabcar/javascript
目录运行以下命令。
npm install
如果命令成功,JavaScript包将安装在npm_modules
文件夹中。
既然我们有了依赖项,我们就可以创建chaincode包了。导航回test-network
文件夹中的工作目录,这样我们就可以将链码与其他网络构件打包在一起。
cd ../../../test-network
您可以使用peer
CLI以所需格式创建链码包。peer
二进制文件位于fabric-samples
存储库的bin
文件夹中。使用以下命令将这些二进制文件添加到CLI路径:
export PATH=${
PWD}/../bin:${
PWD}:$PATH
您还需要将FABRIC_CFG_PATH
设置为指向fabric-samples
存储库中的core.yaml
文件:
export FABRIC_CFG_PATH=$PWD/../config/
要确认您能够使用peer
CLI,请检查二进制文件的版本。二进制文件必须是2.0.0
或更高版本才能运行本教程。
peer version
您还需要将CORE_PEER_MSPCONFIGPATH
设置为管理员或客户端用户的MSP文件夹的位置。运行下面的命令来建立Org1 admin作为我们使用的标识。
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
现在可以使用peer lifecycle chaincode package命令创建链码包:
peer lifecycle chaincode package fabcar.tar.gz --path ../chaincode/fabcar/javascript/ --lang node --label fabcar_1
此命令将在当前目录中创建一个名为fabcar.tar.gz
的文件。--lang
标志用于指定链码语言,--path
标志提供智能合约代码的位置。--label
标志用于指定一个链码标签,该标签将在安装后标识链码。建议您的标签包含链码名称和版本。
现在我们已经创建了链码包,我们可以在测试网络的节点上安装链码。
Java
在打包链码之前,我们需要安装链码依赖项。导航到包含Java版Fabcar链码的文件夹。
cd fabric-samples/chaincode/fabcar/java
示例使用Gradle安装链码依赖项。依赖项列在fabcar/java
目录的build.gradle
文件中。你应该花点时间检查这个文件。您可以在下面找到Dependencies部分:
dependencies {
compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.0.+'
implementation 'com.owlike:genson:1.5'
testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.0.+'
testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
testImplementation 'org.assertj:assertj-core:3.11.1'
testImplementation 'org.mockito:mockito-core:2.+'
}
build.gradle
文件将Java链码填充程序导入到智能合约包中,其中包括contract类。您可以在src
目录中找到Fabcar智能合约。您可以导航到FabCar.java
文件并在文本编辑器中打开,查看如何使用contract类为定义的函数创建交易环境,这些函数用于将数据读写到区块链账本。
通过访问Java链码文档和智能合约处理主题,您可以了解有关Java链码填充程序和合约类的更多信息。
要安装智能合约依赖项,请从fabcar/java
目录运行以下命令。
./gradlew installDist
如果命令成功,您将能够在build
文件夹中找到build智能合约。
现在我们已经安装了依赖项并构建了智能合约,现在可以创建链码包了。导航回test-network
文件夹中的工作目录,这样我们就可以将链码与其他网络构件打包在一起。
cd ../../../test-network
您可以使用peer
CLI以所需格式创建链码包。peer
二进制文件位于fabric-samples
存储库的bin
文件夹中。使用以下命令将这些二进制文件添加到CLI路径:
export PATH=${
PWD}/../bin:${
PWD}:$PATH
您还需要将FABRIC_CFG_PATH
设置为指向fabric-samples
存储库中的core.yaml
文件:
export FABRIC_CFG_PATH=$PWD/../config/
要确认您能够使用peer
CLI,请检查二进制文件的版本。二进制文件必须是2.0.0
或更高版本才能运行本教程。
peer version
您还需要将CORE_PEER_MSPCONFIGPATH
设置为管理员或客户端用户的MSP文件夹的位置。运行下面的命令来建立Org1 admin作为我们使用的标识。
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
现在可以使用peer lifecycle chaincode package命令创建链码包:
peer lifecycle chaincode package fabcar.tar.gz --path ../chaincode/fabcar/java/build/install/fabcar --lang java --label fabcar_1
此命令将在当前目录中创建一个名为fabcar.tar.gz
的文件。--lang
标志用于指定链码语言,--path
标志提供智能合约代码的位置。--label
标志用于指定一个链码标签,该标签将在安装后标识链码。建议您的标签包含链码名称和版本。
现在我们已经创建了链码包,我们可以在测试网络的节点上安装链码。
在我们打包Fabcar智能合约之后,我们可以在我们的节点上安装链码。链码需要安装在每一个背书交易的节点上。因为我们要将背书策略设置为需要Org1和Org2的背书,所以我们需要在两个组织操作的节点上安装链码:
让我们先在Org1节点上安装链码。设置以下环境变量以作为Org1管理用户操作peer
CLI。CORE_PEER_ADDRESS
将被设置为指向Org1节点peer0.org1.example.com
。
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
使用peer lifecycle chaincode install命令在节点上安装链码:
peer lifecycle chaincode install fabcar.tar.gz
如果命令成功,节点将生成并返回包标识符。此包ID将用于在下一步中批准链码。您将看到类似于以下内容的输出:
2020-02-12 11:40:02.923 EST [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed remotely: response:<status:200 payload:"\nIfabcar_1:69de748301770f6ef64b42aa6bb6cb291df20aa39542c3ef94008615704007f3\022\010fabcar_1" >
2020-02-12 11:40:02.925 EST [cli.lifecycle.chaincode] submitInstallProposal -> INFO 002 Chaincode code package identifier: fabcar_1:69de748301770f6ef64b42aa6bb6cb291df20aa39542c3ef94008615704007f3
我们现在可以在Org2节点上安装链码。设置以下环境变量以作为Org2管理员操作,并以Org2节点peer0.org2.example.com
为目标。
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
发出以下命令以安装链码:
peer lifecycle chaincode install fabcar.tar.gz
链码是在安装链码时由节点生成的。如果智能合约代码有问题,install命令将从链码返回任何生成错误。
安装链码包后,需要批准组织的链码定义。定义包括链码管理的重要参数,如名称、版本和链码背书策略。
在可以部署链码之前需要批准它的通道成员集受Application/Channel/lifeycleEndorsement
策略的控制。默认情况下,此策略要求大多数通道成员在链码可用于通道之前需要批准它。因为我们在通道上只有两个组织,而且大多数是2,所以我们需要批准Fabcar的链码定义Org1和Org2。
如果一个组织已在其节点上安装了链码,则需要在其组织批准的链码定义中包含package ID。包ID用于将节点上安装的链码与已批准的链码定义相关联,并允许组织使用链码来背书交易。您可以使用peer lifecycle chaincode queryinstalled命令来查询节点,从而找到链码的包ID。
peer lifecycle chaincode queryinstalled
包ID是链码标签和链码二进制文件的哈希的组合。每个节点都将生成相同的包ID。您应该看到类似于以下内容的输出:
Installed chaincodes on peer:
Package ID: fabcar_1:69de748301770f6ef64b42aa6bb6cb291df20aa39542c3ef94008615704007f3, Label: fabcar_1
我们将在批准链码时使用包ID,所以让我们继续并将其保存为环境变量。将peer lifecycle chaincode queryinstalled
返回的包ID粘贴到下面的命令中。对于所有用户,包ID可能不相同,因此您需要使用从命令窗口返回的包ID来完成此步骤。
CC_PACKAGE_ID=fabcar_1:69de748301770f6ef64b42aa6bb6cb291df20aa39542c3ef94008615704007f3
因为环境变量已经被设置为以Org2管理员的身份操作peer
CLI,所以我们可以批准Fabcar的链码定义为Org2。链码是在组织级别批准的,因此命令只需要针对一个节点。批准通过gossip分发给组织内的其他节点。使用peer lifecycle chaincode approverformyorg命令批准链码定义:
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name fabcar --version 1.0 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
上面的命令使用--package id
标志在链码定义中包含包标识符。--sequence
参数是一个整数,用于跟踪定义或更新链码的次数。因为链码是第一次部署到通道,所以序列号是1。当Fabcar链码升级时,序列号将增加到2。该命令还使用--init required
标志请求调用init函数初始化链码,然后再使用其他函数与账本交互。默认情况下,不需要执行Init函数。不需要请求调用Init函数来初始化链码。因为Fabcar链码使用Fabric contract API,所以我们可以通过调用链码中的任何函数来初始化链码,Init函数将在后台被调用。
我们可以向approveformyorg命令提供--signature policy
或--channel config policy
参数来指定链码背书策略。背书策略指定属于不同通道成员的节点需要根据给定的链码验证交易。因为我们没有设置策略,Fabcar的定义将使用默认的背书策略,这要求在提交交易时,交易必须由在场的大多数通道成员背书。这意味着,如果在通道中添加或删除新组织,则会自动更新背书策略,以要求更多或更少的背书。在本教程中,默认策略将需要2取2的多数,交易将需要由来自Org1和Org2的节点认可。如果要指定自定义背书策略,可以使用“背书策略操作指南”了解策略语法。
您需要批准具有管理员角色的标识的链码定义。因此,CORE_PEER_MSPCONFIGPATH
变量需要指向包含管理员标识的MSP文件夹。不能使用客户端用户批准链码定义。需要将批准提交给排序服务,该服务将验证管理员签名,然后将批准分发给您的节点。
我们仍然需要批准链码定义为Org1。将以下环境变量设置为以Org1管理员的身份运行:
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
现在可以将链码定义批准为Org1。
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name fabcar --version 1.0 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
我们现在有了大部分我们需要部署的Fabcar链码到通道。虽然只有大多数组织需要批准链码定义(使用默认策略),但所有组织都需要批准链码定义才能在其节点上启动链码。如果在通道成员批准链码之前提交定义,组织将无法背书交易。因此,建议所有通道成员在提交链码定义之前批准链码。
在足够多的组织批准了链码定义后,一个组织可以将链码定义提交到通道。如果大多数通道成员已批准该定义,则提交交易将成功,并且在通道上实现链码定义中约定的参数。
您可以使用peer lifecycle chaincode checkcommitreadency命令检查通道成员是否批准了相同的链码定义。用于checkcommitreadency
命令的标志与用于批准组织的链码的标志相同。但是,不需要包含--package id
标志。
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name fabcar --version 1.0 --sequence 1 --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --output json
该命令将生成一个JSON映射,该映射显示通道成员是否已批准checkcommitreadence
命令中指定的参数:
{
"Approvals": {
"Org1MSP": true,
"Org2MSP": true
}
}
由于作为通道成员的两个组织都批准了相同的参数,因此链码定义已准备好提交给通道。可以使用peer lifecycle chaincode commit命令将链码定义提交到通道。commit命令还需要由组织管理员提交。
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name fabcar --version 1.0 --sequence 1 --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses localhost:7051 --tlsRootCertFiles ${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
上面的事务使用--peerAddresses
标志从Org1将peer0.org1.example.com
作为目标,从Org2将peer0.org2.example.com
作为目标。commit
交易被提交给加入通道的节点,以查询由操作节点的组织批准的链码定义。命令需要针对足够多组织的节点,以满足部署链码的策略。因为审批是在每个组织内分发的,所以您可以针对属于通道成员的任何节点。
通道成员的链码定义背书被提交到排序服务,以添加到块中并分发到通道。然后,通道上的节点验证是否有足够数量的组织批准了链码定义。peer lifecycle chaincode commit
命令将在返回响应之前等待节点的验证。
可以使用peer lifecycle chaincode querycommitted命令来确认chaincode定义已提交到通道。
peer lifecycle chaincode querycommitted --channelID mychannel --name fabcar --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
如果成功将链码提交到通道,querycommitted
命令将返回链码定义的序列和版本:
Committed chaincode definition for chaincode 'fabcar' on channel 'mychannel':
Version: 1, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc, Approvals: [Org1MSP: true, Org2MSP: true]
在将链码定义提交给通道后,链码将从连接到安装链码的通道的节点开始。Fabcar链码现在可以由客户端应用程序调用了。使用以下命令在账本上创建一组初始的汽车。注意invoke命令需要有足够数量的节点来满足链码背书策略。
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n fabcar --peerAddresses localhost:7051 --tlsRootCertFiles ${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"initLedger","Args":[]}'
如果以下命令响应成功,则应该能够:
2020-02-12 18:22:20.576 EST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
因为我们在链码定义中包含了--init required
标志,所以第一个交易需要通过将--is Init
标志传递给invoke命令来初始化链码。因为Fabcar智能合约使用Fabric contract API,所以第一个交易可以针对链码中的任何函数。如果我们使用的是Fabric-Chaincode-Shim-API提供的低级API,那么交易将需要以Init函数为目标。链码的第一次调用,无论是Init还是另一个函数,都要遵守链码背书策略。
我们可以使用查询函数来读取由链码创建的汽车集:
peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryAllCars"]}'
对查询的响应应为以下车辆列表:
[{
"Key":"CAR0","Record":{
"make":"Toyota","model":"Prius","colour":"blue","owner":"Tomoko"}},
{
"Key":"CAR1","Record":{
"make":"Ford","model":"Mustang","colour":"red","owner":"Brad"}},
{
"Key":"CAR2","Record":{
"make":"Hyundai","model":"Tucson","colour":"green","owner":"Jin Soo"}},
{
"Key":"CAR3","Record":{
"make":"Volkswagen","model":"Passat","colour":"yellow","owner":"Max"}},
{
"Key":"CAR4","Record":{
"make":"Tesla","model":"S","colour":"black","owner":"Adriana"}},
{
"Key":"CAR5","Record":{
"make":"Peugeot","model":"205","colour":"purple","owner":"Michel"}},
{
"Key":"CAR6","Record":{
"make":"Chery","model":"S22L","colour":"white","owner":"Aarav"}},
{
"Key":"CAR7","Record":{
"make":"Fiat","model":"Punto","colour":"violet","owner":"Pari"}},
{
"Key":"CAR8","Record":{
"make":"Tata","model":"Nano","colour":"indigo","owner":"Valeria"}},
{
"Key":"CAR9","Record":{
"make":"Holden","model":"Barina","colour":"brown","owner":"Shotaro"}}]
您可以使用相同的Fabric链码生命周期过程来升级已部署到通道的链码。通道成员可以通过安装新的链码包,然后使用新的包ID、新的链码版本以及序列号递增1来批准链码定义,从而升级链码。在将链码定义提交给通道后,可以使用新的链码。此过程允许通道成员在链代码升级时进行协调,并确保在将新链代码部署到通道之前有足够数量的通道成员准备好使用它。
通道成员还可以使用升级过程更改链码背书策略。通过使用新的背书策略批准链码定义并将链码定义提交给通道,通道成员可以更改管辖链码的背书策略,而无需安装新的链码包。
为了提供一个升级我们刚刚部署的Fabcar链码的场景,让我们假设Org1和Org2希望安装用另一种语言编写的链码版本。他们将使用Fabric链码生命周期来更新链码版本,并确保两个组织都已在新链码在通道上激活之前安装了新链码。
我们将假设Org1和Org2最初安装了Fabcar链码的GO版本,但是使用JavaScript编写的链码会更舒服。第一步是打包Fabcar链码的JavaScript版本。如果在学习本教程时使用JavaScript指令来打包链码,那么可以按照打包用Go或Java编写的链码的步骤安装新的链码二进制文件。
从test-network
目录发出以下命令以安装链码依赖项。
cd ../chaincode/fabcar/javascript
npm install
cd ../../../test-network
然后,您可以发出以下命令从test-network
目录打包JavaScript链代码。我们将设置再次使用peer
CLI所需的环境变量,以防您关闭终端。
export PATH=${
PWD}/../bin:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
peer lifecycle chaincode package fabcar_2.tar.gz --path ../chaincode/fabcar/javascript/ --lang node --label fabcar_2
运行以下命令以Org1管理员身份操作peer
CLI:
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
我们现在可以使用以下命令在Org1节点上安装新的链码包。
peer lifecycle chaincode install fabcar_2.tar.gz
新的链码包将创建一个新的包ID。我们可以通过查询节点来找到新的包ID。
peer lifecycle chaincode queryinstalled
queryinstalled
命令将返回已安装在节点上的链码列表。
Installed chaincodes on peer:
Package ID: fabcar_1:69de748301770f6ef64b42aa6bb6cb291df20aa39542c3ef94008615704007f3, Label: fabcar_1
Package ID: fabcar_2:1d559f9fb3dd879601ee17047658c7e0c84eab732dca7c841102f20e42a9e7d4, Label: fabcar_2
您可以使用package标签来查找新链码的包ID,并将其另存为新的环境变量。
export NEW_CC_PACKAGE_ID=fabcar_2:1d559f9fb3dd879601ee17047658c7e0c84eab732dca7c841102f20e42a9e7d4
Org1现在可以批准新的链码定义:
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name fabcar --version 2.0 --package-id $NEW_CC_PACKAGE_ID --sequence 2 --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
新的链码定义使用JavaScript链码包的包ID并更新链码版本。因为Fabric链码生命周期使用sequence参数来跟踪链码升级,Org1还需要将序列号从1增加到2。可以使用peer lifecycle chaincode querycommitted命令查找上次提交到通道的链码序列。
我们现在需要安装chaincode包并将chaincode定义批准为Org2,以便升级chaincode。运行以下命令以Org2管理员身份操作peer
CLI:
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
我们现在可以使用以下命令在Org2节点上安装新的链码包。
peer lifecycle chaincode install fabcar_2.tar.gz
现在可以批准Org2的新链码定义。
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name fabcar --version 2.0 --package-id $NEW_CC_PACKAGE_ID --sequence 2 --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
使用peer lifecycle chaincode checkcommitreality命令检查序列2的链码定义是否已准备好提交到通道:
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name fabcar --version 2.0 --sequence 2 --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --output json
如果命令返回以下JSON,则可以升级链码:
{
"Approvals": {
"Org1MSP": true,
"Org2MSP": true
}
}
在提交新的链码定义后,链码将在通道上升级。在此之前,之前的链码将继续在两个组织的节点上运行。Org2可以使用以下命令升级链码:
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name fabcar --version 2.0 --sequence 2 --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses localhost:7051 --tlsRootCertFiles ${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
成功的提交交易将立即启动新的链码。如果链码定义改变了背书策略,新的策略就会生效。
您可以使用docker ps
命令验证新的链码是否已在节点上启动:
$docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
197a4b70a392 dev-peer0.org1.example.com-fabcar_2-1d559f9fb3dd879601ee17047658c7e0c84eab732dca7c841102f20e42a9e7d4-d305a4e8b4f7c0bc9aedc84c4a3439daed03caedfbce6483058250915d64dd23 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes dev-peer0.org1.example.com-fabcar_2-1d559f9fb3dd879601ee17047658c7e0c84eab732dca7c841102f20e42a9e7d4
b7e4dbfd4ea0 dev-peer0.org2.example.com-fabcar_2-1d559f9fb3dd879601ee17047658c7e0c84eab732dca7c841102f20e42a9e7d4-9de9cd456213232033c0cf8317cbf2d5abef5aee2529be9176fc0e980f0f7190 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes dev-peer0.org2.example.com-fabcar_2-1d559f9fb3dd879601ee17047658c7e0c84eab732dca7c841102f20e42a9e7d4
8b6e9abaef8d hyperledger/fabric-peer:latest "peer node start" About an hour ago Up About an hour 0.0.0.0:7051->7051/tcp peer0.org1.example.com
429dae4757ba hyperledger/fabric-peer:latest "peer node start" About an hour ago Up About an hour 7051/tcp, 0.0.0.0:9051->9051/tcp peer0.org2.example.com
7de5d19400e6 hyperledger/fabric-orderer:latest "orderer" About an hour ago Up About an hour 0.0.0.0:7050->7050/tcp orderer.example.com
如果使用了--init-required
标志,则需要在使用升级后的链码之前调用init函数。因为我们没有请求执行Init,所以我们可以通过创建一辆新车来测试新的JavaScript链码:
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n fabcar --peerAddresses localhost:7051 --tlsRootCertFiles ${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"createCar","Args":["CAR11","Honda","Accord","Black","Tom"]}'
您可以再次查询账本上的所有车辆,以查看新车:
peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryAllCars"]}'
您应该可以从JavaScript链码中看到以下结果:
[{
"Key":"CAR0","Record":{
"make":"Toyota","model":"Prius","colour":"blue","owner":"Tomoko"}},
{
"Key":"CAR1","Record":{
"make":"Ford","model":"Mustang","colour":"red","owner":"Brad"}},
{
"Key":"CAR11","Record":{
"color":"Black","docType":"car","make":"Honda","model":"Accord","owner":"Tom"}},
{
"Key":"CAR2","Record":{
"make":"Hyundai","model":"Tucson","colour":"green","owner":"Jin Soo"}},
{
"Key":"CAR3","Record":{
"make":"Volkswagen","model":"Passat","colour":"yellow","owner":"Max"}},
{
"Key":"CAR4","Record":{
"make":"Tesla","model":"S","colour":"black","owner":"Adriana"}},
{
"Key":"CAR5","Record":{
"make":"Peugeot","model":"205","colour":"purple","owner":"Michel"}},
{
"Key":"CAR6","Record":{
"make":"Chery","model":"S22L","colour":"white","owner":"Aarav"}},
{
"Key":"CAR7","Record":{
"make":"Fiat","model":"Punto","colour":"violet","owner":"Pari"}},
{
"Key":"CAR8","Record":{
"make":"Tata","model":"Nano","colour":"indigo","owner":"Valeria"}},
{
"Key":"CAR9","Record":{
"make":"Holden","model":"Barina","colour":"brown","owner":"Shotaro"}}]
使用完链码后,还可以使用以下命令删除logspoute工具。
docker stop logspout
docker rm logspout
然后,您可以通过从test-network
目录发出以下命令来关闭测试网络:
./network.sh down
编写智能合约并将其部署到通道后,可以使用Fabric sdk提供的api从客户机应用程序调用智能合约。这允许最终用户与区块链账本上的资产进行交互。要开始使用Fabric sdk,请参阅编写第一个应用程序教程。
注意:如果您还不熟悉Fabric网络的基本架构,在继续之前,您可能需要访问“关键概念”部分。
还值得注意的是,本教程是对Fabric应用程序的介绍,并使用简单的智能合约和应用程序。要更深入地了解Fabric应用程序和智能合约,请查看我们的开发应用程序部分或商业票据教程。
本教程介绍Fabric应用程序如何与部署的区块链网络交互。本教程使用使用Fabric SDK构建的示例程序(在应用程序主题中有详细描述)来调用智能合约,该智能合约使用智能合约API查询和更新账本,在智能合约处理中有详细说明。我们还将使用我们的示例程序和已部署的证书颁发机构来生成应用程序与许可区块链交互所需的X.509证书。示例应用程序及其调用的智能合约统称为FabCar。
我们将通过三个主要步骤:
完成本教程后,您应该基本了解结构应用程序和智能合约如何协同工作来管理区块链网络分布式账本上的数据。
除了Fabric的标准先决条件外,本教程还利用Hyperledger Fabric SDK Node.js。 见Node.js SDK README文件获取最新的先决条件列表。
如果您正在使用macOS,请完成以下步骤:
brew install node
下载node的最新版本或选择特定版本,例如:brew install node@10
根据先决条件中支持的内容。npm install
。如果您在Windows上,可以使用npm安装Windows构建工具,npm通过运行以下命令安装所有必需的编译器和工具:
npm install --global windows-build-tools
如果你在Linux上,你需要安装Python V2.7,make,和一个C/C++编译器工具链,比如GCC。您可以运行以下命令来安装其他工具:
sudo apt install build-essentials
如果您已经使用了Fabric test network教程并启动并运行了一个网络,那么在启动新的网络之前,本教程将先关闭正在运行的网络。
启动网络
本教程演示了FabCar智能合约和应用程序的JavaScript版本,但fabric-samples
repo还包含了此示例的Go、Java和TypeScript版本。要尝试Go、Java或TypeScript版本,请将下面./startFabric.sh
的javascript
参数更改为Go、Java或TypeScript,并按照写入终端的说明进行操作。
导航到fabric-samples
repo本地克隆中的fabcar
子目录。
cd fabric-samples/fabcar
使用startFabric.sh
shell脚本启动网络。
./startFabric.sh javascript
此命令将使用两个节点和一个排序服务来部署Fabric测试网络。我们不使用cryptogen工具,而是使用证书颁发机构启动测试网络。我们将使用这些CA中的一个来创建证书和密钥,这些证书和密钥将在以后的步骤中由我们的应用程序使用。这个startFabric.sh
脚本还将在通道mychannel上部署和初始化FabCar智能合约的JavaScript版本,然后调用智能合约将初始数据放入账本。
安装应用程序
从fabric-samples
中的fabcar
目录中,导航到javascript
文件夹。
cd javascript
此目录包含使用Fabric SDK为开发的示例程序Node.js. 运行以下命令以安装应用程序依赖项。大约需要一分钟才能完成:
npm install
此过程正在安装package.json
中定义的关键应用程序依赖项。其中最重要的是fabric-network
类;它使应用程序能够使用身份、钱包和网关连接到通道、提交交易和等待通知。本教程还使用fabric-ca-client
类向用户注册其各自的证书颁发机构,生成一个有效的标识,然后由fabric-network
类方法使用。
一旦npm install
完成,就可以运行应用程序了。让我们看看本教程中将使用的示例JavaScript应用程序文件:
ls
您应该看到以下内容:
enrollAdmin.js node_modules package.json registerUser.js
invoke.js package-lock.json query.js wallet
还有其他程序语言的文件,例如在fabcar/java
目录中。一旦您使用了JavaScript示例,您就可以阅读这些内容—原理是相同的。
注意:以下两个部分涉及到与证书颁发机构的通信。当运行即将到来的程序时,打开一个新的终端shell并运行docker logs -f ca_org1
,您可能会发现流化CA日志很有用。
当我们创建网络时,创建了一个管理员用户(字面上叫admin
),作为证书颁发机构(CA)的注册人。我们的第一步是使用enroll.js
程序为admin
生成私钥、公钥和X.509证书。此过程使用证书签名请求(CSR)-首先在本地生成私钥和公钥,然后将公钥发送到CA,后者返回一个编码的证书供应用程序使用。这些凭证随后存储在钱包中,允许我们充当CA的管理员。
注册用户admin
:
node enrollAdmin.js
此命令将CA管理员的凭据存储在wallet
目录中。您可以在钱包中找到管理员证书和wallet/admin.id
文件。
我们的管理员用于与CA合作。现在我们在钱包中有了管理员的凭据,我们可以创建一个新的应用程序用户,该用户将用于与区块链交互。运行以下命令注册并登记名为appUser
的新用户:
node registerUser.js
与管理员注册类似,此程序使用CSR注册appUser
并将其凭据与管理员凭据一起存储在钱包中。我们现在有了两个独立用户的身份-admin
和appUser
-可以被我们的应用程序使用。
区块链网络中的每个节点都拥有账本的副本。应用程序可以通过对节点上运行的智能合约(称为查询)的只读调用来查看账本中的最新数据。
以下是查询工作方式的简化表示:
最常见的查询涉及账本中数据的当前值-其世界状态。世界状态表示为一组键值对,应用程序可以查询单个键或多个键的数据。此外,当您使用CouchDB作为状态数据库并用JSON为数据建模时,您可以使用复杂的查询来读取账本上的数据。这在查找与特定关键字和特定值匹配的所有资产时非常有用;例如,具有特定所有者的所有汽车。
首先,让我们运行query.js
返回账本上所有汽车的列表的程序。此程序使用我们的第二个身份-appUser
访问账本:
node query.js
输出应如下所示:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
[{
"Key":"CAR0","Record":{
"color":"blue","docType":"car","make":"Toyota","model":"Prius","owner":"Tomoko"}},
{
"Key":"CAR1","Record":{
"color":"red","docType":"car","make":"Ford","model":"Mustang","owner":"Brad"}},
{
"Key":"CAR2","Record":{
"color":"green","docType":"car","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},
{
"Key":"CAR3","Record":{
"color":"yellow","docType":"car","make":"Volkswagen","model":"Passat","owner":"Max"}},
{
"Key":"CAR4","Record":{
"color":"black","docType":"car","make":"Tesla","model":"S","owner":"Adriana"}},
{
"Key":"CAR5","Record":{
"color":"purple","docType":"car","make":"Peugeot","model":"205","owner":"Michel"}},
{
"Key":"CAR6","Record":{
"color":"white","docType":"car","make":"Chery","model":"S22L","owner":"Aarav"}},
{
"Key":"CAR7","Record":{
"color":"violet","docType":"car","make":"Fiat","model":"Punto","owner":"Pari"}},
{
"Key":"CAR8","Record":{
"color":"indigo","docType":"car","make":"Tata","model":"Nano","owner":"Valeria"}},
{
"Key":"CAR9","Record":{
"color":"brown","docType":"car","make":"Holden","model":"Barina","owner":"Shotaro"}}]
让我们仔细看看query.js程序使用Fabric Node SDK提供的APIs与我们的Fabric网络进行交互。使用编辑器(例如atom或visual studio)打开query.js。
应用程序首先从fabric-network
模块引入两个关键类:Wallets
和Gateway
。这些类将用于定位钱包中的appUser
标识,并使用它连接到网络:
const {
Gateway, Wallets } = require('fabric-network');
首先,程序使用Wallet类从文件系统中获取应用程序用户。
const identity = await wallet.get('appUser');
一旦程序有了标识,它就使用Gateway类连接到我们的网络。
const gateway = new Gateway();
await gateway.connect(ccpPath, {
wallet, identity: 'appUser', discovery: {
enabled: true, asLocalhost: true } });
ccpPath
描述了连接配置文件的路径,我们的应用程序将使用它来连接到我们的网络。连接配置文件从fabric-samples/test network
目录中加载并解析为JSON文件:
const ccpPath = path.resolve(__dirname, '..', '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json');
如果您想了解更多关于连接配置文件的结构以及它如何定义网络的信息,请参阅连接配置文件主题。
一个网络可以分为多个通道,下一个重要的代码行将应用程序连接到网络中的一个特定通道mychannel
,我们的智能合约部署在这里:
const network = await gateway.getNetwork('mychannel');
在这个通道中,我们可以访问FabCar智能合约与账本进行交互:
const contract = network.getContract('fabcar');
在FabCar中有许多不同的交易,我们的应用程序最初使用queryAllCars
交易来访问ledger world state数据:
const result = await contract.evaluateTransaction('queryAllCars');
evaluateTransaction
方法代表了区块链网络中与智能合约最简单的交互之一。它只需选择一个在连接配置文件中定义的节点,并将请求发送给它,然后在那里对其进行评估。智能合约查询节点账本副本上的所有汽车,并将结果返回给应用程序。此交互不会导致更新账本。
让我们看看FabCar智能合约中的交易。打开一个新终端并导航到fabric-samples
存储库中FabCar智能合约的JavaScript版本:
cd 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。
下面是应用程序如何调用智能合约中的不同交易的表示。每个交易都使用一组广泛的APIs(如getStateByRange
)与账本交互。您可以详细了解这些APIs。
我们可以看到queryAllCars
交易和另一个名为createCar
的交易。我们将在后面的教程中使用它来更新账本,并向区块链添加新的区块。
但首先,返回查询
程序并将evaluateTransaction
请求更改为queryCAR4
。查询
程序现在应该如下所示:
const result = await contract.evaluateTransaction('queryCar', 'CAR4');
保存程序并导航回fabcar/javascript
目录。现在再次运行查询
程序:
node query.js
您应该看到以下内容:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
{
"color":"black","docType":"car","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的黑色本田雅阁。我们在这里使用CAR12
作为识别键,只是为了表明我们不需要使用顺序键。
保存并运行程序:
node invoke.js
如果调用成功,您将看到如下输出:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been submitted
请注意invoke
应用程序是如何使用submitTransaction
API(而不是evaluateTransaction
API)与区块链网络进行交互的。
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
submitTransaction
比evaluateTransaction
复杂得多。SDK不会与单个节点交互,而是将submitTransaction
提案发送给区块链网络中每个所需组织的节点。每个节点将使用此提案执行请求的智能合约,以生成一个交易响应,由它签名并返回给SDK。SDK将所有签名的交易响应收集到单个交易中,然后将其发送给排序节点。排序程序从每个应用程序收集交易并将其排序为一个交易块。然后,它将这些块分发到网络中的每个节点,在那里每个交易都被验证和提交。最后,通知SDK,允许它将控制权返回给应用程序。
submitTransaction
还包括一个监听器,该监听器检查交易是否已被验证并提交到账本。应用程序要么利用确认侦听器,要么利用像submitTransaction
这样的API为您完成这项工作。如果不这样做,您的交易可能无法成功排序、验证和提交到账本。
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:
{
"color":"Black","docType":"car","make":"Honda","model":"Accord","owner":"Tom"}
祝贺 你。您已经创建了一辆汽车并验证了它是否记录在账本上!
现在我们已经做到了,让我们假设汤姆是慷慨的,他想把他的本田雅阁送给一个叫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:
{
"color":"Black","docType":"car","make":"Honda","model":"Accord","owner":"Dave"}
CAR12
的所有权已由汤姆改为戴夫。
注意:在实际应用程序中,智能合约可能有一些访问控制逻辑。例如,只有某些授权用户可以创建新车,并且只有车主可以将汽车转让给其他人。
当您使用完FabCar示例后,可以使用networkDown.sh
脚本。
./networkDown.sh
此命令将关闭我们创建的网络的CA、节点和排序节点。它还将删除存储在wallet
目录中的admin
和appUser
加密材料。请注意,账本上的所有数据都将丢失。如果您想再次学习本教程,您将从一个干净的初始状态开始。
现在我们已经完成了一些查询和一些更新,您应该对应用程序如何使用智能合约查询或更新账本与区块链网络交互有很好的了解。您已经了解了智能合约、APIs和SDK在查询和更新中扮演的角色的基本知识,您应该了解如何使用不同类型的应用程序来执行其他业务任务和操作。
正如我们在引言中所说,我们有一个完整的关于开发应用程序的部分,其中包括有关智能合约、流程和数据设计的深入信息、使用更深入的商业票据教程的教程以及大量与应用程序开发相关的其他材料。
受众:架构师、应用程序和智能合约开发人员、管理员
本教程将向您展示如何安装和使用商业票据示例应用程序和智能合约。它是一个面向任务的主题,所以它强调程序高于概念。如果您想更详细地理解这些概念,可以阅读“开发应用程序”主题。
在本教程中,两个组织,magneticorp和DigiBank,使用PaperNet(一个Hyperledger Fabric区块链网络)相互交易商业票据。
一旦你建立了测试网络,你将扮演伊莎贝拉(Isabella),一位magneticorp的员工,她将代表公司发行一份商业票据。然后,你将转换角色,扮演DigiBank员工Balaji的角色,他将购买这张商业票据,持有一段时间,然后用MagnetorCorp赎回它以获得微利。
您将作为开发人员、最终用户和管理员,分别在不同的组织中执行以下步骤,这些步骤旨在帮助您理解作为两个独立工作的组织进行协作是什么感觉,但是要根据Hyperledger Fabric网络中相互约定的规则。
本教程已经在MacOS和Ubuntu上进行了测试,应该可以在其他Linux发行版上使用。Windows版本正在开发中。
在开始之前,必须安装本教程所需的一些必备技术。我们把这些都控制在最低限度,这样你就可以很快地走了。
必须安装以下技术:
您将发现安装以下技术很有帮助:
许多优秀的代码编辑器可用,包括Atom、Sublime Text和Brackets。
随着您对应用程序和智能合约开发越来越有经验,安装以下技术可能会有所帮助。第一次运行本教程时不需要安装这些:
商业票据教程是fabric-samples
目录中的一个示例。在开始本教程之前,请确保已按照说明安装Fabric先决条件并下载示例、二进制文件和Docker镜像。完成后,您将克隆包含教程脚本、智能合约和应用程序文件的fabric-samples
存储库。
将fabric-samples
GitHub存储库下载到本地计算机。
下载后,请随意查看fabric-samples
的目录结构:
$ cd fabric-samples
$ ls
CODEOWNERS basic-network first-network
CODE_OF_CONDUCT.md chaincode high-throughput
CONTRIBUTING.md chaincode-docker-devmode interest_rate_swaps
Jenkinsfile ci off_chain_data
LICENSE ci.properties scripts
MAINTAINERS.md commercial-paper test-network
README.md docs
SECURITY.md fabcar
注意commercial-paper
目录-这是我们的样本所在的地方!
现在您已经完成了教程的第一阶段!继续操作时,将为不同的用户和组件打开多个命令窗口。例如:
我们将明确说明何时应该从特定的命令窗口运行命令;例如:
(isabella)$ ls
指示您应该从Isabella的窗口运行ls
命令。
本教程将使用Fabric测试网络部署智能合约。测试网络由两个普通节点组织和一个排序组织组成。两个普通节点组织分别运行一个普通节点,而排序组织运行单节点raft排序服务。我们还将使用测试网络创建一个名为mychannel
的单一通道,两个节点组织都将成为其成员。
Fabric测试网络由两个节点组织Org1和Org2组成,每个组织都有一个节点组织及其账本数据库,一个排序节点。每个组件都作为Docker容器运行。
这两个节点,即节点账本、排序节点和CA各自运行在各自的Docker容器中。在生产环境中,组织通常使用与其他系统共享的现有CA;它们不专用于Fabric网络。
测试网络的两个组织允许我们与区块链账本作为两个组织进行交互,这两个组织分别操作不同的节点。在本教程中,我们将把测试网络的Org1操作为DigiBank,Org2作为magneticorp。
您可以启动测试网络并使用商业文件目录中提供的脚本创建通道。更改到fabric-samples
中的commercial-paper
目录:
cd fabric-samples/commercial-paper
然后使用脚本启动测试网络:
./network-starter.sh
如果命令成功,您将在日志中看到正在创建的测试网络。您可以使用docker ps
命令查看本地计算机上运行的Fabric节点:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
321cc489b10f hyperledger/fabric-peer:latest "peer node start" 2 minutes ago Up 2 minutes 0.0.0.0:7051->7051/tcp peer0.org1.example.com
ad668671f95f hyperledger/fabric-peer:latest "peer node start" 2 minutes ago Up 2 minutes 7051/tcp, 0.0.0.0:9051->9051/tcp peer0.org2.example.com
caadbe4d8592 hyperledger/fabric-couchdb "tini -- /docker-ent…" 2 minutes ago Up 2 minutes 4369/tcp, 9100/tcp, 0.0.0.0:7984->5984/tcp couchdb1
ebabe52903b8 hyperledger/fabric-couchdb "tini -- /docker-ent…" 2 minutes ago Up 2 minutes 4369/tcp, 9100/tcp, 0.0.0.0:5984->5984/tcp couchdb0
7c72711c6e18 hyperledger/fabric-orderer:latest "orderer" 2 minutes ago Up 2 minutes 0.0.0.0:7050->7050/tcp orderer.example.com
查看是否可以将这些容器映射到测试网络的节点(您可能需要水平滚动以定位信息):
peer0.org1.example.com
正在容器321cc489b10f
中运行peer0.org2.example.com
正在容器ad668671f95f
中运行couchdb0
正在容器ebabe52903b8
中运行couchdb1
正在容器caadbe4d8592
中运行order.example.com
正在容器7c72711c6e18
中运行这些容器都构成了一个称为net_test
的Docker网络。您可以使用docker network
命令查看网络:
$ docker network inspect net_test
{
"Name": "net_test",
"Id": "b77b99d29e37677fac48b7ecd78383bdebf09ebdd6b00e87e3d9444252b1ce31",
"Created": "2020-01-30T23:04:39.6157465Z",
"Containers": {
"321cc489b10ff46554d0b215da307d38daf35b68bbea635ae0ae3176c3ae0945": {
"Name": "peer0.org1.example.com",
"IPv4Address": "192.168.224.5/20",
},
"7c72711c6e18caf7bff4cf78c27efc9ef3b2359a749c926c8aba1beacfdb0211": {
"Name": "orderer.example.com",
"IPv4Address": "192.168.224.4/20",
},
"ad668671f95f351f0119320198e1d1e19ebbb0d75766c6c8b9bb7bd36ba506af": {
"Name": "peer0.org2.example.com",
"IPv4Address": "192.168.224.6/20",
},
"caadbe4d8592aa558fe14d07a424a9e04365620ede1143b6ce5902ce038c0851": {
"Name": "couchdb1",
"IPv4Address": "192.168.224.2/20",
},
"ebabe52903b8597d016dbc0d0ca4373ef75162d3400efbe6416975abafd08a8f": {
"Name": "couchdb0",
"IPv4Address": "192.168.224.3/20",
}
},
"Labels": {
}
}
]
了解这五个容器如何使用不同的IP地址,同时作为单个Docker网络的一部分。(为了清楚起见,我们对输出进行了简化。)
因为我们使用的测试网络是DigiBank和magneticorp,peer0.org1.example.com
将属于DigiBank组织,而peer0.org2.example.com
将由magneticorp运营。现在测试网络已经启动并运行,我们可以从现在开始将我们的网络称为PaperNet。
回顾一下:您已经从GitHub下载了Hyperledger Fabric samples存储库,并且您已经在本地计算机上运行了测试网络。现在让我们开始扮演magneticorp的角色,他们希望交易商业票据。
商业票据教程可以让你扮演两个组织的角色,为DigiBank和magneticorp提供两个单独的文件夹。这两个文件夹包含每个组织的智能合约和应用程序文件。由于两个交易机构在商业文件中的作用不同,因此两个机构在商业文件中的作用是不同的。在fabric-samples
存储库中打开一个新窗口,然后使用以下命令切换到MagnetorCorp目录:
cd commercial-paper/organization/magnetocorp
作为magneticorp,我们要做的第一件事就是监控PaperNet的组件。管理员可以使用logspout
工具查看一组Docker容器的聚合输出。该工具将不同的输出流收集到一个位置,从而可以很容易地从单个窗口查看正在发生的事情。例如,这对于安装智能合约的管理员或调用智能合约的开发人员非常有用。
在magneticorp目录中,运行以下命令来运行monitordocker.sh
为在net
上运行的PaperNet关联的容器编写脚本并启动logspout
工具:
(magnetocorp admin)$ ./configuration/cli/monitordocker.sh net_test
...
latest: Pulling from gliderlabs/logspout
4fe2ade4980c: Pull complete
decca452f519: Pull complete
(...)
Starting monitoring on all containers on the network net_test
b7f3586e5d0233de5a454df369b8eadab0613886fc9877529587345fc01a3582
请注意,如果monitordocker.sh
中的默认端口已在使用中,则可以将端口号传递给上述命令。
(magnetocorp admin)$ ./monitordocker.sh net_test <port_number>
此窗口现在将显示本教程剩余部分的Docker容器的输出,因此继续并打开另一个命令窗口。下一步我们要做的是研究magneticorp将用来向商业票据发行的智能合约。
发行
、买入
和赎回
是商业票据智能合约的三大核心功能。它被应用程序用来提交交易,相应地发行、买入和赎回账本上的商业票据。我们的下一个任务是研究这个智能合约。
在fabric-samples
目录中打开一个新的终端,然后切换到magneticorp文件夹,充当magneticorp的开发者。
cd commercial-paper/organization/magnetocorp
然后,可以使用所选编辑器(本教程中的VS Code)在contract
目录中查看智能合约:
(magnetocorp developer)$ code contract
在文件夹的lib
目录中,您将看到papercontract.js
文件–包含商业票据智能合约!
在中显示商业票据智能合约的示例代码编辑器papercontract.js
。
papercontract.js
是一个JavaScript程序,用于在Node.js环境。请注意以下关键程序行:
const { Contract, Context } = require('fabric-contract-api');
Contract
和Context
。您可以在fabric-shim
JSDOCS中了解有关这些类的更多信息。class CommercialPaperContract extends Contract {
contract
类定义智能合约类CommercialPaperContract
。此类中定义了实现发行
、买入
和赎回
商业票据的关键交易的方法。async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime...) {
发行
交易。传递给此方法的参数将用于创建新的商业票据。买入
和赎回
交易。let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime...);
issue
交易中,此语句使用CommercialPaper
类和提供的交易输入在内存中创建一个新的商业票据。检查购买
和赎回
交易,以了解它们如何类似地使用此类。await ctx.paperList.addPaper(paper);
ctx.paperList
,在初始化智能合约上下文CommercialPaperContext
时创建的PaperList类的实例。再次检查buy
和redemble
方法,看看它们是如何使用这个类的。return paper;
issue
交易返回二进制缓冲区作为响应,供智能合约的调用方处理。请随意检查contract
目录中的其他文件,以了解智能合约是如何工作的,并详细阅读如何papercontract.js
在智能合约主题中设计。
在应用程序可以调用papercontract
之前,必须将其安装到测试网络的相应节点上,然后使用Fabric chaincode lifecycle在通道上进行定义。Fabric chaincode lifecycle允许多个组织在chainocde部署到通道之前同意chaincode的参数。因此,我们需要以magneticorp和DigiBank的管理员身份安装和批准链码。
magneticorp管理员在magneticorp节点上安装一份papercontract
的副本。
智能合约是应用程序开发的重点,包含在一个名为链码的Hyperledger Fabric构件中。一个或多个智能合约可以在一个链码内定义,安装一个链码将允许PaperNet中的不同组织使用它们。这意味着只有管理员才需要担心链码;其他人都可以从智能合约的角度来思考。
安装并批准作为magneticorp的智能合约
我们将首先安装和批准作为magneticorp管理智能合同。确保您是从magneticorp文件夹操作,或使用以下命令导航回该文件夹:
cd commercial-paper/organization/magnetocorp
magneticorp管理员可以使用peer
CLI与PaperNet交互。但是,管理员需要在命令窗口中设置某些环境变量,以使用正确的peer
二进制文件集,将命令发送到magneticorp节点的地址,并使用正确的加密材料对请求进行签名。
可以使用示例提供的脚本在命令窗口中设置环境变量。在magneticorp
目录中运行以下命令:
source magnetocorp.sh
您将看到在窗口中打印的环境变量的完整列表。我们现在可以使用这个命令窗口作为magneticorp管理员与PaperNet交互。
第一步是安装papercontract
智能合约。可以使用peer lifecycle chaincode package
命令将智能合约打包为链码。在MagnetoCorp管理员的命令窗口中,运行以下命令以创建链码包:
(magnetocorp admin)$ peer lifecycle chaincode package cp.tar.gz --lang node --path ./contract --label cp_0
magneticorp管理员现在可以使用peer lifecycle chaincode install
命令在magneticorp节点上安装链码:
(magnetocorp admin)$ peer lifecycle chaincode install cp.tar.gz
如果命令成功,您将在终端中看到类似于以下内容的消息:
2020-01-30 18:32:33.762 EST [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed remotely: response:<status:200 payload:"\nEcp_0:ffda93e26b183e231b7e9d5051e1ee7ca47fbf24f00a8376ec54120b1a2a335c\022\004cp_0" >
2020-01-30 18:32:33.762 EST [cli.lifecycle.chaincode] submitInstallProposal -> INFO 002 Chaincode code package identifier: cp_0:ffda93e26b183e231b7e9d5051e1ee7ca47fbf24f00a8376ec54120b1a2a335c
因为magneticorp管理员已经设置了CORE_PEER_ADDRESS=localhost:9051
到将它的命令定向到peer0.org2.example.com
,INFO 001 Installed remotely...
表明papercontract
已成功安装在此节点上。
安装智能合约后,我们需要批准papercontract
的链码定义为MagnetorCorp。第一步是找到我们在节点上安装的链码的package ID。我们可以使用peer lifecycle chaincode queryinstalled
命令查询package ID:
peer lifecycle chaincode queryinstalled
该命令将返回与install命令相同的包标识符。您将看到类似于以下内容的输出:
Installed chaincodes on peer:
Package ID: cp_0:ffda93e26b183e231b7e9d5051e1ee7ca47fbf24f00a8376ec54120b1a2a335c, Label: cp_0
在下一步中,我们需要包ID,因此我们将它保存为环境变量。对于所有用户,包ID可能不相同,因此您需要使用从命令窗口返回的包ID来完成此步骤。
export PACKAGE_ID=cp_0:ffda93e26b183e231b7e9d5051e1ee7ca47fbf24f00a8376ec54120b1a2a335c
管理员现在可以使用peer lifecycle chaincode approveformyorg
命令批准magneticorp的链码定义:
(magnetocorp admin)$ peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name papercontract -v 0 --package-id $PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
使用链码定义时,通道成员需要同意的最重要的链码参数之一是链码背书策略。背书策略描述了在确定交易有效之前必须背书(执行和签署)交易的一组组织。通过在没有--policy
标志的情况下批准papercontract
链码,magneticorp管理员同意使用默认的背书策略,该策略要求通道上的大多数组织对交易进行背书。所有交易,无论有效还是无效,都将记录在账本区块链上,但只有有效的交易才会更新世界状态。
安装并批准智能合约为DigiBank
默认情况下,Fabric链码生命周期要求通道上的大多数组织成功地将链码定义提交到通道。这意味着我们需要批准papernet
的链码为magneticorp和DigiBank,以获得所需的2/2的多数。在fabric-sample
中打开一个新的终端窗口,并导航到包含DigiBank智能合约和应用程序文件的旧版本:
(digibank admin)$ cd commercial-paper/organization/digibank/
使用DigiBank文件夹中的脚本设置环境变量,以允许您担任DigiBank管理员:
source digibank.sh
我们现在可以安装和批准papercontract
作为DigiBank。运行以下命令打包链码:
(digibank admin)$ peer lifecycle chaincode package cp.tar.gz --lang node --path ./contract --label cp_0
管理员现在可以在DigiBank节点上安装链码:
(digibank admin)$ peer lifecycle chaincode install cp.tar.gz
然后我们需要查询并保存刚刚安装的链码的package ID:
(digibank admin)$ peer lifecycle chaincode queryinstalled
将包ID另存为环境变量。使用从控制台返回的包ID完成此步骤。
export PACKAGE_ID=cp_0:ffda93e26b183e231b7e9d5051e1ee7ca47fbf24f00a8376ec54120b1a2a335c
Digibank管理员现在可以批准papercontract
的链码定义:
(digibank admin)$ peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name papercontract -v 0 --package-id $PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
将链码定义提交到通道
既然DigiBank和magneticorp都已经批准了papernet
链码,那么我们就有了将链码定义提交给通道所需的大部分(2/2)。一旦在通道上成功地定义了链码,则该通道上的客户端应用程序就可以调用papercontract
链码中的商业票据智能合约。由于任何一个组织都可以向通道提交链码,因此我们将继续以DigiBank管理员的身份运行:
在DigiBank管理员将papercontract
链码的定义提交给通道后,将创建一个新的Docker chaincode容器,以便在两个PaperNet节点上运行papercontract
。
DigiBank管理员使用peer lifecycle chaincode commit
命令将papercontract
的链码定义提交到mychannel
:
(digibank admin)$ peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --peerAddresses localhost:7051 --tlsRootCertFiles ${
PEER0_ORG1_CA} --peerAddresses localhost:9051 --tlsRootCertFiles ${
PEER0_ORG2_CA} --channelID mychannel --name papercontract -v 0 --sequence 1 --tls --cafile $ORDERER_CA --waitForEvent
chaincode容器将在chaincode定义提交到通道之后启动。您可以使用docker ps
命令查看papercontract
容器在两个节点上启动。
(digibank admin)$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d4ba9dc9c55f dev-peer0.org1.example.com-cp_0-ebef35e7f1f25eea1dcc6fcad5019477cd7f434c6a5dcaf4e81744e282903535-05cf67c20543ee1c24cf7dfe74abce99785374db15b3bc1de2da372700c25608 "docker-entrypoint.s…" 30 seconds ago Up 28 seconds dev-peer0.org1.example.com-cp_0-ebef35e7f1f25eea1dcc6fcad5019477cd7f434c6a5dcaf4e81744e282903535
a944c0f8b6d6 dev-peer0.org2.example.com-cp_0-1487670371e56d107b5e980ce7f66172c89251ab21d484c7f988c02912ddeaec-1a147b6fd2a8bd2ae12db824fad8d08a811c30cc70bc5b6bc49a2cbebc2e71ee "docker-entrypoint.s…" 31 seconds ago Up 28 seconds dev-peer0.org2.example.com-cp_0-1487670371e56d107b5e980ce7f66172c89251ab21d484c7f988c02912ddeaec
请注意,容器的命名是为了指示启动它的节点,以及它运行的是papercontract
版本0。
既然我们已经将papercontract
链码部署到了通道,我们就可以使用magneticorp应用程序来发行商业票据了。让我们花点时间检查一下应用程序结构。
包含在papercontract
中的智能合约由magneticorp的应用程序调用issue.js
。 Isabella使用此应用程序向发行商业票据00001
的账本提交交易。让我们快速检查一下issue
应用程序是如何工作的。
网关允许应用程序专注于交易生成、提交和响应。它协调不同网络组件之间的交易提案、排序和通知处理。
因为issue
应用程序代表Isabella提交交易,它首先从她的钱包中检索Isabella的X.509证书,该证书可能存储在本地文件系统或硬件安全模块HSM中。然后,issue
应用程序能够利用网关在通道上提交交易。网关提供了一个网关,Hyperledger Fabric SDK提供了网关抽象,因此应用程序可以在将网络交互委托给网关的同时,将重点放在应用程序逻辑上。网关和钱包使编写Hyperledger Fabric应用程序变得简单明了。
我们来看看Isabella将要使用的issue
应用程序。为她打开一个单独的终端窗口,在fabric-samples
中找到MagnetorCorp/application
文件夹:
(isabella)$ cd commercial-paper/organization/magnetocorp/application/
(isabella)$ ls
addToWallet.js issue.js package.json
addToWallet.js
是Isabella要用来把她的身份载入钱包的程序,而且issue.js
将使用此标识通过调用papercontract
代表MagnetorCorp创建商业票据00001
。
切换到包含magneticorp应用程序副本的目录issue.js
,并使用代码编辑器检查它:
(isabella)$ cd commercial-paper/organization/magnetocorp/application
(isabella)$ code issue.js
检查此目录;它包含问题应用程序及其所有依赖项。
显示商业票据应用程序目录内容的代码编辑器。
请注意中的以下关键程序行issue.js
:
const { Wallets, Gateway } = require('fabric-network');
Wallet
和Gateway
。const wallet = await Wallets.newFileSystemWallet('../identity/user/isabella/wallet');
isabella
钱包。因为Isabella的X.509证书在本地文件系统中,所以应用程序会创建一个新的FileSystemWallet
。应用程序将在isabella
wallet中选择特定的身份。await gateway.connect(connectionProfile, connectionOptions);
connectionProfile
标识的网关连接到网络,使用ConnectionOptions
中引用的标识。../gateway/networkConnection.yaml
和[email protected]
用于这些值。const network = await gateway.getNetwork('mychannel');
papercontract
先前已经实例化。const contract = await network.getContract('papercontract');
papercontract
链码。一旦应用程序发布了getContract,它就可以提交到链码内实现的任何智能合约交易。const issueResponse = await contract.submitTransaction('issue', 'MagnetoCorp', '00001', ...);
magneticorp
,00001
…是发行交易用来创建新商业票据的价值。let paper = CommercialPaper.fromBuffer(issueResponse);
issue
交易的响应。响应需要从缓冲区反序列化为paper
,一个可以被应用程序正确解释的商业票据对象。请随意检查/application
目录中的其他文件,以了解如何issue.js
工作,并详细阅读如何在应用程序主题中实现。
issue.js
应用程序是用JavaScript编写的,设计为在Node.js作为PaperNet网络客户端的环境。按照惯例,magneticorp的应用程序是建立在许多外部节点包上的,以提高开发的质量和速度。考虑如何issue.js
包括用于处理yaml网关连接配置文件的js-yaml
包,或用于访问Gateway
和Wallet
类的fabric-network
包:
const yaml = require('js-yaml');
const {
Wallets, Gateway } = require('fabric-network');
这些包必须使用npm install
命令从npm下载到本地文件系统。按照惯例,包必须安装到与应用程序相关的/node_modules
目录中,以便在运行时使用。
检查package.json
文件查看方式issue.js
标识要下载的软件包及其确切版本:
"dependencies": {
"fabric-network": "~1.4.0",
"fabric-client": "~1.4.0",
"js-yaml": "^3.12.0"
},
npm版本控制非常强大;您可以在这里了解更多。
让我们使用npm install
命令安装这些软件包–这可能需要一分钟的时间来完成:
(isabella)$ cd commercial-paper/organization/magnetocorp/application/
(isabella)$ npm install
( ) extract:lodash: sill extract ansi-styles@3.2.1
(...)
added 738 packages in 46.701s
查看此命令如何更新目录:
(isabella)$ ls
addToWallet.js node_modules package.json
issue.js package-lock.json
检查node_modules
目录以查看已安装的包。有很多,因为js-yaml
和fabric-network
本身是建立在其他npm包上的!很有帮助,package-lock.json
文件标识所安装的确切版本,如果您想精确地再现环境,例如测试、诊断问题或交付经验证的应用程序,则这些版本非常宝贵。
Isabella几乎准备好跑步了issue.js
发行magneticorp商业票据00001
;剩下的任务只有一个了!作为issue.js
它代表Isabella,因此也代表magneticorp,将使用她钱包中的身份来反映这些事实。我们现在需要执行这个一次性活动,在她的钱包中添加适当的X.509凭证。
在Isabella的终端窗口,运行addToWallet.js
为她的钱包添加身份信息的程序:
(isabella)$ node addToWallet.js
done
addToWallet.js
是一个简单的文件复制程序,您可以在空闲时检查它。它将一个身份从测试网络样本转移到伊莎贝拉的钱包中。让我们关注一下这个程序的结果-钱包的内容,它将用于向PaperNet提交交易:
(isabella)$ ls ../identity/user/isabella/wallet/
isabella.id
Isabella可以在她的钱包中存储多个身份,但在我们的示例中,她只使用一个。电子wallet
文件夹包含isabella.id
提供Isabella连接到网络所需的信息的文件。Isabella使用的其他身份都有自己的档案。您可以打开此文件以查看issue.js
将在JSON文件中代表Isabella使用。为了清楚起见,输出已格式化。
(isabella)$ cat ../identity/user/isabella/wallet/*
{
"credentials": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIICKTCCAdCgAwIBAgIQWKwvLG+sqeO3LwwQK6avZDAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDQxOTA5MDBaFw0zMDAyMDExOTA5MDBa\nMGwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMQ8wDQYDVQQLEwZjbGllbnQxHzAdBgNVBAMMFlVzZXIxQG9y\nZzIuZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT4TnTblx0k\ngfqX+NN7F76Me33VTq3K2NUWZRreoJzq6bAuvdDR+iFvVPKXbdORnVvRSATcXsYl\nt20yU7n/53dbo00wSzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADArBgNV\nHSMEJDAigCDOCdm4irsZFU3D6Hak4+84QRg1N43iwg8w1V6DRhgLyDAKBggqhkjO\nPQQDAgNHADBEAiBhzKix1KJcbUy9ey5ulWHRUMbqdVCNHe/mRtUdaJagIgIgYpbZ\nXf0CSiTXIWOJIsswN4Jp+ZxkJfFVmXndqKqz+VM=\n-----END CERTIFICATE-----\n",
"privateKey": "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQggs55vQg2oXi8gNi8\nNidE8Fy5zenohArDq3FGJD8cKU2hRANCAAT4TnTblx0kgfqX+NN7F76Me33VTq3K\n2NUWZRreoJzq6bAuvdDR+iFvVPKXbdORnVvRSATcXsYlt20yU7n/53db\n-----END PRIVATE KEY-----\n"
},
"mspId": "Org2MSP",
"type": "X.509",
"version": 1
}
在该文件中,您可以注意到以下内容:
"privateKey":
用来代表伊莎贝拉签署交易,但不能在她的直接控制之外分发。"certificate":
包含Isabella的公钥和证书颁发机构在创建证书时添加的其他X.509属性。此证书分发到网络,以便不同时间的不同参与者可以加密验证Isabella私钥创建的信息。你可以在这里了解证书的更多信息。在实践中,证书文件还包含一些特定于Fabric的元数据,如Isabella的组织和角色-在wallet主题中阅读更多。
Isabella现在可以使用issue.js
提交将发行MagnetorCorp商业票据00001
的交易:
(isabella)$ node issue.js
Connect to Fabric gateway.
Use network channel: mychannel.
Use org.papernet.commercialpaper smart contract.
Submit commercial paper issue transaction.
Process issue transaction response.{
"class":"org.papernet.commercialpaper","key":"\"MagnetoCorp\":\"00001\"","currentState":1,"issuer":"MagnetoCorp","paperNumber":"00001","issueDateTime":"2020-05-31","maturityDateTime":"2020-11-30","faceValue":"5000000","owner":"MagnetoCorp"}
MagnetoCorp commercial paper : 00001 successfully issued for value 5000000
Transaction complete.
Disconnect from Fabric gateway.
Issue program complete.
node
命令初始化Node.js
环境和运行issue.js
。 我们可以从程序输出中看到,MagnetoCorp商业票据00001的面值为500万美元。
如您所见,为了实现这一点,应用程序在papercontract.js
中调用商业票据智能合约中定义的issue
交易。这是由magneticorp管理员在网络中安装和实例化的。智能合约通过Fabric APIs与账本交互,最著名的是putState()
和getState()
,将新的商业票据表示为世界状态中的向量状态。我们将看到这个向量状态随后是如何被智能合约中定义的买入
和赎回
交易操纵的。
一直以来,底层Fabric SDK处理交易背书、排序和通知过程,使得应用程序的逻辑简单明了;SDK使用网关抽象出网络细节和连接选项来声明更高级的处理策略,例如交易重试。
现在让我们关注magneticorp 00001的生命周期,将重点转移到DigiBank的一名员工Balaji,他将使用DigiBank应用程序购买商业票据。
Balaji使用DigiBank的buy
应用程序向账本提交一笔交易,该交易将商业票据00001
的所有权从magneticorp转移到DigiBank。CommercialPaper
智能合约与magneticorp的应用程序使用的是相同的,不过这次的交易有所不同——它是买入
而不是发行
。让我们来看看DigiBank的应用程序是如何工作的。
为Balaji打开单独的终端窗口。在fabric-sample
中,切换到包含该应用程序的DigiBank应用程序目录,buy.js
,然后用编辑器打开它:
(balaji)$ cd commercial-paper/organization/digibank/application/
(balaji)$ code buy.js
如您所见,该目录包含Balaji将要使用的buy
和redeem
应用程序。
DigiBank的商业票据目录,其中包含buy.js
以及redeem.js
应用。
DigiBank的buy.js
应用程序在结构上与MagnetorCorp的issue.js
非常相似,但有两个重要区别:
Balaji
,而不是magneticorp的Isabella
const wallet = await Wallets.newFileSystemWallet('../identity/user/balaji/wallet');
balaji
钱包。buy.js
选择balaji
钱包中的特定身份。buy
而不是issue
const buyResponse = await contract.submitTransaction('buy', 'MagnetoCorp', '00001', ...);
buy
交易的值为magneticorp
,00001
,…,CommercialPaper
智能合约类使用这些值将商业票据00001
的所有权转移给DigiBank。请随意检查应用程序
目录中的其他文件,以了解应用程序的工作原理,并详细阅读如何工作buy.js
在应用程序主题中实现。
购买和赎回商业票据的DigiBank应用程序的结构与magneticorp的发行应用程序非常相似。因此,让我们安装它们的依赖项并设置Balaji的钱包,以便他可以使用这些应用程序购买和赎回商业票据。
像magneticorp一样,Digibank必须使用npm install
命令安装所需的应用程序包,而且,这需要很短的时间才能完成。
在DigiBank管理员窗口中,安装应用程序依赖项:
(digibank admin)$ cd commercial-paper/organization/digibank/application/
(digibank admin)$ npm install
( ) extract:lodash: sill extract ansi-styles@3.2.1
(...)
added 738 packages in 46.701s
在Balaji的命令窗口中,运行addToWallet.js
将身份添加到钱包的程序:
(balaji)$ node addToWallet.js
done
这个addToWallet.js
程序已经为balaji
的钱包添加了身份信息,这将被buy.js
以及redeem.js
向PaperNet
提交交易。
像Isabella一样,Balaji可以在他的钱包中存储多个身份,但在我们的示例中,他只使用一个。他只用了一个。他在digibank/identity/user/balaji/wallet/balaji.id
的对应身份证文件与Isabella
的非常相似,请随意查看。
Balaji现在可以使用buy.js
提交一项交易,将MagnetoCorp商业票据00001的所有权转让给DigiBank。
在Balaji的窗口中运行buy
应用程序:
(balaji)$ node buy.js
Connect to Fabric gateway.
Use network channel: mychannel.
Use org.papernet.commercialpaper smart contract.
Submit commercial paper buy transaction.
Process buy transaction response.
MagnetoCorp commercial paper : 00001 successfully purchased by DigiBank
Transaction complete.
Disconnect from Fabric gateway.
Buy program complete.
您可以看到程序输出,magneticorp商业票据00001是由Balaji代表DigiBank成功购买的。buy.js
调用在CommercialPaper
智能合约中定义的buy
交易,该合约使用putState()
和getState()
Fabric APIs在世界状态内更新了commercial paper 00001
。如您所见,购买和发行商业票据的应用程序逻辑非常相似,智能合约逻辑也是如此。
商业票据00001生命周期中的最后一笔交易是由DigiBank与MagnetorCorp赎回。Balaji使用redeem.js
提交交易以在智能合约中执行赎回逻辑。
在Balaji的窗口中运行redeem
交易:
(balaji)$ node redeem.js
Connect to Fabric gateway.
Use network channel: mychannel.
Use org.papernet.commercialpaper smart contract.
Submit commercial paper redeem transaction.
Process redeem transaction response.
MagnetoCorp commercial paper : 00001 successfully redeemed with MagnetoCorp
Transaction complete.
Disconnect from Fabric gateway.
Redeem program complete.
同样,请看商业票据00001是如何在redeem.js
调用商业票据中定义的赎回交易时成功赎回的。再次,它更新了世界状态的商业票据00001,以反映所有权归该票据发行人magneticorp所有。
使用完商业票据教程后,可以使用脚本来清理环境。使用命令窗口导航回商业票据样本的根目录:
cd fabric-samples/commercial-paper
然后可以使用以下命令关闭网络:
./network-clean.sh
除了logspout工具外,这个命令将关闭网络的节点、CouchDB容器和排序节点。它还将删除我们为Isabella和Balaji创造的身份。请注意,账本上的所有数据都将丢失。如果您想再次学习本教程,您将从一个干净的初始状态开始。
为了更详细地了解本教程中显示的应用程序和智能合约是如何工作的,您将发现阅读开发应用程序很有帮助。本主题将对商业票据场景、PaperNet业务网络、其参与者以及它们使用的应用程序和智能合约如何工作进行详细解释。
也可以使用此示例开始创建自己的应用程序和智能合约!
本教程将演示如何使用集合为组织的授权节点提供区块链网络上私有数据的存储和检索。
本教程中的信息假定您了解私有数据存储及其用例。有关更多信息,请查看私有数据。
注意:这些说明使用Fabric v2.0版本中引入的新Fabric链码生命周期。如果希望使用以前的生命周期模型将私有数据与链码一起使用,请访问v1.4版本的在Fabric中使用私有数据教程。
本教程将把marbles私有数据示例部署到Fabric测试网络,以演示如何创建、部署和使用私有数据集合。您应该已经完成了安装示例、二进制文件和Docker镜像的任务。
将通道上的数据私有化的第一步是构建一个集合定义,定义对私有数据的访问。
集合定义描述谁可以持久化数据、数据分发到多少节点、需要多少节点来分发私有数据以及私有数据在私有数据库中持久化的时间。稍后,我们将演示如何使用链码APIs PutPrivateData
和GetPrivateData
将集合映射到受保护的私有数据。
集合定义由以下属性组成:
blockToLive
属性设置为0
。true
表示节点自动强制只允许属于集合成员组织之一的客户端读取私有数据。为了说明私有数据的用法,marbles private data示例包含两个私有数据收集定义:collectionMarbles
和collectionMarblePrivateDetails
。collectionMarbles
定义中的policy
属性允许通道的所有成员(Org1和Org2)在私有数据库中拥有私有数据。collectionMarblesPrivateDetails
集合只允许Org1的成员在其私有数据库中拥有私有数据。
有关构建策略定义的更多信息,请参阅“背书策略”主题。
// collections_config.json
[
{
"name": "collectionMarbles",
"policy": "OR('Org1MSP.member', 'Org2MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 3,
"blockToLive":1000000,
"memberOnlyRead": true
},
{
"name": "collectionMarblePrivateDetails",
"policy": "OR('Org1MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 3,
"blockToLive":3,
"memberOnlyRead": true
}
]
这些策略要保护的数据映射到链码中,稍后将在教程中显示。
当使用peer lifecycle chaincode commit命令将链码定义提交到通道时,将部署此集合定义文件。关于这一过程的更多细节见下文第3节。
了解如何私有化通道上的数据的下一步是在链码中构建数据定义。marbles private data示例根据访问数据的方式将私有数据分为两个单独的数据定义。
// Peers in Org1 and Org2 will have this private data in a side database
type marble struct {
ObjectType string `json:"docType"`
Name string `json:"name"`
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
}
// Only peers in Org1 will have this private data in a side database
type marblePrivateDetails struct {
ObjectType string `json:"docType"`
Name string `json:"name"`
Price int `json:"price"`
}
具体而言,对私有数据的访问将受到如下限制:
name, color, size, and owner
将对通道的所有成员可见(Org1和Org2)price
仅对Org1的成员可见GetPrivateData()
和PutPrivateData()
来执行的,可以在这里找到。使用链码API GetPrivateData()
查询数据库中的私有数据。GetPrivateData()
接受两个参数:集合名和数据键。回想一下collectionMarbles
允许Org1和Org2的成员在side数据库中拥有私有数据,collectionMarblePrivateDetails
只允许Org1的成员在side数据库中拥有私有数据。有关实现细节,请参阅以下两个marbles private data functions:
name, color, size and owner
属性的值price
属性值当我们在本教程后面使用peer命令发出数据库查询时,我们将调用这两个函数。
写入私有数据
使用链码API PutPrivateData()
将私有数据存储到专用数据库中。API还需要集合的名称。由于marbles私有数据示例包含两个不同的集合,因此在链码中调用了两次:
collectionMarbles
的集合写入私有数据name, color, size and owner
。collectionMarblePrivateDetails
的集合编写私有数据价格
。例如,在下面initMarble
函数的片段中,PutPrivateData()
被调用两次,每一组私有数据都调用一次。
// ==== Create marble object, marshal to JSON, and save to state ====
marble := &marble{
ObjectType: "marble",
Name: marbleInput.Name,
Color: marbleInput.Color,
Size: marbleInput.Size,
Owner: marbleInput.Owner,
}
marbleJSONasBytes, err := json.Marshal(marble)
if err != nil {
return shim.Error(err.Error())
}
// === Save marble to state ===
err = stub.PutPrivateData("collectionMarbles", marbleInput.Name, marbleJSONasBytes)
if err != nil {
return shim.Error(err.Error())
}
// ==== Create marble private details object with price, marshal to JSON, and save to state ====
marblePrivateDetails := &marblePrivateDetails{
ObjectType: "marblePrivateDetails",
Name: marbleInput.Name,
Price: marbleInput.Price,
}
marblePrivateDetailsBytes, err := json.Marshal(marblePrivateDetails)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutPrivateData("collectionMarblePrivateDetails", marbleInput.Name, marblePrivateDetailsBytes)
if err != nil {
return shim.Error(err.Error())
}
综上所述,上述政策定义collection.json
允许Org1和Org2中的所有节点在其私有数据库中存储和处理marbles私有数据的名称、颜色、大小、所有者
。但只有Org1中的节点可以在其私有数据库中存储和处理price
私有数据。
作为一个额外的数据隐私好处,由于正在使用集合,所以只有私有数据哈希通过排序节点,而不是私有数据本身,从而对排序节点保密。
现在我们已经准备好通过一些命令来演示如何使用私有数据。
在安装、定义和使用下面的marbles私有数据链码之前,我们需要启动Fabric测试网络。在本教程中,我们希望从已知的初始状态开始操作。下面的命令将杀死任何活动的或过时的Docker容器,并删除以前生成的工件。因此,让我们运行以下命令来清理以前的任何环境:
cd fabric-samples/test-network
./network.sh down
如果您以前没有看过本教程,那么在我们将其部署到网络之前,您需要提供链码依赖项。运行以下命令:
cd ../chaincode/marbles02_private/go
GO111MODULE=on go mod vendor
cd ../../../test-network
如果您已经完成了本教程,您还需要删除marbles私有数据链代码的底层Docker容器。让我们运行以下命令来清理以前的环境:
docker rm -f $(docker ps -a | awk '($2 ~ /dev-peer.*.marblesp.*/) {print $1}')
docker rmi -f $(docker images | awk '($1 ~ /dev-peer.*.marblesp.*/) {print $3}')
从test-network
目录中,可以使用以下命令启动带有CouchDB的Fabric测试网络:
./network.sh up createChannel -s couchdb
这个命令将部署一个Fabric网络,它由一个名为mychannel
的通道和两个组织(每个组织维护一个普通节点)和一个排序服务组成,同时使用CouchDB作为状态数据库。LevelDB或CouchDB都可以用于集合。选择CouchDB来演示如何对私有数据使用索引。
注意:为了使收集工作正常进行,正确配置跨组织gossip很重要。请参阅我们关于Gossip数据传播协议的文档,特别注意“锚节点”部分。我们的教程并不关注gossip,因为它已经在测试网络中配置了,但是当配置一个通道时,gossip锚节点对等点对于配置集合正常工作至关重要。
客户端应用程序通过链码与区块链账本交互。因此,我们需要在每一个执行和背书我们的交易的节点上安装一个链码。然而,在我们可以与链码交互之前,通道成员需要就建立链码治理的链码定义达成一致,包括私有数据收集配置。我们将打包、安装,然后使用peer lifecycle chaincode在通道上定义链码。
链码需要打包后才能安装到我们的节点上。我们可以使用peer lifecycle chaincode package命令来打包弹珠链码。
测试网络包括两个组织,Org1和Org2,每个组织有一个节点。因此,链码包必须安装在两个节点上:
在打包链码之后,我们可以使用peer lifecycle chaincode install命令在每个节点上安装弹珠链码。
假设您已经启动了测试网络,那么在CLI中复制并粘贴以下环境变量,以便与网络交互并作为Org1管理员操作。确保您在测试网络目录中。
export PATH=${
PWD}/../bin:${
PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
peer lifecycle chaincode package marblesp.tar.gz --path ../chaincode/marbles02_private/go/ --lang golang --label marblespv1
此命令将创建一个名为marblesp.tar.gz.
2. 使用以下命令将chaincode包安装到节点peer0.org1.example.com
peer lifecycle chaincode install marblesp.tar.gz
成功的安装命令将返回链码标识符,类似于下面的响应:
2019-04-22 19:09:04.336 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed remotely: response:<status:200 payload:"\nKmarblespv1:57f5353b2568b79cb5384b5a8458519a47186efc4fcadb98280f5eae6d59c1cd\022\nmarblespv1" >
2019-04-22 19:09:04.336 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 002 Chaincode code package identifier: marblespv1:57f5353b2568b79cb5384b5a8458519a47186efc4fcadb98280f5eae6d59c1cd
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
peer lifecycle chaincode install marblesp.tar.gz
批准链码定义
希望使用链码的每个通道成员都需要为其组织批准链码定义。由于这两个组织都将在本教程中使用链码,因此我们需要使用peer lifecycle chaincode approverformyorg命令批准Org1和Org2的链码定义。链码定义还包括marbles02_private
示例附带的私有数据收集定义。我们将使用--collections config
标志提供指向collections JSON文件的路径。
从test-network
目录运行以下命令以批准Org1和Org2的定义。
peer lifecycle chaincode queryinstalled
该命令将返回与install命令相同的包标识符。您将看到类似于以下内容的输出:
Installed chaincodes on peer:
Package ID: marblespv1:f8c8e06bfc27771028c4bbc3564341887881e29b92a844c66c30bac0ff83966e, Label: marblespv1
peer lifecycle chaincode queryinstalled
返回的marblespv1包ID粘贴到下面的命令中。对于所有用户,包ID可能不相同,因此您需要使用从控制台返回的包ID来完成此步骤。export CC_PACKAGE_ID=marblespv1:f8c8e06bfc27771028c4bbc3564341887881e29b92a844c66c30bac0ff83966e
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export ORDERER_CA=${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marblesp --version 1.0 --collections-config ../chaincode/marbles02_private/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --init-required --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
当命令成功完成时,您将看到类似于:
2020-01-03 17:26:55.022 EST [chaincodeCmd] ClientWait -> INFO 001 txid [06c9e86ca68422661e09c15b8e6c23004710ea280efda4bf54d501e655bafa9b] committed with status (VALID) at
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marblesp --version 1.0 --collections-config ../chaincode/marbles02_private/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --init-required --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
提交链码定义
一旦有足够数量的组织(在本例中是大多数)批准了链码定义,一个组织就可以将定义提交给通道。
使用peer lifecycle chaincode commit命令提交链码定义。此命令还将集合定义部署到通道。
在将链码定义提交给通道之后,我们就可以使用链码了。因为marbles私有数据链码包含一个初始化函数,我们需要使用peer chaincode invoke命令来调用Init()
,然后才能使用链码中的其他函数。
mychannel
。export ORDERER_CA=${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
export ORG1_CA=${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export ORG2_CA=${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marblesp --version 1.0 --sequence 1 --collections-config ../chaincode/marbles02_private/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --init-required --tls --cafile $ORDERER_CA --peerAddresses localhost:7051 --tlsRootCertFiles $ORG1_CA --peerAddresses localhost:9051 --tlsRootCertFiles $ORG2_CA
当提交交易成功完成时,您应该看到类似于:
2020-01-06 16:24:46.104 EST [chaincodeCmd] ClientWait -> INFO 001 txid [4a0d0f5da43eb64f7cbfd72ea8a8df18c328fb250cb346077d91166d86d62d46] committed with status (VALID) at localhost:9051
2020-01-06 16:24:46.184 EST [chaincodeCmd] ClientWait -> INFO 002 txid [4a0d0f5da43eb64f7cbfd72ea8a8df18c328fb250cb346077d91166d86d62d46] committed with status (VALID) at localhost:7051
Init
函数初始化链码:peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marblesp --isInit --tls --cafile $ORDERER_CA --peerAddresses localhost:7051 --tlsRootCertFiles $ORG1_CA -c '{"Args":["Init"]}'
作为Org1的成员,该成员被授权处理marbles私有数据示例中的所有私有数据,切换回Org1节点并提交添加marble的请求:
将以下命令集复制并粘贴到test-network
目录中的CLI中:
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
调用marblesinitmarble
函数,该函数创建一个私有数据名为marble1
的弹珠,其颜色为blue
,大小为35
,价格为99
。回想一下,私有数据的价格将与私有数据的名称、所有者、颜色、大小分开存储。因此,initMarble
函数调用PutPrivateData()
API两次以持久化私有数据,每个集合一次。还要注意,私有数据是使用--transient
标志传递的。作为临时数据传递的输入将不会在交易中持久化,以保持数据的私有性。瞬态数据作为二进制数据传递,因此在使用CLI时,必须对其进行base64编码。我们使用一个环境变量来捕获base64编码的值,并使用tr
命令去除linux base64命令添加的有问题的换行符。
export MARBLE=$(echo -n "{\"name\":\"marble1\",\"color\":\"blue\",\"size\":35,\"owner\":\"tom\",\"price\":99}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}"
您将看到类似于以下内容的结果:
[chaincodeCmd] chaincodeInvokeOrQuery->INFO 001 Chaincode invoke successful. result: status:200
我们的集合定义允许Org1和Org2的所有成员在其侧数据库中拥有名称、颜色、大小、所有者
私有数据,但只有Org1中的节点可以在其side数据库中拥有price
私有数据。作为Org1中的授权节点,我们将查询这两组私有数据。
第一个query
命令调用readMarble
函数,该函数将collectionMarbles
作为参数传递。
// ===============================================
// readMarble - read a marble from chaincode state
// ===============================================
func (t *SimpleChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var name, jsonResp string
var err error
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
}
name = args[0]
valAsbytes, err := stub.GetPrivateData("collectionMarbles", name) //get the marble from chaincode state
if err != nil {
jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}"
return shim.Error(jsonResp)
} else if valAsbytes == nil {
jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}"
return shim.Error(jsonResp)
}
return shim.Success(valAsbytes)
}
第二个query
命令调用readMarblePrivateDetails
函数,该函数将collectionMarblePrivateDetails
作为参数传递。
// ===============================================
// readMarblePrivateDetails - read a marble private details from chaincode state
// ===============================================
func (t *SimpleChaincode) readMarblePrivateDetails(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var name, jsonResp string
var err error
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
}
name = args[0]
valAsbytes, err := stub.GetPrivateData("collectionMarblePrivateDetails", name) //get the marble private details from chaincode state
if err != nil {
jsonResp = "{\"Error\":\"Failed to get private details for " + name + ": " + err.Error() + "\"}"
return shim.Error(jsonResp)
} else if valAsbytes == nil {
jsonResp = "{\"Error\":\"Marble private details does not exist: " + name + "\"}"
return shim.Error(jsonResp)
}
return shim.Success(valAsbytes)
}
作为Org1的成员查询marble1的名称、颜色、大小和所有者
私有数据。请注意,由于查询不会记录在账本上,因此不需要将弹珠名称作为临时输入传递。
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarble","marble1"]}'
您将看到以下结果:
{
"color":"blue","docType":"marble","name":"marble1","owner":"tom","size":35}
作为Org1成员查询marble1
的price
私有数据。
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
您将看到以下结果:
{
"docType":"marblePrivateDetails","name":"marble1","price":99}
现在我们将切换到Org2的一个成员。Org2在它的side数据库中有弹珠私有数据的名称、颜色、大小、所有者
,但不存储弹珠价格数据。我们将查询这两组私有数据。
切换到Org2中的节点
以Org2管理员的身份运行以下命令并查询Org2节点。
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
查询私有数据Org2被授权的情况
Org2中的节点应该在其side数据库中拥有第一组marbles私有数据(名称、颜色、大小和所有者
),并且可以使用readMarble()
函数访问它,该函数是通过collectionMarbles
参数调用的。
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarble","marble1"]}'
您应该会看到类似以下结果:
{
"docType":"marble","name":"marble1","color":"blue","size":35,"owner":"tom"}
查询私有数据Org2未被授权的情况
Org2中的节点数据库中没有marbles price
私有数据。当他们试图查询这个数据时,他们会得到一个匹配公共状态的私钥哈希,但是不会有私有状态。
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
您将看到类似于以下内容的结果:
Error: endorsement failure during query. response: status:500
message:"{\"Error\":\"Failed to get private details for marble1:
GET_STATE failed: transaction ID: d9c437d862de66755076aeebe79e7727791981606ae1cb685642c93f102b03e5:
tx creator does not have read access permission on privatedata in chaincodeName:marblesp collectionName: collectionMarblePrivateDetails\"}"
Org2的成员只能看到私有数据的公钥哈希。
对于私有数据在复制到链外数据库之前只需要保留在账本上的用例,可以在一定数量的块之后“清除”数据,只留下作为交易不可变证据的数据哈希。
可能存在交易方不希望向通道上的其他组织披露的私人数据,包括个人或机密信息,例如我们示例中的定价数据。因此,它的生命周期是有限的,并且可以在使用集合定义中的blockToLive
属性在区块链上对指定数量的区块保持不变后进行清除。
我们的collectionMarblePrivateDetails
定义的blockToLive
属性值为3,这意味着此数据将在三个块的side数据库中存在,然后将被清除。将所有的部分结合在一起,回想一下这个集合定义collectionMarblePrivateDetails
是在initMarble()
函数中调用PutPrivateData()
API并将collectionMarblePrivateDetails
作为参数传递时关联的。
我们将逐步向链中添加区块,然后通过发布四个新交易(创建一个新弹珠,然后是三个弹珠转移)来观察价格信息被清除,这四个新交易将添加到链中。在第四次交易(第三次弹珠传输)之后,我们将验证价格私有数据是否被清除。
使用以下命令切换回Org1。复制并粘贴以下代码块并在节点容器中运行:
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${
PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${
PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
打开一个新的终端窗口,通过运行以下命令查看这个节点的私有数据日志。注意最高的块编号。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
回到节点容器中,通过运行以下命令查询marble1价格数据。(查询不会在账本上创建新的交易记录,因为没有处理任何数据)。
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
您将看到类似于以下内容的结果:
{
"docType":"marblePrivateDetails","name":"marble1","price":99}
价格数据仍在私人数据账本中。
通过发出以下命令创建一个新的marble2。此交易在链上创建一个新块。
export MARBLE=$(echo -n "{\"name\":\"marble2\",\"color\":\"blue\",\"size\":35,\"owner\":\"tom\",\"price\":99}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}"
切换回终端窗口,再次查看该节点的私有数据日志。您应该看到块高度增加了1。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
回到节点容器中,通过运行以下命令再次查询marble1价格数据:
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
私有数据尚未清除,因此结果与上一次查询相同:
{
"docType":"marblePrivateDetails","name":"marble1","price":99}
通过运行以下命令将marble2转换为“joe”。此交易将在链上添加第二个新块。
export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"joe\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["transferMarble"]}' --transient "{\"marble_owner\":\"$MARBLE_OWNER\"}"
切换回终端窗口,再次查看该节点的私有数据日志。您应该看到块高度增加了1。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
回到节点容器中,通过运行以下命令查询marble1价格数据:
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
你应该仍然可以看到价格私有数据。
{
"docType":"marblePrivateDetails","name":"marble1","price":99}
通过运行以下命令将marble2传输到“tom”。此交易将在链上创建第三个新区块。
export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"tom\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["transferMarble"]}' --transient "{\"marble_owner\":\"$MARBLE_OWNER\"}"
切换回终端窗口,再次查看该节点的私有数据日志。您应该看到块高度增加了1。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
回到节点容器中,通过运行以下命令查询marble1价格数据:
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
你应该还能看到价格数据。
{
"docType":"marblePrivateDetails","name":"marble1","price":99}
最后,通过运行以下命令将marble2转换为“jerry”。此交易将在链上创建第四个新区块。此交易处理后,应清除价格
私有数据。
export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"jerry\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${
PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["transferMarble"]}' --transient "{\"marble_owner\":\"$MARBLE_OWNER\"}"
切换回终端窗口,再次查看该节点的私有数据日志。您应该看到块高度增加了1。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
回到节点容器中,通过运行以下命令查询marble1价格数据:
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
因为价格数据已被清除,您应该无法再看到它。您应该看到类似于:
Error: endorsement failure during query. response: status:500
message:"{\"Error\":\"Marble private details does not exist: marble1\"}"
索引也可以应用于私有数据集合,方法是将索引打包到META-INF/statedb/couchdb/collections/
目录中,并将其与链码一起打包。这里提供了一个示例索引。
对于将链码部署到生产环境中,建议在chaincode旁边定义任何索引,以便在链码安装在节点上并在通道上实例化后,链码和支持索引将作为一个单元自动部署。当指定--collections-config
标志指向集合JSON文件的位置时,将在通道上实例化链码时自动部署相关索引。
参考自官方文档
如有侵权,请联系作者删除,谢谢!
If there is infringement, please contact the author to delete, thank you!