Hyperledger – Bắt đầu với Fabric hay Composer?
Người viết: Do Trung Kien Introduction Bạn mới bắt đầu với Hyperledger và bạn băn khoăn không biết nên bắt đầu với Hyperledger Fabric hay Hyperledger Composer ? Bài viết này sẽ cung cấp cho các bạn một cái nhìn tổng quan về các ưu nhược điểm của 2 cách tiếp cận. Hi vọng sẽ ...
Người viết: Do Trung Kien
Introduction
Bạn mới bắt đầu với Hyperledger và bạn băn khoăn không biết nên bắt đầu với Hyperledger Fabric hay Hyperledger Composer ? Bài viết này sẽ cung cấp cho các bạn một cái nhìn tổng quan về các ưu nhược điểm của 2 cách tiếp cận. Hi vọng sẽ giúp các bạn trong việc lựa chọn con đường phù hợp.
TL;DR
chọn Hyperledger Composer
Tổng quan Hyperledger
Cả Hyperledger Fabric và Hyperledger Composer đều là các project nằm trong hệ sinh thái Hyperledger được phát triển bởi tổ chức Linux Foundation.
- Hyperledger Fabric là một pluggable blockchain, nó cung cấp các một set các peer node với những quyền truy cập khác nhau để thao tác với một sổ cái chung.
- Hyperledger Composer là một set ở tầm cao hơn, sử dụng các API + tools để mô hình hóa, build, integrate và deploy blockchain network. Network này có thể được đóng gói và chạy bên trên Hyperledger Fabric.
Hay nói một cách đơn giản, Composer chạy trên Fabric. Composer cung cấp API bậc cao, và về bản chất, các API này vẫn gọi đến các API của Fabric.
Để có cái nhìn cụ thể hơn, ta sẽ đi vào so sánh việc code giữa Composer và Fabric thông qua một project sample của Hyperledger là Marble Network.
Recommend: sẽ tốt hơn nếu bạn có kiến thức cơ bản về blockchain, golang hay javascript.
Xem thêm Hyperledger là gì
Code với Fabric
Có thể bạn chưa biết: Trong Fabric cũng có khái niệm smart contract, gọi là chaincode
Các bạn có thể viết chaincode trong Fabric bằng Go hoặc bằng Nodejs, Java. Trong bài viết này ta sẽ dùng Go.
Một chaincode sẽ phải implement interface bao gồm 2 function: Init và Invoke.
Hàm Init sẽ được gọi mỗi khi chaincode được instantiate hoặc upgrade trong channel. Nó có dạng như sau:
1 2 3 |
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {} |
Hàm Invoke sẽ được gọi mỗi khi ta muốn query dữ liệu hoặc tạo transaction trong Fabric.
1 2 3 |
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {} |
Fabric không có phân biệt call (query) hay send (transaction) như trong Ethereum, mà tất cả đều là Invoke.
Ta sẽ phải tự check xem Invoke sẽ gọi hàm private nào thông qua tham số đưa vào function stub.GetFunctionAndParameters(), bằng if. Vâng, thủ công vô cùng.
Step 1: Data Model
Các blockchain nói chung đều lưu trữ dữ liệu tại một nơi gọi là sổ cái phân tán – distributed ledger.
Data model trong Go chaincode được định nghĩa dưới dạng Go struct, dưới dạng JSON data.
1 2 3 4 5 6 7 8 9 |
type marble struct { ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around Color string `json:"color"` Size int `json:"size"` Owner string `json:"owner"` } |
Step 2: Dispatching Incoming Calls
Mỗi khi client submit một transaction lên trên blockchain (trong Fabric ta sẽ dùng một node-sdk client), nó sẽ sử dụng một interface dạng RPC bất đồng bộ. RPC calls sẽ chuyển tiếp các transaction đó đến các hàm Go tương ứng để xử lý. Như trên ta cũng có nói, việc này đơn giản là xử lý một loạt các if trong Invoke:
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 |
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println("invoke is running " + function) // Handle different functions if function == "initMarble" { //create a new marble return t.initMarble(stub, args) } else if function == "transferMarble" { //change owner of a specific marble return t.transferMarble(stub, args) } else if function == "transferMarblesBasedOnColor" { //transfer all marbles of a certain color return t.transferMarblesBasedOnColor(stub, args) } else if function == "delete" { //delete a marble return t.delete(stub, args) } else if function == "readMarble" { //read a marble return t.readMarble(stub, args) } else if function == "queryMarblesByOwner" { //find marbles for owner X using rich query return t.queryMarblesByOwner(stub, args) } else if function == "queryMarbles" { //find marbles based on an ad hoc rich query return t.queryMarbles(stub, args) } else if function == "getHistoryForMarble" { //get history of values for a marble return t.getHistoryForMarble(stub, args) } else if function == "getMarblesByRange" { //get marbles based on range query return t.getMarblesByRange(stub, args) } fmt.Println("invoke did not find func: " + function) //error return shim.Error("Received unknown function invocation") } |
Sau đó trong mỗi function, Go code sẽ gọi methods trong shim để đọc hay ghi dữ liệu lên sổ cái chung.
shim chính là phần đứng giữa chaincode và sổ cái, nó sẽ làm các nhiệm vụ CRUD state, emit các events..
Ví dụ:
1 2 3 4 5 6 |
err = stub.DelState(colorNameIndexKey) colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name}) stub.PutState(colorNameIndexKey, value) valAsbytes, err := stub.GetState(name) //get the marble from chaincode state |
Ta sẽ đi sâu hơn vào detail của một function.
Step3: Validate arguments
Mỗi function chỉ nhận vào một mảng bao gồm tên function và các đối số. Vì thế việc thực hiện validate số lượng các đối số luôn là việc cần làm đầu tiên trong function.
1 2 3 4 5 6 7 8 |
func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { // 0 1 // "name", "bob" if len(args) < 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } |
Step4: Lookup Asset
Chaincode có thể tìm kiếm các asset trong mạng bằng id, và return một error nếu nó không tồn tại.
1 2 3 4 5 6 7 8 9 10 11 12 |
marbleName := args[0] newOwner := strings.ToLower(args[1]) fmt.Println("- start transferMarble ", marbleName, newOwner) marbleAsBytes, err := stub.GetState(marbleName) if err != nil { return shim.Error("Failed to get marble:" + err.Error()) } else if marbleAsBytes == nil { return shim.Error("Marble does not exist") } |
Step5: Deserialize data
Nếu asset mà bạn tìm kiếm ở bước 4 tồn tại, nó sẽ trả về dưới dạng raw data, tức bytes, ta sẽ phải tiến hành deserialize dữ liệu đó ra và convert nó trở lại dạng Go struct.
1 2 3 4 5 6 7 |
marbleToTransfer := marble{} err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse() if err != nil { return shim.Error(err.Error()) } |
Step 6: Update data in memory
Chaincode sẽ update state của struct thông qua dữ liệu params truyền vào
1 2 3 |
marbleToTransfer.Owner = newOwner //change the owner |
Step 7: Serialize data and persist
Cuối cùng, để update lại dữ liệu sổ cái (world-state data), ta sẽ phải serialize dữ liệu lại dưới dạng JSON bytes:
1 2 3 4 5 6 7 8 9 10 11 12 |
marbleJSONasBytes, _ := json.Marshal(marbleToTransfer) err = stub.PutState(marbleName, marbleJSONasBytes) //rewrite the marble if err != nil { return shim.Error(err.Error()) } fmt.Println("- end transferMarble (success)") return shim.Success(nil) |
Step 8: Content based query
Để query data trong ledger, ta sẽ có thể lấy trực tiếp bằng key-value, hoặc để tiện lợi hơn thì ta sẽ sử dụng JSON request.
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 48 49 |
queryString := fmt.Sprintf("{"selector":{"docType":"marble","owner":"%s"}}", owner) <snip> func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { fmt.Printf("- getQueryResultForQueryString queryString:
%s
", queryString) resultsIterator, err := stub.GetQueryResult(queryString) if err != nil { return nil, err } defer resultsIterator.Close() // buffer is a JSON array containing QueryRecords var buffer bytes.Buffer buffer.WriteString("[") bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { buffer.WriteString(",") } buffer.WriteString("{"Key":") buffer.WriteString(""") buffer.WriteString(queryResponse.Key) buffer.WriteString(""") buffer.WriteString(", "Record":") // Record is a JSON object, so we write as-is buffer.WriteString(string(queryResponse.Value)) buffer.WriteString("}") bArrayMemberAlreadyWritten = true } buffer.WriteString("]") fmt.Printf("- getQueryResultForQueryString queryResult:
%s
", buffer.String()) return buffer.Bytes(), nil } |
Step 9: Emitting events
Mỗi sự kiện xảy ra trên blockchain đều có thể bắn ra event được. Fabric đặt dữ liệu về event của nó ở trong Event Hub, theo đó phía client có thể subscribe và lắng nghe những event mà mình mong muốn.