深蓝前几篇博客讲了Fabric的环境搭建,在环境搭建好后,我们就可以进行Fabric的开发工作了。Fabric的开发主要分成2部分,ChainCode链上代码开发和基于SDK的Application开发。我们这里先讲ChainCode的开发。Fabric的链上代码支持Java或者Go语言进行开发,因为Fabric本身是Go开发的,所以深蓝建议还是用Go进行ChainCode的开发。

ChainCode的Go代码需要定义一个SimpleChaincode这样一个struct,然后在该struct上定义Init和Invoke两个函数,然后还要定义一个main函数,作为ChainCode的启动入口。以下是ChainCode的模板:

package main

import (
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
"fmt"
) type SimpleChaincode struct {
} func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
} func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
function, args := stub.GetFunctionAndParameters()
fmt.Println("invoke is running " + function)
if function == "test1" {//自定义函数名称
return t.test1(stub, args)//定义调用的函数
}
return shim.Error("Received unknown function invocation")
}
func (t *SimpleChaincode) test1(stub shim.ChaincodeStubInterface, args []string) pb.Response{
return shim.Success([]byte("Called test1"))
}
这里我们可以看到,在Init和Invoke的时候,都会传入参数stub shim.ChaincodeStubInterface,这个参数提供的接口为我们编写ChainCode的业务逻辑提供了大量实用的方法。下面一一讲解:

1.获得调用的参数

前面给出的ChainCode的模板中,我们已经可以看到,在Invoke的时候,由传入的参数来决定我们具体调用了哪个方法,所以需要先使用GetFunctionAndParameters解析调用的时候传入的参数。除了这个方法以外,接口还提供了另外几个方法,不过其本质都是一样的。
  • GetArgs() [][]byte 以byte数组的数组的形式获得传入的参数列表
  • GetStringArgs() []string 以字符串数组的形式获得传入的参数列表
  • GetFunctionAndParameters() (string, []string) 将字符串数组的参数分为两部分,数组第一个字是Function,剩下的都是Parameter
  • GetArgsSlice() ([]byte, error) 以byte切片的形式获得参数列表

2. 增删改查State DB

对于ChainCode来说,核心的操作就是对State Database的增删改查,对此Fabric接口提供了3个对State DB的操作方法。

2.1 增改数据PutState(key string, value []byte) error

对于State DB来说,增加和修改数据是统一的操作,因为State DB是一个Key Value数据库,如果我们指定的Key在数据库中已经存在,那么就是修改操作,如果Key不存在,那么就是插入操作。对于实际的系统来说,我们的Key可能是单据编号,或者系统分配的自增ID+实体类型作为前缀,而Value则是一个对象经过JSON序列号后的字符串。比如说我们定义一个Student的Struct,然后插入一个学生数据,对于的代码应该是这样的:

type Student struct {
Id int
Name string
}
func (t *SimpleChaincode) testStateOp(stub shim.ChaincodeStubInterface, args []string) pb.Response{
student1:=Student{,"Devin Zeng"}
key:="Student:"+strconv.Itoa(student1.Id)//Key格式为 Student:{Id}
studentJsonBytes, err := json.Marshal(student1)//Json序列号
if err != nil {
return shim.Error(err.Error())
}
err= stub.PutState(key,studentJsonBytes)
if(err!=nil){
return shim.Error(err.Error())
}
return shim.Success([]byte("Saved Student!"))
}

2.2 删除数据DelState(key string) error

这个也很好理解,根据Key删除State DB的数据。如果根据Key找不到对于的数据,删除失败。

err= stub.DelState(key)
if err != nil {
return shim.Error("Failed to delete Student from DB, key is: "+key)
}

2.3 查询数据GetState(key string) ([]byte, error)

因为我们是Key Value数据库,所以根据Key来对数据库进行查询,是一件很常见,很高效的操作。返回的数据是byte数组,我们需要转换为string,然后再Json反序列化,可以得到我们想要的对象。
dbStudentBytes,err:= stub.GetState(key)
var dbStudent Student;
err=json.Unmarshal(dbStudentBytes,&dbStudent)//反序列化
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + string(dbStudentBytes)+ "\" to Student}")
}
fmt.Println("Read Student from DB, name:"+dbStudent.Name)

