鏈碼實戰(二)——資產交易平臺

鏈碼實戰(二)——資產交易平臺

本節代碼都是按照教學課程敲出來的,參考教學課程在文末!強烈安利

一、需求分析

資產:某個人擁有的某個可被轉讓的東西 房產、車輛

1、平臺功能

  • 用戶開戶&銷戶
  • 資產登記 資產上鍊 or 用戶綁定資產
  • 資產轉讓 資產所有權的變更
  • 查詢功能 用戶查詢、資產查詢、資產變更歷史查詢

2、業務實體

1)用戶(User)

  • 名字
  • 標識(身份證…)
  • 資產列表

2)資產(Asset)

  • 名字
  • 標識
  • 特殊屬性列表 (車輛:排量、品牌、座位數等)

3) 資產變更記錄(AssetHistory)

  • 資產標識
  • 資產的原始擁有者 (登記==null)
  • 資產變更後的擁有者

3、交互方法(Invoke方法)

1)用戶開戶(userRegister)

參數

  • 名字
  • 標識

2)用戶銷戶(userDestroy)

參數

  • 標識

3)資產登記(assetEnroll)

參數

  • 名字
  • 標識
  • 特殊屬性列表
  • 擁有者

4)資產轉讓(assetExchange)

參數

  • 擁有者
  • 資產標識
  • 受讓者

5)用戶查詢(queryUser)

參數

  • 標識

返回值

  • 用戶實體

6)資產查詢(queryAsset)

參數

  • 標識

返回值

  • 資產實體

7)資產的變更記錄(queryAssetHistor)

參數

  • 資產的標識

  • 記錄類型(登記/轉讓/全部)

返回值

  • 資產變更列表

二、鏈碼開發

其實,開發鏈碼不是想象中的那麼難,只需要知道各程序的調用接口和返回值,再加上簡單的go語言基礎就能開發出一套完整的鏈碼。 下面列出相關步驟:

1、包頭導入

package main

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

2、Invoke函數書寫

根據調用時傳入的參數選擇相應的函數進行處理即可,具體的函分別實現:

func (c *AssertsExchangeCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	funcName, args := stub.GetFunctionAndParameters()

	switch funcName {
	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", funcName))
	}

	// stub.SetEvent("name", []byte("data"))
}

3、userRegister()函數實現

其實各個函數的實現都是有套路的,基本套路都是一樣,下面以userRegister()爲例子重點說一下,其他的都只貼相關的代碼了。

  • 步驟一:檢驗參數個數是否合格
if len(args) != 2 {
    return shim.Error("not enough args")
}
  • 步驟二:驗證參數形式是否正確
name := args[0]
id := args[1]
if name == "" || id == "" {
    return shim.Error("invalid args")
}
  • 步驟三:驗證數據是否存在:應該存在存在 或者 不應該存在
//不應該存在的範例
userBytes, err != stub.GetState(constructUserKey(id))
if err == nil && len(userBytes) != 0 {
    return shim.Error(msg:"user already exist")
}
  • 步驟四:將數據寫入Ledger,這一部分一般都是每部分差距最大的地方,需要序列化和反序列化進行相關處理
user := &User{
	Name:   name,
	Id:     id,
	Assets: make([]string, 0),
}

// 序列化對象
userBytes, err := json.Marshal(user)
if err != nil {
	return shim.Error(fmt.Sprintf("marshal user error %s", err))
}

if err := stub.PutState(constructUserKey(id), userBytes); err != nil {
	return shim.Error(fmt.Sprintf("put user error %s", err))
}
  • 返回值
return shim.Success(nil)

好了,一個完整的用戶開戶函數已經完成,後邊的幾個函數都是按照這個套路來的,只是有些小步驟有一定的區別,後邊就寫在代碼中了。

4、userDestroy()函數實現

// 用戶銷戶
func userDestroy(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	// 套路1:檢查參數的個數
	if len(args) != 1 {
		return shim.Error("not enough args")
	}

	// 套路2:驗證參數的正確性
	id := args[0]
	if id == "" {
		return shim.Error("invalid args")
	}

	// 套路3:驗證數據是否存在 應該存在 or 不應該存在
	userBytes, err := stub.GetState(constructUserKey(id))
	if err != nil || len(userBytes) == 0 {
		return shim.Error("user not found")
	}

	// 套路4:寫入狀態
	if err := stub.DelState(constructUserKey(id)); err != nil {
		return shim.Error(fmt.Sprintf("delete user error: %s", err))
	}

	// 刪除用戶名下的資產
	user := new(User)
	//反序列化
	if err := json.Unmarshal(userBytes, user); 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)
}

