title: HyperLedger Fabric Chaincode(链码)介绍和使用
tags: 区块链,HyperLedger Fabric,Chaincode
链码是什么?
链码也称为智能合约,实质上是控制区块链网络中的不同实体或相关方如何相互交互或交易的业务逻辑。
链码是独立可运行的应用程序,运行在基于Docker的安全容器中,在启动的时候和背书节点建立gRPC连接, ,
可以抽象为以下几条:
- chaincode是Fabric接口的实现代码
- chaincode需要部署在Fabric区块链⽹络结点上
- 与Fabric区块链交互的唯⼀渠道
- chaincode是⽣成Transaction的唯⼀来源 Ledger <- Blocks <- TransacGons
- chaincode是智能合约在Fabric上的实现⽅式
Hyperledger 支持使用 Golang Java 、Node.js 等语言编写链码,链码最终在一个 Docker 容器内运行,本文将采用go编写链码。
相关概念
-
Blockchain(区块链):由一系列区块组成
- 每个区块包含许多交易。每个区块包含 World State 的哈希值,并被链接到前一个区块。
- 区块链采用仅附加模式 (append-only)
-
Transaction(交易) : 一次chaincode函数的运行
- 目前有五类,其中一个是 Undefined,其余均与 chaincode的执行有关。
- Transaction存储 chaincode执行的相关信息,比如 chaincodelD、函数名称、参数等,并不包含操作的数据。 -
Word State(全局状态) :Fabric区块链系统中所有变量的值的集合
- Transaction实际操作的是数据,交易商品的信息。每个 chaincode都有自己的数据。
- Fabric使用Rocksdb存储数据,一个 key-value数据库。
- Fabric将每一对 key-va|ue叫做一个 state,而所有的 chaincode的 state的合集就是Word State。
- 可用于存储序列化 JSON 结构
运⾏原理
Fabric结点有两种运⾏模式:
- ⼀般模式(默认模式)
- Chaincode运⾏在docker容器中
- 开发调试过程⾮常繁杂
部署 -> 调试 -> 修改 -> 创建docker镜像 -> 部署 -> …
⼀般模式,Chaincode是运行在docker容器中,如果需要修改、调试会很麻烦,每次都需要部署docker镜像,开发效率较低。
- 开发模式: --peer-chaincodedev
- Chaincode运⾏在本地
- 开发调试相对容易
部署 -> 调试 -> 修改 -> 部署 -> …
运行过程
-
Peer 收到来自链码容器的
ChaincodeMessage_REGISTER
消息,将其注册到本地的一个 Handler 结构,返回ChaincodeMessage_REGISTERED
消息发给链码容器。之后更新状态为established
,并发送ChaincodeMessage_READY
消息给链码侧,更新状态为ready
。 -
链码侧收到
ChaincodeMessage_REGISTERED
消息后,不进行任何操作,注册成功。更新状态为established
。收到ChaincodeMessage_READY
消息后更新状态为ready
。 -
Peer 侧发出
ChaincodeMessage_INIT
消息给链码容器,准备触发链码侧初始化操作。 -
链码容器收到
ChaincodeMessage_INIT
消息,通过Handler.handleInit()
方法进行进行初始化。初始化成功后,返回ChaincodeMessage_COMPLETED
消息给 Peer。此时,链码容器进入可被调用(Invoke
)状态。 -
链码被调用时,Peer 发出
ChaincodeMessage_TRANSACTION
消息给链码。 -
链码收到
ChaincodeMessage_TRANSACTION
消息,会调用Invoke()
方法,根据 Invoke 方法中用户实现的逻辑,消息给 Peer 侧。Peer 侧收到这些消息,进行相应的处理,并回复ChaincodeMessage_RESPONSE
消息。最后,链码侧会回复调用完成的消息ChaincodeMessage_COMPLETE
给 Peer 侧。 -
在上述过程中,Peer 和链码侧还会定期的发送
ChaincodeMessage_KEEPALIVE
消息给对方,以确保彼此在线。
使用链码
示例代码
在学习链码编写之前,我们来看一段go语言实现的一个简单例子
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
)
type SampleChaincode struct {
}
func (t *SampleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
return nil, nil
}
func (t *SampleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
return nil, nil
}
func (t *SampleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
return nil, nil
}
func main() {
err := shim.Start(new(SampleChaincode))
if err != nil {
fmt.Println("Could not start SampleChaincode")
} else {
fmt.Println("SampleChaincode successfully started")
}
}
代码分析
我们首先为我们的chaincode引入必要的依赖。我们将在此引入chaincode shim package和peer protobuf package。
import (
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
必须要存在这样的一个结构体,用来调用只能合约函数,结构体的名字没有要求,符合go语言的命名规则即可。
type SampleChaincode struct {
}
- Init 方法
在链代码首次部署到区块链网络时调用,将由部署自己的链代码实例的每个对等节点执行。此方法可用于任何与初始化、引导或设置相关的任务, ⼀般情况下仅被调⽤⼀次.
注意:值得留意的是chaincode升级同样会调用该函数。当我们编写的chaincode会升级现有chaincode时,需要确保适当修正Init函数
-
Query 方法
只要在区块链状态上执行任何读取/获取/查询操作,就会调用 Query 方法。根据链代码的复杂性,此方法含有你的读取/获取/查询逻辑,或者它可以外包给可从 Query 方法内调用的不同方法。
Query 方法不会更改底层链代码的状态,因此它不会在交易上下文内运行。如果尝试在 Query 方法内修改区块链的状态,将出现一个错误显示缺少交易上下文。另外,因为此方法仅用于读取区块链的状态,所以对它的调用不会记录在区块链上。 -
Invoke 方法
只要修改区块链的状态,就会调用 Invoke 方法。简言之,所有创建、更新和删除操作都应封装在 Invoke 方法内。因为此方法将修改区块链的状态,所以区块链 Fabric 代码会自动创建一个交易上下文,以便此方法在其中执行。对此方法的所有调用都会在区块链上记录为交易,这些交易最终被写入区块中。 -
使用shim.Start 启动链码
在main函数中shim.Start(new(SampleChaincode)) 启动了链码并向对等节点注册它
shim常用方法
这里只给出一些常用的方法说明,其他的方法和具体使用可以到官方文档查看。
https://godoc.org/github.com/hyperledger/fabric/core/chaincode/shim
方法的使用说明,请移步 :https://www.cnblogs.com/studyzy/p/7360733.html
1.获得调用的参数
Fabric提供了不同的参数传递函数
- GetArgs() [][]byte 以byte数组的数组的形式获得传入的参数列表
- GetStringArgs() []string 以字符串数组的形式获得传入的参数列表
- GetFunctionAndParameters() (string, []string) 将字符串数组的参数分为两部分,数组第一个字是Function,剩下的都是Parameter
- GetArgsSlice() ([]byte, error) 以byte切片的形式获得参数列表
2.增删改查State DB
对于ChainCode来说,核心的操作就是对State Database的增删改查,对此Fabric接口提供了3个对State DB的操作方法
- PutState(key string, value []byte) error 增加和修改数据
- DelState(key string) error 删除数据
- GetState(key string) ([]byte, error) 查询数据
注意 :不能在一个ChainCode函数中PutState后又马上GetState,这个时候GetState是没有最新值的,因为在这时Transaction并没有完成,还没有提交到StateDB里面
3.复合键的处理
- CreateCompositeKey(objectType string, attributes []string) (string, error) 生成复合键
- SplitCompositeKey(compositeKey string) (string, []string, error) 拆分复合键
- GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) 部分复合键的查询
- 获取当前用户
- GetCreator() ([]byte, error)
- 高级查询
前面提到的GetState只是最基本的根据Key查询值的操作,但是对于很多时候,我们需要查询返回的是一个集合,比如我要知道某个区间的Key对于所有对象,或者我们需要对Value对象内部的属性进行查询。
- GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) Key区间查询
- GetQueryResult(query string) (StateQueryIteratorInterface, error) 富查询
- GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) 历史数据查询
- GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) 部分复合键查询
6.调用另外的链上代码
这个比较好理解,就是在我们的链上代码中调用别人已经部署好的链上代码。
- InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response
7.获得提案对象Proposal属性
- GetSignedProposal() (*pb.SignedProposal, error) 获得签名的提案
- GetTransient() (map[string][]byte, error) 获得Transient对象
- GetTxTimestamp() (*timestamp.Timestamp, error) 获得交易时间戳
- GetBinding() ([]byte, error) 获得Binding对象
8.事件设置
- SetEvent(name string, payload []byte) error