前言
在上个实验中,我们已经实现了简单智能合约实现及客户端开发,但该实验中智能合约只有基础的增删改查功能,且其中的数据管理功能与传统 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