Hyperledger Fabric:V2.5.4

写在最前

使用Fabric搭建自定义网络参考:https://blog.csdn.net/yeshang_lady/article/details/134113296
使用Fabric创建应用通道参考:https://blog.csdn.net/yeshang_lady/article/details/134668458
接下来将介绍如何在自定义的网络和通道上部署以及执行链码。

1 链码部署

Fabric中链码的部署一般包括以下步骤:编写链码->打包链码->安装链码->实例化链码->部署链码等。下面按照此步骤依次介绍。这里在已经搭建好的finance_network和通道channel1上部署链码。

1.1 编写链码

创建好网络和应用通道之后回到finance_network目录下创建链码目录usersChaincode。接着在链码目录usersChaincode下创建链码文件asset-transfer.go文件,其代码如下(这个文件中的代码是用test-network中的链码示例来构造的):

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
type SmartContract struct {
	contractapi.Contract
}

type Asset struct {
	AppraisedValue int    `json:"AppraisedValue"`
	Color          string `json:"Color"`
	ID             string `json:"ID"`
	Owner          string `json:"Owner"`
	Size           int    `json:"Size"`
}
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
	assets := []Asset{
		{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
		{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
		{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
		{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
		{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
		{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
	}
	for _, asset := range assets {
		assetJSON, err := json.Marshal(asset)
		if err != nil {
			return err
		}
		err = ctx.GetStub().PutState(asset.ID, assetJSON)
		if err != nil {
			return fmt.Errorf("failed to put to world state. %v", err)
		}
	}
	return nil
}
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
	exists, err := s.AssetExists(ctx, id)
	if err != nil {
		return err
	}
	if exists {
		return fmt.Errorf("the asset %s already exists", id)
	}
	asset := Asset{
		ID:             id,
		Color:          color,
		Size:           size,
		Owner:          owner,
		AppraisedValue: appraisedValue,
	}
	assetJSON, err := json.Marshal(asset)
	if err != nil {
		return err
	}
	return ctx.GetStub().PutState(id, assetJSON)
}
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
	assetJSON, err := ctx.GetStub().GetState(id)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state: %v", err)
	}
	if assetJSON == nil {
		return nil, fmt.Errorf("the asset %s does not exist", id)
	}
	var asset Asset
	err = json.Unmarshal(assetJSON, &asset)
	if err != nil {
		return nil, err
	}
	return &asset, nil
}

func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
	exists, err := s.AssetExists(ctx, id)
	if err != nil {
		return err
	}
	if !exists {
		return fmt.Errorf("the asset %s does not exist", id)
	}
	asset := Asset{
		ID:             id,
		Color:          color,
		Size:           size,
		Owner:          owner,
		AppraisedValue: appraisedValue,
	}
	assetJSON, err := json.Marshal(asset)
	if err != nil {
		return err
	}
	return ctx.GetStub().PutState(id, assetJSON)
}
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
	exists, err := s.AssetExists(ctx, id)
	if err != nil {
		return err
	}
	if !exists {
		return fmt.Errorf("the asset %s does not exist", id)
	}
	return ctx.GetStub().DelState(id)
}
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
	assetJSON, err := ctx.GetStub().GetState(id)
	if err != nil {
		return false, fmt.Errorf("failed to read from world state: %v", err)
	}
	return assetJSON != nil, nil
}
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
	asset, err := s.ReadAsset(ctx, id)
	if err != nil {
		return "", err
	}
	oldOwner := asset.Owner
	asset.Owner = newOwner
	assetJSON, err := json.Marshal(asset)
	if err != nil {
		return "", err
	}
	err = ctx.GetStub().PutState(id, assetJSON)
	if err != nil {
		return "", err
	}
	return oldOwner, nil
}
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
	resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()
	var assets []*Asset
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return nil, err
		}
		var asset Asset
		err = json.Unmarshal(queryResponse.Value, &asset)
		if err != nil {
			return nil, err
		}
		assets = append(assets, &asset)
	}
	return assets, nil
}
func main() {
	assetChaincode, err := contractapi.NewChaincode(&SmartContract{})
	if err != nil {
		log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
	}
	if err := assetChaincode.Start(); err != nil {
		log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
	}
}

然后在该链码目录下使用go mod等命令初始化该模块。具体包括以下:

#先进入usersChaincode目录下
go mod init
sudo chmod -R 777 go.mod
#下载链码中的需要的模块信息
go get github.com/hyperledger/fabric-contract-api-go/contractapi
sudo chmod -R 777 go.sum
#将项目的依赖库复制都vendor目录中去
GO111MODULE=on go mod vendor

