Hyperledger Fabric using CouchDB and complex smart contract development

Foreword

In the last experiment, we have implemented a simple smart contract implementation and client development, but the smart contract in this experiment only has basic addition, deletion, modification and query functions, and its data management functions are far from traditional MySQL. Based on the previous experiments, this article will change Hyperledger Fabric’s default database support LevelDB to CouchDB mode to achieve more complex data retrieval functions. In addition, the function and design of the simple smart contract in the previous experiment were further expanded, and finally the functions of smart contract subcontracting, pagination query, multi-field rich query, and transaction history query were realized.

Network Architecture

The network structure of this article directly copies the 4-2_RunOrdererByCouncil created in the Hyperledger Fabric non-sorting organization using the Raft protocol to start multiple Orderer services and the TLS organization operation and maintenance Orderer service as 7_CouchDBAndComplexContract and modifies it (it is recommended to directly copy the 7_CouchDBAndComplexContract directory under FabricLearn in this case warehouse to Local operation), most of the commands in this article have been introduced in the Hyperledger Fabric custom alliance chain network engineering practice, so they will not be described in detail. By default, all operations are executed under the root directory of 7_CouchDBAndComplexContract. After the modification is successful, the network consists of four organizations-council, soft, web, and hard. The council organization provides TLS-CA services for the network, and operates and maintains three orderer services; each of the other organizations operates and maintains a peer node , a couchDB service, an admin user and a user user, the final network structure of the experiment is as follows:

Item Run Port Description
council.ifantasy.net 7050 CA service organized by council, providing TLS-CA service for alliance chain network
orderer1.council.ifantasy.net 7051 council organization’s orderer1 service
orderer1.council.ifantasy.net 7052 The admin service of the orderer1 service organized by the council
orderer2.council.ifantasy.net 7054 council organization’s orderer2 service
orderer2.council.ifantasy.net 7055 The admin service of the orderer2 service organized by the council
orderer3.council.ifantasy.net 7057 council’s orderer3 service
orderer3.council.ifantasy.net 7058 admin service of the orderer3 service organized by the council
soft.ifantasy.net 7250 The CA service of the soft organization, including members: peer1, admin1, user1
peer1.soft.ifantasy.net 7251 soft organization peer1 member node
couchdb.soft.ifantasy.net 7255 soft organization’s couchdb member node
web.ifantasy.net 7350 CA service of web organization, including members: peer1, admin1, user1
peer1.web.ifantasy.net 7351 web organization peer1 member node
couchdb.web.ifantasy.net 7355 web organization’s couchdb member node
hard.ifantasy.net 7450 CA service of hard organization, including members: peer1, admin1, user1
peer1.hard.ifantasy.net 7451 hard organization peer1 member node
couchdb.hard.ifantasy.net 7455 hard organization couchdb member node

Add CouchDB support and start network

Add CouchDB support

First, add the CouchDB version variables in envpeer1soft , envpeer1soft , envpeer1soft :

export COUCHDB_VERSION=3.2

Then, add the base CouchDB image to the compose/docker-base.yaml file:

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

Afterwards, add a CouchDB container to each org in compose/docker-compose.yaml :

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

Finally, modify the storage method of each peer container in compose/docker-compose.yaml (take peer1.soft.ifantasy.net as an example):

 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 # must be the port in the container
      - 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

Note that the service port after the parameter CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS must be the internal port of the couchdb container. The reason is unknown. See the 7_CouchDBAndComplexContract/compose directory under FabricLearn for the complete code.

Start Experimental Network

After the above modifications are completed, execute the following commands in sequence in the 7_CouchDBAndComplexContract directory to start the basic experimental network:

  1. Set DNS (if not set): ./setDNS.sh
  2. Set environment variables: source envpeer1soft
  3. Start the CA network: ./0_Restart.sh
  4. Register user: ./1_RegisterUser.sh
  5. Get user credentials: ./2_EnrollUser.sh
  6. Configuration channel: ./3_Configtxgen.sh

After the network starts successfully, it can be seen that the couchdb container is included:

Contract Development

The smart contracts used in this section are improved (split) from the previous article Hyperledger Fabric smart contract development and fabric-sdk-go/fabric-gateway usage examples. On the basis of the previous article, the contracts are subcontracted and file-processed , so that the project has a better directory structure. Create a directory project_contract under the root directory of the experiment 7_CouchDBAndComplexContract as the root directory of the smart contract, and execute the following command under project_contract to initialize the GO module:

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

tools layer

The tools layer is mainly used to write common tools for smart contracts, creating tools/contract.go tool class, which mainly includes the following functions:

  • ConstructResultByIterator : Generate the corresponding slice according to the fabric query result shim.StateQueryIteratorInterface.
    // Generate slices based on query results
    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 : Complete the query operation according to the couchdb query string and return the corresponding slice.
    // Query according to the query string
    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 : According to the couchdb query string paging query, and return the corresponding slice.
    // Paging query according to the query string
    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 : Obtain all changes (blockchain ledger) after the transaction is created.
    // Get all the changes after the transaction was created.
    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 layer

