HyperLedger Fabric ChainCode开发——shim.ChaincodeStubInterf
深蓝前几篇博客讲了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的模板: import (
<span style="color: #800000;">"<span style="color: #800000;">github.com/hyperledger/fabric/core/chaincode/shim<span style="color: #800000;">"<span style="color: #000000;"> pb <span style="color: #800000;">"<span style="color: #800000;">github.com/hyperledger/fabric/protos/peer<span style="color: #800000;">" <span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"<span style="color: #000000;"> ) type SimpleChaincode <span style="color: #0000ff;">struct<span style="color: #000000;"> { func main() { func (t <span style="color: #000000;">SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 这里我们可以看到,在Init和Invoke的时候,都会传入参数stub shim.ChaincodeStubInterface,这个参数提供的接口为我们编写ChainCode的业务逻辑提供了大量实用的方法。下面一一讲解: 1.获得调用的参数前面给出的ChainCode的模板中,我们已经可以看到,在Invoke的时候,由传入的参数来决定我们具体调用了哪个方法,所以需要先使用GetFunctionAndParameters解析调用的时候传入的参数。除了这个方法以外,接口还提供了另外几个方法,不过其本质都是一样的。
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 *SimpleChaincode) testStateOp(stub shim.ChaincodeStubInterface,args []=Student{,=+strconv.Itoa(student1.Id)
studentJsonBytes,err := json.Marshal(student1)
err !==(err!= shim.Success([](
2.2 删除数据DelState(key string) error这个也很好理解,根据Key删除State DB的数据。如果根据Key找不到对于的数据,删除失败。 err= err != shim.Error(+
2.3 查询数据GetState(key string) ([]byte,error)因为我们是Key Value数据库,所以根据Key来对数据库进行查询,是一件很常见,很高效的操作。返回的数据是byte数组,我们需要转换为string,然后再Json反序列化,可以得到我们想要的对象。 dbStudentBytes,err:==json.Unmarshal(dbStudentBytes,&dbStudent)
err != shim.Error( + (dbStudentBytes)+ +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
StudentId
Confirm
}
其中CourseNumber+StudentId构成了这个对象的联合主键,我们要获得生成的复核主键,那么可写为: cc:=ChooseCourse{,, key1,_= stub.CreateCompositeKey(,[]
【注:其实Fabric就是用U+0000来把各个字段分割开的,因为这个字符太特殊,所以很适合做分割】 3.2 拆分复合键SplitCompositeKey(compositeKey string) (string,[]string,error)既然有组合那么就有拆分,当我们从数据库中获得了一个复合键的Key之后,怎么知道其具体是由哪些字段组成的呢。其实就是用U+0000把这个复合键再Split开,得到结果中第一个是objectType,剩下的就是复合键用到的列的值。 objType,attrArray,_:=+objType++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 []== bytes.IndexAny(creatorByte, certStart == -== bl ==cert,err :=<span style="color: #000000;"> x509.ParseCertificate(bl.Bytes)
<span style="color: #0000ff;">if err !=<span style="color: #000000;"> nil { fmt.Errorf(<span style="color: #800000;">"<span style="color: #800000;">ParseCertificate failed<span style="color: #800000;">"<span style="color: #000000;">) } uname:=<span style="color: #000000;">cert.Subject.CommonName fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">Name:<span style="color: #800000;">"+<span style="color: #000000;">uname) <span style="color: #0000ff;">return shim.Success([]<span style="color: #0000ff;">byte(<span style="color: #800000;">"<span style="color: #800000;">Called testCertificate <span style="color: #800000;">"+<span style="color: #000000;">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) ([]defer resultsIterator.Close()
<span style="color: #008000;">//<span style="color: #008000;"> buffer is a JSON array containing QueryRecords <span style="color: #0000ff;">var<span style="color: #000000;"> buffer bytes.Buffer buffer.WriteString(<span style="color: #800000;">"<span style="color: #800000;">[<span style="color: #800000;">"<span style="color: #000000;">) bArrayMemberAlreadyWritten := <span style="color: #0000ff;">false
} 比如我们要查询编号从1号到3号的所有学生,那么我们的查询代码可以这么写: func (t *SimpleChaincode) testRangeQuery(stub shim.ChaincodeStubInterface,args []= stub.GetStateByRange(, err!= shim.Error(= err!= shim.Error(
5.2 富查询GetQueryResult(query string) (StateQueryIteratorInterface,error)这是一个“富查询”,是对Value的内容进行查询,如果是LevelDB,那么是不支持,只有CouchDB时才能用这个方法。 关于传入的query这个字符串,其实是CouchDB所使用的Mango查询,我们可以在官方博客了解到一些信息: 其基本语法可以在 这里看到。 比如我们仍然以前面的Student为例,我们要按Name来进行查询,那么我们的代码可以写为: func (t *SimpleChaincode) testRichQuery(stub shim.ChaincodeStubInterface,args []=
queryString := fmt.Sprintf(= stub.GetQueryResult(queryString)
err!= shim.Error(= err!= shim.Error(
5.3历史数据查询GetHistoryForKey(key string) (HistoryQueryIteratorInterface,error)对同一个数据(也就是Key相同)的更改,会记录到区块链中,我们可以通过GetHistoryForKey方法获得这个对象在区块链中记录的更改历史,包括是在哪个TxId,修改的数据,修改的时间戳,以及是否是删除等。比如之前的Student:1这个对象,我们更改和删除过数据,现在要查询这个对象的更改记录,那么对应代码为: func (t *SimpleChaincode) testHistoryQuery(stub shim.ChaincodeStubInterface,=+= err!= result,_===
5.4部分复合键查询GetStateByPartialCompositeKey(objectType string,error)这个我在前面3.3已经说过了,只是因为那个函数即是复合键的,也是高级查询的,所以我在这里给这个函数留了一个位置。 6.调用另外的链上代码 InvokeChaincode(chaincodeName string,args [][]byte,channel string) pb.Response这个比较好理解,就是在我们的链上代码中调用别人已经部署好的链上代码。比如官方提供的example02,我们要在代码中去实现a->b的转账,那么我们的代码应该如下: func (t *SimpleChaincode) testInvokeChainCode(stub shim.ChaincodeStubInterface,args []=[][]{[](),[](),[](),[](= stub.InvokeChaincode(,trans, shim.Success([]
这里需要注意,我们使用的是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 []= = stub.SetEvent(,[] err !=
事件设置完毕后,需要在客户端也做相应的修改。由于我现在还没有做Application的开发,所以了解的还不够。以后也需要写一篇博客探讨这个话题。 最后,大家如果想进一步探讨Fabric或者使用中遇到什么问题可以加入QQ群【494085548】大家一起讨论。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |