前幾章已經分別把三臺虛擬機環境和配置文件準備好了,在啓動fabric網絡以前咱們要準備好寫好的chaincode。chaincode的開發通常是使用GO或者JAVA,而我選擇的是GO語言。先分析一下官方最典型的一個chaincode--fabcar,而後着重介紹一下shim.ChaincodeSubInterface,最後在貼上我本身的chaincode。html
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) } }
經過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) }
經過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) }
把對象轉換爲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) }
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()) }
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的分析
做爲chaincode中的利器shim.ChaincodeSubInterface提供了一系列API供開發者在編寫鏈碼時靈活選擇使用。
這些API可分爲四類:帳本狀態交互API、交易信息相關API、參數讀取API、其餘API,下面分別介紹。
chaincode須要將一些數據記錄在分佈式帳本中。須要記錄的數據稱爲狀態state,以鍵值對(key-value)的形式存儲。帳本狀態API能夠對帳本狀態進行操做,十分重要。方法的調用會更新交易提案的讀寫集和,在committer進行驗證時會在此執行,跟帳本狀態進行比對,這類API的大體功能以下:
交易信息相關API能夠獲取到與交易信息自身相關的數據。用戶對鏈碼的調用(初始化和升級時調用Init()方法,運行時調用Invoke()方法)過程當中會產生交易提案。這些API支持查詢當前交易提案的一些屬性,具體信息以下:
調用鏈碼時支持傳入若干參數,參數可經過API讀取。具體信息以下:
除了上面一些API之外還有一些輔助API,以下:
本函數功能爲存入一個帖子。
參數:
Post 爲JSON 格式數據,包含帖子的的基本信息。須將字段名做爲Key,字段的值做爲 Value。
返回值:
-1:操做不成功
某個正整數:操做成功,返回值表示新帖的ID
本函數功能爲更改一個已經存在的帖子。
參數:
Post 爲JSON 格式數據,包含帖子的的基本信息,其中帖子ID不可爲空。須將字段名做爲Key,字段的值做爲 Value。
返回值:
-1:操做不成功
1:操做成功
本函數功能爲查找帖子。
參數:
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格式的數組,數組中每一個元素爲一個帖子。
注:目前僅支持單屬性的查找。
本函數功能爲查找返回某個條件的帖子的數量。
參數的含義與函數QueryPost相同。
返回值:
-1:操做不成功
某個正整數:操做成功,返回值符合查詢條件的帖子的數量
在實現有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
可是模糊查詢功能暫時沒有實現。(下個版本會實現)
咱們將本身編寫符合業務邏輯的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) } }