HyperLeger Fabric開發(十)——資產交易平臺實例
一、資產交易平臺需求分析
1、資產交易平臺的功能
A、用戶開戶、銷戶功能
B、資產登記,即資產上鏈,資產綁定到用戶
C、資產轉讓,即資產所有權的變更
D、查詢功能,包括用戶查詢、資產查詢、資產歷史變更查詢
2、業務實體
A、用戶,屬性包括名字、標識、資產列表、
B、資產,屬性包括名字、標識、特殊屬性列表
C、資產變更記錄,屬性包括資產標識、資產源所有者、資產變更後所有者
3、交互方法
A、用戶註冊,參數包括用戶名稱、用戶標識
B、銷戶,參數包括用戶標識
C、資產登記,參數包括資產名稱、資產標識、特殊屬性列表、資產所有者
D、資產轉讓,參數包括資產所有者、資產標識、資產受讓者
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/deploydocker-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開發(十)——資產交易平臺實例