Hyperledger fabric智能合约实战–存证溯源案例2.0

?

存证溯源案例2.0概览

本文分享如何使用 Go 语言和Fabric 2.0链码开发fabric智能合约实战案例-存证溯源案例,本篇主要介绍K-V数据库的ID一致性、范围查询的使用方法、K-V数据库范围查询的弊端以及解决方案。如下图所示:

在这里插入图片描述

1、K-V数据库的ID一致性

Hyperledger fabric默认采用LevelDB 作为peer 节点的状态数据库。因此,我们在链码中进行数据存储时,基于K-V数据库来进行设计。

在本存证溯源案例中,我们采用LevelDB数据库来作为存储溯源记录的组件,但作为键值数据库,存储的Key多为字符串,因此就产生了如何实现ID主键自增的问题。

我们设计了一个全局自增变量IdNum来作为signal,标记全局ID进行存储。

type AssetGlobal struct {

    IdNum int `json:"idNum"`

}

对初始化方法进行相应调整

func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
	key := "assetGlobal"
	exist, err := ctx.GetStub().GetState(key)
	if err != nil {
		return fmt.Errorf("can not find assetGlobal record, error :%v", err)
	}
	if exist != nil {
		return fmt.Errorf("assetGlobal exist, can not initLedger again.")
	}

	assetGlobalInstance := &AssetGlobal{
		IdNum: 0,
	}
	assetGlobalInstanceBytes, err := json.Marshal(assetGlobalInstance)
	if err != nil {
		return fmt.Errorf("json assetGlobalInstance error :%v", err)
	}

	err = ctx.GetStub().PutState(assetGlobalName, assetGlobalInstanceBytes)
	if err != nil {
		return fmt.Errorf("PutState assetGlobalInstance error:  %v", err)
	}
	return nil
}

相应的增删改方法也做出调整

func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, color string, size int, owner string, appraisedValue int) error {
	assetGlobalInstance, err := s.ReadAssetGlobal(ctx)
	if err != nil {
		return fmt.Errorf("get assetGlobalInstance error: %s", err)
	}

	var nextAssetID = assetGlobalInstance.IdNum + 1

	assetGlobalNewInstance := &AssetGlobal{
		IdNum: nextAssetID,
	}
	// id补零
	nextAssetIDStr := transIDToStr(nextAssetID)

	exists, err := s.AssetExists(ctx, nextAssetIDStr)
	if err != nil {
		return fmt.Errorf("failed to get asset: %v", err)
	}
	if exists {
		return fmt.Errorf("asset already exists: %s", nextAssetIDStr)
	}

	asset := &Asset{
		DocType:        "asset",
		ID:             nextAssetIDStr,
		Color:          color,
		Size:           size,
		Owner:          owner,
		AppraisedValue: appraisedValue,
	}

	assetBytes, err := json.Marshal(asset)
	if err != nil {
		return err
	}

	err = ctx.GetStub().PutState(nextAssetIDStr, assetBytes)
	if err != nil {
		return err
	}
	return nil
}

func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, assetID int, color string, size int, owner string, appraisedValue int) error {
	exists, err := s.AssetExists(ctx, transIDToStr(assetID))
	if err != nil {
		return fmt.Errorf("failed to get asset: %v", err)
	}
	if !exists {
		return fmt.Errorf("asset not exists: %d", assetID)
	}
	asset, err := s.ReadAsset(ctx, assetID)
	if err != nil {
		return err
	
	asset.Color = color
	asset.Size = size
	asset.Owner = owner
	asset.AppraisedValue = appraisedValue

	assetJSON, err := json.Marshal(asset)
	if err != nil {
		return fmt.Errorf("asset json marshal error:%s", err)
	}

	return ctx.GetStub().PutState(transIDToStr(assetID), assetJSON)
}

func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, assetID int) error {
	asset, err := s.ReadAsset(ctx, assetID)
	if err != nil {
		return fmt.Errorf("failed to get asset: %v", err)
	}
	if asset != nil {
		return fmt.Errorf("asset not exists: %d", assetID)
	}

	err = ctx.GetStub().DelState(transIDToStr(assetID))
	if err != nil {
		return fmt.Errorf("failed to delete asset %d: %v", assetID, err)
	}
}

2、范围查询的使用方法

增加了范围查询

func (s *SmartContract) GetAssetsByRangeLatest(ctx contractapi.TransactionContextInterface, pageSize, pageIndex int) ([]*Asset, error) {
	assetGlobalInstance, err := s.ReadAssetGlobal(ctx)
	if err != nil {
		return nil, fmt.Errorf("get assetGlobalInstance error: %s", err)
	}
	latestKey := assetGlobalInstance.IdNum
	var endKey int = latestKey - (pageIndex-1)*pageSize + 1
	var startKey int = endKey - pageSize
	startKeyStr := transIDToStr(startKey)
	endKeyStr := transIDToStr(endKey)
	resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKeyStr, endKeyStr, int32(pageSize), "")
	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	return constructQueryResponseFromIterator(resultsIterator)
}

3、K-V数据库范围查询的弊端与解决方案

在实际调用主键ID作为Key来范围查询时,我们发现当,id作为自增的key逐渐来存入数据库时,范围查询排序以字符串的排序逻辑进行排序,即:1,10,11,2等。

这与我们设想的ID自增排序不同,因此我们需对原有ID进行补零操作

func transIDToStr(assetID int) string {
	assetIDStr := strconv.Itoa(assetID)
	if len(assetIDStr) < idStringLong {
		size := idStringLong - len(assetIDStr)
		for i := 0; i < size; i++ {
			assetIDStr = "0" + assetIDStr
		}
	}
	return assetIDStr
}

?