【注意:不能在一个ChainCode函数中PutState后又马上GetState,这个时候GetState是没有最新值的,因为在这时Transaction并没有完成,还没有提交到StateDB里面】

3. 复合键的处理

3.1 生成复合键CreateCompositeKey(objectType string, attributes []string) (string, error)

前面在进行数据库的增删改查的时候,都需要用到Key,而我们使用的是我们自己定义的Key格式:{StructName}:{Id},这是有单主键Id还比较简单,如果我们有多个列做联合主键怎么办?实际上,ChainCode也为我们提供了生成Key的方法CreateCompositeKey,通过这个方法,我们可以将联合主键涉及到的属性都传进去,并声明了对象的类型即可。
以选课表为例,里面包含了以下属性:
type ChooseCourse struct {
CourseNumber string //开课编号
StudentId int //学生ID
Confirm bool //是否确认
}
其中CourseNumber+StudentId构成了这个对象的联合主键,我们要获得生成的复核主键,那么可写为:
cc:=ChooseCourse{"CS101",,true}
var key1,_= stub.CreateCompositeKey("ChooseCourse",[]string{cc.CourseNumber,strconv.Itoa(cc.StudentId)})
fmt.Println(key1)
【注:其实Fabric就是用U+0000来把各个字段分割开的,因为这个字符太特殊,所以很适合做分割】

3.2 拆分复合键SplitCompositeKey(compositeKey string) (string, []string, error)

既然有组合那么就有拆分,当我们从数据库中获得了一个复合键的Key之后,怎么知道其具体是由哪些字段组成的呢。其实就是用U+0000把这个复合键再Split开,得到结果中第一个是objectType,剩下的就是复合键用到的列的值。

objType,attrArray,_:= stub.SplitCompositeKey(key1)
fmt.Println("Object:"+objType+" ,Attributes:"+strings.Join(attrArray,"|"))

3.3 部分复合键的查询GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)

这里其实是一种对Key进行前缀匹配的查询,也就是说,我们虽然是部分复合键的查询,但是不允许拿后面部分的复合键进行匹配,必须是前面部分。

4. 获得当前用户GetCreator() ([]byte, error)

这个方法可以获得调用这个ChainCode的客户端的用户的证书,这里虽然返回的是byte数组,但是其实是一个字符串,内容格式如下:

-----BEGIN CERTIFICATE-----
MIICGjCCAcCgAwIBAgIRAMVe0+QZL+67Q+R2RmqsD90wCgYIKoZIzj0EAwIwczEL

MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG

cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh

Lm9yZzEuZXhhbXBsZS5jb20wHhcNMTcwODEyMTYyNTU1WhcNMjcwODEwMTYyNTU1

WjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN

U2FuIEZyYW5jaXNjbzEfMB0GA1UEAwwWVXNlcjFAb3JnMS5leGFtcGxlLmNvbTBZ

MBMGByqGSM49AgEGCCqGSM49AwEHA0IABN7WqfFwWWKynl9SI87byp0SZO6QU1hT

JRatYysXX5MJJRzvvVsSTsUzQh5jmgwkPbFcvk/x4W8lj5d2Tohff+WjTTBLMA4G

A1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAIO2os1zK9BKe

Lb4P8lZOFU+3c0S5+jHnEILFWx2gNoLkMAoGCCqGSM49BAMCA0gAMEUCIQDAIDHK

gPZsgZjzNTkJgglZ7VgJLVFOuHgKWT9GbzhwBgIgE2YWoDpG0HuhB66UzlA+6QzJ

+jvM0tOVZuWyUIVmwBM=

-----END CERTIFICATE-----

我们常见的需求是在ChainCode中获得当前用户的信息,方便进行权限管理。那么我们怎么获得当前用户呢?我们可以把这个证书的字符串转换为Certificate对象。一旦转换成这个对象,我们就可以通过Subject获得当前用户的名字。

