HyperLedger Fabric链码开发及测试
HyperLedger Fabric链码开发及测试
1.链码开发
先设计一个简单的应用场景,假设有这样的业务需求:
可以添加学校,信息包括学校名称、学校ID;
添加该学校的学生,信息包括姓名,用户ID,所在学校ID,所在班级名称;
更新学生信息;
根据学生ID查询学生信息;
根据学生ID删除学生信息;
根据学校ID删除学校信息,包括该学校下的所有学生。
接下来开发满足这些需求的链码。关键代码如下
1.1 定义School,Student结构体
type StudentChaincode struct {
}
type Student struct {
UserId int `json:"user_id"` //学生id
Name string `json:"name"` //姓名
SchoolId string `json:"school_id"` //学校id
Class string `jsong:"class"` //班级名称
}
type School struct {
SchoolId string `json:"id"` //学校id
School string `json:"name"` //学校名称
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.2 部分业务需求的实现
1.2.1 添加学校
这里用到了shim.ChaincodeStubInterface的CreateCompositeKey,来创建联合主键。其实Fabric就是用U+0000来把各个联合主键的字段拼接起来,因为这个字符太特殊,所以很适合,对应的拆分联合主键的字段的方法是SplitCompositeKey。
func (t *StudentChaincode) initSchool(stub shim.ChaincodeStubInterface, args []string) pd.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2(school_id, school_name)")
}
schoolId := args[0]
schoolName := args[1]
school := &School{schoolId, schoolName}
//这里利用联合主键,使得查询school时,可以通过主键的“school”前缀找到所有school
schoolKey, err := stub.CreateCompositeKey("School", []string{"school", schoolId})
if err != nil {
return shim.Error(err.Error())
}
//结构体转json字符串
schoolJSONasBytes, err := json.Marshal(school)
if err != nil {
return shim.Error(err.Error())
}
//保存
err = stub.PutState(schoolKey, schoolJSONasBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(schoolJSONasBytes)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1.2.2 添加学生,需要检查所属学校是否已经存在。
抽出来的工具类方法querySchoolIds 中用到的GetStateByPartialCompositeKey 是对联合主键进行前缀匹配的查询
func (t *StudentChaincode) addStudent(stub shim.ChaincodeStubInterface, args []string) pd.Response {
st, err := studentByArgs(args)
if err != nil {
return shim.Error(err.Error())
}
useridAsString := strconv.Itoa(st.UserId)
//检查学校是否存在,不存在则添加失败
schools := querySchoolIds(stub)
if len(schools) > 0 {
for _, schoolId := range schools {
if schoolId == st.SchoolId {
goto SchoolExists;
}
}
fmt.Println("school " + st.SchoolId+ " does not exist")
return shim.Error("school " + st.SchoolId+ " does not exist")
} else {
fmt.Println("school " + st.SchoolId+ " does not exist")
return shim.Error("school " + st.SchoolId+ " does not exist")
}
SchoolExists:
//检查学生是否存在
studentAsBytes, err := stub.GetState(useridAsString)
if err != nil {
return shim.Error(err.Error())
} else if studentAsBytes != nil {
fmt.Println("This student already exists: " + useridAsString)
return shim.Error("This student already exists: " + useridAsString)
}
//结构体转json字符串
studentJSONasBytes, err := json.Marshal(st)
if err != nil {
return shim.Error(err.Error())
}
//保存
err = stub.PutState(useridAsString, studentJSONasBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(studentJSONasBytes)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//将参数构造成学生结构体
func studentByArgs(args []string) (*Student, error) {
if len(args) != 4 {
return nil, errors.New("Incorrect number of arguments. Expecting 4(name, userid, schoolid, classid)")
}
name := args[0]
userId, err := strconv.Atoi(args[1]) //字符串转换int
if err != nil {
return nil, errors.New("2rd argument must be a numeric string")
}
schoolId := args[2]
class := args[3]
st := &Student{userId, name, schoolId, class}
return st, nil
}
//获取所有创建的学校id
func querySchoolIds(stub shim.ChaincodeStubInterface) []string {
resultsIterator, err := stub.GetStateByPartialCompositeKey("School", []string{"school"})
if err != nil {
return nil
}
defer resultsIterator.Close()
scIds := make([]string,0)
for i := 0; resultsIterator.HasNext(); i++ {
responseRange, err := resultsIterator.Next()
if err != nil {
return nil
}
_, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key)
if err != nil {
return nil
}
returnedSchoolId := compositeKeyParts[1]
scIds = append(scIds, returnedSchoolId)
}
return scIds
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
1.2.3 删除学校,包括删除所有对应学生信息
删除学校下的所有学生时,需要根据根据学校ID找到匹配的所有学生。这里用到了富查询方法GetQueryResult ,可以查询value中匹配的项。但如果是LevelDB,那么是不支持,只有CouchDB时才能用这个方法。
func (t *StudentChaincode) deleteSchool(stub shim.ChaincodeStubInterface, args []string) pd.Response {
if len(args) < 1 {
return shim.Error("Incorrect number of arguments. Expecting 1(schoolid)")
}
schoolidAsString := args[0]
schoolKey, err := stub.CreateCompositeKey("School", []string{"school", schoolidAsString})
if err != nil {
return shim.Error(err.Error())
}
schoolAsBytes, err := stub.GetState(schoolKey)
if err != nil {
return shim.Error("Failed to get school:" + err.Error())
} else if schoolAsBytes == nil {
return shim.Error("School does not exist")
}
//删除学校
err = stub.DelState(schoolKey)
if err != nil {
return shim.Error("Failed to delete school:" + schoolidAsString + err.Error())
}
//删除学校下的所有学生
queryString := fmt.Sprintf("{\"selector\":{\"school_id\":\"%s\"}}", schoolidAsString)
resultsIterator, err := stub.GetQueryResult(queryString)//富查询,必须是CouchDB才行
if err != nil {
return shim.Error("Rich query failed")
}
defer resultsIterator.Close()
for i := 0; resultsIterator.HasNext(); i++ {
responseRange, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
err = stub.DelState(responseRange.Key)
if err != nil {
return shim.Error("Failed to delete student:" + responseRange.Key + err.Error())
}
}
return shim.Success(nil)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
1.2.4 删除学生
func (t *StudentChaincode) deleteStudent(stub shim.ChaincodeStubInterface, args []string) pd.Response {
if len(args) < 1 {
return shim.Error("Incorrect number of arguments. Expecting 1(userid)")
}
useridAsString := args[0]
studentAsBytes, err := stub.GetState(useridAsString)
if err != nil {
return shim.Error("Failed to get student:" + err.Error())
} else if studentAsBytes == nil {
return shim.Error("Student does not exist")
}
err = stub.DelState(useridAsString)
if err != nil {
return shim.Error("Failed to delete student:" + useridAsString + err.Error())
}
return shim.Success(nil)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.2.5 更新学生信息
对于State DB来说,增加和修改数据是统一的操作,因为State DB是一个Key Value数据库,如果我们指定的Key在数据库中已经存在,那么就是修改操作,如果Key不存在,那么就是插入操作。
func (t *StudentChaincode) updateStudent(stub shim.ChaincodeStubInterface, args []string) pd.Response {
st, err := studentByArgs(args)
if err != nil {
return shim.Error(err.Error())
}
useridAsString := strconv.Itoa(st.UserId)
//检查学校是否存在,不存在则添加失败
schools := querySchoolIds(stub)
if len(schools) > 0 {
for _, schoolId := range schools {
if schoolId == st.SchoolId {
goto SchoolExists;
}
}
fmt.Println("school " + st.SchoolId+ " does not exist")
return shim.Error("school " + st.SchoolId+ " does not exist")
} else {
fmt.Println("school " + st.SchoolId+ " does not exist")
return shim.Error("school " + st.SchoolId+ " does not exist")
}
SchoolExists:
//因为State DB是一个Key Value数据库,如果我们指定的Key在数据库中已经存在,那么就是修改操作,如果Key不存在,那么就是插入操作。
studentJSONasBytes, err := json.Marshal(st)
if err != nil {
return shim.Error(err.Error())
}
//保存
err = stub.PutState(useridAsString, studentJSONasBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(studentJSONasBytes)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1.2.6 链码的Init、Invoke实现
func (t *StudentChaincode) Init(stub shim.ChaincodeStubInterface) pd.Response {
return shim.Success(nil)
}
func (t *StudentChaincode) Invoke(stub shim.ChaincodeStubInterface) pd.Response {
fn, args := stub.GetFunctionAndParameters()
fmt.Println("invoke is running " + fn)
if fn == "initSchool" {
return t.initSchool(stub, args)
} else if fn == "addStudent" {
return t.addStudent(stub, args)
} else if fn == "queryStudentByID" {
return t.queryStudentByID(stub, args)
} else if fn == "deleteSchool" {
return t.deleteSchool(stub, args)
} else if fn == "updateStudent" {
return t.updateStudent(stub, args)
}
fmt.Println("invoke did not find func: " + fn)
return shim.Error("Received unknown function invocation")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
完整代码:https://github.com/mh4u/chaincode_demo
2.单元测试
在开发完链码后,我们并不需要在区块链环境中部署链码才能进行调试。可以利用shim.MockStub 来编写单元测试代码,直接在无网络的环境中debug。
先新建一个编写测试代码的go文件,如student_test.go。假如我们想测试前面的创建学习功能,可以这样码代码:
func TestInitSchool(t *testing.T) {
//模拟链码部署
scc := new(StudentChaincode)
stub := shim.NewMockStub("StudentChaincode", scc)
mockInit(t, stub, nil)
//调用链码
initSchool(t, stub, []string{"schoolId_A", "学校1"})
initSchool(t, stub, []string{"schoolId_B", "学校2"})
}
func mockInit(t *testing.T, stub *shim.MockStub, args [][]byte) {
res := stub.MockInit("1", args)
if res.Status != shim.OK {
fmt.Println("Init failed", string(res.Message))
t.FailNow()
}
}
func initSchool(t *testing.T, stub *shim.MockStub, args []string) {
res := stub.MockInvoke("1", [][]byte{[]byte("initSchool"), []byte(args[0]), []byte(args[1])})
if res.Status != shim.OK {
fmt.Println("InitSchool failed:", args[0], string(res.Message))
t.FailNow()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
完整代码:https://github.com/mh4u/chaincode_demo
3. 开发环境测试链码
经过单元测试后,我们还需要在区块链开发环境中跑链码进行测试。
3.1 下载fabric-samples
3.2 下载或拷贝二进制文件到fabric-samples目录下
3.3 将开发好的链码文件拷贝到fabric-samples/chaincode目录下
3.4 进入链码开发环境目录
cd fabric-samples/chaincode-docker-devmode
1
3.5 打开3个终端,每个都进入到chaincode-docker-devmode文件夹
终端 1 – 启动网络
$ docker-compose -f docker-compose-simple.yaml up
1
终端 2 – 编译和部署链码
$ docker exec -it chaincode bash
$ cd Student
$ go build
$ CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./Student
1
2
3
4
终端3 – 使用链码
启动cli
$ docker exec -it cli bash
1
安装、实例化链码
$ cd ../
$ peer chaincode install -p chaincodedev/chaincode/Student -n mycc -v 0
$ peer chaincode instantiate -n mycc -v 0 -c '{"Args":[]}' -C myc
1
2
3
调用链码
$ peer chaincode invoke -n mycc -c '{"Args":["initSchool", "schoolId_A", "学校A"]}' -C myc
$ peer chaincode invoke -n mycc -c '{"Args":["addStudent", "张三", "1", "schoolId_A", "classA"]}' -C myc
HyperLedger Fabric链码开发及测试相关推荐
- Hyperledger Fabric链码修改与测试(一)
在上一篇博客中我使用智能涡轮流量计采集了一些数据上传到Hyperledger的链码上,此前对链码的一些数据结构进行了修改,但是还存在一些小问题. https://blog.csdn.net/qq_43 ...
- HyperLeger Fabric开发(七)——HyperLeger Fabric链码开发
HyperLeger Fabric开发(七)--HyperLeger Fabric链码开发 一.链码开发模式 1.链码开发模式简介 Fabric的链码开发调试比较繁琐.在不使用链码开发模式的情况下,链 ...
- hyperledger fabric 实战开发——水产品溯源交易平台(二)
文章目录 前言 一.技术学习 1.Hyperledger fabric 1.1 流程 1.2 配置 1.3 范例解析并自写 1.3 算法实现 二.Web编写 前言 hyperledger fabric ...
- hyperledger fabric 2.3.3 测试网络
教程 一.注意事项 确保通道名称应用以下限制: 仅包含小写 ASCII 字母数字.点"."和破折号"-" 少于 250 个字符 以字母开头 二.hyperled ...
- Fabric链码开发——富查询
Fabric链码开发--富查询 常见的链码使用PutState和GetState就可以进行简单的增加和查找了,再不济就加入DelState.因为fabric底层都是KV数据库,所以这三个接口就可以进行 ...
- hyperledger fabric 实战开发——水产品溯源交易平台(一)
文章目录 前言 环境准备 水产品溯源交易平台设计 实现步骤 1. 模板获取 2. 模板修改 虚拟机优化(根据个人喜好选择) 前言 在万字解析--区块链hyperledger fabric2.2部署实战 ...
- Hyperledger Fabric Chaincode 开发
好了,进入正题.我今天分享的内容的题目是Fabric1.0 Chaincode介绍.除了介绍Chaincode程序编写.调试的基本方法之外,我还加入了一些有关Chaincode原理的内容,希望能够帮助 ...
- Hyperledger Fabric chaincode 开发(疑难解答)
Q&A Q1: 使用fabric release 1.2 进行golang chaincode开发时报错: ..\..\hyperledger\fabric\vendor\github.com ...
- Hyperledger Fabric 链码(1) 类型
1. 用户链码 由应用开发人员编写(Go/Java/JS语言) 基于区块链分布式账本的状态及处理逻辑运行在链码容器中, 通过Fabric提供的接口与账本平台进行交互. 2. 系统链码(ESCC) 负责 ...
最新文章
- linux的mysql本地yum安装_Linux下MySQL5.7.18 yum方式从卸载到安装过程图解
- alook浏览器_alook浏览器下载-Alook浏览器iOS版下载 苹果版v10.8-PC6苹果网
- 【万字长文】探讨可信构架之道
- Openlayer:学习笔记之Source和Layer
- 图数据库 Titan 快速入门
- win10状态栏点击没反应解决办法
- 单条知识:什么是平面束方程
- 某互联网大厂亿级大数据服务平台的建设和实践
- Unity Driven 属性(代码控制属性)使用示例
- 请求指纹认证授权秘钥使用
- H12-723题库-个人整理笔记
- 计算机在医疗设备中的应用,计算机在医疗设备管理中的应用
- Unity 瞄准镜实现
- POJ 1144 Network
- 下载 线代 薛威_考研线代李永乐真的首选吗?
- 高价NFT不利于流动性
- 红海厮杀的超融合 泽塔云竟用GPU云开辟一片蓝海
- 我是这样走进IBM的(转载)
- ThinkPhp学习笔记——创建数据数据库中的表单
- 【CS231n】斯坦福大学李飞飞视觉识别课程笔记(一):图像分类笔记(下)
热门文章
- mysql当前时间加一天_MySQL 的加锁处理,你都了解的一清二楚了吗?
- linux mysql 5.7 配置_linux下mysql5.7的安装配置
- unity保存运行时的操作_Unity运行时保存prefab的方法一则
- Markdown 中的常用 LaTex 数学公式
- deep$wide keras
- 朴素贝叶斯 python 实现
- 验证码的产生 python
- 数据库笔记——数据模型
- 深度学习核心技术精讲100篇(八十)-脏数据如何处理?置信学习解决方案
- Tableau可视化分析实战系列Tableau基础概念全解析 (一)-数据结构及字段