Chaincode教程
1. 概要
1.1 什么是Chaincode?
Chaincode是一个程序,用Go,node.js,java等编程语言编写,并实现了特定的接口(后面会详细介绍,分别为Init
和Invoke
)。Chaincode在一个安全的Docker容器中运行,该容器与背书peer进程隔离。Chaincode通过应用程序提交的事务来初始化和管理账本状态。
Chaincode通常处理区块链网络成员商定的业务逻辑,因此可以将其视为“智能合约”。由chaincode创建的状态仅限于该chaincode,不能由另一个chaincode直接访问。然而,在同一个区块链网络中,给定适当的权限,chaincode可以调用另一个chaincode来访问其状态。
1.2 两种角色
我们可以从两种不同的角色来认识chaincode。一个是从应用程序开发人员的角度出发,应用开发者会开发一个名为Chaincode for Developers的区块链应用程序/解决方案;另一个是面向区块链网络运维人员Chaincode for Operators,区块链网络运维人员负责管理区块链网络,并利用Hyperledger Fabric API来安装、实例化和升级chaincode,但很可能不会涉及chaincode应用程序的开发。
下面我们将分别从chaincode开发者和运维人员两方面对chaincode做一个较为详细的介绍,最后通过结合源码分析,加深对chaincode的理解。最后希望能帮助chaincode开发者能快速上手chaincode的开发,还有帮助chaincode运维人员能够保证chaincode能正常的运行。
2. chaincode开发者教程
2.1 chaincode API
每个chaincode程序必须实现Chaincode接口
:
Chaincode接口被调用以回应接收到的事务。特别是当chaincode接收instantiate
或upgrade
事务时,会调用Init
方法,以便chaincode可以执行任何必要的初始化,包括应用程序状态的初始化。Invoke
方法是为了响应接收调用事务来处理事务提案。PS:当通过命令行方式peer chaincode invoke XXX 就会调用指定chaincode重写的Invoke
方法。
chaincode “shim” API的另外一个接口是ChaincodeStubInterface
:
这个接口用于访问和修改区块账本,并在chaincode之间能够互相调用。
在本教程中,我们将通过实现一个用于管理简单“资产”的简单chiancode应用来描述如何使用这些API,所有API请参考文末的图1。如GetState
、PutState
、GetHistoryForKey
、CreateCompositeKey
、GetStateByPartialCompositeKey
、DelState
等等,通过这些API基本能完成传统关系型数据的增删改查操作。本教程会在最后分析这些接口具体是什么,怎么用,实现原理是什么。希望能帮助读者更加深入了解chaincode,并能根据自身业务场景选择合适的API,保证区块链有较高的存储速度时,空间效率也不会太低。
2.2 一个简单示例:“资产管理” chaincode
我们的应用程序是一个基本示例chaincode,用于在账本上创建资产(键值对)。
2.2.1 选择代码的位置
如果你之前没用过Go语言编程,你可能需要确保已安装Go编程语言并且已正确设置好GO的开发环境。
现在,你需要为chaincode应用程序创建一个目录作为$GOPATH/src/
的子目录。
为了简单起见,我们使用下面的命令:
mkdir -p $GOPATH/src/sacc && cd $GOPATH/src/sacc
现在,让我们创建即将补充代码的源文件:
touch sacc.go
2.2.2 基本框架
首先,我们从chaincode的基本框架开始。每个chaincode都一样,都会实现chaincode接口,即Init
和Invoke
函数。因此,让我们添加go import语句以获取chaincode的必要依赖代码(类似于java的import、c语言的#include)。我们将导入chaincode shim包和peer protobuf package。接下来,让我们添加一个结构SimpleAsset
作为chaincode shim函数的接收方。
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}
2.2.3 初始化chaincode
接下来,我们将实现Init
函数。
// Init is called during chaincode instantiation to initialize any data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
}
Note.请注意,chaincode升级也会调用此函数。在编写将升级现有chaincode的chiancode的时候,请确保是当地修改Init
函数。特别是,如果没有“迁移”或没有任何内容作为升级的一部分进行初始化,请提供一个空洞“Init”方法。
接下来,我们将使用ChaincodeStubInterface.GetStringArgs函数来获取Init
调用的参数并检查其有效性。在我们的例子中,我们将获取到一个键值对。
// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger's data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
// Get the args from the transaction proposal
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
}
再接下来,现在我们已经确定这个调用成功,我们将把初始状态存储到账本中。要完成这个存储动作,我们将调用函数ChaincodeStubInterface.PutState,并将key、value作为参数传入。假设一切顺利,返回一个指示初始化成功的peer.Response对象。
// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger's data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
// Get the args from the transaction proposal
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
// Set up any variables or assets here by calling stub.PutState()
// We store the key and the value on the ledger
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}
调用 chaincode
首先,我们添加Invoke
函数的签名。
// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The 'set'
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
}
3. chaincode运维者教程
4. 参考
图 1 chaincode api类图