func (t *SimpleChaincode) testCertificate(stub shim.ChaincodeStubInterface, args []string) pb.Response{
creatorByte,_:= stub.GetCreator()
certStart := bytes.IndexAny(creatorByte, "-----BEGIN")
if certStart == - {
fmt.Errorf("No certificate found")
}
certText := creatorByte[certStart:]
bl, _ := pem.Decode(certText)
if bl == nil {
fmt.Errorf("Could not decode the PEM structure")
} cert, err := x509.ParseCertificate(bl.Bytes)
if err != nil {
fmt.Errorf("ParseCertificate failed")
}
uname:=cert.Subject.CommonName
fmt.Println("Name:"+uname)
return shim.Success([]byte("Called testCertificate "+uname))
}

5.高级查询

前面提到的GetState只是最基本的根据Key查询值的操作,但是对于很多时候,我们需要查询返回的是一个集合,比如我要知道某个区间的Key对于所有对象,或者我们需要对Value对象内部的属性进行查询。

5.1 Key区间查询GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)

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

   defer resultsIterator.Close()
// buffer is a JSON array containing QueryRecords
var buffer bytes.Buffer
buffer.WriteString("[") bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"") buffer.WriteString(", \"Record\":")
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("queryResult:\n%s\n", buffer.String())
return buffer.Bytes(), nil
}
比如我们要查询编号从1号到3号的所有学生,那么我们的查询代码可以这么写:
func (t *SimpleChaincode) testRangeQuery(stub shim.ChaincodeStubInterface, args []string) pb.Response{
resultsIterator,err:= stub.GetStateByRange("Student:1","Student:3")
if err!=nil{
return shim.Error("Query by Range failed")
}
students,err:=getListResult(resultsIterator)
if err!=nil{
return shim.Error("getListResult failed")
}
return shim.Success(students)
}

5.2 富查询GetQueryResult(query string) (StateQueryIteratorInterface, error)

这是一个“富查询”,是对Value的内容进行查询,如果是LevelDB,那么是不支持,只有CouchDB时才能用这个方法。
关于传入的query这个字符串,其实是CouchDB所使用的Mango查询,我们可以在官方博客了解到一些信息:https://blog.couchdb.org/2016/08/03/feature-mango-query/ 其基本语法可以在https://github.com/cloudant/mango 这里看到。
比如我们仍然以前面的Student为例,我们要按Name来进行查询,那么我们的代码可以写为:
func (t *SimpleChaincode) testRichQuery(stub shim.ChaincodeStubInterface, args []string) pb.Response{
name:="Devin Zeng"//这里按理来说应该是参数传入
queryString := fmt.Sprintf("{\"selector\":{\"Name\":\"%s\"}}", name)
resultsIterator,err:= stub.GetQueryResult(queryString)//必须是CouchDB才行
if err!=nil{
return shim.Error("Rich query failed")
}
students,err:=getListResult(resultsIterator)
if err!=nil{
return shim.Error("Rich query failed")
}
return shim.Success(students)
}

5.3历史数据查询GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)

对同一个数据(也就是Key相同)的更改,会记录到区块链中,我们可以通过GetHistoryForKey方法获得这个对象在区块链中记录的更改历史,包括是在哪个TxId,修改的数据,修改的时间戳,以及是否是删除等。比如之前的Student:1这个对象,我们更改和删除过数据,现在要查询这个对象的更改记录,那么对应代码为:

func (t *SimpleChaincode) testHistoryQuery(stub shim.ChaincodeStubInterface, args []string) pb.Response{
student1:=Student{,"Devin Zeng"}
key:="Student:"+strconv.Itoa(student1.Id)
it,err:= stub.GetHistoryForKey(key)
if err!=nil{
return shim.Error(err.Error())
}
var result,_= getHistoryListResult(it)
return shim.Success(result)
}
func getHistoryListResult(resultsIterator shim.HistoryQueryIteratorInterface) ([]byte,error){ defer resultsIterator.Close()
// buffer is a JSON array containing QueryRecords
var buffer bytes.Buffer
buffer.WriteString("[") bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
item,_:= json.Marshal( queryResponse)
buffer.Write(item)
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("queryResult:\n%s\n", buffer.String())
return buffer.Bytes(), nil
}

