Fabric Chaincode是智能合约在Fabric上的实现方式,是与Fabric区块链交互的唯一渠道,也是生成Transaction的唯一来源。
开发语言:go、java
本文中选用go
Fabric节点运行模式有两种:
本文中采用开发模式调试链码。
需要fabric-samples源码
下载好后进入到chaincode目录,新建一个mychaincode文件夹,接下来,我们将会在这个目录中编写我们的ChainCode。
还有一个chaincode-docker-devmode目录。在这个目录中,我们可以借助构建自带区块链样例网络时已经预先生成好的order和channel来启动“开发者模式”。这样,用户可以立即编译chaincode并调用函数。
接下来,我们将会在这个目录中调试我们编写的ChainCode。包括编译,安装,实例化等等。
在mychaincode目录下新建一个go文件,编写相应内容,给出一个建立链路合约(仿照官方转账示例)的示例代码如下:
package main
import (
"fmt"
"encoding/json"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
"time"
)
// 合约状态
const (
BillInfo_State_Publish = "Publish"
)
// 账单数据结构
type Bill struct {
BillId string `json:"bill_id"`
PurchaseUserCode string `json:"purchase_user_code"`
BidingStartTime string `json:"biding_start_time"`
BidingEndTime string `json:"biding_end_time"`
ContractStartTime string `json:"contract_start_time"`
ContractEndTime string `json:"contract_end_time"`
LinkStart string `json:"link_start"`
LinkEnd string `json:"link_end"`
State string `json:"state"`
}
//链码返回结构
type chaincodeRet struct {
Code int // 0 success otherwise 1
Des string //description
}
// 根据返回码和描述返回序列号后的字节数组
func getRetByte(code int,des string) []byte {
var r chaincodeRet
r.Code = code
r.Des = des
b,err := json.Marshal(r)
if err!=nil {
fmt.Println("marshal Ret failed")
return nil
}
return b
}
// 根据返回码和描述返回序列号后的字符串
func getRetString(code int,des string) string {
var r chaincodeRet
r.Code = code
r.Des = des
b,err := json.Marshal(r)
if err!=nil {
fmt.Println("marshal Ret failed")
return ""
}
return string(b[:])
}
//初始化默认
func (a *Bill) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
//链码Invoke接口
func (a *Bill) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
function,args := stub.GetFunctionAndParameters()
//invoke
if function == "issue" {
//发布合约(招标)
return a.issue(stub, args)
}
if function == "query" {
// the old "Query" is now implemtned in invoke
return a.query(stub, args)
}
return shim.Error("ChainnovaChaincode Unkown method!")
}
//保存合约
func (a *Bill) putBill(stub shim.ChaincodeStubInterface, bill Bill) ([]byte, bool) {
byte,err := json.Marshal(bill)
if err!=nil {
return nil, false
}
err = stub.PutState(bill.BillId, byte)
if err!=nil {
return nil, false
}
return byte, true
}
//根据合约号取出合约
func (a *Bill) getBill(stub shim.ChaincodeStubInterface, bill_No string) (Bill, bool) {
var bill Bill
key := bill_No
b,err := stub.GetState(key)
if b!=nil {
return bill, false
}
err = json.Unmarshal(b,&bill)
if err!=nil {
return bill, false
}
return bill, true
}
//发布合约
func (a *Bill) issue(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args)!=9 {
return shim.Error("ChainnovaChaincode Invoke issue args != 9")
}
bill := Bill{BillId:args[0],PurchaseUserCode:args[1],BidingStartTime:args[2],BidingEndTime:args[3],ContractStartTime:args[4],ContractEndTime:args[5],LinkStart:args[6],LinkEnd:args[7],State:args[8]}
//根据合约ID查找合约ID是否已存在
_, existbl :=a.getBill(stub,bill.BillId)
if existbl {
return shim.Error("ChainnovaChaincode Invoke issue failed : the billNo has exist")
}
if bill.BillId == "" {
bill.BillId = fmt.Sprintf("%d",time.Now().UnixNano())
}
//更改合约信息和状态并保存合约:合约状态设为新发布
bill.State = BillInfo_State_Publish
//保存票据
_, bl := a.putBill(stub, bill)
if !bl {
return shim.Error("ChainnovaChaincode Invoke issue putdata failed!")
}
return shim.Success(nil)
}
//查询合约
func (a *Bill) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
billAsBytes, _ := stub.GetState(args[0])
return shim.Success(billAsBytes)
}
func main() {
if err := shim.Start(new(Bill)); err != nil {
fmt.Printf("Error starting Bill chaincode: %s", err)
}
}
调试ChainCode主要分为三个步骤:
这三个步骤,需要进入chaincode-docker-devmode目录下面打开三个独立的终端分别完成。
docker-compose -f docker-compose-simple.yaml up
上述指令启动了一个带有SingleSampleMSPSoloorderer profile的网络,并将peer和orderer节点在“开发者模式”下启动。它还启动了两个额外的容器:一个包含chaincode运行环境;另一个是CLI命令行,可与chaincode进行交互。创建并加入channel(管道)的指令内嵌于CLI容器中。
进入容器:
docker exec -it chaincode bash
在容器中ls可以看到mychaincode文件夹,cd进入后可以看到我们编写的源码。
编译chaincode:
go build
如果没有报错,则可以看到多了一个可执行的chaincode文件。
(注:编译这一步也可以在容器外做,即链码编写完之后直接在目录中执行go build)
运行chaincode:
CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./mychaincode
Chaincode被peer节点启动,chaincode日志表明peer节点成功注册。
下面我们将进入CLI容器进行chaincode在链上的安装和初始化:
docker exec -it cli bash
peer chaincode install -p chaincodedev/chaincode/mychaincode -n mycc -v 0
peer chaincode instantiate -n mycc -v0 -c'{"Args":[]}' -C myc
因为链码功能为新建合约,所以初始化的时候参数为空。
在CLI内部会为mychaincode创建SignedChaincodeDeploymentSpec,并将其发送到本地peer节点。这些节点会调用LSCC上的Install方法。上述的-p选项指明chaincode的路径,其必须在用户的GOPATH目录下(比如$GOPATH/src/sacc)。
现在我们执行一次invoke调用,新建一条从北京到纽约的链路:
peer chaincode invoke -n mycc -v 0 -c '{"Args":["issue","lc1522512000","CMCC","1525104000","1525881600","1527782400","1559318400","beijing","new york","new"]}' -C myc
最后查询该链路,我们会看到链路的内容。
peer chaincode invoke -n mycc -v 0 -c '{"Args":["query","lc1522512000"]}' -C myc
Terminal2和Terminal3用exit命令退出docker。
Terminal 1直接ctrl+c退出,然后再执行
docker-compose -f docker-compose-simple.yaml down
一定要确保容器清理干净。
当测试时出错时,如果每次都关闭网络环境退出容器后重新修改代码并编译,过于麻烦。一个简单做法是第一个终端不关,在第二个终端中退出容器,然后进入mychaincode文件夹修改链码并编译后重新进入容器,甚至可以在容器内安装vim并修改编译链码,完成之后在第三个终端中继续调用即可。
区块链和HyperLedger系列(IBM微讲堂)——区块链第四讲
官方文档
《深度探索区块链——hyperledger技术与应用》