博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
搭建基于hyperledger fabric的联盟社区(四) --chaincode开发
阅读量:4679 次
发布时间:2019-06-09

本文共 16090 字,大约阅读时间需要 53 分钟。

前几章已经分别把三台虚拟机环境和配置文件准备好了,在启动fabric网络之前我们要准备好写好的chaincode。chaincode的开发一般是使用GO或者JAVA,而我选择的是GO语言。先分析一下官方最典型的一个chaincode--fabcar,然后着重介绍一下shim.ChaincodeSubInterface,最后在贴上我自己的chaincode。

 

一.chaincode主要框架结构

1.1 引入了4个程序库,用于格式化、处理字节、读取和写入JSON,以及字符串操作。2个Hyperledger Fabric 特定的智能合同库。shim包提供了一些 API,以便chaincode与底层区块链网络交互来访问状态变量、交易上下文、调用方证书和属性,并调用其他chaincode和执行其他操作。

import (	"bytes"	"encoding/json"	"fmt"	"strconv"	"github.com/hyperledger/fabric/core/chaincode/shim"	sc "github.com/hyperledger/fabric/protos/peer")

1.2 定义SmartContract结构体,然后在该struct上定义Init和Invoke两个函数

type SmartContract struct {}

1.3 定义具有四个变量的汽车结构体,结构体标签被编码/json库使用。

type Car struct {	Make   string `json:"make"`	Model  string `json:"model"`	Colour string `json:"colour"`	Owner  string `json:"owner"`}

1.4 当智能合同“fabcar”由区块链网络实例化时,Init方法被调用。在Go中通过给函数标明所属类型,来给该类型定义方法,下面的 s *SmartContract即表示给SmartContract声明了一个方法。在Init和Invoke的时候,都会传入参数stub shim.ChaincodeStubInterface,这个参数提供的接口为我们编写ChainCode的业务逻辑提供了大量的实用方法。假设一切顺利,将返回一个表示初始化已经成功的sc.Response对象。

func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {	return shim.Success(nil)}

1.5 应用程序请求运行智能合约fabcar后,Invoke方法被调用。在Invoke的时候,由传入的参数来决定我们具体调用了哪个方法,所以需要用GetFunctionAndParameters解析调用的时候传入的参数。

func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {	// 检索请求的智能合约的函数和参数,GetFunctionAndParameters() (string, []string)将字符串数组的参数分为两部分,数组第一个字是Function,剩下的都是Parameter	function, args := APIstub.GetFunctionAndParameters()	// 找到合适的处理函数以便与账本进行交互	if function == "queryCar" {		return s.queryCar(APIstub, args)	} else if function == "initLedger" {		return s.initLedger(APIstub)	} else if function == "createCar" {		return s.createCar(APIstub, args)	} else if function == "queryAllCars" {		return s.queryAllCars(APIstub)	} else if function == "changeCarOwner" {		return s.changeCarOwner(APIstub, args)	}	return shim.Error("Invalid Smart Contract function name.")}

1.6 主函数仅与单元测试模式相关,任何GO程序的起点都是main函数,在这里只是为了完整性。

func main() {	// 创建了新的智能合同,并向对等节点注册它	err := shim.Start(new(SmartContract))	if err != nil {		fmt.Printf("Error creating new Smart Contract: %s", err)	}}

 

二.对State DB (状态数据库)的增删改查 

2.1 查询某辆汽车

通过GetState(key string) ([]byte, error)查询数据。因为我们是Key Value数据库,所以根据Key来对数据库进行查询,是一件很常见,很高效的操作,返回的数据是byte数组。

func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {	if len(args) != 1 {		return shim.Error("Incorrect number of arguments. Expecting 1")	}	carAsBytes, _ := APIstub.GetState(args[0])	return shim.Success(carAsBytes)}

2.2 初始化账本

通过PutState(key string, value []byte) error 增改数据,对于State DB来说,增加和修改数据是统一的操作,因为State DB是一个Key Value数据库,如果我们指定的Key在数据库中已经存在,那么就是修改操作,如果Key不存在,那么就是插入操作。对于实际的系统来说,我们的Key可能是单据编号,或者系统分配的自增ID+实体类型作为前缀,而Value则是一个对象经过JSON序列号后的字符串。

func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {	cars := []Car{		Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},		Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},		Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},		Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},		Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},		Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},		Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},		Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},		Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},		Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},	}	i := 0	for i < len(cars) {		fmt.Println("i is ", i)		carAsBytes, _ := json.Marshal(cars[i])		APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)		fmt.Println("Added", cars[i])		i = i + 1	}	return shim.Success(nil)}

