11, Blockchain learning Hyperledger Fabric (based on release-1.0) chain code development marbles management

Chain code development marbles management

1. overview

According to the introduction of the previous ten articles, I have basically understood the network environment of fabric and the process of development, deployment and debugging of chain code. This article, once again consolidate chain code development and deployment call debugging.

2. marble marbles management

marble

// Declaration bullet structure
type Marble struct {
	// object type
	ObjectType string `json:"objectType"`
	// Marbles name
	Name string `json:"name"`
	// Pearl color
	Color string `json:"color"`
	// Marbles size
	Size int `json:"size"`
	// Marbles owner
	Owner string `json:"owner"`
}

2.1 implementation function

  1. Marbles creation
  2. Marbles inquiry
  3. Marbles removal
  4. Marbles Trading
  5. Marble operation history query
  6. Query a user's marbles list

2.2chaincode chain code

markbels.go

package main

// Introduce dependency package
import (
	"fmt"
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"github.com/hyperledger/fabric/protos/peer"

	"time"
	"bytes"
	"strconv"
	"encoding/json"
)

// Declaration structure
type MarblesChaincode struct{

}

// Declaration bullet structure
type Marble struct {
	// object type
	ObjectType string `json:"objectType"`
	// Marbles name
	Name string `json:"name"`
	// Pearl color
	Color string `json:"color"`
	// Marbles size
	Size int `json:"size"`
	// Marbles owner
	Owner string `json:"owner"`
}

// Instantiation chain code interface
func (t *MarblesChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {

	// No operation for initialization here
	fmt.Println("MarblesChaincode Chain code instantiation")
	return shim.Success(nil)
}

// invoke operation
func (t *MarblesChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

	// Get the methods and parameters of the call
	fn, args := stub.GetFunctionAndParameters()

	// Judge distribution business method
	if fn == "initMarble" {
		// Call create marbles method
		return t.initMarble(stub,args)
	}else if fn == "readMarble" {
		// Call to read marbles information
		return t.readMarble(stub,args)
	}else if fn == "deleteMarble" {
		// Call to delete marble information
		return t.deleteMarble(stub,args)
	}else if fn == "transferMarble" {
		// Call transaction marbles method
		return t.transferMarble(stub,args)
	}else if fn == "getMarbleByRange" {
		// Call scope query
		return t.getMarbleByRange(stub,args)
	}else if fn == "queryMarblesByOwner" {
		// Call to query the marble information owned by the user
		return t.queryMarblesByOwner(stub,args)
	}else if fn == "queryHistoryForMarble" {
		// Query the historical operation information of marbles
		return t.queryHistoryForMarble(stub,args)
	}

	// If no corresponding method returns an error
	return shim.Error(fn +" Method does not exist!")

}


// Create marble args: name color size owner
func (t *MarblesChaincode) initMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {

	// Judge whether the marble already exists according to the name
	name := args[0]

	// Find if there is a marble named name before
	marbleBytes, err := stub.GetState(name)

	// query error
	if err != nil {
		return shim.Error(err.Error())
	}

	// If marbleBytes already exists 
	if marbleBytes != nil {
		return shim.Error("be known as"+ name + "Our marbles already exist");
	}

	// Write to ledger if not present
	color := args[1]
	size,err := strconv.Atoi(args[2])
	owner := args[3]

	// Assembly and measurement structure
	marble := &Marble{"marble",name,color,size,owner}

	// Convert marble to json string and store it in the ledger
	marbleJsonStr, err := json.Marshal(marble)
	if err != nil {
		return shim.Error(err.Error())
	}

	// PutState json information written into the ledger
	err = stub.PutState(name,marbleJsonStr)
	if err != nil {
		return shim.Error(err.Error())
	}

	fmt.Println("Create marbles successfully!!")
	// Create key combination for query at the same time
	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)
}

// Read marble args: marblename
func (t *MarblesChaincode) readMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {
	// Get the name whose parameter is marble
	name := args[0]

	// Read the data of marble according to name
	marbleBytes, err := stub.GetState(name)

	if err != nil {
		return shim.Error(err.Error())
	}

	if marbleBytes == nil {
		return shim.Error(name + " The marble information of does not exist")
	}

	// Return information 
	return shim.Success(marbleBytes)

}

// Delete marble args: marbleName
func (t *MarblesChaincode) deleteMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {
	// Get the name of the marker from the parameter total
	name := args[0]

	// Judge whether the marbles exist
	marbleBytes, err := stub.GetState(name)
	if err != nil {
		return shim.Error(err.Error())
	}

	if marbleBytes == nil {
		return shim.Error(name + "The marble information of does not exist")
	}

	// Delete marbles
	err = stub.DelState(name)

	if err != nil {
		return shim.Error(err.Error())
	}

	fmt.Println(name + " Marbles deleted successfully!")
	return shim.Success(nil)
}

