1. 程式人生 > >HyperLeger Fabric開發(十)——資產交易平臺例項

HyperLeger Fabric開發(十)——資產交易平臺例項

HyperLeger Fabric開發(十)——資產交易平臺例項

一、資產交易平臺需求分析

1、資產交易平臺的功能

A、使用者開戶、銷戶功能
B、資產登記,即資產上鍊,資產繫結到使用者
C、資產轉讓,即資產所有權的變更
D、查詢功能,包括使用者查詢、資產查詢、資產歷史變更查詢

2、業務實體

A、使用者,屬性包括名字、標識、資產列表、
B、資產,屬性包括名字、標識、特殊屬性列表
C、資產變更記錄,屬性包括資產標識、資產源所有者、資產變更後所有者

3、互動方法

A、使用者註冊,引數包括使用者名稱稱、使用者標識
B、銷戶,引數包括使用者標識
C、資產登記,引數包括資產名稱、資產標識、特殊屬性列表、資產所有者
D、資產轉讓,引數包括資產所有者、資產標識、資產受讓者
E、使用者查詢,引數包括使用者標識,返回使用者實體
F、資產查詢,引數包括資產標識,返回資產實體
G、資產變更記錄,引數包括資產標識,記錄型別(登記/轉讓/全部),返回資產變更記錄列表。

二、鏈碼開發

1、鏈碼編寫

鏈碼需要定義資產交易平臺定義的所有實體和互動方法,然後在Invoke介面中呼叫相應的互動方法。
在Fabric工程目錄AssetExchange/chaincode/assetexchange目錄下進行鏈碼開發,鏈碼AssetExchangeChainCode.go實現如下:

package main

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

// 使用者
type User struct {
   Name string `json:"name"`
   ID string `json:"id"`
   Assets []string `json:"assets"`
}
// 資產
type Asset struct {
   Name string `json:"name"`
   ID string `json:"id"`
   Metadata string `json:"metadata"`
}
// 資產變更記錄
type AssetHistory struct {
   AssetID string `json:"asset_id"`
   OriginOwnerID string `json:"origin_owner_id"`
   CurrentOwnerID string `json:"current_owner_id"`
}
// 原始使用者佔位符
const (
   originOwner = "originOwnerPlaceholder"
)

func constructUserKey(userId string)string{
   return fmt.Sprint("user_%s",userId)
}

func constructAssetKey(assetID string)string{
   return fmt.Sprint("asset_%s",assetID)
}
// 使用者註冊(開戶)
func userRegister(stub shim.ChaincodeStubInterface, args []string) peer.Response{
   // step 1:檢查引數個數
   if len(args) != 2{
      return shim.Error("Not enough args")
   }

   // step 2:驗證引數正確性
   name := args[0]
   id := args[1]
   if name == "" || id == ""{
      return shim.Error("Invalid args")
   }
   // step 3:驗證資料是否存在
   if userBytes, err := stub.GetState(constructUserKey(id));err != nil || len(userBytes) != 0{
      return shim.Error("User alreay exist")
   }
   // step 4: 寫入狀態
   user := User{
      Name:name,
      ID:id,
      Assets:make([]string,0),
   }
   // 序列化物件
   userBytes, err := json.Marshal(user)
   if err != nil{
      return shim.Error(fmt.Sprint("marshal user error %s",err))
   }
   err = stub.PutState(constructUserKey(id), userBytes)
   if err != nil {
      return shim.Error(fmt.Sprint("put user error %s", err))
   }
   return shim.Success(nil)
}

// 使用者登出
func userDestroy(stub shim.ChaincodeStubInterface,args []string)peer.Response{
   // step 1:檢查引數個數
   if len(args) != 1{
      return shim.Error("Not enough args")
   }

   // step 2:驗證引數正確性
   id := args[0]
   if id == ""{
      return shim.Error("Invalid args")
   }
   // step 3:驗證資料是否存在
   userBytes, err := stub.GetState(constructUserKey(id));
   if err != nil || len(userBytes) == 0{
      return shim.Error("User not found")
   }
   // step 4: 寫入狀態
   if err := stub.DelState(constructUserKey(id)); err != nil {
      return shim.Error(fmt.Sprintf("delete user error: %s", err))
   }
   // 刪除使用者名稱下的資產
   user := new(User)
   err = json.Unmarshal(userBytes,user)
   if err != nil{
      return shim.Error(fmt.Sprintf("unmarshal user error: %s", err))
   }
   for _,assetId := range user.Assets{
      if err := stub.DelState(constructAssetKey(assetId)); err != nil {
         return shim.Error(fmt.Sprintf("delete asset error: %s", err))
      }
   }

   return shim.Success(nil)
}