The model layer is mainly used to declare the data structure used by the contract, where the content of model/project.go is as follows:

package model

type Project struct {
Table string `json:"table" form:"table"` // database markup
ID string `json:"ID"` // Unique ID of the project
Name string `json:"Name"` // project name
Username string `json:"username"` // The main person in charge of the project
Organization string `json:"Organization"` // The organization to which the project belongs
Category string `json:"Category"` // The category the item belongs to
Url string `json:"Url"` // project introduction address
Describes string `json:"Describes"` // project description
}

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}
}

The Index function is used to identify the unique primary key of the model; the IndexKey function is used to identify the field of the self-built index, and the naming method must match the structure tag declared by the field json consistent (case); IndexAttr is used to construct a specific index. model/user.go declares the field information of the user:

package model

// User user table
type User struct {
Table string `json:"table" form:"table"` // database markup
Username string `json:"username" form:"username"` //User account
Name string `json:"name" form:"name"` //real name
Email string `json:"email" form:"email"` // Email
Phone string `json:"phone" form:"phone"` // 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 declares the rich query result model based on CouchDB:

package model

import "time"

// historical query results
type HistoryQueryResult[T interface{}] struct {
Record T `json:"record"`
TxId string `json:"txId"`
Timestamp time.Time `json:"timestamp"`
IsDelete bool `json:"isDelete"`
}

// paging query results
type PaginatedQueryResult[T interface{}] struct {
Records []T `json:"records"`
FetchedRecordsCount int32 `json:"fetchedRecordsCount"`
Bookmark string `json:"bookmark"`
}

contract layer

The contract layer is used to implement the core logic of the smart contract (this example is the addition, deletion, modification and query of the model). Due to the combination of CouchDB, it requires a more complex implementation than the previous experiment. Take contract/project.go as an example. Since the code is too long, I won’t paste it here (see project.go for the complete code). The main functions and implementation methods are as follows:

  • Insert data ( Insert ): first use the ctx.GetStub().PutState(tx.Index(), txb) method to insert data, and then call ctx.GetStub().CreateCompositeKey(tx. The IndexKey(), tx.IndexAttr()) method creates a CouchDB index for the data, and finally calls ctx.GetStub().PutState(indexKey, value) to store the index on the chain.
  • Update data (Update): first use indexKey, err := ctx.GetStub().CreateCompositeKey(otx.IndexKey(), otx.IndexAttr()) to get the index of the old data, and then call ctx.GetStub().DelState(indexKey) Delete the index of the old data, then call ctx.GetStub().PutState(tx.Index(), txb) to update the data, and finally Call ctx.GetStub().CreateCompositeKey(tx.IndexKey(), tx.IndexAttr()) and ctx.GetStub().PutState(indexKey, value) respectively to create New data is indexed and stored on-chain.
  • Delete data ( Delete ): first use ctx.GetStub().DelState(anstx.Index()) to delete old data, then call indexKey, err := ctx.GetStub().CreateCompositeKey (tx.IndexKey(), tx.IndexAttr()) Get the old data index, and finally delete the old data index through ctx.GetStub().DelState(indexKey).
  • Read the record of the specified index ( SelectByIndex ): use the form {"selector":{"ID":"%s", "table":"project"}} The CouchDB query syntax queries data based on the index.
  • Read all data ( SelectAll ): Use CouchDB query syntax like {"selector":{"table":"project"}} to query all relevant data.
  • Query all data by an index ( SelectBySome ): Use the form {"selector":{"%s":"%s", "table":"project"} } CouchDB query syntax to query data based on the index.
  • Rich paging query for all data ( SelectAllWithPagination ): Use the CouchDB query syntax in the form of {"selector":{"table":"project"}} to call the above paging query data tool tools.SelectByQueryStringWithPagination to query data.
  • Query all data by keyword rich pagination (SelectBySomeWithPagination): use the form {"selector":{"%s":"%s","table":"project" }} CouchDB query syntax invokes the above-mentioned pagination query data tool tools.SelectByQueryStringWithPagination to query data.
  • Query data history by an index ( SelectHistoryByIndex ): Call the above historical data query tool tools.SelectHistoryByIndex to query data.

contract/user.go is the core operation logic of model/user.go. This example only contains simple functions. For the complete source code, refer to user.go.

main main function

The complete code of the main function is as follows:

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)
        }
}

Multiple smart contracts only need to be declared sequentially in the contractapi.NewChaincode function of main. After the smart contract is written, use go mod vendor to package dependencies. After the above work is completed, the project_contract directory structure and explanation are as follows:

project_contract
├── contract // smart contract core logic
│ ├── project.go
│ └── user.go
├── go.mod
├── go.sum
├── main.go // smart contract entry function
├── model // declare data model
│ ├── base.go // Declare data structures such as paging
│ ├── project.go
│ └── user.go
├── tools // tool directory
│ └── contract.go // Smart contract general tools, query history/page query, etc.
└── vendor // dependency directory

Contract deployment and testing

Unless otherwise specified, the following commands are run by default under the experimental root directory 7_CouchDBAndComplexContract:

  1. Contract packaging
    source envpeer1soft
    peer lifecycle chaincode package basic.tar.gz --path project_contract --label basic_1
    
  2. Three organization installation
     source envpeer1soft
     peer lifecycle chaincode install basic.tar.gz
     peer lifecycle chaincode query installed
     source envpeer1web
     peer lifecycle chaincode install basic.tar.gz
     peer lifecycle chaincode query installed
     source envpeer1hard
     peer lifecycle chaincode install basic.tar.gz
     peer lifecycle chaincode query installed
    
  3. Three organizations approved
    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
    

    Note: Since we have two smart contracts, and each smart contract contains the InitLedger function to initialize data, we need to delete -- init-required parameter (because the contract doesn’t need to be initialized).

  4. Submit chaincode
    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. Initialize chaincode data and test
    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", "Workshop alliance chain management system" ]}'
    

Note that when calling a chaincode in the case of multiple contracts, it is necessary to specify the contract to which it belongs before the called contract function, such as ProjectContract:SelectBySome , the calling method of other contract examples is roughly the same, so I won’t go into details here. In addition, since CouchDB comes with a database management interface, you can access the data on the chain through any CouchDB service address in this example, such as http://192.168.27.72:7355/_utils/#login (The IP of the virtual machine is 192.168.27.72, the CouchDB port 7355 of the soft organization), enter the account admin password adminpw configured in docker to enter the system:

So far, the experiment is basically completed.

Possible problems

  1. peer lifecycle chaincode install encountered an error:
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

Reason and solution: The Golang version of the docker fabric 2.4 image used is too low to support generics, so you need to delete and reinstall docker fabric 2.4 (although the tag is the same, the image content will be updated).

  1. An error was encountered while calling the smart contract:
[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) <=
...

Reason and solution: It may be that the docker volume has taken up the hard disk, use docker volume rm $(docker volume ls -qf dangling=true) to clear all and try again
use

  1. An error was encountered:
# github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/discovery/client
/root/go/pkg/mod/github.com/hyperledger/[email protected]/internal/github.com/hyperledger/fabric/discovery/client/api.go:47:38: undefined: discovery .ChaincodeCall
/root/go/pkg/mod/github.com/hyperledger/[email protected]/internal/github.com/hyperledger/fabric/discovery/client/client.go:83:63: undefined: discovery .ChaincodeInterest
/root/go/pkg/mod/github.com/hyperledger/[email protected]/internal/github.com/hyperledger/fabric/discovery/client/client.go:120:65: undefined: discovery .ChaincodeCall
/root/go/pkg/mod/github.com/hyperledger/[email protected]/internal/github.com/hyperledger/fabric/discovery/client/client.go:124:23: undefined: discovery .ChaincodeInterest
/root/go/pkg/mod/github.com/hyperledger/[email protected]/internal/github.com/hyperledger/fabric/discovery/client/client.go:229:105: undefined: discovery .ChaincodeCall
/root/go/pkg/mod/github.com/hyperledger/[email protected]/internal/github.com/hyperledger/fabric/discovery/client/client.go:247:64: undefined: discovery .ChaincodeCall
/root/go/pkg/mod/github.com/hyperledger/[email protected]/internal/github.com/hyperledger/fabric/discovery/client/client.go:604:48: undefined: discovery .ChaincodeInterest
/root/go/pkg/mod/github.com/hyperledger/[email protected]/internal/github.com/hyperledger/fabric/discovery/client/client.go:620:35: undefined: discovery .ChaincodeCall

Reason and solution: github.com/hyperledger/fabric-sdk-go needs to specify version 20220117, and replace the corresponding dependencies of the go.mod file as follows:

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

Reason and solution: The smart contract return value does not support generics, just replace the smart contract return value with interface{}.

  1. An error was encountered while querying history:
Error: could not assemble transaction: ProposalResponsePayloads do not match (base64):

Reasons and solutions: Do not use address transfer (recommended value transfer) in chaincode output (return) data, because addresses are allocated dynamically, and the values obtained each time are different, resulting in consensus failure.

  1. An error was encountered:
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:\\
1. 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:\\
1. return: Invalid type. Expected: array, given: string

Reason and solution: The return value of the chain code cannot be []byte, which is a fabric bug. For complex return types, it is recommended to return the string string directly