Tips:以下几点需要注意

  • 首先,生成go.mod文件的时候自动指定了go语言的版本为当前环境中安装的go语言版本号。比如go 1.21.3。但go.mod中的go语言版本号应该由两个数字组成,用点号分隔,例如1.13、1.14等。
  • 其次,go.mod文件中的go语言版本还要考虑此处现在的第三方包兼容的版本。
  • 再次,go.mod文件修改后要运行go mod tidy命令重新生成go.sum文件。
  • 最后,如果在安装链码时遇到以下问题invalid go version 1.21.3: must match format 1.21,则需要返回修改go.mod文件(在最新的Fabric版本中似乎不再需要此修改)。这里要将go.mod文件中的go语言版本改为go 1.17(参考fabric-samples),然后重新生成go.sum文件,还要重新打包链码。为了方便,最好提前修改go.mod文件。在这里插入图片描述
  • 另外,如果不执行GO111MODULE=on go mod vendor命令,那么后续在安装链码会遇到超时问题:Error: chaincode install failed with status: 500 ... error sending: timeout expired while executing transaction
    在这里插入图片描述

1.2 打包链码

打包链码是指将链码文件打包成一个tar格式的文件。可以使peer lifecycle chaincode package命令。其具体执行命令如下:

#先使用cd命令跳转到finance_network目录下
export FABRIC_CFG_PATH=$PWD/config
#basic即为链码的名字
peer lifecycle chaincode package basic.tar.gz --path usersChaincode --lang "golang" --label basic_1.0.1

其中basic为链码名称。代码执行结束将会在finance_network目录下看到basic.tar.gz文件。Tips:peer lifecycle chaincode package命令只是将链码打包成一个tar格式的文件,这个过程不需要与具体的peer节点交互,因此这个命令的执行不需要事先绑定节点。
另外,关于--label部分需要做如下说明:

  • --label后的内容由3部分组成${cc_name}_${cc_version}.${cc_sequence}。如果在同一个peer节点上安装同一个链码的多个版本,则cc_version{cc_sequece}要发生变动。
  • cc_version一般选择浮点型,cc_sequece一般选择整型。

1.3 安装链码

安装链码需要将链码部署到每个需要执行链码的Peer节点上。通过调用Peer节点的peer lifecycle chaincode install命令将链码安装到Peer节点的本地文件系统。下面仅以peer0.org1.finance.com节点说明链码安装过程。
具体如下:

#先设置环境变量将peer CLI绑定到peer0.org1.finance.com节点上
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=$PWD/organizations/peerOrganizations/org1.finance.com/peers/peer0.org1.finance.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=$PWD/organizations/peerOrganizations/org1.finance.com/users/Admin@org1.finance.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export FABRIC_CFG_PATH=$PWD/config
# 安装链码
peer lifecycle chaincode install basic.tar.gz

结果如下:
在这里插入图片描述
可以使用peer lifecycle chaincode queryinstalled查看peer节点已经安装的链码。具体如下:
在这里插入图片描述
另外,也将该链码安装在peer0.org2.finance.com上,安装步骤这里省略。Tips: 虽然在fabric_test网络中创建了3个peer节点,但链码不一定需要在所有peer节点上都安装,但至少需要在通道中的每个组织内的一个peer节点安装链码。

1.4 实例化链码

  • 使用peer lifecycle chaincode approveformyorg命令完成组织对链码部署的批准。假设这里需要两个组织都同意链码的部署。这里以Org1为例进行说明,其具体代码如下:
export ORDERER_CA=$PWD/organizations/ordererOrganizations/finance.com/orderers/orderer.finance.com/msp/tlscacerts/tlsca.finance.com-cert.pem
export PACKAGE_ID=$(peer lifecycle chaincode calculatepackageid basic.tar.gz)
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.finance.com --tls --cafile "$ORDERER_CA" --channelID channel1 --name basic --version 1.0 --sequence 1 --package-id ${PACKAGE_ID} 

其结果如下:
在这里插入图片描述

  • 使用peer lifecycle chaincode checkcommitreadiness命令检查链码定义的提交准备情况。其具体命令如下:
peer lifecycle chaincode checkcommitreadiness --channelID channel1 --name basic --version 1.0 --sequence 1 

其执行结果如下:
在这里插入图片描述

  • 使用peer lifecycle chaincode commit命令提交链码定义的交易。其命令如下:
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.finance.com --tls --cafile "$ORDERER_CA" --channelID channel1 --name basic --version 1.0 --sequence 1  --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.finance.com/peers/peer0.org1.finance.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.finance.com/peers/peer0.org2.finance.com/tls/ca.crt"

其代码执行结果如下:
在这里插入图片描述
可以使用下述代码判断提交是否成功。具体如下:

peer lifecycly chaincode querycommitted -C channel1 -n basic

其执行结果如下:
在这里插入图片描述
至此链码的实例化已经完成。可以使用docker ps -a看到链码的容器信息。具体如下:
在这里插入图片描述

2 链码执行

关于链码的执行,这里只介绍两个命令。

  • peer chaincode invoke:可以使链码执行自定义的业务逻辑,并且可以改变区块链账本中的状态。举例如下:
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.finance.com --tls --cafile "${PWD}/organizations/ordererOrganizations/finance.com/orderers/orderer.finance.com/msp/tlscacerts/tlsca.finance.com-cert.pem" -C channel1 -n basic --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.finance.com/peers/peer0.org1.finance.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.finance.com/peers/peer0.org2.finance.com/tls/ca.crt" -c '{"Args":["InitLedger"]}'

