日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

HyperLedger Fabric链码开发及测试

發布時間:2025/3/21 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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链码开发及测试的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。