2.3 创建汽车

把对象转换为JSON的方法(函数)为 json.Marshal(),也就是说,这个函数接收任意类型的数据 v,并转换为字节数组类型,返回值就是我们想要的JSON数据和一个错误代码。当转换成功的时候,这个错误代码为nil。

func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {	if len(args) != 5 {		return shim.Error("Incorrect number of arguments. Expecting 5")	}	var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}	carAsBytes, _ := json.Marshal(car)	APIstub.PutState(args[0], carAsBytes)	return shim.Success(nil)}

2.4 查询所有汽车

Key区间查询GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) 提供了对某个区间的Key进行查询的接口,适用于任何State DB。由于返回的是一个StateQueryIteratorInterface(迭代器)接口,我们需要通过这个接口再做一个for循环,才能读取返回的信息,所有我们可以独立出一个方法,专门将该接口返回的数据以string的byte数组形式返回。

func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {	startKey := "CAR0"	endKey := "CAR999"	resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)	if err != nil {		return shim.Error(err.Error())	}   //defer关键字用来标记最后执行的Go语句,一般用在资源释放、关闭连接等操作,会在函数关闭前调用。多个defer的定义与执行类似于栈的操作:先进后出,最先定义的最后执行。	defer resultsIterator.Close()	// buffer 是一个包含查询结果的JSON数组,bytes.buffer是一个缓冲byte类型的缓冲器存放着都是byte,这样直接定义一个 Buffer 变量,而不用初始化。	var buffer bytes.Buffer	buffer.WriteString("["]	bArrayMemberAlreadyWritten := false   //迭代器的两个方法,hasNext:没有指针下移操作,只是判断是否存在下一个元素。next:指针下移,返回该指针所指向的元素。	for resultsIterator.HasNext() {		queryResponse, err := resultsIterator.Next()		if err != nil {			return shim.Error(err.Error())		}		// 在数组成员前加一个逗号		if bArrayMemberAlreadyWritten == true {			buffer.WriteString(",")		}		buffer.WriteString("{\"Key\":")		buffer.WriteString("\"")		buffer.WriteString(queryResponse.Key)		buffer.WriteString("\"")		buffer.WriteString(", \"Record\":")		// Record 是一个JSON对象,所以我们按原样写		buffer.WriteString(string(queryResponse.Value))		buffer.WriteString("}")		bArrayMemberAlreadyWritten = true	}	buffer.WriteString("]")	fmt.Printf("- queryAllCars:\n%s\n", buffer.String())	return shim.Success(buffer.Bytes())}

2.5 改变汽车拥有人

Unmarshal是用于反序列化json的函数根据data将数据反序列化到传入的对象中。

func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {	if len(args) != 2 {		return shim.Error("Incorrect number of arguments. Expecting 2")	}	carAsBytes, _ := APIstub.GetState(args[0])	car := Car{}	json.Unmarshal(carAsBytes, &car)	car.Owner = args[1]	carAsBytes, _ = json.Marshal(car)	APIstub.PutState(args[0], carAsBytes)	return shim.Success(nil)}

以上就是官方链代码fabcar的分析  

 

三.链码开发API

作为chaincode中的利器shim.ChaincodeSubInterface提供了一系列API供开发者在编写链码时灵活选择使用。

这些API可分为四类:账本状态交互API、交易信息相关API、参数读取API、其他API,下面分别介绍。

3.1账本状态交互API

chaincode需要将一些数据记录在分布式账本中。需要记录的数据称为状态state,以键值对(key-value)的形式存储。账本状态API可以对账本状态进行操作,十分重要。方法的调用会更新交易提案的读写集和,在committer进行验证时会在此执行,跟账本状态进行比对,这类API的大致功能如下:

 

3.2交易信息相关API

交易信息相关API可以获取到与交易信息自身相关的数据。用户对链码的调用(初始化和升级时调用Init()方法,运行时调用Invoke()方法)过程中会产生交易提案。这些API支持查询当前交易提案的一些属性,具体信息如下:

 

3.3 参数读取API

调用链码时支持传入若干参数,参数可通过API读取。具体信息如下:

 

3.4其他API

