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.net | 7050 | council 组织的 CA 服务, 为联盟链网络提供 TLS-CA 服务 |
orderer1.council.ifantasy.net | 7051 | council 组织的 orderer1 服务 |
orderer1.council.ifantasy.net | 7052 | council 组织的 orderer1 服务的 admin 服务 |
orderer2.council.ifantasy.net | 7054 | council 组织的 orderer2 服务 |
orderer2.council.ifantasy.net | 7055 | council 组织的 orderer2 服务的 admin 服务 |
orderer3.council.ifantasy.net | 7057 | council 组织的 orderer3 服务 |
orderer3.council.ifantasy.net | 7058 | council 组织的 orderer3 服务的 admin 服务 |
soft.ifantasy.net | 7250 | soft 组织的 CA 服务, 包含成员: peer1 、 admin1 、user1 |
peer1.soft.ifantasy.net | 7251 | soft 组织的 peer1 成员节点 |
couchdb.soft.ifantasy.net | 7255 | soft 组织的 couchdb 成员节点 |
web.ifantasy.net | 7350 | web 组织的 CA 服务, 包含成员: peer1 、 admin1 、user1 |
peer1.web.ifantasy.net | 7351 | web 组织的 peer1 成员节点 |
couchdb.web.ifantasy.net | 7355 | web 组织的 couchdb 成员节点 |
hard.ifantasy.net | 7450 | hard 组织的 CA 服务, 包含成员: peer1 、 admin1 、user1 |
peer1.hard.ifantasy.net | 7451 | hard 组织的 peer1 成员节点 |
couchdb.hard.ifantasy.net | 7455 | hard 组织的 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 目录下按顺序执行以下命令启动基础实验网络:
- 设置DNS(如果未设置):
./setDNS.sh
- 设置环境变量:
source envpeer1soft
- 启动CA网络:
./0_Restart.sh
- 注册用户:
./1_RegisterUser.sh
- 获取用户证书:
./2_EnrollUser.sh
- 配置通道:
./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 下:
- 合约打包
source envpeer1soft peer lifecycle chaincode package basic.tar.gz --path project_contract --label basic_1
- 三组织安装
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
- 三组织批准
注意:由于我们有两个智能合约,且每个智能合约都包含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
参数(因为合约不需要初始化)。 - 提交链码
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
- 初始化链码数据并测试
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
即可进入系统:
至此,本实验基本完成。
可能存在的问题
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 一样,但镜像内容会更新)。
- 智能合约调用时遇到错误:
[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)
清除所有再重试
使用
- 遇到错误:
# 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。
- 遇到错误:
Error compiling schema for ****[**]. Return schema invalid. Object has no key 'Wrapper[[]<part of module name>'
原因及解决方法:智能合约返回值不支持泛型,将智能合约返回值换成 interface{} 即可。
- 查询历史记录出现遇到错误:
Error: could not assemble transaction: ProposalResponsePayloads do not match (base64):
原因及解决方法:链码输出(返回)数据中不要使用地址传递(推荐值传递),因为地址都是动态分配,每次取到的值都不一样,造成共识失败。
- 遇到错误:
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
相关文章:

Hyperledger Fabric 使用 CouchDB 和复杂智能合约开发
前言 在上个实验中,我们已经实现了简单智能合约实现及客户端开发,但该实验中智能合约只有基础的增删改查功能,且其中的数据管理功能与传统 MySQL 比相差甚远。本文将在前面实验的基础上,将 Hyperledger Fabric 的默认数据库支持 …...

这应该是最全的,Fiddler手机App抓包详解,看完还不会来找我...
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 什么是抓包&#…...

