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链码开发及测试相关推荐

  1. Hyperledger Fabric链码修改与测试(一)

    在上一篇博客中我使用智能涡轮流量计采集了一些数据上传到Hyperledger的链码上,此前对链码的一些数据结构进行了修改,但是还存在一些小问题. https://blog.csdn.net/qq_43 ...

  2. HyperLeger Fabric开发(七)——HyperLeger Fabric链码开发

    HyperLeger Fabric开发(七)--HyperLeger Fabric链码开发 一.链码开发模式 1.链码开发模式简介 Fabric的链码开发调试比较繁琐.在不使用链码开发模式的情况下,链 ...

  3. hyperledger fabric 实战开发——水产品溯源交易平台(二)

    文章目录 前言 一.技术学习 1.Hyperledger fabric 1.1 流程 1.2 配置 1.3 范例解析并自写 1.3 算法实现 二.Web编写 前言 hyperledger fabric ...

  4. hyperledger fabric 2.3.3 测试网络

    教程 一.注意事项 确保通道名称应用以下限制: 仅包含小写 ASCII 字母数字.点"."和破折号"-" 少于 250 个字符 以字母开头 二.hyperled ...

  5. Fabric链码开发——富查询

    Fabric链码开发--富查询 常见的链码使用PutState和GetState就可以进行简单的增加和查找了,再不济就加入DelState.因为fabric底层都是KV数据库,所以这三个接口就可以进行 ...

  6. hyperledger fabric 实战开发——水产品溯源交易平台(一)

    文章目录 前言 环境准备 水产品溯源交易平台设计 实现步骤 1. 模板获取 2. 模板修改 虚拟机优化(根据个人喜好选择) 前言 在万字解析--区块链hyperledger fabric2.2部署实战 ...

  7. Hyperledger Fabric Chaincode 开发

    好了,进入正题.我今天分享的内容的题目是Fabric1.0 Chaincode介绍.除了介绍Chaincode程序编写.调试的基本方法之外,我还加入了一些有关Chaincode原理的内容,希望能够帮助 ...

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

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

  9. Hyperledger Fabric 链码(1) 类型

    1. 用户链码 由应用开发人员编写(Go/Java/JS语言) 基于区块链分布式账本的状态及处理逻辑运行在链码容器中, 通过Fabric提供的接口与账本平台进行交互. 2. 系统链码(ESCC) 负责 ...

最新文章

  1. linux的mysql本地yum安装_Linux下MySQL5.7.18 yum方式从卸载到安装过程图解
  2. alook浏览器_alook浏览器下载-Alook浏览器iOS版下载 苹果版v10.8-PC6苹果网
  3. 【万字长文】探讨可信构架之道
  4. Openlayer:学习笔记之Source和Layer
  5. 图数据库 Titan 快速入门
  6. win10状态栏点击没反应解决办法
  7. 单条知识:什么是平面束方程
  8. 某互联网大厂亿级大数据服务平台的建设和实践
  9. Unity Driven 属性(代码控制属性)使用示例
  10. 请求指纹认证授权秘钥使用
  11. H12-723题库-个人整理笔记
  12. 计算机在医疗设备中的应用,计算机在医疗设备管理中的应用
  13. Unity 瞄准镜实现
  14. POJ 1144 Network
  15. 下载 线代 薛威_考研线代李永乐真的首选吗?
  16. 高价NFT不利于流动性
  17. 红海厮杀的超融合 泽塔云竟用GPU云开辟一片蓝海
  18. 我是这样走进IBM的(转载)
  19. ThinkPhp学习笔记——创建数据数据库中的表单
  20. 【CS231n】斯坦福大学李飞飞视觉识别课程笔记(一):图像分类笔记(下)

热门文章

  1. mysql当前时间加一天_MySQL 的加锁处理,你都了解的一清二楚了吗?
  2. linux mysql 5.7 配置_linux下mysql5.7的安装配置
  3. unity保存运行时的操作_Unity运行时保存prefab的方法一则
  4. Markdown 中的常用 LaTex 数学公式
  5. deep$wide keras
  6. 朴素贝叶斯 python 实现
  7. 验证码的产生 python
  8. 数据库笔记——数据模型
  9. 深度学习核心技术精讲100篇(八十)-脏数据如何处理?置信学习解决方案
  10. Tableau可视化分析实战系列Tableau基础概念全解析 (一)-数据结构及字段