Hyperledger Fabric 使用 CouchDB 和复杂智能合约开发

前言

在上个实验中,我们已经实现了简单智能合约实现及客户端开发,但该实验中智能合约只有基础的增删改查功能,且其中的数据管理功能与传统 MySQL 比相差甚远。本文将在前面实验的基础上,将 Hyperledger Fabric 的默认数据库支持 LevelDB 改为 CouchDB 模式,以实现更复杂的数据检索功能。此外,对上个实验的简单智能合约进一步进行功能上和设计上的扩展,最终实现了智能合约的分包、分页查询、多字段富查询、查询交易历史记录等功能。

网络架构

本文网络结构直接将 Hyperledger Fabric无排序组织以Raft协议启动多个Orderer服务、TLS组织运行维护Orderer服务 中创建的 4-2_RunOrdererByCouncil 复制为 7_CouchDBAndComplexContract 并修改(建议直接将本案例仓库 FabricLearn 下的 7_CouchDBAndComplexContract 目录拷贝到本地运行),文中大部分命令在 Hyperledger Fabric定制联盟链网络工程实践 中已有介绍因此不会详细说明,默认情况下,所有操作皆在 7_CouchDBAndComplexContract 根目录下执行。修改成功后网络共包含四个组织—— council 、 soft 、 web 、 hard , 其中 council 组织为网络提供 TLS-CA 服务,并且运行维护着三个 orderer 服务;其余每个组织都运行维护着一个 peer 节点、一个 couchDB 服务、一个 admin 用户和一个 user 用户,实验最终网络结构如下:

运行端口说明
council.ifantasy.net7050council 组织的 CA 服务, 为联盟链网络提供 TLS-CA 服务
orderer1.council.ifantasy.net7051council 组织的 orderer1 服务
orderer1.council.ifantasy.net7052council 组织的 orderer1 服务的 admin 服务
orderer2.council.ifantasy.net7054council 组织的 orderer2 服务
orderer2.council.ifantasy.net7055council 组织的 orderer2 服务的 admin 服务
orderer3.council.ifantasy.net7057council 组织的 orderer3 服务
orderer3.council.ifantasy.net7058council 组织的 orderer3 服务的 admin 服务
soft.ifantasy.net7250soft 组织的 CA 服务, 包含成员: peer1 、 admin1 、user1
peer1.soft.ifantasy.net7251soft 组织的 peer1 成员节点
couchdb.soft.ifantasy.net7255soft 组织的 couchdb 成员节点
web.ifantasy.net7350web 组织的 CA 服务, 包含成员: peer1 、 admin1 、user1
peer1.web.ifantasy.net7351web 组织的 peer1 成员节点
couchdb.web.ifantasy.net7355web 组织的 couchdb 成员节点
hard.ifantasy.net7450hard 组织的 CA 服务, 包含成员: peer1 、 admin1 、user1
peer1.hard.ifantasy.net7451hard 组织的 peer1 成员节点
couchdb.hard.ifantasy.net7455hard 组织的 couchdb 成员节点

添加CouchDB支持并启动网络

添加CouchDB支持

首先,在 envpeer1soft 、 envpeer1soft 、 envpeer1soft 中添加 CouchDB 版本变量:

export COUCHDB_VERSION=3.2

然后,向 compose/docker-base.yaml 文件添加基础 CouchDB 镜像:

couchdb-base:image: couchdb:${COUCHDB_VERSION}environment:- COUCHDB_USER=admin- COUCHDB_PASSWORD=adminpwnetworks:- ${DOCKER_NETWORKS}

之后,向 compose/docker-compose.yaml 中的每个组织添加 CouchDB 容器:

couchdb.soft.ifantasy.net:container_name: couchdb.soft.ifantasy.netextends:file: docker-base.yamlservice: couchdb-baseports:- 7255:5984couchdb.web.ifantasy.net:container_name: couchdb.web.ifantasy.netextends:file: docker-base.yamlservice: couchdb-baseports:- 7355:5984couchdb.hard.ifantasy.net:container_name: couchdb.hard.ifantasy.netextends:file: docker-base.yamlservice: couchdb-baseports:- 7455:5984

最后,修改 compose/docker-compose.yaml 中每个 peer 容器的储存方式(以 peer1.soft.ifantasy.net 为例):

  peer1.soft.ifantasy.net:container_name: peer1.soft.ifantasy.netextends:file: docker-base.yamlservice: peer-baseenvironment:- CORE_PEER_ID=peer1.soft.ifantasy.net- CORE_PEER_LISTENADDRESS=0.0.0.0:7251- CORE_PEER_ADDRESS=peer1.soft.ifantasy.net:7251- CORE_PEER_LOCALMSPID=softMSP- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.soft.ifantasy.net:7251- CORE_LEDGER_STATE_STATEDATABASE=CouchDB- CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb.soft.ifantasy.net:5984   # 必须为容器内端口- CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin- CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpwvolumes:- ${LOCAL_CA_PATH}/soft.ifantasy.net/registers/peer1:${DOCKER_CA_PATH}/peerports:- 7251:7251depends_on:- couchdb.soft.ifantasy.net

注意,参数 CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS 后的服务端口必须为 couchdb 容器的内部端口,原因不得而知, 完整代码见 FabricLearn 下的 7_CouchDBAndComplexContract/compose 目录。

启动实验网络

在上述修改完成后,在 7_CouchDBAndComplexContract 目录下按顺序执行以下命令启动基础实验网络:

  1. 设置DNS(如果未设置): ./setDNS.sh
  2. 设置环境变量: source envpeer1soft
  3. 启动CA网络: ./0_Restart.sh
  4. 注册用户: ./1_RegisterUser.sh
  5. 获取用户证书: ./2_EnrollUser.sh
  6. 配置通道: ./3_Configtxgen.sh

网络启动成功后可见包含 couchdb 容器:

合约开发

本节所用智能合约由前一篇文章 Hyperledger Fabric 智能合约开发及 fabric-sdk-go/fabric-gateway 使用示例 改进(拆分)而来,在上篇文章的基础上对合约进行分包分文件处理,使项目具有更好的目录结构。在实验根目录 7_CouchDBAndComplexContract 下创建目录 project_contract 作为智能合约根目录,在 project_contract 下执行以下命令初始化 GO 模块:

go mod init github.com/wefantasy/FabricLearn/7_CouchDBAndComplexContract/project_contract

tools 层

