搭建基於hyperledger fabric的聯盟社區(四) --chaincode開發

前幾章已經分別把三臺虛擬機環境和配置文件準備好了,在啓動fabric網絡以前咱們要準備好寫好的chaincode。chaincode的開發通常是使用GO或者JAVA,而我選擇的是GO語言。先分析一下官方最典型的一個chaincode--fabcar,而後着重介紹一下shim.ChaincodeSubInterface,最後在貼上我本身的chaincode。html

 

一.chaincode主要框架結構

1.1 引入了4個程序庫,用於格式化、處理字節、讀取和寫入JSON,以及字符串操做。2個Hyperledger Fabric 特定的智能合同庫。shim包提供了一些 API,以便chaincode與底層區塊鏈網絡交互來訪問狀態變量、交易上下文、調用方證書和屬性,並調用其餘chaincode和執行其餘操做。node

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/hyperledger/fabric/core/chaincode/shim"
	sc "github.com/hyperledger/fabric/protos/peer"
)

1.2 定義SmartContract結構體,而後在該struct上定義Init和Invoke兩個函數git

type SmartContract struct {
}

1.3 定義具備四個變量的汽車結構體,結構體標籤被編碼/json庫使用。github

type Car struct {
	Make   string `json:"make"`
	Model  string `json:"model"`
	Colour string `json:"colour"`
	Owner  string `json:"owner"`
}

1.4 當智能合同「fabcar」由區塊鏈網絡實例化時,Init方法被調用。在Go中經過給函數標明所屬類型,來給該類型定義方法,下面的 s *SmartContract即表示給SmartContract聲明瞭一個方法。在Init和Invoke的時候,都會傳入參數stub shim.ChaincodeStubInterface,這個參數提供的接口爲咱們編寫ChainCode的業務邏輯提供了大量的實用方法。假設一切順利,將返回一個表示初始化已經成功的sc.Response對象。web

func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
	return shim.Success(nil)
}

1.5 應用程序請求運行智能合約fabcar後,Invoke方法被調用。在Invoke的時候,由傳入的參數來決定咱們具體調用了哪一個方法,因此須要用GetFunctionAndParameters解析調用的時候傳入的參數。docker

func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {

	// 檢索請求的智能合約的函數和參數,GetFunctionAndParameters() (string, []string)將字符串數組的參數分爲兩部分,數組第一個字是Function,剩下的都是Parameter
	function, args := APIstub.GetFunctionAndParameters()
	// 找到合適的處理函數以便與帳本進行交互
	if function == "queryCar" {
		return s.queryCar(APIstub, args)
	} else if function == "initLedger" {
		return s.initLedger(APIstub)
	} else if function == "createCar" {
		return s.createCar(APIstub, args)
	} else if function == "queryAllCars" {
		return s.queryAllCars(APIstub)
	} else if function == "changeCarOwner" {
		return s.changeCarOwner(APIstub, args)
	}

	return shim.Error("Invalid Smart Contract function name.")
}

1.6 主函數僅與單元測試模式相關,任何GO程序的起點都是main函數,在這裏只是爲了完整性。數據庫

func main() {

	// 建立了新的智能合同,並向對等節點註冊它
	err := shim.Start(new(SmartContract))
	if err != nil {
		fmt.Printf("Error creating new Smart Contract: %s", err)
	}
}

 

二.對State DB (狀態數據庫)的增刪改查 

2.1 查詢某輛汽車

經過GetState(key string) ([]byte, error)查詢數據。由於咱們是Key Value數據庫,因此根據Key來對數據庫進行查詢,是一件很常見,很高效的操做,返回的數據是byte數組。json

func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 1")
	}

	carAsBytes, _ := APIstub.GetState(args[0])
	return shim.Success(carAsBytes)
}

2.2 初始化帳本

