百度区块链引擎BBE

    链码开发及部署

    IDE环境准备

    本文采用Go语言开发Fabric链码,Golang IDE请读者自行准备。

    链码实现

    编写链码的Golang基础语法请读者自行阅读或参考完整源码,这里主要介绍便签板合约的主要实现。

    链码对象

    Golang实现的链码必须定义合约对象,对象定义为空即可,代码如下

    type SmartContract struct {
    }

    便签对象的实现

    便签板合约的实现关键就是定义一个便签在区块链上存储的数据格式。我们首先定义一个便签的结构体

    type Note struct {
    	Id		int		`json:"id"`
    	Title	string	`json:"title"`
    	Content	string	`json:"content"`
    }

    便签由三部分构成,string类型的标题,string类型的便签正文以及一个64位(也有可能为32位,与CPU位数有关)的有符号整数类型表示的便签ID。

    Init接口的实现

    在本合约中,并不需要初始化部分便签数据,所以Init接口直接返回成功,具体实现方法如下

    func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
    	return shim.Success(nil)
    }

    Invoke接口的实现

    Fabric链码的核心接口是invoke,这也是用户实现业务逻辑的地方。在本示例中,主要实现的业务逻辑包括新增便签、更新便签、获取所有的便签。以下就这三个业务进行详细说明。

    新增便签的实现

    新增便签需要构建一个Note对象,对象中的title和content由用户定义传入,因为本Dapp只是用于示例,所以Id由提交时的时间戳生成,实际生产环境中可以采用其他算法,保证Id的唯一性。Fabric的链上的数据必须为key-value格式,而且key必须为string类型,value必须为byte数组。在本合约中,我们使用Id作为key,所以在调用该方法时,传入的三个参数,Id、title、content,都为字符串类型。在构建Note对象时需要先将string类型的Id转换为int类型,并将Note对象转换为byte数组格式。 新增便签的函数如下

    func (s *SmartContract) insert(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    	if len(args) != 3 {
    		return shim.Error("Incorrect number of arguments. Expecting 3")
    	}
    
    	noteId, transErr := strconv.Atoi(args[0])
    	if transErr != nil {
    		fmt.Printf("Error trans args to get note id: %s", transErr)
    		return shim.Error("Incorrect type of arguments. Id expecting int")
    	}
    
    	var note = Note {
    		Id:	noteId,
    		Title: args[1],
    		Content: args[2],
    	}
    
    	carAsBytes, _ := json.Marshal(note)
    	err := APIstub.PutState(args[0], carAsBytes)
    	if err != nil {
    		return shim.Error(err.Error())
    	}
    
    	return shim.Success(nil)
    }

    更新便签的实现

    更新便签与新增便签稍有差别,需要先从给定的Note ID中找到note对象,如果对象不存在进行报错,如果对象存在再进行更新。 具体代码如下

    func (s *SmartContract) update(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    	if len(args) != 3 {
    		return shim.Error("Incorrect number of arguments. Expecting 3")
    	}
    
    	noteAsBytes, _ := APIstub.GetState(args[0])
    	if noteAsBytes == nil {
    		return shim.Error("Note not found.")
    	}
    	note := Note{}
    
    	json.Unmarshal(noteAsBytes, &note)
    	note.Title = args[1]
    	note.Content = args[2]
    
    	noteAsBytes, _ = json.Marshal(note)
    	err := APIstub.PutState(args[0], noteAsBytes)
    	if err != nil {
    		return shim.Error(err.Error())
    	}
    	return shim.Success(nil)
    }

    获取所有的便签

    Fabric链码提供的接口中支持遍历功能,通过指定start和endkey,以字典排序查询到相应记录,如果start和endkey都设置为空字符串,查询的是所有记录。与新增便签一样,查询出来的note对象也是以byte数组方式存储的,需要进行解码。之后以Id为key,以note对象为value构建Map,并将Map编码为byte数组返回。 具体的代码如下

    func (s *SmartContract) queryAll(APIstub shim.ChaincodeStubInterface) sc.Response {
    	resultsIterator, err := APIstub.GetStateByRange("", "")
    	if err != nil {
    		return shim.Error(err.Error())
    	}
    	defer resultsIterator.Close()
    
    	// buffer is a JSON array containing QueryResults
    	notes := map[int]Note{}
    
    	for resultsIterator.HasNext() {
    		queryResponse, err := resultsIterator.Next()
    		if err != nil {
    			return shim.Error(err.Error())
    		}
    
    		var tmpNote Note
    		noteId, keyTransErr := strconv.Atoi(queryResponse.Key)
    		if keyTransErr != nil {
    			fmt.Printf("Error trans note id: %s", keyTransErr)
    			continue
    		}
    		if transErr := json.Unmarshal(queryResponse.Value, &tmpNote); err != nil {
    			fmt.Printf("Error trans note: %s", transErr)
    			notes[noteId] = Note{}
    		} else {
    			notes[noteId] = tmpNote
    		}
    	}
    
    	result, _ := json.Marshal(notes)
    	return shim.Success(result)
    }

    链码完整代码

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"strconv"
    
    	"github.com/hyperledger/fabric/core/chaincode/shim"
    	sc "github.com/hyperledger/fabric/protos/peer"
    )
    
    // Define the Smart Contract structure
    type SmartContract struct {
    }
    
    type Note struct {
    	Id		int		`json:"id"`
    	Title	string	`json:"title"`
    	Content	string	`json:"content"`
    }
    
    
    func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
    	return shim.Success(nil)
    }
    
    func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
    	function, args := APIstub.GetFunctionAndParameters()
    	if function == "queryAll" {
    		return s.queryAll(APIstub)
    	} else if function == "insert" {
    		return s.insert(APIstub, args)
    	} else if function == "update" {
    		return s.update(APIstub, args)
    	}
    
    	return shim.Error("Invalid Smart Contract function name.")
    }
    
    func (s *SmartContract) queryAll(APIstub shim.ChaincodeStubInterface) sc.Response {
    	resultsIterator, err := APIstub.GetStateByRange("", "")
    	if err != nil {
    		return shim.Error(err.Error())
    	}
    	defer resultsIterator.Close()
    
    	// buffer is a JSON array containing QueryResults
    	notes := map[int]Note{}
    
    	for resultsIterator.HasNext() {
    		queryResponse, err := resultsIterator.Next()
    		if err != nil {
    			return shim.Error(err.Error())
    		}
    
    		var tmpNote Note
    		noteId, keyTransErr := strconv.Atoi(queryResponse.Key)
    		if keyTransErr != nil {
    			fmt.Printf("Error trans note id: %s", keyTransErr)
    			continue
    		}
    		if transErr := json.Unmarshal(queryResponse.Value, &tmpNote); err != nil {
    			fmt.Printf("Error trans note: %s", transErr)
    			notes[noteId] = Note{}
    		} else {
    			notes[noteId] = tmpNote
    		}
    	}
    
    	result, _ := json.Marshal(notes)
    	return shim.Success(result)
    }
    
    func (s *SmartContract) insert(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    
    	if len(args) != 3 {
    		return shim.Error("Incorrect number of arguments. Expecting 3")
    	}
    
    	noteId, transErr := strconv.Atoi(args[0])
    	if transErr != nil {
    		fmt.Printf("Error trans args to get note id: %s", transErr)
    		return shim.Error("Incorrect type of arguments. Id expecting int")
    	}
    
    	var note = Note {
    		Id:	noteId,
    		Title: args[1],
    		Content: args[2],
    	}
    
    	carAsBytes, _ := json.Marshal(note)
    	err := APIstub.PutState(args[0], carAsBytes)
    	if err != nil {
    		return shim.Error(err.Error())
    	}
    
    	return shim.Success(nil)
    }
    
    func (s *SmartContract) update(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    
    	if len(args) != 3 {
    		return shim.Error("Incorrect number of arguments. Expecting 3")
    	}
    
    	noteAsBytes, _ := APIstub.GetState(args[0])
    	if noteAsBytes == nil {
    		return shim.Error("Note not found.")
    	}
    	note := Note{}
    
    	json.Unmarshal(noteAsBytes, &note)
    	note.Title = args[1]
    	note.Content = args[2]
    
    	noteAsBytes, _ = json.Marshal(note)
    	err := APIstub.PutState(args[0], noteAsBytes)
    	if err != nil {
    		return shim.Error(err.Error())
    	}
    	return shim.Success(nil)
    }
    
    // The main function is only relevant in unit test mode. Only included here for completeness.
    func main() {
    	// Create a new Smart Contract
    	err := shim.Start(new(SmartContract))
    	if err != nil {
    		fmt.Printf("Error creating new Smart Contract: %s", err)
    	}
    }

    链码部署

    进入Fabric列表页,找到希望安装链码的网络,点击右侧的“通道管理”链接
    devf_fabric_5.png
    进入通道管理页面后,找到希望安装链码的通道,点击右侧的“链码管理”链接
    devf_fabric_6.png
    进入链码管理页面后点击左侧的“新增链码”按钮
    devf_fabric_7.png
    在弹出的链码配置框中填写链码配置信息(单个链码文件请置于文件夹下,将文件夹整体打包上传)
    devf_fabric_8.png
    点击确认后会显示已上传的链码列表
    devf_fabric_9.png

    链码安装

    已上传的链码可以进行安装,点击链码列表页中对应链码右侧的”安装“按钮,进行链码的安装,安装完成后链码状态会变为安装完成
    devf_fabric_10.png

    链码实例化

    安装成功的链码可以进行实例化,点击对应链码右侧的”实例化“按钮,进行链码的实例化,实例化成功后链码状态变为运行中,至此链码安装完成
    devf_fabric_11.png

    上一篇
    Dapp架构及概要设计
    下一篇
    Dapp应用层开发