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 mainimport ( "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":[""]}'