在本文中,我将解释在Hyperledger Fabric中使用Protobuf序列化结构化数据时使用的技术。Protobuf简化了数据处理和格式化。它使用特殊生成的源代码来构造数据,以便在相同的SmartContract中轻松地读写数据。
Chaincode和SmartContract
在Hyperledger结构中,Chaincode是一个特定的程序,它处理由区块链网络中的一组参与者同意的核心业务逻辑。
Hyperledger fabric还使用了一种名为SmartContract的数据格式技术,该技术是为链码中特定的一组数据模型定义的。Chaincode可以有多组SmartContract作为交易逻辑来控制不同的数据模型。简单来说,SmartContract管理具体,而Chaincode管理如何打包SmartContract进行部署。
例如:如果需要将一些用户记录存储到分类帐中,那么我们需要一个SmartContract来定义所有需要的数据字段
用户SmartContract
type User struct {
ID string `json:”id”`
Email string `json:”email”`
Name string `json:”name”`
Mobile string `json:”mobile”`
Age string `json:”age”`
}
在这个SmartContract中,有与单个用户记录相关的ID, Email, Name, Mobile, Age数据字段。类似地,如果我们需要存储每个用户的财务记录,那么我们将拥有另一个名为金融的SmartContract。
金融SmartContract
type Financial struct {
ID string `json:”id”`
BankName string `json:”bankName”`
IFSCCode string `json:”ifscCode”`
AccNumber string `json:”accNumber”`
CreatedDate string `json:”createdDate”`
}
因此,这两个SmartContract将被部署到Chaincode中。并将处理的交易逻辑为两个分类帐状态-世界状态,区块链。
SmartContract主要执行世界状态下的Put、Get、Delete和GetHistory。
1. PutState——它为每个不同的键创建新对象,或者覆盖现有的对象。
2. GetState——它从分类帐状态中检索不同键的对象。
3.DelState -它从分类账的世界状态中删除对象。
4. GetHistoryForKey—跨时间返回键的所有交易历史记录。
所有记录都作为世界状态记录存储在CouchDB中。对象以JSON数据格式存储为键值对。CouchDB可以更快地从数据库查询JSON集合。在区块链状态下,所有这些记录都存储在字节中,并且是不可变的。
Protobuf
协议缓冲区(简称protobuf)是谷歌使用语言无关、平台无关、可扩展的机制序列化结构化数据的机制。与传统的数据格式(如XML或JSON)相比,序列化的结构化数据以字节、更小、更快、更简单的方式进行编译。
为什么使用Protobuf ?
正如我们所看到的,有两个SmartContract将信息存储到同一个用户的分类帐中,这意味着用户存储基本的信息,金融存储用户的银行详细信息。
但是,如果我们从查询目的的角度来研究SmartContract,就会发现两个数据收集之间没有联系。我们不能为用户和金融数据模型定义相同的ID作为密钥,因为分类帐数据存储在键-值对中,对于相同的密钥,信息将被覆盖。
在分类账状态下,这两条记录将以两个不同的ID存储
为了解决这个问题,Protobuf提供了一个更快、更灵活的解决方案。我们只需要编写一个.proto文件来描述数据结构,在本例中,就是我们要存储的金融数据结构。
因此,protobuf消息格式的字节结果直接调用用户SmartContract并完全删除金融SmartContract。
Protobuf是如何运作的?
我们将在这里看到如何设置protobuf编译器和生成protobuf消息格式。
安装
首先,我们需要遵循一些安装过程来使用protobuf编译器。
$ go get github.com/golang/protobuf
$ go get github.com/golang/protobuf/proto
$ go get -u github.com/golang/protobuf/protoc-gen-go
$ export PATH=$PATH:$GOPATH/bin
现在,安装protobuf编译器
$ sudo apt install protobuf-compiler
在命令行中输入protoc ‘。它应该说“缺少输入文件”,这意味着成功安装了protobuf编译器。
例子
首先,我们需要创造一个金融原型文件。它由金融类型的消息格式组成,包含四个字段:银行名称、ifsc代码、帐号、创建日期。
financial.proto
syntax=”proto3″;
package main;
message Financial {
string bankName = 1;
string ifscCode = 2;
string accNumber = 3;
string createdDate = 4;
}
编译原型文件,生成用于金融消息格式的protobuf数据模型文件。
$ protoc –go_out=. *.proto
您可以看到protobuf文件已生成为financial.pb.go。此文件是与proto包的兼容性数据模型,它将用于将proto消息格式转换为字节。
financial.pb.go
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: financial.proto
package main
import (
fmt “fmt”
proto “github.com/golang/protobuf/proto”
math “math”
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Financial struct {
BankName string `protobuf:”bytes,1,opt,name=bankName,proto3″ json:”bankName,omitempty”`
IfscCode string `protobuf:”bytes,2,opt,name=ifscCode,proto3″ json:”ifscCode,omitempty”`
AccNumber string `protobuf:”bytes,3,opt,name=accNumber,proto3″ json:”accNumber,omitempty”`
CreatedDate string `protobuf:”bytes,4,opt,name=createdDate,proto3″ json:”createdDate,omitempty”`
XXX_NoUnkeyedLiteral struct{} `json:”-“`
XXX_unrecognized []byte `json:”-“`
XXX_sizecache int32 `json:”-“`
}
func (m *Financial) Reset() { *m = Financial{} }
func (m *Financial) String() string { return proto.CompactTextString(m) }
func (*Financial) ProtoMessage() {}
func (*Financial) Descriptor() ([]byte, []int) {
return fileDescriptor_a283ebe7677acfbc, []int{0}
}
func (m *Financial) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Financial.Unmarshal(m, b)
}
func (m *Financial) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Financial.Marshal(b, m, deterministic)
}
func (m *Financial) XXX_Merge(src proto.Message) {
xxx_messageInfo_Financial.Merge(m, src)
}
func (m *Financial) XXX_Size() int {
return xxx_messageInfo_Financial.Size(m)
}
func (m *Financial) XXX_DiscardUnknown() {
xxx_messageInfo_Financial.DiscardUnknown(m)
}
var xxx_messageInfo_Financial proto.InternalMessageInfo
func (m *Financial) GetBankName() string {
if m != nil {
return m.BankName
}
return “”
}
func (m *Financial) GetIfscCode() string {
if m != nil {
return m.IfscCode
}
return “”
}
func (m *Financial) GetAccNumber() string {
if m != nil {
return m.AccNumber
}
return “”
}
func (m *Financial) GetCreatedDate() string {
if m != nil {
return m.CreatedDate
}
return “”
}
func init() {
proto.RegisterType((*Financial)(nil), “main.Financial”)
}
func init() { proto.RegisterFile(“financial.proto”, fileDescriptor_a283ebe7677acfbc) }
var fileDescriptor_a283ebe7677acfbc = []byte{
// 136 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0xcb, 0xcc, 0x4b,
0xcc, 0x4b, 0xce, 0x4c, 0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x4d, 0xcc,
0xcc, 0x53, 0x6a, 0x66, 0xe4, 0xe2, 0x74, 0x83, 0xc9, 0x08, 0x49, 0x71, 0x71, 0x24, 0x25, 0xe6,
0x65, 0xfb, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xf9, 0x20, 0xb9,
0xcc, 0xb4, 0xe2, 0x64, 0xe7, 0xfc, 0x94, 0x54, 0x09, 0x26, 0x88, 0x1c, 0x8c, 0x2f, 0x24, 0xc3,
0xc5, 0x99, 0x98, 0x9c, 0xec, 0x57, 0x9a, 0x9b, 0x94, 0x5a, 0x24, 0xc1, 0x0c, 0x96, 0x44, 0x08,
0x08, 0x29, 0x70, 0x71, 0x27, 0x17, 0xa5, 0x26, 0x96, 0xa4, 0xa6, 0xb8, 0x24, 0x96, 0xa4, 0x4a,
0xb0, 0x80, 0xe5, 0x91, 0x85, 0x92, 0xd8, 0xc0, 0x4e, 0x32, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff,
0x44, 0x01, 0xf8, 0x14, 0xa5, 0x00, 0x00, 0x00,
}
现在,我们将 在 User smartcontract中创建一个额外的数据字段financial
type User struct {
ID string `json:”id”`
Email string `json:”email”`
Name string `json:”name”`
Mobile string `json:”mobile”`
Age string `json:”age”`
Financial string `json:”financial”`
}
财务信息格式参考
financial := &Financial {
ID: “F1”,
BankName: “Hellenic Bank”,
IFSCCode: “1234”,
AccNumber: “8765”,
CreatedDate : “12/12/08,
}
在将用户记录添加到分类账时,您还可以将财务信息格式引用添加到相同的用户SmartContract中。
package main
import (
“fmt”
“log”
“github.com/golang/protobuf/proto”
)
func main() {
financial := &Financial {
BankName: “Hellenic Bank”,
IFSCCode: “1234”,
AccNumber: “8765”,
CreatedDate : “12/12/08,
}
financialdata, err := proto.Marshal(financial)
if err != nil {
log.Fatal(“marshaling error: “, err)
}
userdata := &User {
ID: “1”,
Email: “[email protected]”,
Name: “James”,
Mobile: “8765432”,
Age: “34”,
Financial: string(financialdata),
}
userDataJSONasBytes, err := json.Marshal(userdata)
if err != nil {
return shim.Error(err.Error())
}
indexName := “id”
userNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{userdata.ID})
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(userNameIndexKey, userDataJSONasBytes)
if err != nil {
return shim.Error(err.Error())
}
}
解析Protobuf
解析protobuf数据非常简单,因为它以字节的形式存储记录,可以使用金融proto引用轻松地对其进行解组。
financialByteData, err := proto.Marshal(financialData)
if err != nil {
log.Fatal(“marshaling error: “, err)
}
//Parsing the financial byte data
financial := &Financial{}
err = proto.Unmarshal(financialByteData, financial)
if err != nil {
log.Fatal(“unmarshaling error: “, err)
}
fmt.Println(“BankName : “+financial.GetBankName())
fmt.Println(“IFSCCode : “+financial.GetIfscCode())
fmt.Println(“AccNumber : “+financial.GetAccNumber())
fmt.Println(“CreatedDate : “+financial.GetCreatedDate())
结论
Protobuf简化了数据处理和格式化。它使用特殊生成的源代码来构造数据,以便在相同的SmartContract中轻松地读写数据。
参考资料
1. https://developers.google.com/protocol-buffers
2. https://developers.google.com/protocol-buffers/docs/gotutorial