tools 层主要用于编写智能合约通用工具,创建 tools/contract.go 工具类,主要包含以下函数:

  • ConstructResultByIterator : 根据 fabric 查询结果 shim.StateQueryIteratorInterface 生成对应切片。
    // 根据查询结果生成切片
    func ConstructResultByIterator[T interface{}](resultsIterator shim.StateQueryIteratorInterface) ([]*T, error) {var txs []*Tfor resultsIterator.HasNext() {queryResult, err := resultsIterator.Next()if err != nil {return nil, err}var tx Terr = json.Unmarshal(queryResult.Value, &tx)if err != nil {return nil, err}txs = append(txs, &tx)}fmt.Println("select result length: ", len(txs))return txs, nil
    }
    
  • SelectByQueryString : 根据 couchdb 查询字符串完成查询操作,并返回对应切片。
    // 根据查询字符串查询
    func SelectByQueryString[T interface{}](ctx contractapi.TransactionContextInterface, queryString string) ([]*T, error) {resultsIterator, err := ctx.GetStub().GetQueryResult(queryString)if err != nil {return nil, err}defer resultsIterator.Close()return ConstructResultByIterator[T](resultsIterator)
    }
    
  • SelectByQueryStringWithPagination : 根据 couchdb 查询字符串分页查询,并返回对应切片。
    // 根据擦查询字符串分页查询
    func SelectByQueryStringWithPagination[T interface{}](ctx contractapi.TransactionContextInterface, queryString string, pageSize int32, bookmark string) (*model.PaginatedQueryResult[T], error) {resultsIterator, responseMetadata, err := ctx.GetStub().GetQueryResultWithPagination(queryString, pageSize, bookmark)if err != nil {return nil, err}defer resultsIterator.Close()var txs []Tfor resultsIterator.HasNext() {queryResult, err := resultsIterator.Next()if err != nil {return nil, err}var tx Terr = json.Unmarshal(queryResult.Value, &tx)if err != nil {return nil, err}txs = append(txs, tx)}return &model.PaginatedQueryResult[T]{Records:             txs,FetchedRecordsCount: responseMetadata.FetchedRecordsCount,Bookmark:            responseMetadata.Bookmark,}, nil
    }
    
  • SelectHistoryByIndex : 获得交易创建之后的所有变化(区块链账本)。
    // 获得交易创建之后的所有变化.
    func SelectHistoryByIndex[T interface{}](ctx contractapi.TransactionContextInterface, index string) ([]model.HistoryQueryResult[T], error) {resultsIterator, err := ctx.GetStub().GetHistoryForKey(index)if err != nil {return nil, err}defer resultsIterator.Close()var records []model.HistoryQueryResult[T]for resultsIterator.HasNext() {response, err := resultsIterator.Next()if err != nil {return nil, err}var tx Tif len(response.Value) > 0 {err = json.Unmarshal(response.Value, &tx)if err != nil {return nil, err}}record := model.HistoryQueryResult[T]{TxId:      response.TxId,Record:    tx,IsDelete:  response.IsDelete,}records = append(records, record)}return records, nil
    }
    

model 层

model层主要用于申明合约所用数据结构,其中 model/project.go 内容如下:

package modeltype Project struct {
Table        string `json:"table" form:"table"` //  数据库标记
ID           string `json:"ID"`                 // 项目唯一ID
Name         string `json:"Name"`               // 项目名称
Username     string `json:"username"`           // 项目主要负责人
Organization string `json:"Organization"`       // 项目所属组织
Category     string `json:"Category"`           // 项目所属类别
Url          string `json:"Url"`                // 项目介绍地址
Describes    string `json:"Describes"`          // 项目描述
}func (o *Project) Index() string {
o.Table = "project"
return o.ID
}func (o *Project) IndexKey() string {
return "table~ID~name"
}func (o *Project) IndexAttr() []string {
return []string{o.Table, o.ID, o.Name}
}

其中 Index 函数用于标识模型的唯一主键; IndexKey 函数用于标识自建索引的字段,其中命名方式必须与字段申明的结构体标记 json 一致(大小写);IndexAttr 用于构造具体的索引。model/user.go 申明了用户的字段信息:

package model// User  用户表
type User struct {
Table    string `json:"table" form:"table"`       //  数据库标记
Username string `json:"username" form:"username"` //用户账户
Name     string `json:"name" form:"name"`         //真实姓名
Email    string `json:"email" form:"email"`       //  邮箱
Phone    string `json:"phone" form:"phone"`       //  手机
}func (o *User) Index() string {
o.Table = "user"
return o.Username
}func (o *User) IndexKey() string {
return "table~username~name"
}func (o *User) IndexAttr() []string {
return []string{o.Table, o.Username, o.Name}
}

model/base.go 申明了基于 CouchDB 的富查询结果模型:

package modelimport "time"// 历史查询结果
type HistoryQueryResult[T interface{}] struct {
Record    T         `json:"record"`
TxId      string    `json:"txId"`
Timestamp time.Time `json:"timestamp"`
IsDelete  bool      `json:"isDelete"`
}// 分页查询结果
type PaginatedQueryResult[T interface{}] struct {
Records             []T    `json:"records"`
FetchedRecordsCount int32  `json:"fetchedRecordsCount"`
Bookmark            string `json:"bookmark"`
}

