十一、区块链学习-Hyperledger Fabric (基于release-1.0) 链码开发-marbles管理

天涯浪子 提交于 2020-02-19 02:55:06

1. 概述

根据前面十篇文章的介绍,基本已经了解了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实现功能

  1. 弹珠创建
  2. 弹珠查询
  3. 弹珠删除
  4. 弹珠交易
  5. 弹珠操作历史查询
  6. 查询某个用户的弹珠列表

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

停止并关闭所有容器

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!