經過PutState(key string, value []byte) error 增改數據,對於State DB來講,增長和修改數據是統一的操做,由於State DB是一個Key Value數據庫,若是咱們指定的Key在數據庫中已經存在,那麼就是修改操做,若是Key不存在,那麼就是插入操做。對於實際的系統來講,咱們的Key多是單據編號,或者系統分配的自增ID+實體類型做爲前綴,而Value則是一個對象通過JSON序列號後的字符串。api

func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
	cars := []Car{
		Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
		Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
		Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
		Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
		Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
		Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
		Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
		Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
		Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
		Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
	}

	i := 0
	for i < len(cars) {
		fmt.Println("i is ", i)
		carAsBytes, _ := json.Marshal(cars[i])
		APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
		fmt.Println("Added", cars[i])
		i = i + 1
	}

	return shim.Success(nil)
}

2.3 建立汽車

把對象轉換爲JSON的方法(函數)爲 json.Marshal(),也就是說,這個函數接收任意類型的數據 v,並轉換爲字節數組類型,返回值就是咱們想要的JSON數據和一個錯誤代碼。當轉換成功的時候,這個錯誤代碼爲nil。數組

func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 5 {
		return shim.Error("Incorrect number of arguments. Expecting 5")
	}

	var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}

	carAsBytes, _ := json.Marshal(car)
	APIstub.PutState(args[0], carAsBytes)

	return shim.Success(nil)
}

2.4 查詢全部汽車

Key區間查詢GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) 提供了對某個區間的Key進行查詢的接口,適用於任何State DB。因爲返回的是一個StateQueryIteratorInterface(迭代器)接口,咱們須要經過這個接口再作一個for循環,才能讀取返回的信息,全部咱們能夠獨立出一個方法,專門將該接口返回的數據以string的byte數組形式返回。

func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

	startKey := "CAR0"
	endKey := "CAR999"

	resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
	if err != nil {
		return shim.Error(err.Error())
	}
   //defer關鍵字用來標記最後執行的Go語句,通常用在資源釋放、關閉鏈接等操做,會在函數關閉前調用。多個defer的定義與執行相似於棧的操做:先進後出,最早定義的最後執行。
	defer resultsIterator.Close()

	// buffer 是一個包含查詢結果的JSON數組,bytes.buffer是一個緩衝byte類型的緩衝器存放着都是byte,這樣直接定義一個 Buffer 變量,而不用初始化。
	var buffer bytes.Buffer
	buffer.WriteString("["]

	bArrayMemberAlreadyWritten := false
   //迭代器的兩個方法,hasNext:沒有指針下移操做,只是判斷是否存在下一個元素。next:指針下移,返回該指針所指向的元素。
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return shim.Error(err.Error())
		}
		// 在數組成員前加一個逗號
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		// Record 是一個JSON對象,因此咱們按原樣寫
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	fmt.Printf("- queryAllCars:\n%s\n", buffer.String())

	return shim.Success(buffer.Bytes())
}

2.5 改變汽車擁有人

Unmarshal是用於反序列化json的函數根據data將數據反序列化到傳入的對象中。

func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 2 {
		return shim.Error("Incorrect number of arguments. Expecting 2")
	}

	carAsBytes, _ := APIstub.GetState(args[0])
	car := Car{}

	json.Unmarshal(carAsBytes, &car)
	car.Owner = args[1]

	carAsBytes, _ = json.Marshal(car)
	APIstub.PutState(args[0], carAsBytes)

	return shim.Success(nil)
}

以上就是官方鏈代碼fabcar的分析  

 

三.鏈碼開發API

做爲chaincode中的利器shim.ChaincodeSubInterface提供了一系列API供開發者在編寫鏈碼時靈活選擇使用。

這些API可分爲四類:帳本狀態交互API、交易信息相關API、參數讀取API、其餘API,下面分別介紹。

3.1帳本狀態交互API

chaincode須要將一些數據記錄在分佈式帳本中。須要記錄的數據稱爲狀態state,以鍵值對(key-value)的形式存儲。帳本狀態API能夠對帳本狀態進行操做,十分重要。方法的調用會更新交易提案的讀寫集和,在committer進行驗證時會在此執行,跟帳本狀態進行比對,這類API的大體功能以下:

 