除了上面一些API以外还有一些辅助API,如下:

 

 

四. 社区联盟chaincode

4.1 业务功能需求

4.1.1 AddPost(Post)

本函数功能为存入一个帖子。

参数: 

Post 为JSON 格式数据,包含帖子的的基本信息。须将字段名作为Key,字段的值作为 Value。

返回值:

-1:操作不成功

某个正整数:操作成功,返回值表示新帖的ID

 

4.1.2 UpdatePost(Post)

本函数功能为更改一个已经存在的帖子。

参数: 

Post 为JSON 格式数据,包含帖子的的基本信息,其中帖子ID不可为空。须将字段名作为Key,字段的值作为 Value。

返回值:

-1:操作不成功

1:操作成功

 

4.1.3 RichQueryPost (Attribute, Operator, Value)

本函数功能为查找帖子。

参数: 

Attribute 表示帖子的属性名称,须符合附件一中的字段名。

Operator 表示比较运算符的代码。如下所示 

0: =

1: >

2: >=

3: <

4: <=

5: between

6: like

Value 为属性的值。若Operator 为0(等于),则本函数将返回对应属性中等于指定Value的所有帖子;若Operator为1~4,则Value 视为单个值(单个数值或单个字符串);若Operator为5,则Value须为两个值,中间以逗号分开;若Operator为6,则本函数将返回对应属性中包含指定Value的所有帖子。

返回值:

-1:操作不成功

JSON格式字符串:操作成功,所有符合条件的帖子将组织为JSON格式的数组,数组中每个元素为一个帖子。

注:目前仅支持单属性的查找。

 

4.1.4 GetPostNum(Attribute, Operator, Value)

本函数功能为查找返回某个条件的帖子的数量。

参数的含义与函数QueryPost相同。

返回值:

-1:操作不成功

某个正整数:操作成功,返回值符合查询条件的帖子的数量

 

 

 

4.2 编写chaincode遇到的问题

在实现有transaction功能的函数时,在函数里面写一个返回值,并不能像查询类型的函数一样在用fabric SDK调用时得到对应的返回值。addpost功能需要返回新帖子的ID,但是我的帖子id正好就是键值对的数量,我通过fabric node SDK 返回了键值对的数量,也就是返回了新帖子的ID。

RichQueryPost和GetPostNum两个函数都会用到富查询。富查询的语法可以参考couchdb官方文档关于Selector语法部分的介绍:

http://docs.couchdb.org/en/2.0.0/api/database/find.html#find-selectors

https://github.com/cloudant/mango

但是模糊查询功能暂时没有实现。(下个版本会实现)

 

4.3 chaincode代码

我们将自己编写符合业务逻辑的chaincode放在peer0.org1和peer0.org2的/go/src/github.com/hyperledger/fabric/examples/chaincode/go/community

目录下,之后启动docker容器的时候会自动挂载chaincode

cd ~/go/src/github.com/hyperledger/fabric/examples/chaincode/go/ mkdir community

 

完整chaincode代码如下