contract 层

contract 层用于实现智能合约的核心逻辑(本示例为 model 的增删改查),由于结合了 CouchDB ,所以相比上个实验需要更复杂的实现。以 contract/project.go 为例进行介绍,由于代码太长在此就不再粘贴(完整代码参考 project.go),其中主要功能及实现方式如下:

  • 插入数据( Insert ):先使用 ctx.GetStub().PutState(tx.Index(), txb) 方法插入数据,然后调用ctx.GetStub().CreateCompositeKey(tx.IndexKey(), tx.IndexAttr()) 方法为该数据创建 CouchDB 索引,最后调用 ctx.GetStub().PutState(indexKey, value) 将索引存入链上。
  • 更新数据( Update ):先使用 indexKey, err := ctx.GetStub().CreateCompositeKey(otx.IndexKey(), otx.IndexAttr()) 得到旧数据的索引,再调用 ctx.GetStub().DelState(indexKey) 删除旧数据的索引,然后调用 ctx.GetStub().PutState(tx.Index(), txb) 更新数据,最后分别调用 ctx.GetStub().CreateCompositeKey(tx.IndexKey(), tx.IndexAttr()) 和 ctx.GetStub().PutState(indexKey, value) 创建新数据索引并存入链上。
  • 删除数据( Delete ):先使用 ctx.GetStub().DelState(anstx.Index()) 删除旧数据,再调用 indexKey, err := ctx.GetStub().CreateCompositeKey(tx.IndexKey(), tx.IndexAttr()) 得到旧数据索引,最后通过 ctx.GetStub().DelState(indexKey) 删除旧数据索引。
  • 读取指定index的记录( SelectByIndex ):使用形如 {"selector":{"ID":"%s", "table":"project"}} 的 CouchDB 查询语法根据索引查询数据。
  • 读取所有数据( SelectAll ):使用形如 {"selector":{"table":"project"}} 的 CouchDB 查询语法查询所有相关数据。
  • 按某索引查询所有数据( SelectBySome ):使用形如 {"selector":{"%s":"%s", "table":"project"}} 的 CouchDB 查询语法根据索引查询数据。
  • 富分页查询所有数据( SelectAllWithPagination ):使用形如 {"selector":{"table":"project"}} 的 CouchDB 查询语法调用上述分页查询数据工具 tools.SelectByQueryStringWithPagination 来查询数据。
  • 按关键字富分页查询所有数据 SelectBySomeWithPagination ):使用形如 {"selector":{"%s":"%s","table":"project"}} 的 CouchDB 查询语法调用上述分页查询数据工具 tools.SelectByQueryStringWithPagination 来查询数据。
  • 按某索引查询数据历史( SelectHistoryByIndex ):调用上述历史数据查询工具 tools.SelectHistoryByIndex 来查询数据。

contract/user.go 为 model/user.go 的核心操作逻辑,此示例只包含简单的功能,完整源码参考 user.go。

main 主函数

主函数完整代码如下所示:

package mainimport ("github.com/hyperledger/fabric-contract-api-go/contractapi""github.com/wefantasy/FabricLearn/7_CouchDBAndComplexContract/project_contract/contract"
)func main() {chaincode, err := contractapi.NewChaincode(&contract.UserContract{}, &contract.ProjectContract{})if err != nil {panic(err)}if err := chaincode.Start(); err != nil {panic(err)}
}

多智能合约只需在 main 的 contractapi.NewChaincode 函数中按顺序申明即可。在智能合约编写完毕后使用 go mod vendor 来打包依赖,上述工作完成后 project_contract 目录结构及解释如下所示:

project_contract
├── contract            // 智能合约核心逻辑
│   ├── project.go
│   └── user.go
├── go.mod
├── go.sum
├── main.go             // 智能合约入口函数
├── model               // 申明数据模型
│   ├── base.go         // 申明分页等数据结构
│   ├── project.go
│   └── user.go
├── tools               // 工具目录
│   └── contract.go     // 智能合约通用工具,查询历史/分页查询等
└── vendor              // 依赖目录

合约部署和测试

如无特殊说明,以下命令默认运行于实验根目录 7_CouchDBAndComplexContract 下:

  1. 合约打包
    source envpeer1soft
    peer lifecycle chaincode package basic.tar.gz --path project_contract --label basic_1
    
  2. 三组织安装
     source envpeer1softpeer lifecycle chaincode install basic.tar.gzpeer lifecycle chaincode queryinstalledsource envpeer1webpeer lifecycle chaincode install basic.tar.gzpeer lifecycle chaincode queryinstalledsource envpeer1hardpeer lifecycle chaincode install basic.tar.gzpeer lifecycle chaincode queryinstalled
    
  3. 三组织批准
    export CHAINCODE_ID=basic_1:22e38a78d2ddfe9c3cbeff91140ee209c901adcc24cd2b11f863a53abcdc825a
    source envpeer1soft
    peer lifecycle chaincode approveformyorg -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA  --channelID testchannel --name basic --version 1.0 --sequence 1 --waitForEvent --package-id $CHAINCODE_ID
    peer lifecycle chaincode queryapproved -C testchannel -n basic --sequence 1
    source envpeer1web
    peer lifecycle chaincode approveformyorg -o orderer3.council.ifantasy.net:7057 --tls --cafile $ORDERER_CA  --channelID testchannel --name basic --version 1.0 --sequence 1 --waitForEvent --package-id $CHAINCODE_ID
    peer lifecycle chaincode queryapproved -C testchannel -n basic --sequence 1
    source envpeer1hard
    peer lifecycle chaincode approveformyorg -o orderer2.council.ifantasy.net:7054 --tls --cafile $ORDERER_CA  --channelID testchannel --name basic --version 1.0 --sequence 1 --waitForEvent --package-id $CHAINCODE_ID
    peer lifecycle chaincode queryapproved -C testchannel -n basic --sequence 1
    
    注意:由于我们有两个智能合约,且每个智能合约都包含 InitLedger 函数来初始化数据,所以在这里以及后续链码操作中需要删除 --init-required 参数(因为合约不需要初始化)。
  4. 提交链码
    source envpeer1soft
    peer lifecycle chaincode commit -o orderer2.council.ifantasy.net:7054 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --version 1.0 --sequence 1 --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE
    
  5. 初始化链码数据并测试
    source envpeer1soft
    peer chaincode invoke -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE -c '{"Args":["UserContract:InitLedger"]}'
    peer chaincode invoke -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE -c '{"Args":["ProjectContract:InitLedger"]}'peer chaincode invoke -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE -c '{"Args":["UserContract:GetAllUsers"]}'
    peer chaincode invoke -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE -c '{"Args":["ProjectContract:SelectAll"]}'
    peer chaincode invoke -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE -c '{"Args":["ProjectContract:SelectBySome", "name", "工作室联盟链管理系统"]}'
    

注意,在多合约的情况下调用链码,需要在所调用的合约函数前指定所属合约,如 ProjectContract:SelectBySome ,其它合约示例调用方式大致一样,在此不再赘述。此外由于 CouchDB 自带了数据库管理界面,则可以通过本例中任意一个 CouchDB 的服务地址来访问链上数据,如 http://192.168.27.72:7355/_utils/#login (虚拟机IP为 192.168.27.72 ,soft 组织的 CouchDB 端口 7355),输入docker中配置的账户admin密码adminpw即可进入系统:
 


 


至此,本实验基本完成。

可能存在的问题

  1. peer lifecycle chaincode install 时遇到错误:
Error creating tx-manage chaincode: Error compiling schema for DataContract [SelectBySomeWithPagination]. Return schema invalid. Object has no key 'PaginatedQueryResult[github.com'
panic: Error creating tx-manage chaincode: Error compiling schema for DataContract [SelectBySomeWithPagination]. Return schema invalid. Object has no key 'PaginatedQueryResult[github.com'goroutine 1 [running]:
log.Panicf({0xa24b02?, 0x1?}, {0xc00014ff50?, 0x407679?, 0x404c71?})/usr/local/go/src/log/log.go:392 +0x67
main.main()/chaincode/input/src/main.go:201 +0x8e

原因及解决方法: 所用 docker fabric 2.4 镜像的 Golang 版本太低不支持泛型,需要删除并重新安装 docker fabric 2.4 (尽管 tag 一样,但镜像内容会更新)。

  1. 智能合约调用时遇到错误:
[notice] 2022-11-13T12:13:49.502557Z nonode@nohost <0.286.0> -------- rexi_server : started servers
[notice] 2022-11-13T12:13:49.504490Z nonode@nohost <0.290.0> -------- rexi_buffer : started servers
[warning] 2022-11-13T12:13:49.530610Z nonode@nohost <0.298.0> -------- creating missing database: _nodes
[info] 2022-11-13T12:13:49.530670Z nonode@nohost <0.299.0> -------- open_result error {not_found,no_db_file} for _nodes
[error] 2022-11-13T12:13:49.537681Z nonode@nohost <0.304.0> -------- CRASH REPORT Process  (<0.304.0>) with 2 neighbors crashed with reason: no match of right hand value {error,enospc} at couch_bt_engine:init/2(line:154) <= 
……

原因及解决方法: 可能是 docker volume 把硬盘占满了,使用 docker volume rm $(docker volume ls -qf dangling=true) 清除所有再重试
使用

  1. 遇到错误:
# github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/discovery/client
/root/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/internal/github.com/hyperledger/fabric/discovery/client/api.go:47:38: undefined: discovery.ChaincodeCall
/root/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/internal/github.com/hyperledger/fabric/discovery/client/client.go:83:63: undefined: discovery.ChaincodeInterest
/root/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/internal/github.com/hyperledger/fabric/discovery/client/client.go:120:65: undefined: discovery.ChaincodeCall
/root/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/internal/github.com/hyperledger/fabric/discovery/client/client.go:124:23: undefined: discovery.ChaincodeInterest
/root/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/internal/github.com/hyperledger/fabric/discovery/client/client.go:229:105: undefined: discovery.ChaincodeCall
/root/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/internal/github.com/hyperledger/fabric/discovery/client/client.go:247:64: undefined: discovery.ChaincodeCall
/root/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/internal/github.com/hyperledger/fabric/discovery/client/client.go:604:48: undefined: discovery.ChaincodeInterest
/root/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/internal/github.com/hyperledger/fabric/discovery/client/client.go:620:35: undefined: discovery.ChaincodeCall

原因及解决方法: github.com/hyperledger/fabric-sdk-go 需要指定 20220117 版本,将 go.mod 文件对应依赖替换如下:

github.com/hyperledger/fabric-sdk-go v1.0.1-0.20220117114400-c848d119936b。
  1. 遇到错误:
Error compiling schema for ****[**]. Return schema invalid. Object has no key 'Wrapper[[]<part of module name>'

原因及解决方法:智能合约返回值不支持泛型,将智能合约返回值换成 interface{} 即可。

  1. 查询历史记录出现遇到错误:
Error: could not assemble transaction: ProposalResponsePayloads do not match (base64):

原因及解决方法:链码输出(返回)数据中不要使用地址传递(推荐值传递),因为地址都是动态分配,每次取到的值都不一样,造成共识失败。

  1. 遇到错误:
