本篇主要记录里在链码的编写及配置过程中遇到的问题及解决方法。
1. Init方法
在Hyperledger Fabric中,链码的Init()
方法是一个可选的方法,它主要用于链码实例化时执行一些初始化操作。如果希望账本的初始化方法可以在链码部署完成时执行,则可以将相关方法的执行放到Init()
方法中。举例如下:
func (s *SmartContract) Init(ctx contractapi.TransactionContextInterface) error {
//账本初始化操作
err := s.InitLedger(ctx)
if err != nil {
return err
}
return nil
}
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
assets := []Asset{
{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
}
for _, asset := range assets {
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
err = ctx.GetStub().PutState(asset.ID, assetJSON)
if err != nil {
return fmt.Errorf("failed to put to world state. %v", err)
}
}
return nil
}
但关于这种写法,有以下几点需要说明:
- 这种利用
Init()
方法进行账本初始化的操作不一定能生效,在用Fabric-gateway-go
调用链码时能生效,但使用Peer CLI调用链码时不生效,仍然需要使用peer invoke
命令执行InitLedger
方法。原因暂时不明。 - 虽然在
fabric-contract-api-go
的官方文档中的Init
方法的返回类型为peer.Response
。假如按照这种返回类型编写Init
写法,具体如下:
func (s *SmartContract) Init(ctx contractapi.TransactionContextInterface) peer.Response {
err := s.InitLedger(ctx)
if err != nil {
return peer.Response{
Status:500,
Message: "账本初始化失败",
}
}
return peer.Response{
Status:200,
Message: "账本初始化成功",
}
}
则在链码部署时会提示如下错误: Error creating business chaincode: Cannot use metadata. Metadata did not match schema:components.schemas..required: Array must have at least 1 items
(需要进入链码所在的docker容器中才能该错误提示信息)
2. 链码返回类型
如果希望链码返回由多个JSON串组成的数组时,如果用[][]byte
,则可能产生Error: endorsement failure during query. response: status:500 message:"Error handling success response. Value did not match schema:\n1. return: Invalid type. Expected: array, given: string"
。这种情况下,最后将返回类型改成[]string
。具体案例如下:
func (s *SmartContract) GetTableAllItems(ctx contractapi.TransactionContextInterface, tableName string) ([]string, error) {
query := `{"selector":{"docType":"` + tableName + `"}}`
resultsIterator, err := ctx.GetStub().GetQueryResult(query)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var tableItems []string
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
tableItems = append(tableItems, string(queryResponse.Value))
}
return tableItems, nil
}
3. Struct、byte和string等的互相转换
在Hyperledger Fabric的链码编写中,通常都遇到JSON串的编码、解码等问题,这里就涉及到很多byte
和string
及其数组的相互转换问题。这里就以简单的go语言代码为例做一个简单的说明。
3.1 Struct转化为JSON串和string类型
Go
语言中用[]byte
表示JSON串。从Struct变量到JSON串和string类型的转换举例如下:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
UserID string `json:"customerID"`
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
func main() {
usersList := []User{
{UserID: "user1", Name: "Alice", Age: 25, Email: "alice@example.com"},
{UserID: "user2", Name: "Bob", Age: 30, Email: "bob@example.com"},
{UserID: "user3", Name: "Charlie", Age: -5, Email: "charlie@example.com"},
{UserID: "user4", Name: "David", Age: 40, Email: "david@@example.com"},
{UserID: "user5", Name: "Eve", Age: 35, Email: "eve@example,com"},
}
//将Struct数组转换成JSON串,返回类型为[]byte
userListJson, err := json.Marshal(usersList)
if err != nil {
fmt.Println(err)
}
//将[]byte转换为string
fmt.Println(string(userListJson))
//将单独的Struct变量转换成JSON串,返回类型仍然为[]byte
userJson, err := json.Marshal(usersList[0])
fmt.Println(string(userJson))
}
代码执行结果如下:
[{"customerID":"user1","name":"Alice","age":25,"email":"alice@example.com"},{"customerID":"user2","name":"Bob","age":30,"email":"bob@example.com"},{"customerID":"user3","name":"Charlie","age":-5,"email":"charlie@example.com"},{"customerID":"user4","name":"David","age":40,"email":"david@@example.com"},{"customerID":"user5","name":"Eve","age":35,"email":"eve@example,com"}]
{"customerID":"user1","name":"Alice","age":25,"email":"alice@example.com"}
从上述代码中可以看到,无论是单独的Struct变量还是Struct变量组成的数组,使用json.Marshal()
方法生成的JSON串都是[]byte
类型。
3.2 JSON串转化为Struct变量
这里先用string类型保存JSON串,再转化为Struct变量。其具体代码如下:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
UserID string `json:"customerID"`
UserName string `json:"name"`
UserAge int `json:"age"`
UserEmail string `json:"email"`
}
func main() {
userString := `[{"customerID":"user1","name":"Alice","age":25,"email":"alice@example.com"},{"customerID":"user2","name":"Bob","age":30,"email":"bob@example.com"}]`
userJSON := []byte(userString)
var user []User
err := json.Unmarshal(userJSON, &user)
if err != nil {
fmt.Println(err)
}
fmt.Println(user)
}
其代码执行结果如下:
[{user1 Alice 25 alice@example.com} {user2 Bob 30 bob@example.com}]
3.3 json.Indent方法
在Go语言中,json.Indent
是一个非常有用的函数,用于将原本压缩或者未格式化的JSON数据进行缩进处理,使其更加易读。它将JSON数据格式化为带有缩进和换行的形式,方便进行调试或展示。
package main
import (
"bytes"
"encoding/json"
"fmt"
)
func main() {
userString := `[{"customerID":"user1","name":"Alice","age":25,"email":"alice@example.com"},{"customerID":"user2","name":"Bob","age":30,"email":"bob@example.com"}]`
userJSON := []byte(userString)
var prettyJSON bytes.Buffer
err := json.Indent(&prettyJSON, userJSON, "", " ")
if err != nil {
fmt.Println(err)
}
fmt.Println(prettyJSON.String())
}
其代码执行结果如下:
[
{
"customerID": "user1",
"name": "Alice",
"age": 25,
"email": "alice@example.com"
},
{
"customerID": "user2",
"name": "Bob",
"age": 30,
"email": "bob@example.com"
}
]