// 資產登記
func assetEnroll(stub shim.ChaincodeStubInterface,args []string)peer.Response{
   // step 1:檢查引數個數
   if len(args) != 4 {
      return shim.Error("Not enough args")
   }

   // step 2:驗證引數正確性
   assetName := args[0]
   assetId := args[1]
   metadata := args[2]
   ownerId := args[3]
   if assetName == "" || assetId == "" || ownerId == ""{
      return shim.Error("Invalid args")
   }
   // step 3:驗證資料是否存在
   userBytes, err := stub.GetState(constructUserKey(ownerId))
   if err != nil || len(userBytes) == 0{
      return shim.Error("User not found")
   }
   if assetBytes, err := stub.GetState(constructAssetKey(assetId)); err == nil && len(assetBytes) != 0 {
      return shim.Error("Asset already exist")
   }
   // step 4: 寫入狀態
   asset := &Asset{
      Name:     assetName,
      ID:       assetId,
      Metadata: metadata,
   }
   assetBytes, err := json.Marshal(asset)
   if err != nil {
      return shim.Error(fmt.Sprintf("marshal asset error: %s", err))
   }
   if err := stub.PutState(constructAssetKey(assetId), assetBytes); err != nil {
      return shim.Error(fmt.Sprintf("save asset error: %s", err))
   }

   user := new(User)
   // 反序列化user
   if err := json.Unmarshal(userBytes, user); err != nil {
      return shim.Error(fmt.Sprintf("unmarshal user error: %s", err))
   }
   user.Assets = append(user.Assets, assetId)
   // 序列化user
   userBytes, err = json.Marshal(user)
   if err != nil {
      return shim.Error(fmt.Sprintf("marshal user error: %s", err))
   }
   if err := stub.PutState(constructUserKey(user.ID), userBytes); err != nil {
      return shim.Error(fmt.Sprintf("update user error: %s", err))
   }

   // 資產變更歷史
   history := &AssetHistory{
      AssetID:        assetId,
      OriginOwnerID:  originOwner,
      CurrentOwnerID: ownerId,
   }
   historyBytes, err := json.Marshal(history)
   if err != nil {
      return shim.Error(fmt.Sprintf("marshal assert history error: %s", err))
   }

   historyKey, err := stub.CreateCompositeKey("history", []string{
      assetId,
      originOwner,
      ownerId,
   })
   if err != nil {
      return shim.Error(fmt.Sprintf("create key error: %s", err))
   }

   if err := stub.PutState(historyKey, historyBytes); err != nil {
      return shim.Error(fmt.Sprintf("save assert history error: %s", err))
   }

   return shim.Success(historyBytes)
}

// 資產轉讓
func assetExchange(stub shim.ChaincodeStubInterface,args []string)peer.Response{
   // step 1:檢查引數個數
   if len(args) != 3 {
      return shim.Error("Not enough args")
   }

   // step 2:驗證引數正確性
   ownerID := args[0]
   assetID := args[1]
   currentOwnerID := args[2]
   if ownerID == "" || assetID == "" || currentOwnerID == ""{
      return shim.Error("Invalid args")
   }
   // step 3:驗證資料是否存在
   originOwnerBytes, err := stub.GetState(constructUserKey(ownerID))
   if err != nil || len(originOwnerBytes) == 0 {
      return shim.Error("user not found")
   }

   currentOwnerBytes, err := stub.GetState(constructUserKey(currentOwnerID))
   if err != nil || len(currentOwnerBytes) == 0 {
      return shim.Error("user not found")
   }

   assetBytes, err := stub.GetState(constructAssetKey(assetID))
   if err != nil || len(assetBytes) == 0 {
      return shim.Error("asset not found")
   }

   // 校驗原始擁有者確實擁有當前變更的資產
   originOwner := new(User)
   // 反序列化user
   if err := json.Unmarshal(originOwnerBytes, originOwner); err != nil {
      return shim.Error(fmt.Sprintf("unmarshal user error: %s", err))
   }
   aidexist := false
   for _, aid := range originOwner.Assets {
      if aid == assetID {
         aidexist = true
         break
      }
   }
   if !aidexist {
      return shim.Error("asset owner not match")
   }
   // step 4: 寫入狀態
   assetIds := make([]string, 0)
   for _, aid := range originOwner.Assets {
      if aid == assetID {
         continue
      }

      assetIds = append(assetIds, aid)
   }
   originOwner.Assets = assetIds

   originOwnerBytes, err = json.Marshal(originOwner)
   if err != nil {
      return shim.Error(fmt.Sprintf("marshal user error: %s", err))
   }
   if err := stub.PutState(constructUserKey(ownerID), originOwnerBytes); err != nil {
      return shim.Error(fmt.Sprintf("update user error: %s", err))
   }

   // 當前擁有者插入資產id
   currentOwner := new(User)
   // 反序列化user
   if err := json.Unmarshal(currentOwnerBytes, currentOwner); err != nil {
      return shim.Error(fmt.Sprintf("unmarshal user error: %s", err))
   }
   currentOwner.Assets = append(currentOwner.Assets, assetID)

   currentOwnerBytes, err = json.Marshal(currentOwner)
   if err != nil {
      return shim.Error(fmt.Sprintf("marshal user error: %s", err))
   }
   if err := stub.PutState(constructUserKey(currentOwnerID), currentOwnerBytes); err != nil {
      return shim.Error(fmt.Sprintf("update user error: %s", err))
   }

   // 插入資產變更記錄
   history := &AssetHistory{
      AssetID:        assetID,
      OriginOwnerID:  ownerID,
      CurrentOwnerID: currentOwnerID,
   }
   historyBytes, err := json.Marshal(history)
   if err != nil {
      return shim.Error(fmt.Sprintf("marshal asset history error: %s", err))
   }

   historyKey, err := stub.CreateCompositeKey("history", []string{
      assetID,
      ownerID,
      currentOwnerID,
   })
   if err != nil {
      return shim.Error(fmt.Sprintf("create key error: %s", err))
   }

   if err := stub.PutState(historyKey, historyBytes); err != nil {
      return shim.Error(fmt.Sprintf("save asset history error: %s", err))
   }

   return shim.Success(nil)
}