3.2交易信息相關API

交易信息相關API能夠獲取到與交易信息自身相關的數據。用戶對鏈碼的調用(初始化和升級時調用Init()方法,運行時調用Invoke()方法)過程當中會產生交易提案。這些API支持查詢當前交易提案的一些屬性,具體信息以下:

 

3.3 參數讀取API

調用鏈碼時支持傳入若干參數,參數可經過API讀取。具體信息以下:

 

3.4其餘API

除了上面一些API之外還有一些輔助API,以下:

 

 

四. 社區聯盟chaincode

4.1 業務功能需求

4.1.1 AddPost(Post)

本函數功能爲存入一個帖子。

參數: 

Post 爲JSON 格式數據,包含帖子的的基本信息。須將字段名做爲Key,字段的值做爲 Value。

返回值:

-1:操做不成功

某個正整數:操做成功,返回值表示新帖的ID

 

4.1.2 UpdatePost(Post)

本函數功能爲更改一個已經存在的帖子。

參數: 

Post 爲JSON 格式數據,包含帖子的的基本信息,其中帖子ID不可爲空。須將字段名做爲Key,字段的值做爲 Value。

返回值:

-1:操做不成功

1:操做成功

 

4.1.3 RichQueryPost (Attribute, Operator, Value)

本函數功能爲查找帖子。

參數: 

Attribute 表示帖子的屬性名稱,須符合附件一中的字段名。

Operator 表示比較運算符的代碼。以下所示 

0: =

1: >

2: >=

3: <

4: <=

5: between

6: like

Value 爲屬性的值。若Operator 爲0(等於),則本函數將返回對應屬性中等於指定Value的全部帖子;若Operator爲1~4,則Value 視爲單個值(單個數值或單個字符串);若Operator爲5,則Value須爲兩個值,中間以逗號分開;若Operator爲6,則本函數將返回對應屬性中包含指定Value的全部帖子。

返回值:

-1:操做不成功

JSON格式字符串:操做成功,全部符合條件的帖子將組織爲JSON格式的數組,數組中每一個元素爲一個帖子。

注:目前僅支持單屬性的查找。

 

4.1.4 GetPostNum(Attribute, Operator, Value)

本函數功能爲查找返回某個條件的帖子的數量。

參數的含義與函數QueryPost相同。

返回值:

-1:操做不成功

某個正整數:操做成功,返回值符合查詢條件的帖子的數量

 

 

 

4.2 編寫chaincode遇到的問題

在實現有transaction功能的函數時,在函數裏面寫一個返回值,並不能像查詢類型的函數同樣在用fabric SDK調用時獲得對應的返回值。addpost功能須要返回新帖子的ID,可是個人帖子id正好就是鍵值對的數量,我經過fabric node SDK 返回了鍵值對的數量,也就是返回了新帖子的ID。

RichQueryPost和GetPostNum兩個函數都會用到富查詢。富查詢的語法能夠參考couchdb官方文檔關於Selector語法部分的介紹:

http://docs.couchdb.org/en/2.0.0/api/database/find.html#find-selectors

https://github.com/cloudant/mango

可是模糊查詢功能暫時沒有實現。(下個版本會實現)

 

4.3 chaincode代碼

咱們將本身編寫符合業務邏輯的chaincode放在peer0.org1和peer0.org2的/go/src/github.com/hyperledger/fabric/examples/chaincode/go/community

目錄下,以後啓動docker容器的時候會自動掛載chaincode

cd ~/go/src/github.com/hyperledger/fabric/examples/chaincode/go/
mkdir community

 

完整chaincode代碼以下

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
    
	"github.com/hyperledger/fabric/core/chaincode/shim"
	sc "github.com/hyperledger/fabric/protos/peer"
)

type SmartContract struct {
}

