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

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

插入 keys 用戶名 value erb 網絡部署 ready 擁有 composite

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進行開發。

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