其执行结果如下:
在这里插入图片描述

  • peer chaincode query: 可以从区块链账本中获取数据,而不会对账本进行任何的状态更新。
peer chaincode query -C channel -n basic -c '{"Args":["GetAllAssets"]}'

其执行结果如下:
在这里插入图片描述
为了说明peer chaincode query没有对账本进行修改, 执行以下两条命令,具体如下:

#删除id为asset6的记录
peer chaincode query -C channel1 -n basic -c '{"Args":["DeleteAsset","asset6"]}'
#读取id为asset6的记录
peer chaincode query -C channel1 -n basic -c '{"Args":["ReadAsset","asset6"]}'

其结果如下:
在这里插入图片描述

3 其它

由于链码的安装和实例化过程需要频繁设置环境变量,所有这里同样适用bash文件来完成这些操作。具体包括:

3.1 链码打包文件

具体的链码打包文件package.sh内容如下:

cc_name=$1
cc_path=$2
cc_version=$3
export FABRIC_CFG_PATH=../fixtures/config
peer lifecycle chaincode package ${cc_name}.tar.gz --path ${cc_path} --lang "golang" --label ${cc_name}_${cc_version}

3.2 安装和实例化过程

具体的链码安装和实例化文件install.sh内容如下:

domain_name=$1
cc_name=$2
channel_name=$3
cc_version=$4
cc_sequence=$5
export CORE_PEER_TLS_ENABLED=true
export FABRIC_CFG_PATH=$PWD/../fixtures/config
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=$PWD/../fixtures/organizations/peerOrganizations/org1.${domain_name}.com/peers/peer0.org1.${domain_name}.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=$PWD/../fixtures/organizations/peerOrganizations/org1.${domain_name}.com/users/Admin@org1.${domain_name}.com/msp
export CORE_PEER_ADDRESS=localhost:7051
# 安装链码
peer lifecycle chaincode install ${cc_name}.tar.gz
#查看链码
peer lifecycle chaincode queryinstalled
echo "链码安装完成"

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=$PWD/../fixtures/organizations/peerOrganizations/org2.${domain_name}.com/peers/peer0.org2.${domain_name}.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=$PWD/../fixtures/organizations/peerOrganizations/org2.${domain_name}.com/users/Admin@org2.${domain_name}.com/msp
export CORE_PEER_ADDRESS=localhost:9051
# 安装链码
peer lifecycle chaincode install ${cc_name}.tar.gz
#查看链码
peer lifecycle chaincode queryinstalled
echo "链码安装完成"

#组织批准
export ORDERER_CA=$PWD/../fixtures/organizations/ordererOrganizations/${domain_name}.com/orderers/orderer.${domain_name}.com/msp/tlscacerts/tlsca.${domain_name}.com-cert.pem
export PACKAGE_ID=$(peer lifecycle chaincode calculatepackageid ${cc_name}.tar.gz)

peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.${domain_name}.com --tls --cafile "$ORDERER_CA" --channelID ${channel_name} --name ${cc_name} --version ${cc_version} --sequence ${cc_sequence} --package-id ${PACKAGE_ID}
echo "组织org2批准完成"

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=$PWD/../fixtures/organizations/peerOrganizations/org1.${domain_name}.com/peers/peer0.org1.${domain_name}.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=$PWD/../fixtures/organizations/peerOrganizations/org1.${domain_name}.com/users/Admin@org1.${domain_name}.com/msp
export CORE_PEER_ADDRESS=localhost:7051
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.${domain_name}.com --tls --cafile "$ORDERER_CA" --channelID ${channel_name} --name ${cc_name} --version ${cc_version} --sequence ${cc_sequence} --package-id ${PACKAGE_ID}
echo "组织org1批准完成"

peer lifecycle chaincode checkcommitreadiness --channelID ${channel_name} --name ${cc_name} --version ${cc_version} --sequence ${cc_sequence}

peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.${domain_name}.com --tls --cafile "$ORDERER_CA" --channelID ${channel_name} --name ${cc_name} --version ${cc_version} --sequence ${cc_sequence} --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/../fixtures/organizations/peerOrganizations/org1.${domain_name}.com/peers/peer0.org1.${domain_name}.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/../fixtures/organizations/peerOrganizations/org2.${domain_name}.com/peers/peer0.org2.${domain_name}.com/tls/ca.crt"
echo "链码提交完成"
peer lifecycle chaincode querycommitted -C ${channel_name} -n ${cc_name}

关于这个文件在使用时要注意以下几点:

  • 文件中各个环境变量的具体路径需要依据具体的情况调整。目前文件上的内容是依据以下文件结构设置的:
    在这里插入图片描述
  • 目前该文件中是将两个组织中的peer0(并且端口号分别为7051和9051)节点加入到链码中,如果不符合这个条件,则自行修改文件内容。

3.3 执行过程

这里以上述链码文件夹中的businessChainCode文件下的链码为例展示上述两个文件的执行过程(这里新建了网络和通道,与第1、2部分无关):
在这里插入图片描述
其执行结果如下:
在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