type Post struct {
	Id   string `json:"id"`
	OriginalWebsite  string `json:"originalwebsite"`
	OriginalID string `json:"originalid"`
	Title  string `json:"title"`
	Content  string `json:"content"`
	AuthorId  string `json:"authorid"`
	PublishTime  string `json:"publishtime"`
	UpdateTime  string `json:"updatetime"`
	Category  string `json:"category"`
	SourceId  string `json:"sourceid"`
	Labels  string `json:"labels"`
	Follower_num  int `json:"follower_num"`
	Browse_num  int `json:"browse_num"`
	Star_num  int `json:"star_num"`
} 

type PostLength struct {
    Length int `json:"length"`
}


func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
	return shim.Success(nil)
}


func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
	
	function, args := APIstub.GetFunctionAndParameters()
	
	if function == "queryPost" {
		return s.queryPost(APIstub, args)
	} else if function == "initLedger" {
		return s.initLedger(APIstub)
	} else if function == "addPost" {
		return s.addPost(APIstub, args)
	} else if function == "updatePost" {
		return s.updatePost(APIstub, args)
	} else if function == "richQueryPosts" {
		return s.richQueryPosts(APIstub, args)
	} else if function == "getPostNum" {
		return s.getPostNum(APIstub, args)
	} 
	return shim.Error("Invalid Smart Contract function name.")
}

func (s *SmartContract) queryPost(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 1")
	}
	postAsBytes, _ := APIstub.GetState(args[0])
	return shim.Success(postAsBytes)
}

func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
	posts := []Post{
		Post{Id: "1", OriginalWebsite: "b", OriginalID: "c", Title: "d",Content:"e",AuthorId:"f",PublishTime:"g",UpdateTime:"h",Category:"i",SourceId:"j",Labels:"k",Follower_num:100,Browse_num:200,Star_num:300},
		Post{Id: "2", OriginalWebsite: "bb", OriginalID: "bb", Title: "dd",Content:"ee",AuthorId:"ff",PublishTime:"gg",UpdateTime:"hh",Category:"ii",SourceId:"jj",Labels:"kk",Follower_num:400,Browse_num:500,Star_num:600},	
	}
	length := PostLength{Length:len(posts)}
	lengthAsBytes,_ := json.Marshal(length)
	APIstub.PutState("POSTLENGTH",lengthAsBytes)

	i := 0
	for i < len(posts) {
		fmt.Println("i is ", i)
		postAsBytes, _ := json.Marshal(posts[i])
		APIstub.PutState("POST"+strconv.Itoa(i), postAsBytes)
		fmt.Println("Added", posts[i])
		i = i + 1
	}

	return shim.Success(nil)
}

func (s *SmartContract) addPost(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 13 {
		return shim.Error("Incorrect number of arguments. Expecting 13")
	} 
	args10,error := strconv.Atoi(args[10])
	args11,error := strconv.Atoi(args[11])
	args12,error := strconv.Atoi(args[12])

    if error != nil{
     fmt.Println("String conversion integer failed!")
    }
    lengthAsBytes, _ := APIstub.GetState("POSTLENGTH")
    length := PostLength{}
    json.Unmarshal(lengthAsBytes,&length)
    newlength := length.Length+1   
	var post = Post{Id: strconv.Itoa(newlength), OriginalWebsite: args[0], OriginalID: args[1], Title: args[2],Content:args[3],AuthorId:args[4],PublishTime:args[5],UpdateTime:args[6],Category:args[7],SourceId:args[8],Labels:args[9],Follower_num:args10,Browse_num:args11,Star_num:args12}
	postAsBytes, _ := json.Marshal(post)
	APIstub.PutState("POST"+strconv.Itoa(newlength), postAsBytes)
	length.Length = newlength
	lengthAsBytes,_ = json.Marshal(length)
	APIstub.PutState("POSTLENGTH",lengthAsBytes)
	return shim.Success(lengthAsBytes)
}