// Transaction: marblename newowner
func (t *MarblesChaincode) transferMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {

	// Get parameters
	marbleName := args[0]

	newOwner := args[1]

	// Check for marbles
	marbleBytes, err := stub.GetState(marbleName)
	if err != nil{
		return shim.Error(err.Error())
	}
	if marbleBytes == nil {
		return shim.Error(marbleName + " The marble information of does not exist")
	}

	// Turn information in the ledger into a Marble structure
	marbleInfo := Marble{}
	err = json.Unmarshal(marbleBytes,&marbleInfo)
	if err != nil {
		return shim.Error(err.Error())
	}

	// Modify owner
	marbleInfo.Owner = newOwner

	// Convert to json data 
	newMarbleBytes,err := json.Marshal(marbleInfo)
	if err != nil {
		return shim.Error(err.Error())
	}

	// Write account
	err = stub.PutState(marbleName,newMarbleBytes)
	if err != nil {
		return shim.Error(err.Error())
	}

	fmt.Println(marbleName +"Transfer to"+ newOwner+ "'s marbles deal completed")
	return shim.Success(nil)

}

// Query marble args in a range: startMarble endMarble
func (t *MarblesChaincode) getMarbleByRange(stub shim.ChaincodeStubInterface, args []string) peer.Response {
	// Get parameters
	startMarble := args[0]
	endMarble := args[1]

	// Call query method
	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
	// Traverse 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 result
	return shim.Success(buffer.Bytes())

}

// Query the marble information owned by users
func (t *MarblesChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface,args []string) peer.Response {
	owner := args[0]
	fmt.Println("Start query user"+owner+"Marble information you have")
	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 result
	return shim.Success(buffer.Bytes())


	// Get parameters. The following is to query through couchdb. However, if my local environment starts couchdb, the CLI container cannot connect to couchdb when the container starts. Guess that couchdb starts unfinished cli in the container and tries to connect.
	// owner := args[0]
	// fmt.Println("start query user" + owner + "owned marbles information")

	// 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
	// //Traverse 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 results
	// return shim.Success(buffer.Bytes())

}

// Query users
func (t *MarblesChaincode) queryHistoryForMarble(stub shim.ChaincodeStubInterface,args []string) peer.Response {
	// Get parameters
	marbleName := args[0]
	// Get historical information
	resultIterator,err := stub.GetHistoryForKey(marbleName)
	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultIterator.Close()


	var buffer bytes.Buffer
	buffer.WriteString("[")

	isWriteSplit := false
	// Traverse 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("]")

	// If no corresponding method returns an error
	return shim.Success(buffer.Bytes())

}

// main function
func main (){
	err := shim.Start(new (MarblesChaincode))
	if err != nil {
		fmt.Printf("Error start MarblesChaincode")
	}
}

2.3 write test class

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 running test

Go to the directory where marbles.go and marbles'ou test.go are located
I put it in the $gopath / my "chaincode / marbles directory

cd $GOPATH/my_chaincode/marbles 
go test marbles_test.go -v marbles.go --tags=nopkcs11

Try modifying marbles test.go to test other methods

3 build local test environment and test chain code

Using the chaincode docker DevMode environment test in the fabric samples project

3.1 mount chain code

Move the written marbles.go and marbles_test.go files to

$GOAPTH/src/github.com/hyperledger/fabric-samples/chaincode

Easy to mount in container

3.2 start Network Environment

Go to the chaincode docker DevMode directory

cd $GOPATH/src/github.com/hyperledger/fabric-samples/chaincode-docker-devmode

Starting network environment with docker compose

docker-compose -f docker-compose-simple.yaml up

List of containers after startup

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 enter chain code container to compile chain code to provide chain code service

Start a new terminal

Container entry

docker exec -it chaincode /bin/bash

Go to the mount go file directory

cd marbles/

Compiler

go build

Start the chain code service after compilation

CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./marbles

3.4 enter cli container to install chain code instantiation chain code

Start a new terminal
Enter the cli container

docker exec -it cli /bin/bash

Chain code installation

peer chaincode install -p chaincodedev/chaincode/marbles -n mycc -v 0 

Initialization chain code

peer chaincode instantiate -n mycc -v 0 -c '{"Args":[]}' -C myc

3.5 test chain code

Create three marbles

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

Inquire for marbles

peer chaincode query -n mycc -c '{"Args":["readMarble","marble-1"]}' -C myc

Range query

peer chaincode query -n mycc -c '{"Args":["getMarbleByRange","marble-1","marble-3"]}' -C myc

Transfer marble

peer chaincode invoke -n mycc -c '{"Args":["transferMarble","marble-1","WX"]}' -C myc

Query marble-1 again: whether owner changes to WX

peer chaincode query -n mycc -c '{"Args":["readMarble","marble-1"]}' -C myc

Query all marbles of the owner

peer chaincode query -n mycc -c '{"Args":["queryMarblesByOwner","WX"]}' -C myc

View the modification history of marbles: one initialization transaction and one transfer transaction can be found in the result

peer chaincode query -n mycc -c '{"Args":["queryHistoryForMarble","marble-1"]}' -C myc 

4 turn off the network

Press control+c twice at the terminal that starts the network

 docker-compose -f docker-compose-simple.yaml down

Stop and close all containers

14 original articles published, praised 1, 776 visitors
Private letter follow

Keywords: JSON Docker network github

Added by griffith on Tue, 18 Feb 2020 08:39:03 +0200