在上个实验中,我们已经实现了简单智能合约实现及客户端开发,但该实验中智能合约只有基础的增删改查功能,且其中的数据管理功能与传统 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 成员节点 |
首先,在 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=adminpw
networks:
- ${DOCKER_NETWORKS}
之后,向 compose/docker-compose.yaml 中的每个组织添加 CouchDB 容器:
couchdb.soft.ifantasy.net:
container_name: couchdb.soft.ifantasy.net
extends:
file: docker-base.yaml
service: couchdb-base
ports:
- 7255:5984
couchdb.web.ifantasy.net:
container_name: couchdb.web.ifantasy.net
extends:
file: docker-base.yaml
service: couchdb-base
ports:
- 7355:5984
couchdb.hard.ifantasy.net:
container_name: couchdb.hard.ifantasy.net
extends:
file: docker-base.yaml
service: couchdb-base
ports:
- 7455:5984
最后,修改 compose/docker-compose.yaml 中每个 peer 容器的储存方式(以 peer1.soft.ifantasy.net 为例):
peer1.soft.ifantasy.net:
container_name: peer1.soft.ifantasy.net
extends:
file: docker-base.yaml
service: peer-base
environment:
- 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=adminpw
volumes:
- ${LOCAL_CA_PATH}/soft.ifantasy.net/registers/peer1:${DOCKER_CA_PATH}/peer
ports:
- 7251:7251
depends_on:
- couchdb.soft.ifantasy.net
注意,参数 CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS 后的服务端口必须为 couchdb 容器的内部端口,原因不得而知, 完整代码见 FabricLearn 下的 7_CouchDBAndComplexContract/compose 目录。
在上述修改完成后,在 7_CouchDBAndComplexContract 目录下按顺序执行以下命令启动基础实验网络:
./setDNS.shsource envpeer1soft./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/contract.go 工具类,主要包含以下函数:
ConstructResultByIterator : 根据 fabric 查询结果 shim.StateQueryIteratorInterface 生成对应切片。// 根据查询结果生成切片
func ConstructResultByIterator[T interface{}](resultsIterator shim.StateQueryIteratorInterface) ([]*T, error) {
var txs []*T
for resultsIterator.HasNext() {
queryResult, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var tx T
err = 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 []T
for resultsIterator.HasNext() {
queryResult, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var tx T
err = 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 T
if 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/project.go 内容如下:
package model
type 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 model
import "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 层用于实现智能合约的核心逻辑(本示例为 model 的增删改查),由于结合了 CouchDB ,所以相比上个实验需要更复杂的实现。以 contract/project.go 为例进行介绍,由于代码太长在此就不再粘贴(完整代码参考 project.go),其中主要功能及实现方式如下:
ctx.GetStub().PutState(tx.Index(), txb) 方法插入数据,然后调用ctx.GetStub().CreateCompositeKey(tx.IndexKey(), tx.IndexAttr()) 方法为该数据创建 CouchDB 索引,最后调用 ctx.GetStub().PutState(indexKey, value) 将索引存入链上。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) 创建新数据索引并存入链上。ctx.GetStub().DelState(anstx.Index()) 删除旧数据,再调用 indexKey, err := ctx.GetStub().CreateCompositeKey(tx.IndexKey(), tx.IndexAttr()) 得到旧数据索引,最后通过 ctx.GetStub().DelState(indexKey) 删除旧数据索引。{"selector":{"ID":"%s", "table":"project"}} 的 CouchDB 查询语法根据索引查询数据。{"selector":{"table":"project"}} 的 CouchDB 查询语法查询所有相关数据。{"selector":{"%s":"%s", "table":"project"}} 的 CouchDB 查询语法根据索引查询数据。{"selector":{"table":"project"}} 的 CouchDB 查询语法调用上述分页查询数据工具 tools.SelectByQueryStringWithPagination 来查询数据。{"selector":{"%s":"%s","table":"project"}} 的 CouchDB 查询语法调用上述分页查询数据工具 tools.SelectByQueryStringWithPagination 来查询数据。tools.SelectHistoryByIndex 来查询数据。contract/user.go 为 model/user.go 的核心操作逻辑,此示例只包含简单的功能,完整源码参考 user.go。
主函数完整代码如下所示:
package main
import (
"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 envpeer1soft
peer lifecycle chaincode install basic.tar.gz
peer lifecycle chaincode queryinstalled
source envpeer1web
peer lifecycle chaincode install basic.tar.gz
peer lifecycle chaincode queryinstalled
source envpeer1hard
peer lifecycle chaincode install basic.tar.gz
peer 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
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po