package mainimport (	"bytes"	"encoding/json"	"fmt"	"strconv"	"strings"    	"github.com/hyperledger/fabric/core/chaincode/shim"	sc "github.com/hyperledger/fabric/protos/peer")type SmartContract struct {}type Post struct {	Id   string `json:"id"`	OriginalWebsite  string `json:"originalwebsite"`	OriginalID string `json:"originalid"`	Title  string `json:"title"`	Content  string `json:"content"`	AuthorId  string `json:"authorid"`	PublishTime  string `json:"publishtime"`	UpdateTime  string `json:"updatetime"`	Category  string `json:"category"`	SourceId  string `json:"sourceid"`	Labels  string `json:"labels"`	Follower_num  int `json:"follower_num"`	Browse_num  int `json:"browse_num"`	Star_num  int `json:"star_num"`} type PostLength struct {    Length int `json:"length"`}func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {	return shim.Success(nil)}func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {		function, args := APIstub.GetFunctionAndParameters()		if function == "queryPost" {		return s.queryPost(APIstub, args)	} else if function == "initLedger" {		return s.initLedger(APIstub)	} else if function == "addPost" {		return s.addPost(APIstub, args)	} else if function == "updatePost" {		return s.updatePost(APIstub, args)	} else if function == "richQueryPosts" {		return s.richQueryPosts(APIstub, args)	} else if function == "getPostNum" {		return s.getPostNum(APIstub, args)	} 	return shim.Error("Invalid Smart Contract function name.")}func (s *SmartContract) queryPost(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {	if len(args) != 1 {		return shim.Error("Incorrect number of arguments. Expecting 1")	}	postAsBytes, _ := APIstub.GetState(args[0])	return shim.Success(postAsBytes)}func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {	posts := []Post{		Post{Id: "1", OriginalWebsite: "b", OriginalID: "c", Title: "d",Content:"e",AuthorId:"f",PublishTime:"g",UpdateTime:"h",Category:"i",SourceId:"j",Labels:"k",Follower_num:100,Browse_num:200,Star_num:300},		Post{Id: "2", OriginalWebsite: "bb", OriginalID: "bb", Title: "dd",Content:"ee",AuthorId:"ff",PublishTime:"gg",UpdateTime:"hh",Category:"ii",SourceId:"jj",Labels:"kk",Follower_num:400,Browse_num:500,Star_num:600},		}	length := PostLength{Length:len(posts)}	lengthAsBytes,_ := json.Marshal(length)	APIstub.PutState("POSTLENGTH",lengthAsBytes)	i := 0	for i < len(posts) {		fmt.Println("i is ", i)		postAsBytes, _ := json.Marshal(posts[i])		APIstub.PutState("POST"+strconv.Itoa(i), postAsBytes)		fmt.Println("Added", posts[i])		i = i + 1	}	return shim.Success(nil)}func (s *SmartContract) addPost(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {	if len(args) != 13 {		return shim.Error("Incorrect number of arguments. Expecting 13")	} 	args10,error := strconv.Atoi(args[10])	args11,error := strconv.Atoi(args[11])	args12,error := strconv.Atoi(args[12])    if error != nil{     fmt.Println("String conversion integer failed!")    }    lengthAsBytes, _ := APIstub.GetState("POSTLENGTH")    length := PostLength{}    json.Unmarshal(lengthAsBytes,&length)    newlength := length.Length+1   	var post = Post{Id: strconv.Itoa(newlength), OriginalWebsite: args[0], OriginalID: args[1], Title: args[2],Content:args[3],AuthorId:args[4],PublishTime:args[5],UpdateTime:args[6],Category:args[7],SourceId:args[8],Labels:args[9],Follower_num:args10,Browse_num:args11,Star_num:args12}	postAsBytes, _ := json.Marshal(post)	APIstub.PutState("POST"+strconv.Itoa(newlength), postAsBytes)	length.Length = newlength	lengthAsBytes,_ = json.Marshal(length)	APIstub.PutState("POSTLENGTH",lengthAsBytes)	return shim.Success(lengthAsBytes)}func (s *SmartContract) updatePost(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {	if len(args) != 14 {		return shim.Error("Incorrect number of arguments. Expecting 14")	}	args11,error := strconv.Atoi(args[11])	args12,error := strconv.Atoi(args[12])	args13,error := strconv.Atoi(args[13])	if error != nil{     fmt.Println("String conversion integer failed!")    }	var post = Post{Id: args[0], OriginalWebsite: args[1], OriginalID: args[2], Title: args[3],Content:args[4],AuthorId:args[5],PublishTime:args[6],UpdateTime:args[7],Category:args[8],SourceId:args[9],Labels:args[10],Follower_num:args11,Browse_num:args12,Star_num:args13}	postAsBytes, _ := json.Marshal(post)	APIstub.PutState("POST"+args[0], postAsBytes)	return shim.Success(nil)}func (s *SmartContract) richQueryPosts(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {	if len(args) != 3 {		return shim.Error("Incorrect number of arguments. Expecting 3")	}	var queryString string        if args[1] == "0" {		queryString = fmt.Sprintf("{\"selector\":{\"%s\":\"%s\"}}", args[0],args[2])	} else if args[1] == "1" {	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$gt\":%s}}}", args[0],args[2])	} else if args[1] == "2" {	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$gte\":%s}}}", args[0],args[2])	} else if args[1] == "3" {	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$lt\":%s}}}", args[0],args[2])	} else if args[1] == "4" {	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$lte\":%s}}}", args[0],args[2])	} else if args[1] == "5" {	  between := strings.Split(args[2], ",")	  queryString = fmt.Sprintf("{\"selector\":{\"$and\":[{\"%s\":{\"$gte\":%s}},{\"%s\":{\"$lte\":%s}}]}}", args[0],between[0],args[0],between[1])	}  else {		return shim.Error("Incorrect number of arguments. Expecting 0~5")	}	resultsIterator, err := APIstub.GetQueryResult(queryString)	if err != nil {		return shim.Error(err.Error())	}	defer resultsIterator.Close()	var buffer bytes.Buffer	buffer.WriteString("[")	bArrayMemberAlreadyWritten := false	for resultsIterator.HasNext() {		queryResponse, err := resultsIterator.Next()		if err != nil {			return shim.Error(err.Error())		}				if bArrayMemberAlreadyWritten == true {			buffer.WriteString(",")		}		buffer.WriteString("{\"Key\":")		buffer.WriteString("\"")		buffer.WriteString(queryResponse.Key)		buffer.WriteString("\"")		buffer.WriteString(", \"Record\":")				buffer.WriteString(string(queryResponse.Value))		buffer.WriteString("}")		bArrayMemberAlreadyWritten = true	}	buffer.WriteString("]")	fmt.Printf("- richQueryPosts:\n%s\n", buffer.String())	return shim.Success(buffer.Bytes())}func  (s *SmartContract) getPostNum(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {         if len(args) != 3 {	    return shim.Error("Incorrect number of arguments. Expecting 3")	}   var queryString string        if args[1] == "0" {		queryString = fmt.Sprintf("{\"selector\":{\"%s\":\"%s\"}}", args[0],args[2])	} else if args[1] == "1" {	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$gt\":%s}}}", args[0],args[2])	} else if args[1] == "2" {	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$gte\":%s}}}", args[0],args[2])	} else if args[1] == "3" {	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$lt\":%s}}}", args[0],args[2])	} else if args[1] == "4" {	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$lte\":%s}}}", args[0],args[2])	} else if args[1] == "5" {	  between := strings.Split(args[2], ",")	  queryString = fmt.Sprintf("{\"selector\":{\"$and\":[{\"%s\":{\"$gte\":%s}},{\"%s\":{\"$lte\":%s}}]}}", args[0],between[0],args[0],between[1])	}  else {		return shim.Error("Incorrect number of arguments. Expecting 0~5")	}	resultsIterator, err := APIstub.GetQueryResult(queryString)	if err != nil {		return shim.Error(err.Error())	}	defer resultsIterator.Close()	i := 0	for resultsIterator.HasNext() {	  resultsIterator.Next()			  i = i + 1			}		fmt.Printf("- getPostNum:\n%s\n", strconv.Itoa(i))	return shim.Success([]byte(strconv.Itoa(i)))}func main() {	err := shim.Start(new(SmartContract))	if err != nil {		fmt.Printf("Error creating new Smart Contract: %s", err)	}}

  

  

 

