链码开发及部署
IDE环境准备
本文采用Go语言开发Fabric链码,Golang IDE请读者自行准备。
链码实现
编写链码的Golang基础语法请读者自行阅读或参考完整源码,这里主要介绍便签板合约的主要实现。
链码对象
Golang实现的链码必须定义合约对象,对象定义为空即可,代码如下
1type SmartContract struct {
2}
便签对象的实现
便签板合约的实现关键就是定义一个便签在区块链上存储的数据格式。我们首先定义一个便签的结构体
1type Note struct {
2 Id int `json:"id"`
3 Title string `json:"title"`
4 Content string `json:"content"`
5}
便签由三部分构成,string类型的标题,string类型的便签正文以及一个64位(也有可能为32位,与CPU位数有关)的有符号整数类型表示的便签ID。
Init接口的实现
在本合约中,并不需要初始化部分便签数据,所以Init接口直接返回成功,具体实现方法如下
1func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
2 return shim.Success(nil)
3}
Invoke接口的实现
Fabric链码的核心接口是invoke,这也是用户实现业务逻辑的地方。在本示例中,主要实现的业务逻辑包括新增便签、更新便签、获取所有的便签。以下就这三个业务进行详细说明。
新增便签的实现
新增便签需要构建一个Note对象,对象中的title和content由用户定义传入,因为本Dapp只是用于示例,所以Id由提交时的时间戳生成,实际生产环境中可以采用其他算法,保证Id的唯一性。Fabric的链上的数据必须为key-value格式,而且key必须为string类型,value必须为byte数组。在本合约中,我们使用Id作为key,所以在调用该方法时,传入的三个参数,Id、title、content,都为字符串类型。在构建Note对象时需要先将string类型的Id转换为int类型,并将Note对象转换为byte数组格式。 新增便签的函数如下
1func (s *SmartContract) insert(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
2 if len(args) != 3 {
3 return shim.Error("Incorrect number of arguments. Expecting 3")
4 }
5
6 noteId, transErr := strconv.Atoi(args[0])
7 if transErr != nil {
8 fmt.Printf("Error trans args to get note id: %s", transErr)
9 return shim.Error("Incorrect type of arguments. Id expecting int")
10 }
11
12 var note = Note {
13 Id: noteId,
14 Title: args[1],
15 Content: args[2],
16 }
17
18 carAsBytes, _ := json.Marshal(note)
19 err := APIstub.PutState(args[0], carAsBytes)
20 if err != nil {
21 return shim.Error(err.Error())
22 }
23
24 return shim.Success(nil)
25}
更新便签的实现
更新便签与新增便签稍有差别,需要先从给定的Note ID中找到note对象,如果对象不存在进行报错,如果对象存在再进行更新。 具体代码如下
1func (s *SmartContract) update(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
2 if len(args) != 3 {
3 return shim.Error("Incorrect number of arguments. Expecting 3")
4 }
5
6 noteAsBytes, _ := APIstub.GetState(args[0])
7 if noteAsBytes == nil {
8 return shim.Error("Note not found.")
9 }
10 note := Note{}
11
12 json.Unmarshal(noteAsBytes, ¬e)
13 note.Title = args[1]
14 note.Content = args[2]
15
16 noteAsBytes, _ = json.Marshal(note)
17 err := APIstub.PutState(args[0], noteAsBytes)
18 if err != nil {
19 return shim.Error(err.Error())
20 }
21 return shim.Success(nil)
22}
获取所有的便签
Fabric链码提供的接口中支持遍历功能,通过指定start和endkey,以字典排序查询到相应记录,如果start和endkey都设置为空字符串,查询的是所有记录。与新增便签一样,查询出来的note对象也是以byte数组方式存储的,需要进行解码。之后以Id为key,以note对象为value构建Map,并将Map编码为byte数组返回。 具体的代码如下
1func (s *SmartContract) queryAll(APIstub shim.ChaincodeStubInterface) sc.Response {
2 resultsIterator, err := APIstub.GetStateByRange("", "")
3 if err != nil {
4 return shim.Error(err.Error())
5 }
6 defer resultsIterator.Close()
7
8 // buffer is a JSON array containing QueryResults
9 notes := map[int]Note{}
10
11 for resultsIterator.HasNext() {
12 queryResponse, err := resultsIterator.Next()
13 if err != nil {
14 return shim.Error(err.Error())
15 }
16
17 var tmpNote Note
18 noteId, keyTransErr := strconv.Atoi(queryResponse.Key)
19 if keyTransErr != nil {
20 fmt.Printf("Error trans note id: %s", keyTransErr)
21 continue
22 }
23 if transErr := json.Unmarshal(queryResponse.Value, &tmpNote); err != nil {
24 fmt.Printf("Error trans note: %s", transErr)
25 notes[noteId] = Note{}
26 } else {
27 notes[noteId] = tmpNote
28 }
29 }
30
31 result, _ := json.Marshal(notes)
32 return shim.Success(result)
33}
链码完整代码
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "strconv"
7
8 "github.com/hyperledger/fabric/core/chaincode/shim"
9 sc "github.com/hyperledger/fabric/protos/peer"
10)
11
12// Define the Smart Contract structure
13type SmartContract struct {
14}
15
16type Note struct {
17 Id int `json:"id"`
18 Title string `json:"title"`
19 Content string `json:"content"`
20}
21
22
23func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
24 return shim.Success(nil)
25}
26
27func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
28 function, args := APIstub.GetFunctionAndParameters()
29 if function == "queryAll" {
30 return s.queryAll(APIstub)
31 } else if function == "insert" {
32 return s.insert(APIstub, args)
33 } else if function == "update" {
34 return s.update(APIstub, args)
35 }
36
37 return shim.Error("Invalid Smart Contract function name.")
38}
39
40func (s *SmartContract) queryAll(APIstub shim.ChaincodeStubInterface) sc.Response {
41 resultsIterator, err := APIstub.GetStateByRange("", "")
42 if err != nil {
43 return shim.Error(err.Error())
44 }
45 defer resultsIterator.Close()
46
47 // buffer is a JSON array containing QueryResults
48 notes := map[int]Note{}
49
50 for resultsIterator.HasNext() {
51 queryResponse, err := resultsIterator.Next()
52 if err != nil {
53 return shim.Error(err.Error())
54 }
55
56 var tmpNote Note
57 noteId, keyTransErr := strconv.Atoi(queryResponse.Key)
58 if keyTransErr != nil {
59 fmt.Printf("Error trans note id: %s", keyTransErr)
60 continue
61 }
62 if transErr := json.Unmarshal(queryResponse.Value, &tmpNote); err != nil {
63 fmt.Printf("Error trans note: %s", transErr)
64 notes[noteId] = Note{}
65 } else {
66 notes[noteId] = tmpNote
67 }
68 }
69
70 result, _ := json.Marshal(notes)
71 return shim.Success(result)
72}
73
74func (s *SmartContract) insert(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
75
76 if len(args) != 3 {
77 return shim.Error("Incorrect number of arguments. Expecting 3")
78 }
79
80 noteId, transErr := strconv.Atoi(args[0])
81 if transErr != nil {
82 fmt.Printf("Error trans args to get note id: %s", transErr)
83 return shim.Error("Incorrect type of arguments. Id expecting int")
84 }
85
86 var note = Note {
87 Id: noteId,
88 Title: args[1],
89 Content: args[2],
90 }
91
92 carAsBytes, _ := json.Marshal(note)
93 err := APIstub.PutState(args[0], carAsBytes)
94 if err != nil {
95 return shim.Error(err.Error())
96 }
97
98 return shim.Success(nil)
99}
100
101func (s *SmartContract) update(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
102
103 if len(args) != 3 {
104 return shim.Error("Incorrect number of arguments. Expecting 3")
105 }
106
107 noteAsBytes, _ := APIstub.GetState(args[0])
108 if noteAsBytes == nil {
109 return shim.Error("Note not found.")
110 }
111 note := Note{}
112
113 json.Unmarshal(noteAsBytes, ¬e)
114 note.Title = args[1]
115 note.Content = args[2]
116
117 noteAsBytes, _ = json.Marshal(note)
118 err := APIstub.PutState(args[0], noteAsBytes)
119 if err != nil {
120 return shim.Error(err.Error())
121 }
122 return shim.Success(nil)
123}
124
125// The main function is only relevant in unit test mode. Only included here for completeness.
126func main() {
127 // Create a new Smart Contract
128 err := shim.Start(new(SmartContract))
129 if err != nil {
130 fmt.Printf("Error creating new Smart Contract: %s", err)
131 }
132}
链码部署
进入Fabric列表页,找到希望安装链码的网络,点击右侧的“通道管理”链接
进入通道管理页面后,找到希望安装链码的通道,点击右侧的“链码管理”链接
进入链码管理页面后点击左侧的“新增链码”按钮
在弹出的链码配置框中填写链码配置信息(单个链码文件请置于文件夹下,将文件夹整体打包上传)
点击确认后会显示已上传的链码列表
链码安装
已上传的链码可以进行安装,点击链码列表页中对应链码右侧的”安装“按钮,进行链码的安装,安装完成后链码状态会变为安装完成
链码实例化
安装成功的链码可以进行实例化,点击对应链码右侧的”实例化“按钮,进行链码的实例化,实例化成功后链码状态变为运行中,至此链码安装完成