随手记录下期末的链码复习,参考官方提供的代码和文档完成链码的编写和测试部署,能看懂示例代码,代码解释见书和相关注释,书(Go语言Hyperledger实战)很详细,不重复搬运内容。
此文件夹下主要有三个go文件,关注smartcontract.go和assetTransfer.go即可,前者是智能合约文件使用函数编写了资产转移的相关逻辑,后者是启动智能合约,了解不同文件需要导入的包。完成以上两步,可以在终端使用命令行部署调用。
1.智能合约编写
smartcontract.go业务逻辑很简单,一个资产的增删改查,这里的区块链本质就是个状态数据库,即对数据库数据的增删改查,包括以下几点(正确处理数据以及调用对应的函数)
- 导入包
- ctx.GetSub.FUNCTION()
- json的序列化
- 错误处理
1.1 导包
关注ContractAPI,通过此包传递上下文参数以及结构体定义
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
定义结构体SmartContract,此结构体内嵌了contractapi包的结构体。
type SmartContract struct {
contractapi.Contract
}
并将此结构体与合约的函数绑定(继承公共属性方法)。
func (s *SmartContract)
1.2ctx.GetStub账本处理函数
所有函数的传参都会传递一个ctx上下文,它定义为一个关于交易的接口实例。
ctx contractapi.TransactionContextInterface
此交易接口定义如图,又内嵌了两个接口实例,我们主要用到第一个GetStub。
之后,通过此接口的GetStub()函数可以得到一个stub对象(ChaincodeStuInterface的实例对象),stub对象提供了若干函数支持对账本的操作,本代码主要用到PutState,GetState和DelState函数,即资产的写入,读取和删除。此接口下三个函数定义如图。
通过以上说明和阅读函数定义,正确写出函数的调用以及需要传入的参数和返回值,也就可以明白本智能合约八个函数的核心就是围绕这三个(读,写,删)函数。
//读取数据,输入资产数据的ID,返回相对应数据和错误信息
ctx.GetStub.GetState(key string) ([]byte,error)
//写入数据,输入当前资产ID和它对应的数据,返回错误信息
ctx.GetStub.PutState(key string, value []byte) error
//删除数据,输入资产ID,删除此ID对应数据,返回错误信息
ctx.GetStub.DelState(key string) error
//合约的最后一个函数GetAllAsset,还调用了该对象的查询方法,本合约就出现一次了解即可
ctx.GetStub.GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
1.3 (反)序列化JSON
因为这里的资产是定义为了一个结构体数组,数组里放了若干条数据,每个数据对应结构体定义的字段,但是9.2介绍的读(写)函数返回值(接收值)都是[]byte类型,所以要对数组做序列化处理,简单来说,写入资产前要变成json格式,读取资产要反序列化json为正常格式,主要用到两个函数,需要注意下第二个函数用法。
json.Marshal(v any) ([]byte, error)//写入数据前先序列化
json.Unmarshal(data []byte, v any) error//读出数据后要反序列化,这个处理后的data将指向v
将合约中的结构体数组作为参数,演示此过程
package main
import (
"encoding/json"
"fmt"
)
type Asset struct {
AppraisedValue int `json:"AppraisedValue"`
Color string `json:"Color"`
ID string `json:"ID"`
Owner string `json:"Owner"`
Size int `json:"Size"`
}
func main() {
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},
}
//定义一个数组接受反序列化结果
var deserializedAssets []Asset
//1.序列化,它将逐条输出JSON
for _, asset := range assets {
assetsJSON, err := json.Marshal(assets)
if err != nil {
fmt.Println(err)
}
fmt.Println(asset.ID, assetsJSON)
//关注这一句,将上面的JSON反序列化再填入数组
err = json.Unmarshal(assetsJSON, &deserializedAssets)
}
//逐条输出数组内容,这就是反序列化结果
for _, asset := range deserializedAssets {
fmt.Println(asset)
}
}
上半部分是部分JSON信息(序列化结果太长),下半部分是反序列化结果。
1.4 错误处理
上面一共主要介绍了五个函数(读,写,删,序列化,反序列化)的定义,基本都要接受一个错误信息,简单模仿书上写法即可,每个合约函数省去错误处理也没几行。经过以上几个介绍,可以看一个获取所有资产GetAllAssets()简单例子(书上并没有给出第八个函数解释,其它都有看书就行)。不详细介绍GetStateByRange函数,也是嵌套了好几层接口,返回个迭代器,然后从里面拿结果,在这里包括了关闭退出,循环检查等功能。
// GetAllAssets returns all assets found in world state
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
// range query with empty string for startKey and endKey does an
// open-ended query of all assets in the chaincode namespace.
//GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
//传两个空的字符串,表示查询所有信息。
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
if err != nil {
return nil, err
}
//函数结束时退出迭代器
defer resultsIterator.Close()
//定义一个数组接受信息
var assets []*Asset
//循环检查是否还有数据
for resultsIterator.HasNext() {
//按顺序指向一下个值,这个值就是从账本(状态数据库)得到的JSON
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
//一个中间状态,传递反序列化的值
var asset Asset
//反序列化值存入数组
err = json.Unmarshal(queryResponse.Value, &asset)
if err != nil {
return nil, err
}
//将asset值给assets
assets = append(assets, &asset)
}
//返回数组,此为查询结果
return assets, nil
}
2.使用智能合约
assetTransfer.go主要是启动合约的,所以代码量不大,这里不对链码和智能合约概念作区分,链码更像是生产环境的智能合约合约,统一认为一个意思,
- 导包
- 创建链码对象的方法
- 启动合约的方法
2.1 导包
借助log包记录日志输出,上一步说到的contractapi包,还有代码路径(chaincode包)。
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode"
)
2.2 创建链码对象
使用此contractapi下面的NewChaincode方法获取链码对象,传入合约对象指针,返回链码对象和错误信息。
func NewChaincode(contracts ...ContractInterface) (*ContractChaincode, error)
这里的指针也就是上一步介绍的那个SmartContract结构体指针,它关联了整个合约文件
assetChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
if err != nil {
log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
}
2.3 启动合约
得到链码对象,运行此对象关联的Strat方法即可启动智能合约。
func (cc *ContractChaincode) Start() error
这里启动了合约并进行了错误处理,并且err的声明写法更简洁。
if err := assetChaincode.Start(); err != nil {
log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
}