根据官网文档编写go语言版本的链码。
一、链码是什么?
根据官方文档的描述,链码是用go、node.js或java编写的程序,可实现规定的接口。类似于EOS的智能合约,我们可以通过调用链码更新和查询账本内容。
二、编写简单的链码(go版本)
2.1 前期准备
确保已经安装go并进行了正确的配置。因网络原因,可配置代理如下:
go env -w GOPROXY=https://goproxy.io,direct
本文基于go1.14进行实践。
2.2 创建链码目录
mkdir atcc && cd atcc
2.3 创建模块和源文件
go mod init atcc # 生成go.mod文件
touch atcc.go
2.4 导入API包和自定义SmartContract
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// SmartContract provides functions for managing an Asset
type SmartContract struct {
contractapi.Contract
}
2.5 定义资产结构
// Asset describes basic details of what makes up a simple asset
type Asset struct {
ID string `json:"ID"`
Owner string `json:"owner"`
Value int `json:"Value"`
}
2.6 初始化账本
// InitLedger adds a base set of assets to the ledger
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
assets := []Asset{
{ID: "asset1", Owner: "ZhangSan", Value: 300},
{ID: "asset2", Owner: "LiSi", Value: 400},
{ID: "asset3", Owner: "Klay", Value: 500},
}
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
}
2.7 编写新增资产函数
// CreateAsset issues a new asset to the world state with given details.
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, owner string, Value int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if exists {
return fmt.Errorf("the asset %s already exists", id)
}
asset := Asset{
ID: id,
Owner: owner,
Value: Value,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
2.8 编写读取资产函数
// ReadAsset returns the asset stored in the world state with given id.
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("the asset %s does not exist", id)
}
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
2.9 编写更新资产函数
// UpdateAsset updates an existing asset in the world state with provided parameters.
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, owner string, Value int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}
// overwriting original asset with new asset
asset := Asset{
ID: id,
Owner: owner,
Value: Value,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
2.10 编写删除资产函数
// DeleteAsset deletes an given asset from the world state.
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}
return ctx.GetStub().DelState(id)
}
2.11 编写判断资产存在函数
// AssetExists returns true when asset with given ID exists in world state
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return false, fmt.Errorf("failed to read from world state: %v", err)
}
return assetJSON != nil, nil
}
2.12 编写资产转移函数
// TransferAsset updates the owner field of asset with given id in world state.
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return err
}
asset.Owner = newOwner
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
2.13 编写查询所有资产函数
// 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.
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var assets []*Asset
for resultsIterator.HasNext() {
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
}
assets = append(assets, &asset)
}
return assets, nil
}
2.14 编写主函数
func main() {
assetChaincode, err := contractapi.NewChaincode(&SmartContract{})
if err != nil {
log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
}
if err := assetChaincode.Start(); err != nil {
log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
}
}
将以上内容整合到一起,就编写出了简单的链码,能实现对自定义资产结构的增删查改等功能。
三、管理链码依赖
在将依赖部署到网络上前,需要将链码的相关依赖包含在软件包中,最简单的方法就是利用go mod 将相关依赖下载到本地。
go mod tidy #增加缺失的包,移除没用的包
go mod vendor #将依赖包复制到项目下的 vendor 目录