// 使用者查詢
func queryUser(stub shim.ChaincodeStubInterface,args []string)peer.Response{
   // step 1:檢查引數個數
   if len(args) != 1 {
      return shim.Error("Not enough args")
   }

   // step 2:驗證引數正確性
   userID := args[0]
   if userID == ""{
      return shim.Error("Invalid args")
   }
   // step 3:驗證資料是否存在
   userBytes, err := stub.GetState(constructUserKey(userID))
   if err != nil || len(userBytes) == 0 {
      return shim.Error("user not found")
   }

   return shim.Success(userBytes)
}

// 資產查詢
func queryAsset(stub shim.ChaincodeStubInterface,args []string)peer.Response{
   // step 1:檢查引數個數
   if len(args) != 1 {
      return shim.Error("Not enough args")
   }

   // step 2:驗證引數正確性
   assetID := args[0]
   if assetID == ""{
      return shim.Error("Invalid args")
   }
   // step 3:驗證資料是否存在
   assetBytes, err := stub.GetState(constructAssetKey(assetID))
   if err != nil || len(assetBytes) == 0 {
      return shim.Error("asset not found")
   }

   return shim.Success(assetBytes)
}

// 資產交易記錄查詢
func queryAssetHistory(stub shim.ChaincodeStubInterface,args []string)peer.Response{
   // step 1:檢查引數個數
   if len(args) != 2 && len(args) != 1 {
      return shim.Error("Not enough args")
   }

   // step 2:驗證引數正確性
   assetID := args[0]
   if assetID == ""{
      return shim.Error("Invalid args")
   }
   queryType := "all"
   if len(args) == 2 {
      queryType = args[1]
   }

   if queryType != "all" && queryType != "enroll" && queryType != "exchange" {
      return shim.Error(fmt.Sprintf("queryType unknown %s", queryType))
   }
   // step 3:驗證資料是否存在
   assetBytes, err := stub.GetState(constructAssetKey(assetID))
   if err != nil || len(assetBytes) == 0 {
      return shim.Error("asset not found")
   }

   // 查詢相關資料
   keys := make([]string, 0)
   keys = append(keys, assetID)
   switch queryType {
   case "enroll":
      keys = append(keys, originOwner)
   case "exchange", "all": // 不新增任何附件key
   default:
      return shim.Error(fmt.Sprintf("unsupport queryType: %s", queryType))
   }
   result, err := stub.GetStateByPartialCompositeKey("history", keys)
   if err != nil {
      return shim.Error(fmt.Sprintf("query history error: %s", err))
   }
   defer result.Close()

   histories := make([]*AssetHistory, 0)
   for result.HasNext() {
      historyVal, err := result.Next()
      if err != nil {
         return shim.Error(fmt.Sprintf("query error: %s", err))
      }

      history := new(AssetHistory)
      if err := json.Unmarshal(historyVal.GetValue(), history); err != nil {
         return shim.Error(fmt.Sprintf("unmarshal error: %s", err))
      }

      // 過濾掉不是資產轉讓的記錄
      if queryType == "exchange" && history.OriginOwnerID == originOwner {
         continue
      }

      histories = append(histories, history)
   }

   historiesBytes, err := json.Marshal(histories)
   if err != nil {
      return shim.Error(fmt.Sprintf("marshal error: %s", err))
   }

   return shim.Success(historiesBytes)
}

