链码实战(二)——资产交易平台
本节代码都是按照教学课程敲出来的,参考教学课程在文末!强烈安利
一、需求分析
资产:某个人拥有的某个可被转让的东西 房产、车辆
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上进行相关的测试。