存证溯源案例1.0整体规划
为了持续的进行信息的更新,以及对账本进行管理(写入交易,进行查询等),区块链网络引入了智能合约来实现对账本的访问和控制;智能合约在 Fabric 中可以简单的理解为链码,是区块链应用的业务逻辑。
本文分享如何使用 Go
语言和Fabric 2.0链码
开发fabric智能合约实战案例-存证溯源案例,借存证溯源案例来熟练fabric的智能合约开发基本用法,下图为本文大致内容。
一、fabric链码版本区别
本节简单介绍下fabric链码版本区别,详情区别请查看之前的技术文章:
Hyperledger fabric智能合约编写(一)
####1、导入包的不同
1.x导入的包为:
"[github.com/hyperledger/fabric/core/chaincode/shim](http://github.com/hyperledger/fabric/core/chaincode/shim)"
pb "[github.com/hyperledger/fabric/protos/peer](http://github.com/hyperledger/fabric/protos/peer)"
2.0导入的包为:
"[github.com/hyperledger/fabric-contract-api-go/contractapi](http://github.com/hyperledger/fabric-contract-api-go/contractapi)"
####2、方法结构不同
Fabric2.0链码中不需要invoke和init方法
####3、方法中调用形式参数类型、返回值不同
1.x方法为:
createCar1(stub shim.ChaincodeStubInterface, args []string) pb.Response { }
2.x方法为:
Create(ctx contractapi.TransactionContextInterface,key string,value string)error { }
二、初始化
初始化代码示例:
//设置存证溯源记录的结构
type Asset struct {
AppraisedValue int `json:"AppraisedValue"`
Color string `json:"Color"`
ID string `json:"ID"`
Owner string `json:"Owner"`
Size int `json:"Size"`
}
//初始化
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
assets := []Asset{
{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
}
for _, asset := range assets {
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
err = ctx.GetStub().PutState(asset.ID, assetJSON)
if err != nil {
return fmt.Errorf("failed to put to world state. %v", err)
}
}
return nil
}
三、账本操作
1、账本ledger与state数据库
fabric中的ledger分为两部分内容,一部分是基于文件的存储,基于文件的存储满足区块链不可篡改的特性,此种方式存储基本是采用Merkle Tree,整个存储的方式是只能追加,不能删除和修改。另一部分则是使用数据库进行存储此种方式在fabric中叫做world state,如leveldb、couchdb等K-V数据库(也叫状态数据库,state DB),使用此类数据库的优势是数据库只存储当前的最新值,便于业务的拓展,这样可以很快的查找到当前的值,而无需通过遍历的方式来查找。
无论是world state 还是文件存储都是分布式的存储,在多个节点里都存在副本的。通过共识保证数据在各个节点上的一致性。couchdb和leveldb都可以作为state的数据库,但是couchdb在处理json和富查询方面有着独特的的优势。fabric 此处可以支持插件方式的数据库,可以是关系型,图形数据库等,这样便于fabric扩展。
链码中,我们通常基于业务流程需要来操作state数据库作为账本操作,以下为账本操作基本内容。
2、创建Asset
创建Asset代码示例:
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if exists {
return fmt.Errorf("the asset %s already exists", id)
}
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
3、查询Asset
查询Asset代码示例:
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("the asset %s does not exist", id)
}
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
4、修改Asset
修改Asset代码示例:
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}
// overwriting original asset with new asset
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
5、删除Asset
删除Asset代码示例:
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}
return ctx.GetStub().DelState(id)
}
四、Asset溯源
Asset溯源代码示例:
//溯源查询结果结构体
type HistoryQueryResult struct {
Record *Asset `json:"record"`
TxId string `json:"txId"`
Timestamp time.Time `json:"timestamp"`
IsDelete bool `json:"isDelete"`
}
//Asset溯源结果查询
func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]HistoryQueryResult, error) {
log.Printf("GetAssetHistory: ID %v", assetID)
resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var records []HistoryQueryResult
for resultsIterator.HasNext() {
response, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset Asset
if len(response.Value) > 0 {
err = json.Unmarshal(response.Value, &asset)
if err != nil {
return nil, err
}
} else {
asset = Asset{
ID: assetID,
}
}
timestamp, err := ptypes.Timestamp(response.Timestamp)
if err != nil {
return nil, err
}
record := HistoryQueryResult{
TxId: response.TxId,
Timestamp: timestamp,
Record: &asset,
IsDelete: response.IsDelete,
}
records = append(records, record)
}
return records, nil
}