type AssetExchangeChainCode struct {

}

func (t *AssetExchangeChainCode) Init(stub shim.ChaincodeStubInterface) peer.Response{

   return shim.Success(nil)
}

func (t *AssetExchangeChainCode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{
   functionName, args := stub.GetFunctionAndParameters()
   switch functionName {
   case "userRegister":
      return userRegister(stub,args)
   case "userDestroy":
      return userDestroy(stub,args)
   case "assetEnroll":
      return assetEnroll(stub,args)
   case "assetExchange":
      return assetExchange(stub,args)
   case "queryUser":
      return queryUser(stub,args)
   case "queryAsset":
      return queryAsset(stub,args)
   case "queryAssetHistory":
      return queryAssetHistory(stub,args)
   default:
      return shim.Error(fmt.Sprintf("unsupported function: %s", functionName))
   }
   return shim.Success(nil)
}

func main() {
   err := shim.Start(new(AssetExchangeChainCode))
   if err != nil {
      fmt.Printf("Error starting AssetExchange chaincode: %s", err)
   }
}

2、鏈碼編譯

在Fabric工程AssetExchange/chaincode/assetexchange目錄下編譯鏈碼:
go build -o assetexchange
編譯成功的關鍵在於確保Fabric依賴的第三方包能夠正確被從網路上下載到本地。由於部分第三方依賴包可能從google.golang.org下載,因此需要確保網路可以正確下載。

三、鏈碼部署

1、Fabric區塊鏈網路部署

進入Fabric工程AssetExchange/deploy
docker-compose up
啟動服務是否正確啟動,確保orderer、peer、cli節點都正確啟動。
docker ps
進入cli容器:
docker exec -it cli bash


進入/etc/hyperledger/channel-artifacts目錄:
cd /etc/hyperledger/channel-artifacts
建立業務通道:
peer channel create -o orderer.example.com:7050 -c assetchannel -f assetchannel.tx
在當前目錄下會生成assetchannel.block區塊
加入通道
peer channel join -b assetchannel.block
設定主節點
peer channel update -o orderer.example.com:7050 -c assetchannel -f Org1MSPanchors.tx

2、鏈碼安裝

安裝assetexchange鏈碼:
peer chaincode install -n assetexchange -v 1.0.0 -l golang -p github.com/chaincode/assetexchange

3、鏈碼例項化

例項化assetexchange鏈碼:
peer chaincode instantiate -o orderer.example.com:7050 -C assetchannel -n assetexchange -l golang -v 1.0.0 -c '{"Args":["user1","0"]}'

4、鏈碼互動

使用者註冊:
peer chaincode invoke -C assetchannel -n assetexchange -c '{"Args":["userRegister", "user1", "user1"]}'
資產登記:
peer chaincode invoke -C assetchannel -n assetexchange -c '{"Args":["assetEnroll", "asset1", "asset1", "metadata", "user1"]}'
使用者註冊:
peer chaincode invoke -C assetchannel -n assetexchange -c '{"Args":["userRegister", "user2", "user2"]}'
資產轉讓:
peer chaincode invoke -C assetchannel -n assetexchange -c '{"Args":["assetExchange", "user1", "asset1", "user2"]}'
使用者登出:
peer chaincode invoke -C assetchannel -n assetexchange -c '{"Args":["userDestroy", "user1"]}'

5、鏈碼查詢

使用者查詢:
peer chaincode query -C assetchannel -n assetexchange -c '{"Args":["queryUser", "user1"]}'
資產查詢:
peer chaincode query -C assetchannel -n assetexchange -c '{"Args":["queryAsset", "asset1"]}'
使用者查詢:
peer chaincode query -C assetchannel -n assetexchange -c '{"Args":["queryUser", "user2"]}'
資產交易記錄查詢:
peer chaincode query -C assetchannel -n assetexchange -c '{"Args":["queryAssetHistory", "asset1"]}'
peer chaincode query -C assetchannel -n assetexchange -c '{"Args":["queryAssetHistory", "asset1", "all"]}'

6、鏈碼升級

peer chaincode install -n assetexchange -v 1.0.1 -l golang -p github.com/chaincode/assetexchange
peer chaincode upgrade -C assetchannel -n assetexchange -v 1.0.1 -c '{"Args":[""]}'

四、Fabric區塊鏈外部服務

Fabric區塊鏈網路的外部服務根據應用場景分為三種:
A、智慧硬體,基於Socket提供外部服務,如光伏發電裝置。
B、遊戲、電商、社交,基於HTTP提供web服務。
C、企業內部,基於RPC(gRPC)提供外部服務。
Fabric區塊鏈網路的外部服務使用Fabric SDK進行開發。