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 基础知识的一定了解,包括函数和事件处理程序…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...