使用ubuntu-base制作根文件系统
1:ubuntu官网下载最小根文件系统: 放置到电脑的ubuntu中, Mkdir Ubuntu_rootfs Cd Ubuntu_rootfs Sudo tar –zxvf Ubuntu-bash-xxxxxx.tar.gz 2:电脑的ubuntu安装qemu搭建arm模拟系统 将/usr/bin/qemu-arm-static/(64位拷贝…...
springboot-mybatis的分页查询的接口
一、安装依赖 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version> </dependency> 二、代码定义 分页对象类 Data AllArgsConstruct…...

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

cordova 12 编译失败Could not find method compile() for arguments [com....]
问题: Could not find method compile() for arguments [com.tencent.mm.opensdk:wechat-sdk-android-with-mta:] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler. 问题原因: 在Cordova项目中&…...

快速开发平台 WebBuilder 9 发布
WebBuilder 是一款强大,全面和高效的应用开发和运行平台。基于浏览器的集成开发环境,智能化的设计,能轻松完成常规桌面应用和面向手机等的移动应用开发。高效、稳定和可扩展的特点,适合复杂企业级应用的运行。跨平台、数据库和浏览…...

GPT-5出世,需5万张H100!英伟达GPU陷短缺风暴
随着人工智能技术的不断突破,自然语言处理领域也掀起了一波又一波的革命。从GPT-3.5的惊艳登场,到紧随其后的GPT-4的惊世震人,人们仿佛置身于科幻电影中,亲历了一场场技术的奇迹。然而,这场奇迹背后却逐渐显现出一道不…...

Jmeter学习和一个关于jmeter获取X-XSRF-TOKEN时的坑
Jmeter学习和一个关于jmeter获取X-XSRF-TOKEN时的坑 现在想对一个接口做性能测试,需要测试它多个线程并发下的调用 1.新建测试计划和线程组 略 2.新建http接口 一个完整的http接口包含请求头和请求,这里就需要两个组件:HTTP request、HT…...
SQL注入总结
0x00 前言 简单的总结一下SQL注入的内容 0x01 常规注入 通过特殊字符测试闭合,通常使用的闭合方式有 " ) ")等 1.判断字段 a union select 1,2,3 #2.执行基础内容 a union select 1,database(),3 #3.查表 a union select 1,group_concat(table_na…...
Java 实现 后端分页
背景说明 在实际开发中,基于数据库表结构进行SQL查询,如果要对结果进行分页,可以借助一些工具类,如:基于Mybatis的 工具类 PageHelper。 但是,有时分页的对象是经过一些业务逻辑处理的列表,如…...
数据结构 | 搜索和排序——排序
目录 一、冒泡排序 二、选择排序 三、插入排序 四、希尔排序 五、归并排序 六、快速排序 排序是指将集合中的元素按照某种顺序排序的过程。 一、冒泡排序 冒泡排序多次遍历列表。它比较相邻的元素,将不合顺序的交换。每一轮遍历都将下一个最大值放到正确的位…...

【嵌入式环境下linux内核及驱动学习笔记-(18)LCD驱动框架1-LCD控制原理】
目录 1、LCD显示系统介绍1.1 LCD显示基本原理1.1.1 颜色的显示原理:1.1.2 图像的构成 1.2 LCD接口介绍1.2.1 驱动接口 - MCU接口1.2.2 驱动接口 - RGB接口1.2.3 驱动接口 - LVDS接口1.2.4 驱动接口 - MIPI接口1.2.5 RGB / MIPI / LVDS三种接口方式的区别:…...

【unity】ShaderGraph实现等高线和高程渐变设色
【unity】ShaderGraph实现等高线和高程渐变设色 等高线的实现思路 方法一: 通过Position节点得到顶点的高度(y)值,将高度值除去等高距离取余,设定余数的输出边界(step) 方法二: 将…...

快速修复应用程序中的问题的利器—— Android热修复
热修复技术在Android开发中扮演着重要的角色,它可以帮助开发者在不需要重新发布应用程序的情况下修复已经上线的应用程序中的bug或者添加新的功能。 一、热修复是什么? 热修复(HotFix)是一种在运行时修复应用程序中的问题的技术…...

什么是全局代理,手机怎么设置全局代理
目录 什么是全局代理 全局代理的优缺点 优点 缺点 手机怎么设置全局代理 注意事项 总结 在计算机网络和信息安全中,全局代理是一种常用的技术手段,用于将网络流量通过代理服务器进行转发和处理。本文将介绍什么是全局代理,探讨全局代理…...

技术领先产品ASSAR300一一基于SAR成像的角雷达产品,助力自动泊车
作为自动驾驶应用场景中最先被推广和商业化落地的自动泊车功能,目前是在一些限定环境下实现了功能跑通。面对多种多样的复杂停车场场景,系统需要不断增强感知算法能力或寻求新的传感器技术,来提升对周围环境感知和对障碍物探测的精准度。 传…...

单元测试之 - Spring框架提供的单元/集成测试注解
Spring框架提供了很多注解来辅助完成单元测试和集成测试(备注:这里的集成测试指容器内部的集成测试,非系统间的集成测试),先看看Spring框架提供了哪些注解以及对应的作用。RunWith(SpringRunner.class) / ExtendWith(SpringExtension.class)&…...

深入学习 Redis - 事务、实现原理、指令使用及场景
目录 一、Redis 事务 vs MySQL事务 二、Redis 事务的执行原理 2.1、执行原理 2.2、Redis 事务设计这么简单,为什么不涉及成 MySQL 那样强大呢? 三、Redis 事务的使用 3.1、使用场景 3.2、具体演示 开启/执行/放弃事务 watch 监控 watch 实现原理…...
异步javaScript
在本文中,我们将解释什么是异步编程,为什么我们需要它,并简要讨论 JavaScript 历史上异步函数是怎样被实现的。 预备知识:基本的计算机素养,以及对 JavaScript 基础知识的一定了解,包括函数和事件处理程序…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...