根据前面十篇文章的介绍,基本已经了解了fabric的网络环境和链码开发 部署 调试的过程。这一篇文章,再次巩固链码开发以及部署调用调试。
2. marble弹珠管理marble
// 声明弹珠结构体
type Marble struct {
// 对象类型
ObjectType string `json:"objectType"`
// 弹珠名称
Name string `json:"name"`
// 弹珠颜色
Color string `json:"color"`
// 弹珠大小
Size int `json:"size"`
// 弹珠拥有者
Owner string `json:"owner"`
}
2.1实现功能
弹珠创建
弹珠查询
弹珠删除
弹珠交易
弹珠操作历史查询
查询某个用户的弹珠列表
2.2chaincode链码
markbels.go
package main
// 引入依赖包
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
"time"
"bytes"
"strconv"
"encoding/json"
)
// 声明结构体
type MarblesChaincode struct{
}
// 声明弹珠结构体
type Marble struct {
// 对象类型
ObjectType string `json:"objectType"`
// 弹珠名称
Name string `json:"name"`
// 弹珠颜色
Color string `json:"color"`
// 弹珠大小
Size int `json:"size"`
// 弹珠拥有者
Owner string `json:"owner"`
}
// 实例化链码接口
func (t *MarblesChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
// 这里初始化不做操作
fmt.Println("MarblesChaincode 链码实例化")
return shim.Success(nil)
}
// invoke 操作
func (t *MarblesChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 获取调用的方法和参数
fn, args := stub.GetFunctionAndParameters()
// 判断分发 业务方法
if fn == "initMarble" {
// 调用创建弹珠方法
return t.initMarble(stub,args)
}else if fn == "readMarble" {
// 调用读取弹珠信息的方法
return t.readMarble(stub,args)
}else if fn == "deleteMarble" {
// 调用删除弹珠信息
return t.deleteMarble(stub,args)
}else if fn == "transferMarble" {
// 调用交易弹珠的方法
return t.transferMarble(stub,args)
}else if fn == "getMarbleByRange" {
// 调用范围查询
return t.getMarbleByRange(stub,args)
}else if fn == "queryMarblesByOwner" {
// 调用查询用户拥有的弹珠信息
return t.queryMarblesByOwner(stub,args)
}else if fn == "queryHistoryForMarble" {
// 查询弹珠的历史操作信息
return t.queryHistoryForMarble(stub,args)
}
// 如果没有对应的方法 返回错误
return shim.Error(fn +" 方法不存在!")
}
// 创建marble args: name color size owner
func (t *MarblesChaincode) initMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {
// 根据名称判断marble是否已经存在
name := args[0]
// 查找之前是否存在 名为 name的 marble
marbleBytes, err := stub.GetState(name)
// 查询错误
if err != nil {
return shim.Error(err.Error())
}
// 如果marbleBytes 已经存在
if marbleBytes != nil {
return shim.Error("名为"+ name + "的弹珠已经存在");
}
// 如果不存在 则写入到账本中
color := args[1]
size,err := strconv.Atoi(args[2])
owner := args[3]
// 组装测结构体
marble := &Marble{"marble",name,color,size,owner}
// 将marble 转成json字符串 存储到账本
marbleJsonStr, err := json.Marshal(marble)
if err != nil {
return shim.Error(err.Error())
}
// PutState json信息写入账本
err = stub.PutState(name,marbleJsonStr)
if err != nil {
return shim.Error(err.Error())
}
fmt.Println("创建弹珠成功!!")
// 同时创建组合键用于查询
indexName := "owner~record"
indexKey, err := stub.CreateCompositeKey(indexName,[]string{ owner,string(marbleJsonStr)})
if err != nil{
return shim.Error(err.Error())
}
err = stub.PutState(indexKey,[]byte{0x00})
if err != nil{
return shim.Error(err.Error())
}
fmt.Println(indexKey)
return shim.Success(nil)
}
// 读取marble args: marbleName
func (t *MarblesChaincode) readMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {
// 获取参数 参数为marble的name
name := args[0]
// 根据name 读取marble的数据
marbleBytes, err := stub.GetState(name)
if err != nil {
return shim.Error(err.Error())
}
if marbleBytes == nil {
return shim.Error(name + " 的弹珠信息不存在")
}
// 返回信息
return shim.Success(marbleBytes)
}
// 删除marble args: marbleName
func (t *MarblesChaincode) deleteMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {
// 从参数总获取到markble的name
name := args[0]
// 判断弹珠是否存在
marbleBytes, err := stub.GetState(name)
if err != nil {
return shim.Error(err.Error())
}
if marbleBytes == nil {
return shim.Error(name + "的弹珠信息不存在")
}
// 删除弹珠
err = stub.DelState(name)
if err != nil {
return shim.Error(err.Error())
}
fmt.Println(name + " 弹珠删除成功!")
return shim.Success(nil)
}
// 交易marble args: marbleName newOwner
func (t *MarblesChaincode) transferMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {
// 获取到参数
marbleName := args[0]
newOwner := args[1]
// 检查弹珠是否存在
marbleBytes, err := stub.GetState(marbleName)
if err != nil{
return shim.Error(err.Error())
}
if marbleBytes == nil {
return shim.Error(marbleName + " 的弹珠信息不存在")
}
// 将账本中的信息转为 Marble 结构体
marbleInfo := Marble{}
err = json.Unmarshal(marbleBytes,&marbleInfo)
if err != nil {
return shim.Error(err.Error())
}
// 修改拥有者
marbleInfo.Owner = newOwner
// 转为json数据
newMarbleBytes,err := json.Marshal(marbleInfo)
if err != nil {
return shim.Error(err.Error())
}
// 写入账本
err = stub.PutState(marbleName,newMarbleBytes)
if err != nil {
return shim.Error(err.Error())
}
fmt.Println(marbleName +"转给"+ newOwner+ "的弹珠交易完成")
return shim.Success(nil)
}
// 查询某一范围内的marble args:startMarble endMarble
func (t *MarblesChaincode) getMarbleByRange(stub shim.ChaincodeStubInterface, args []string) peer.Response {
// 获取参数
startMarble := args[0]
endMarble := args[1]
// 调用查询方法
resultIterator, err := stub.GetStateByRange(startMarble,endMarble)
if err!=nil {
return shim.Error(err.Error())
}
defer resultIterator.Close();
var buffer bytes.Buffer
buffer.WriteString("[")
isWriteSplit := false
// 遍历resultIterator
for resultIterator.HasNext() {
item,err := resultIterator.Next()
if err!= nil {
return shim.Error(err.Error())
}
if isWriteSplit==true {
buffer.WriteString(",")
}
buffer.WriteString("{\"key\":")
buffer.WriteString("\""+item.Key+"\"")
buffer.WriteString(",\"record\":")
buffer.WriteString(string(item.Value))
buffer.WriteString("}")
isWriteSplit = true
}
buffer.WriteString("]")
// 返回结果
return shim.Success(buffer.Bytes())
}
// 查询用户拥有的弹珠信息
func (t *MarblesChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface,args []string) peer.Response {
owner := args[0]
fmt.Println("开始查询用户"+owner+"拥有的弹珠信息")
indexName := "owner~record"
resultIterator,err := stub.GetStateByPartialCompositeKey(indexName ,[]string{owner})
if err != nil {
return shim.Error(err.Error())
}
defer resultIterator.Close();
var buffer bytes.Buffer
buffer.WriteString("[")
isWriteSplit := false
for resultIterator.HasNext() {
item,_ := resultIterator.Next()
_,compositeKeyParts,err := stub.SplitCompositeKey(item.Key)
if err != nil {
shim.Error(err.Error())
}
if isWriteSplit==true {
buffer.WriteString(",")
}
buffer.WriteString(compositeKeyParts[1])
isWriteSplit = true
}
buffer.WriteString("]")
// 返回结果
return shim.Success(buffer.Bytes())
// 获取参数 下面是通过couchdb的方式来查询 但是 我本地环境如果 启动了couchdb总是 在容器启动的时候cli容器无法连接couchdb 猜测应该是couchdb在容器中启动未完成 cli就去尝试连接了。
// owner := args[0]
// fmt.Println("开始查询用户"+owner+"拥有的弹珠信息")
// queryString := fmt.Sprintf("{\"selector\":{\"owner\": %s }}",owner)
// resultIterator,err := stub.GetQueryResult(queryString)
// if err != nil {
// return shim.Error(err.Error())
// }
// defer resultIterator.Close();
// var buffer bytes.Buffer
// buffer.WriteString("[")
// isWriteSplit := false
// // 遍历resultIterator
// for resultIterator.HasNext() {
// item,err := resultIterator.Next()
// if err!= nil {
// return shim.Error(err.Error())
// }
// if isWriteSplit==true {
// buffer.WriteString(",")
// }
// buffer.WriteString("{\"key\":")
// buffer.WriteString("\""+item.Key+"\"")
// buffer.WriteString(",\"record\":")
// buffer.WriteString(string(item.Value))
// buffer.WriteString("}")
// isWriteSplit = true
// }
// buffer.WriteString("]")
// // 返回结果
// return shim.Success(buffer.Bytes())
}
// 查询用户
func (t *MarblesChaincode) queryHistoryForMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {
// 获取参数
marbleName := args[0]
// 获取历史信息
resultIterator,err := stub.GetHistoryForKey(marbleName)
if err != nil {
return shim.Error(err.Error())
}
defer resultIterator.Close()
var buffer bytes.Buffer
buffer.WriteString("[")
isWriteSplit := false
// 遍历resultIterator
for resultIterator.HasNext() {
item,err := resultIterator.Next()
if err!= nil {
return shim.Error(err.Error())
}
if isWriteSplit==true {
buffer.WriteString(",")
}
buffer.WriteString("{\"TxId\":")
buffer.WriteString("\""+item.TxId+"\"")
buffer.WriteString(",\"Timestamp\":")
buffer.WriteString(time.Unix(item.Timestamp.Seconds,int64(item.Timestamp.Nanos)).String())
buffer.WriteString(",\"Value\":")
buffer.WriteString(string(item.Value))
buffer.WriteString(",\"IsDelete\":")
buffer.WriteString(strconv.FormatBool(item.IsDelete))
buffer.WriteString("}")
isWriteSplit = true
}
buffer.WriteString("]")
// 如果没有对应的方法 返回错误
return shim.Success(buffer.Bytes())
}
// main 函数
func main (){
err := shim.Start(new (MarblesChaincode))
if err != nil {
fmt.Printf("Error start MarblesChaincode")
}
}
2.3编写测试类
markbels_test.go
package main
import(
"fmt"
"testing"
"github.com/hyperledger/fabric/core/chaincode/shim"
)
func checkInit(t *testing.T,stub *shim.MockStub,args [][]byte) {
res := stub.MockInit("1",args)
if (res.Status != shim.OK){
fmt.Println(string(res.Message))
t.FailNow()
}
}
func checkInvoke(t *testing.T,stub *shim.MockStub,args [][]byte) {
res := stub.MockInvoke("1",args)
if (res.Status != shim.OK){
fmt.Println(string(res.Message))
t.FailNow()
}
}
func checkReadMarble(t *testing.T,stub *shim.MockStub, name string) {
res := stub.MockInvoke("1",[][]byte{[]byte("readMarble"),[]byte(name)})
if(res.Status != shim.OK){
fmt.Println(string(res.Message))
t.FailNow()
}
if(res.Payload == nil){
fmt.Println("checkReadMarble",name,"failed to get value")
t.FailNow()
}
fmt.Println(string(res.Payload))
}
func checkReadMarbleByRange(t *testing.T,stub *shim.MockStub, startKey string,endKey string) {
res := stub.MockInvoke("1",[][]byte{[]byte("getMarbleByRange"),[]byte(startKey),[]byte(endKey)})
if(res.Status != shim.OK){
fmt.Println(string(res.Message))
t.FailNow()
}
if(res.Payload == nil){
fmt.Println("checkReadMarbleByRange",startKey,endKey,"failed to get value")
t.FailNow()
}
fmt.Println(string(res.Payload))
}
func checkQueryMarblesByOwner(t *testing.T,stub *shim.MockStub, owner string) {
res := stub.MockInvoke("1",[][]byte{[]byte("queryMarblesByOwner"),[]byte(owner)})
if(res.Status != shim.OK){
fmt.Println(string(res.Message))
t.FailNow()
}
if(res.Payload == nil){
fmt.Println("checkQueryMarblesByOwner",owner,"failed to get value")
t.FailNow()
}
fmt.Println(string(res.Payload))
}
func checkQueryMarblesHistoryByKey(t *testing.T,stub *shim.MockStub, marbleName string) {
res := stub.MockInvoke("1",[][]byte{[]byte("queryHistoryForMarble"),[]byte(marbleName)})
if(res.Status != shim.OK){
fmt.Println(string(res.Message))
t.FailNow()
}
if(res.Payload == nil){
fmt.Println("checkReadMarbleByRange",marbleName,"failed to get value")
t.FailNow()
}
fmt.Println(string(res.Payload))
}
func Test_MarblesChaincode(t *testing.T) {
hello := new(MarblesChaincode)
stub := shim.NewMockStub("marble",hello)
checkInit(t,stub,nil)
// name color size owner
checkInvoke(t,stub,[][]byte{[]byte("initMarble"),[]byte("marble-1"),[]byte("red"),[]byte("10"),[]byte("LH")})
checkInvoke(t,stub,[][]byte{[]byte("initMarble"),[]byte("marble-2"),[]byte("yellow"),[]byte("11"),[]byte("LH")})
checkInvoke(t,stub,[][]byte{[]byte("initMarble"),[]byte("marble-3"),[]byte("blue"),[]byte("12"),[]byte("WX")})
checkQueryMarblesByOwner(t,stub,"WX")
// checkReadMarble(t,stub,"marble-1")
// checkInvoke(t,stub,[][]byte{[]byte("transferMarble"),[]byte("marble-1"),[]byte("WX")})
// checkInvoke(t,stub,[][]byte{[]byte("transferMarble"),[]byte("marble-1"),[]byte("LH")})
// checkInvoke(t,stub,[][]byte{[]byte("deleteMarble"),[]byte("marble-1")})
// checkQueryMarblesHistoryByKey(t,stub,"marble-1")
// checkInvoke(t,stub,[][]byte{[]byte("initMarble"),[]byte("marble-4"),[]byte("green"),[]byte("12"),[]byte("WX")})
// checkReadMarbleByRange(t,stub,"marble-1","marble-4")
// checkReadMarble(t,stub,"marble-1")
// checkInvoke(t,stub,[][]byte{[]byte("initMarble"),[]byte("marble-1"),[]byte("red"),[]byte("10"),[]byte("LH")})
// checkReadMarble(t,stub,"marble-2")
}
2.4 跑测试类
进入到 marbles.go 和 marbles_test.go所在的目录
我是放到了 $GOPATH/my_chaincode/marbles 目录下
cd $GOPATH/my_chaincode/marbles
go test marbles_test.go -v marbles.go --tags=nopkcs11
可以尝试修改marbles_test.go 来测试其他的方法
3 搭建本地测试环境 并测试链码使用fabric-samples项目中 的chaincode-docker-devmode 环境测试
3.1 挂载链码将编写好的 marbles.go 和marbles_test.go文件移动到
$GOAPTH/src/github.com/hyperledger/fabric-samples/chaincode
方便挂载到容器中
3.2 启动网络环境进入到chaincode-docker-devmode目录下
cd $GOPATH/src/github.com/hyperledger/fabric-samples/chaincode-docker-devmode
利用docker-compose启动网络环境
docker-compose -f docker-compose-simple.yaml up
启动完成后的容器列表
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a7b22f7351fa hyperledger/fabric-tools "/bin/bash -c ./scri…" 7 seconds ago Up 5 seconds cli
d761b06a034a hyperledger/fabric-ccenv "/bin/bash -c 'sleep…" 7 seconds ago Up 5 seconds chaincode
7b329ef1311f hyperledger/fabric-peer "peer node start --p…" 8 seconds ago Up 6 seconds 0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp peer
6302ffa111e2 hyperledger/fabric-orderer "orderer" 9 seconds ago Up 7 seconds 0.0.0.0:7050->7050/tcp orderer
3.3 进入chaincode容器编译链码 提供链码服务
启动一个新的终端
进入容器
docker exec -it chaincode /bin/bash
进入到挂载go文件目录
cd marbles/
编译程序
go build
编译完成后启动链码服务
CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./marbles
3.4 进入cli容器 安装链码 实例化链码
启动一个新的终端
进入cli容器
docker exec -it cli /bin/bash
安装链码
peer chaincode install -p chaincodedev/chaincode/marbles -n mycc -v 0
初始化链码
peer chaincode instantiate -n mycc -v 0 -c '{"Args":[]}' -C myc
3.5 测试链码
创建三个弹珠
peer chaincode invoke -n mycc -c '{"Args":["initMarble","marble-1","red","10","LH"]}' -C myc
peer chaincode invoke -n mycc -c '{"Args":["initMarble","marble-2","yellow","11","LH"]}' -C myc
peer chaincode invoke -n mycc -c '{"Args":["initMarble","marble-3","blue","12","WX"]}' -C myc
查询弹珠
peer chaincode query -n mycc -c '{"Args":["readMarble","marble-1"]}' -C myc
范围查询
peer chaincode query -n mycc -c '{"Args":["getMarbleByRange","marble-1","marble-3"]}' -C myc
转让弹珠
peer chaincode invoke -n mycc -c '{"Args":["transferMarble","marble-1","WX"]}' -C myc
再查询marble-1:是否owner变为WX
peer chaincode query -n mycc -c '{"Args":["readMarble","marble-1"]}' -C myc
查询owner所有的弹珠
peer chaincode query -n mycc -c '{"Args":["queryMarblesByOwner","WX"]}' -C myc
查看弹珠修改历史:结果中 可以查询到一次 初始化交易 一次 转让交易
peer chaincode query -n mycc -c '{"Args":["queryHistoryForMarble","marble-1"]}' -C myc
4 关闭网络
在最开始启动网络的终端 按两次 control+c后执行
docker-compose -f docker-compose-simple.yaml down
停止并关闭所有容器