5.4部分复合键查询GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)

这个我在前面3.3已经说过了,只是因为那个函数即是复合键的,也是高级查询的,所以我在这里给这个函数留了一个位置。

6.调用另外的链上代码 InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response

这个比较好理解,就是在我们的链上代码中调用别人已经部署好的链上代码。比如官方提供的example02,我们要在代码中去实现a->b的转账,那么我们的代码应该如下:
func (t *SimpleChaincode) testInvokeChainCode(stub shim.ChaincodeStubInterface, args []string) pb.Response{
trans:=[][]byte{[]byte("invoke"),[]byte("a"),[]byte("b"),[]byte("")}
response:= stub.InvokeChaincode("mycc",trans,"mychannel")
fmt.Println(response.Message)
return shim.Success([]byte( response.Message))
}
这里需要注意,我们使用的是example02的链上代码的实例名mycc,而不是代码的名字example02.

7.获得提案对象Proposal属性

7.1 获得签名的提案GetSignedProposal() (*pb.SignedProposal, error)

从客户端发现背书节点的Transaction或者Query都是一个提案,GetSignedProposal获得当前的提案对象包括客户端对这个提案的签名。提案的内容如果直接打印出来感觉就像是乱码,其内包含了提案Header,Payload和Extension,里面更包含了复杂的结构,这里不讲,以后可以写一篇博客专门研究提案对象。

7.2获得Transient对象 GetTransient() (map[string][]byte, error)

Transient是在提案中Payload对象中的一个属性,也就是ChaincodeProposalPayload.TransientMap

7.3获得交易时间戳GetTxTimestamp() (*timestamp.Timestamp, error)

交易时间戳也是在提案对象中获取的,提案对象的Header部分,也就是proposal.Header.ChannelHeader.Timestamp

7.4 获得Binding对象 GetBinding() ([]byte, error)

这个Binding对象也是从提案对象中提取并组合出来的,其中包含proposal.Header中的SignatureHeader.Nonce,SignatureHeader.Creator和ChannelHeader.Epoch。关于Proposal对象确实很8复杂,我目前了解的并不对,接下来得详细研究。

8.事件设置SetEvent(name string, payload []byte) error