func (s *SmartContract) updatePost(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 14 {
		return shim.Error("Incorrect number of arguments. Expecting 14")
	}
	args11,error := strconv.Atoi(args[11])
	args12,error := strconv.Atoi(args[12])
	args13,error := strconv.Atoi(args[13])
	if error != nil{
     fmt.Println("String conversion integer failed!")
    }
	var post = Post{Id: args[0], OriginalWebsite: args[1], OriginalID: args[2], Title: args[3],Content:args[4],AuthorId:args[5],PublishTime:args[6],UpdateTime:args[7],Category:args[8],SourceId:args[9],Labels:args[10],Follower_num:args11,Browse_num:args12,Star_num:args13}
	postAsBytes, _ := json.Marshal(post)
	APIstub.PutState("POST"+args[0], postAsBytes)
	return shim.Success(nil)
}


func (s *SmartContract) richQueryPosts(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 3 {
		return shim.Error("Incorrect number of arguments. Expecting 3")
	}

	var queryString string
    
    if args[1] == "0" {
		queryString = fmt.Sprintf("{\"selector\":{\"%s\":\"%s\"}}", args[0],args[2])
	} else if args[1] == "1" {
	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$gt\":%s}}}", args[0],args[2])
	} else if args[1] == "2" {
	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$gte\":%s}}}", args[0],args[2])
	} else if args[1] == "3" {
	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$lt\":%s}}}", args[0],args[2])
	} else if args[1] == "4" {
	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$lte\":%s}}}", args[0],args[2])
	} else if args[1] == "5" {
	  between := strings.Split(args[2], ",")
	  queryString = fmt.Sprintf("{\"selector\":{\"$and\":[{\"%s\":{\"$gte\":%s}},{\"%s\":{\"$lte\":%s}}]}}", args[0],between[0],args[0],between[1])
	}  else {
		return shim.Error("Incorrect number of arguments. Expecting 0~5")
	}

	resultsIterator, err := APIstub.GetQueryResult(queryString)
	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultsIterator.Close()

	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return shim.Error(err.Error())
		}
		
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	fmt.Printf("- richQueryPosts:\n%s\n", buffer.String())

	return shim.Success(buffer.Bytes())
}


func  (s *SmartContract) getPostNum(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
     
    if len(args) != 3 {
	    return shim.Error("Incorrect number of arguments. Expecting 3")
	}
   var queryString string
    
    if args[1] == "0" {
		queryString = fmt.Sprintf("{\"selector\":{\"%s\":\"%s\"}}", args[0],args[2])
	} else if args[1] == "1" {
	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$gt\":%s}}}", args[0],args[2])
	} else if args[1] == "2" {
	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$gte\":%s}}}", args[0],args[2])
	} else if args[1] == "3" {
	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$lt\":%s}}}", args[0],args[2])
	} else if args[1] == "4" {
	  queryString = fmt.Sprintf("{\"selector\":{\"%s\":{\"$lte\":%s}}}", args[0],args[2])
	} else if args[1] == "5" {
	  between := strings.Split(args[2], ",")
	  queryString = fmt.Sprintf("{\"selector\":{\"$and\":[{\"%s\":{\"$gte\":%s}},{\"%s\":{\"$lte\":%s}}]}}", args[0],between[0],args[0],between[1])
	}  else {
		return shim.Error("Incorrect number of arguments. Expecting 0~5")
	}

	resultsIterator, err := APIstub.GetQueryResult(queryString)
	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultsIterator.Close()

	i := 0

	for resultsIterator.HasNext() {
	  resultsIterator.Next()
		
	  i = i + 1
		
	}
	

	fmt.Printf("- getPostNum:\n%s\n", strconv.Itoa(i))

	return shim.Success([]byte(strconv.Itoa(i)))
}


func main() {
	err := shim.Start(new(SmartContract))
	if err != nil {
		fmt.Printf("Error creating new Smart Contract: %s", err)
	}
}
相關文章
相關標籤/搜索