5、assetEnrol()函數實現

// 資產登記
func assetEnroll(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	// 套路1:檢查參數的個數
	if len(args) != 4 {
		return shim.Error("not enough args")
	}

	// 套路2:驗證參數的正確性
	assetName := args[0]
	assetId := args[1]
	metadata := args[2]
	ownerId := args[3]
	if assetName == "" || assetId == "" || ownerId == "" {
		return shim.Error("invalid args")
	}

	// 套路3:驗證數據是否存在 應該存在 or 不應該存在
	// ownerId是否存在
	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")
	}

	// 套路4:寫入狀態
	// 1. 寫入資產對象 2. 更新用戶對象 3. 寫入資產變更記錄
	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(nil)
}

6、assetExchange()函數實現

// 資產轉讓
func assetExchange(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	// 套路1:檢查參數的個數
	if len(args) != 3 {
		return shim.Error("not enough args")
	}

	// 套路2:驗證參數的正確性
	ownerId := args[0]
	assetId := args[1]
	currentOwnerId := args[2]
	if ownerId == "" || assetId == "" || currentOwnerId == "" {
		return shim.Error("invalid args")
	}

	// 套路3:驗證數據是否存在 應該存在 or 不應該存在
	// ownerId 是否存在
	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")
	}

	// 套路4:寫入狀態
	// 1. 原是擁有者刪除資產id 2. 新擁有者加入資產id 3. 資產變更記錄。不操作資產實體
	// 刪除擁有者資產
	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 assert 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 assert history error: %s", err))
	}

	return shim.Success(nil)
}

7、queryUser()函數實現

// 用戶查詢
func queryUser(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	// 套路1:檢查參數的個數
	if len(args) != 1 {
		return shim.Error("not enough args")
	}

	// 套路2:驗證參數的正確性
	ownerId := args[0]
	if ownerId == "" {
		return shim.Error("invalid args")
	}

	// 套路3:驗證數據是否存在 應該存在 or 不應該存在
	userBytes, err := stub.GetState(constructUserKey(ownerId))
	if err != nil || len(userBytes) == 0 {
		return shim.Error("user not found")
	}

	return shim.Success(userBytes)
}

8、queryAsset()函數實現

// 資產查詢
func queryAsset(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	// 套路1:檢查參數的個數
	if len(args) != 1 {
		return shim.Error("not enough args")
	}

	// 套路2:驗證參數的正確性
	assetId := args[0] //別整成1了就會造成越界。
	if assetId == "" {
		return shim.Error("invalid args")
	}

	// 套路3:驗證數據是否存在 應該存在 or 不應該存在
	assetBytes, err := stub.GetState(constructAssetKey(assetId))
	if err != nil || len(assetBytes) == 0 {
		return shim.Error("asset not found")
	}

	return shim.Success(assetBytes)
}

9、queryAssetHistory()函數實現

// 資產變更歷史查詢
func queryAssetHistory(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	// 套路1:檢查參數的個數
	if len(args) != 2 && len(args) != 1 {
		return shim.Error("not enough args")
	}

	// 套路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))
	}

	// 套路3:驗證數據是否存在 應該存在 or 不應該存在
	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)
}

10、Init()相關

別忘了Init函數的實現,如果不想有功能上的實現直接返回忘了創建成功即可

func (c *AssertsExchangeCC) Init(stub shim.ChaincodeStubInterface) pb.Response {
	return shim.Success(nil)
}

11、main()函數相關

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

三、總結

瞭解相關go相關語法、有基本的編程開發功底、熟悉相關API的調用(可以不熟悉,但是要會查找)最後最重要的就是有套路。這樣就可以開發出一個鏈碼了。 上邊的代碼功能錯誤已經沒有了,剩下的只是邏輯錯誤了,需要我們將其安裝到Fabric上進行相關的測試。

四、參考鏈接

學習Hyperledger Fabric實戰聯盟鏈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章