搭建基于hyperledger fabric的联盟社区(四) --chaincode开发

前几章已经分别把三台虚拟机环境和配置文件准备好了,在启动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的大致功能如下:

搭建基于hyperledger fabric的联盟社区(四) --chaincode开发

3.2交易信息相关API

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

搭建基于hyperledger fabric的联盟社区(四) --chaincode开发

3.3 参数读取API

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

搭建基于hyperledger fabric的联盟社区(四) --chaincode开发

3.4其他API

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

搭建基于hyperledger fabric的联盟社区(四) --chaincode开发

四. 社区联盟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 main

import (
"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)
}
}

  

  






上一篇:Java开发核心知识笔记共2100页,volatile怎么实现的内存可见


下一篇:Xcode6 管理provisioning profile