当ChainCode提交完毕,会通过Event的方式通知Client。而通知的内容可以通过SetEvent设置。
func (t *SimpleChaincode) testEvent(stub shim.ChaincodeStubInterface, args []string) pb.Response{
tosend := "Event send data is here!"
err := stub.SetEvent("evtsender", []byte(tosend))
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
事件设置完毕后,需要在客户端也做相应的修改。由于我现在还没有做Application的开发,所以了解的还不够。以后也需要写一篇博客探讨这个话题。
最后,大家如果想进一步探讨Fabric或者使用中遇到什么问题可以加入QQ群【494085548】大家一起讨论。

HyperLedger Fabric ChainCode开发——shim.ChaincodeStubInterface用法的更多相关文章

  1. Hyperledger Fabric ChainCode开发

    预览 Hyperledger Fabric的chaincode开发目前支持Go.Java.Node.js语言,下面以Go语言作为例子,我们先看下面的一个官方提供chaincode模板 ··· pack ...

  2. Hyperledger Fabric chaincode 开发(疑难解答)

    Q&A Q1: 使用fabric release 1.2 进行golang chaincode开发时报错: ..\..\hyperledger\fabric\vendor\github.com ...

  3. 【转】shim.ChaincodeStubInterface用法

    作为记录 shim.ChaincodeStubInterface用法

  4. Hyperledger Fabric Chaincode for Operators——实操智能合约

    什么是Chaincode(智能合约)? chaincode是一个程序,它是使用Go语言编写的,最终在Java等其他编程语言中实现了指定的接口.chaincode运行在一个被背书peer进程独立出来的安 ...

  5. Hyperledger Fabric CA的命令行用法

    介绍Hyperledger Fabric CA的命令行方式简单用法 Hyperledger Fabric CA由server和client两部分组成. 设置两个环境变量 export FABRIC_C ...

  6. Hyperledger Fabric Chaincode解析

    首先看下Blockchain结构,除了header指向下一个block的hash value外,block是由一组transaction构成, Transactions --> Blocks - ...

  7. Hyperledger Fabric 1.0 从零开始(十二)——fabric-sdk-java应用

    Hyperledger Fabric 1.0 从零开始(十)--智能合约 Hyperledger Fabric 1.0 从零开始(十一)--CouchDB 上述两章,最近网上各路大神文章云集,方案多多 ...

  8. Hyperledger Fabric 1.0 从零开始(十二)——fabric-sdk-java应用【补充】

    在 Hyperledger Fabric 1.0 从零开始(十二)--fabric-sdk-java应用 中我已经把官方sdk具体改良办法,即使用办法发出来了,所有的类及文件都是完整的,在文章的结尾也 ...

  9. Centos7 HyperLedger Fabric 1.4 生产环境部署

    Kafka生产环境部署案例采用三个排序(orderer)服务.四个kafka.三个zookeeper和四个节点(peer)组成,共准备八台服务器,每台服务器对应的服务如下所示: kafka案例网络拓扑 ...

随机推荐

  1. 【ASP.NET Core快速入门】(七)WebHost的配置、 IHostEnvironment和 IApplicationLifetime介绍、dotnet watch run 和attach到进程调试

    WebHost的配置 我们用vs2017新建一个空网站HelloCore 这里的CreateDefaultBuilde实际上已经在内部替我们做好了默认配置. UseKestrel 使用kestrel ...

  2. leetcode — subsets

    import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Source : https://o ...

  3. 如何优雅地查看 JS 错误堆栈?

    本文由云+社区发表 在前端,我们经常会通过 window.onerror 事件来捕获未处理的异常.假设捕获了一个异常,上报的堆栈是这个: TypeError: Cannot read property ...

  4. [二十]JavaIO之StringReader 与 StringWriter

    功能简介 还记得前面说过的CharArrayReader 和 CharArrayWriter吗? CharArray 是数据源 CharArrayReader 是读,  从一个CharArray 中读 ...

  5. php ip2long负数的问题

    大家可能都知道php提供了ip2long与long2ip方法对ip地址处理.抛砖引玉,说点概念性滴: 1.ip2long — 将一个IPV4的字符串互联网协议转换成数字格式 int ip2long ( ...

  6. 详解Linux高效命令head、tail和cat

    Linux中提供了多种命令和程序用于浏览文件.无论对于新手.普通用户.高级用户.开发人员还是管理员来说,与诸多文件打交道都是一项艰巨的任务.而如何做到高效更称得上是一门艺术. 今天就让我们来探讨几个最 ...

  7. angularjs+webapi2 跨域Basic 认证授权(二)

    在上一篇中大概演示了 整个认证授权的过程.今天在这篇博客中将结合上一篇的例子继续在跨域的情况 我们用ionic 写一个简单的页面 值得注意的是 在ionic.bundle.js 里面集成了angula ...

  8. 驰骋工作流引擎JFlow与activiti的对比 -总结

    共同点: 1. 嵌入式的工作流引擎,降低集群复杂性. 2. 严格而灵活的流程版本控制 3. 支持多种数据库 4. 支持多种流程设计模式 5. 成熟度高的开源工作流,具有可靠的稳定性和性能. 区别: 1 ...

  9. C# 进程间通讯

    扩展阅读:http://www.cnblogs.com/joye-shen/archive/2012/06/16/2551864.html 一.进程间通讯的方式 1)共享内存 包括:内存映射文件,共享 ...

  10. 【公众号系列】SAP将裁员4400人,颤抖吧!

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[公众号系列]SAP将裁员4400人,颤抖吧! ...