Failed to evaluate: Multiple errors occurred: - Transaction processing for endorser [localhost:7451]: Chaincode status Code: (500) UNKNOWN. Description: Error handling success response. Value did not match schema:\n1. return: Invalid type. Expected: array, given: string - Transaction processing for endorser [localhost:7251]: Chaincode status Code: (500) UNKNOWN. Description: Error handling success response. Value did not match schema:\n1. return: Invalid type. Expected: array, given: string

原因及解决方法:链码返回值不能为 []byte ,这是一个 fabric 的 bug,对于复杂返回类型建议直接返回字符串 string

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/77017.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka3.0.0版本——Broker(Zookeeper服务端存储的Kafka相关信息)

目录 一、启动zookeeper集群及kafka集群服务启动1.1、先启动三台zookeeper集群服务&#xff0c;再启动三台kafka集群服务1.2、使用PrettyZoo连接zookeeper客户端工具 二、在zookeeper服务端存储的Kafka相关信息 一、启动zookeeper集群及kafka集群服务启动 1.1、先启动三台zook…

C++初阶引用

目录 引用引用的特性使用输出型参数作返回值小总结引用的权限引用和指针 引用 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用的变量共用同一块内存空间。 比如周树人&#xff0c;在外…

探索创意之路:稳定扩散AI绘画指南

文章目录 引言第一部分&#xff1a;了解稳定扩散AI绘画1.1 稳定扩散AI绘画简介1.2 稳定扩散AI绘画的优势 第二部分&#xff1a;使用稳定扩散AI绘画2.1 获取稳定扩散AI绘画工具2.2 准备绘画素材和设置参数2.3 进行AI绘画 第三部分&#xff1a;发挥创意&#xff0c;创作精彩绘画3…

阿里云AK创建

要在阿里云上创建 Access Key&#xff08;AK&#xff09;&#xff0c;您需要按照以下步骤进行操作&#xff1a; 登录到阿里云控制台&#xff08;[https://www.aliyun.com/?utm_contentse_1014243503)&#xff09;。 点击右上方的主账号&#xff0c;点击“AccessKey管理”。 …

P1064 [NOIP2006 提高组] 金明的预算方案 (依赖背包问题)(内附封面)

[NOIP2006 提高组] 金明的预算方案 题目描述 金明今天很开心&#xff0c;家里购置的新房就要领钥匙了&#xff0c;新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是&#xff0c;妈妈昨天对他说&#xff1a;“你的房间需要购买哪些物品&#xff0c;怎么布置&#xff0…

R语言【Tidyverse、Tidymodel】的机器学习方法

机器学习已经成为继理论、实验和数值计算之后的科研“第四范式”&#xff0c;是发现新规律&#xff0c;总结和分析实验结果的利器。机器学习涉及的理论和方法繁多&#xff0c;编程相当复杂&#xff0c;一直是阻碍机器学习大范围应用的主要困难之一&#xff0c;由此诞生了Python…

python人工智能可以干什么,python人工智能能干什么

大家好&#xff0c;给大家分享一下python做人工智能需要什么水平&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 人工智能包含常用机器学习和深度学习两个很重要的模块&#xff0c;而python拥有matplotlib、Numpy、sklearn、keras等大量的…

【深度学习笔记】深度学习框架

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…

snap xxx has “install-snap“ change in progress

error description * 系重复安装&#xff0c;进程冲突 solution 展示snap的改变 然后sudo snap abort 22即可终止该进程 之后重新运行install command&#xff5e;&#xff5e; PS: ubuntu有时候加载不出来&#xff0c;执行resolvectl flush-caches&#xff0c;清除dns缓存…

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(8 月 1 日论文合集)

文章目录 一、分割|语义相关(16篇)1.1 DPMix: Mixture of Depth and Point Cloud Video Experts for 4D Action Segmentation1.2 Investigating and Improving Latent Density Segmentation Models for Aleatoric Uncertainty Quantification in Medical Imaging1.3 Domain Ada…

网络音频终端音频编码解码终端