转载于:https://www.cnblogs.com/preminem/p/7754811.html

你可能感兴趣的文章
解决【win10管理员已阻止程序运行】问题时有感
查看>>
NumPy切片和索引
查看>>
Linux CentOS7 VMware 文件和目录权限chmod、更改所有者和所属组chown、umask、隐藏权限lsattr/chattr...
查看>>
易语言拖拽文件命令,拖放对象组件应用
查看>>
MySQL同步故障:" Slave_SQL_Running:No" 两种解决办法 (转载)
查看>>
Activiti系列——如何在eclipse中安装 Activiti Designer插件
查看>>
Linux系统下,启动Tomcat有时报Address already in use
查看>>
读苹果开发文档时遇到瓶颈,转而花2天看了Objc基本语法
查看>>
来创个博客分享学习一下
查看>>
URAL 1779 F - The Great Team 构造
查看>>
HDU 5629 Clarke and tree dp+prufer序列
查看>>
Codeforces Round #407 div2 题解【ABCDE】
查看>>
23种设计模式的C++实现
查看>>
【关于HBITMAP, DC, MEM DC, Clipboard】将HBITMAP拷贝到Clipboard(Windows Clipboard & OLE Clipboard)...
查看>>
freemarker自定义标签
查看>>
F5-WAF-12.0
查看>>
霸道总裁之所以迷人,才不是因为霸道!
查看>>
本地通知UILocalNotification
查看>>
STM32单片机是如何启动的?
查看>>
数据结构:链表 >> 链表按结点中第j个数据属性排序(冒泡排序法)
查看>>