网络对讲终端SV-7011V 网络对讲终端SV-7011V&#xff0c;采用了ARM音频DSP架构&#xff0c;集网络对讲、网络广播、监听等功能于一身&#xff0c;内置麦克风、配置line out、line in、Mic in功能输出接口&#xff0c;适用于学校&#xff0c;机场&#xff0c;广场等场所。 产品…

纯css实现九宫格图片

本篇文章所分享的内容主要涉及到结构伪类选择器&#xff0c;不熟悉的小伙伴可以了解一下&#xff0c;在常用的css选择器中我也有分享相关内容。 话不多说&#xff0c;接下来我们直接上代码&#xff1a; <!DOCTYPE html> <html lang"en"><head>&l…

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验一 点亮第一个LED

目录 前言 一、原理图及知识点介绍 1.1、LED原理图 1.2、MCU51原理图 二、代码分析 知识点一&#xff1a;#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器 知识点二&#xff1a;你知道sfr P0 0x80;是怎么来的呢为什么要赋值0x80&#xff…

Stable Diffusion AI绘画学习指南【本地环境搭建win+mac】

一、硬件配配置要求 系统&#xff1a;windows 10 / Mac os 硬盘&#xff1a;C 盘预留 15GB 以上&#xff0c;其他盘 50GB 以上,Stable Ddiffusion的很多大模型都是以 GB 起步。 显卡&#xff1a;4GB 以上&#xff0c;建议 8GB, 效率高&#xff0c;能玩大尺寸的图 CPU&…

SpringMVC框架——First Day

目录 三层架构 MVC模型 SpringMVC 快速入门案例 SpringMVC的概述&#xff08;了解&#xff09; SpringMVC在三层架构的位置 SpringMVC的优势&#xff08;了解&#xff09; 创建SpringMVC的Maven项目 1.在pom.xml中添加所需要的jar包 2.在工程的web.xml中配置核心Spring…

Linux修改系统语言

sudo dpkg-reconfigure locales 按pagedown键&#xff0c;移动红色光标到 zh_CN.UTF-8 UTF-8&#xff0c;空格标记*号&#xff08;没标记下一页没有这一项&#xff09;&#xff0c;回车。 下一页选择 zh_CN.UTF-8。 如果找不到 dpkg-reconfigure whereis dpkg-reconfigure …

Java的变量与常量

目录 变量 声明变量 变量的声明类型 变量的声明方式&#xff1a;变量名 变量名的标识符 初始化变量 常量 关键字final 类常量 总结 变量和常量都是用来存储值和数据的基本数据类型存储方式&#xff0c;但二者之间有一些关键差别。 变量 在Java中&#xff0c;每个变…

深入理解TCP三次握手:连接可靠性与安全风险

目录 导言TCP简介和工作原理的回顾TCP三次握手的目的和步骤TCP三次握手过程中可能出现的问题和安全风险为什么TCP三次握手是必要的&#xff1f;是否可以增加或减少三次握手的次数&#xff1f;TCP四次挥手与三次握手的异同点 导言 在网络通信中&#xff0c;TCP&#xff08;Tra…

sqoop

一、bg 可以在关系型数据库和hdfs、hive、hbase之间导数 导入&#xff1a;从RDBMS到hdfs、hive、hbase 导出&#xff1a;相反 sqoop1 和sqoop2 (1.99.x)不兼容&#xff0c;sqoop2 并没有生产的稳定版本&#xff0c; Sqoop1 import原理(导入) 从传统数据库获取元数据信息&…

8.5day06 框架基础--反射+注解

文章目录 反射获取类的各种信息获取类的字节码文件 注解元注解 复习redis两道算法题 摆烂了&#xff0c;不想学啦&#xff01;&#xff01;&#xff01; 反射 反射主要用来做框架; 学习内容 获取类的各种信息 第一步 加载类&#xff0c;获取类的字节码文件 第二步 获取类的…