Chaincode的开发环境和Hello world
Chaincode的开发环境和Hello world
使用GO lang对Chaincode进行开发
# 安装Golang的SDK
go get github.com/hyperledger/fabric-sdk-go
# 安装shim包
go get -u github.com/hyperledger/fabric/core/chaincode/shim
启动链码必须通过调用shim包中的Start函数实现,而Start函数被调用时需要传递一个类型为Chaincode的参数,这个Chaincode参数是一个接口类型,该接口中有两个重要的函数——Init和Invoke。
type Chaincode interface{
Init(stub ChaincodeStubInterface) peer.Response
Invoke(stub ChaincodeStubInterface) peer.Response
}
- Init: 在链码实例化或升级时被调用,完成初始化数据的工作。
- Invoke: 在更新或查询提案事务中的分类账本数据状态时被调用,因此响应调用或查询的业务实现逻辑都需要在此函数中编写实现。
在实际开发中,开发人员可以自行定义一个结构体,然后重写Chaincode接口的两个函数,并将两个函数指定为自定义结构体的成员方法
必要结构
package main
// 引入必要的包
import (
"fmt"
// shim包提供了链码与账本交互的中间层
// 链码通过shim.ChaincodeStub提供的相应函数来读取和修改账本的状态
"github.com/hyperledger/fabric/core/chiancode/shim"
// 链码被调用执行之后通过peer包中的Response来封装执行结果的响应信息
"github.com/hyperledger/fabric/protos/peer"
)
// 声明一个结构体
type SimpleChaincode struct {
}
// 为结构体添加Init函数
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
// 在该方法中实现链码初始化或升级时的处理逻辑
// 编写时可灵活使用stub中的API
}
// 为结构体添加Init函数
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 该方法中实现链码运行中被调用或查询时的处理逻辑
// 编写时可灵活使用stub中的API
}
// 主函数,需要调用shim.Start()方法
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Print("Error starting Simple chaincode: %s", err)
}
}
shim包中提供的API
参数解析API
- GetArgs()[][]byte: 返回调用链码时在交易提案中指定提供的被调用函数及参数列表
- GetArgsSlice() ([]byte, error): 返回调用链码时在交易提案中指定提供的参数列表
- GetFunctionAndParameters() (function string, Params[]string): 返回调用链码时在交易提案中指定提供的被调用函数名称及其参数列表
- GetStringArgs()[]string: 返回调用链码时指定提供的参数列表
账本数据状态操作API
- GetState(key string) ([]byte, error): 根据指定的key查询相应的数据状态
- PutState(key string, value[]byte) error: 根据指定的key,将对应的value保存在分类账本中
- DelState(key string) error: 根据指定的key将对应的数据状态删除
- GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error): 根据指定的开始key及结束key,查询范围内的所有数据状态。注意结束key对应的数据状态不包含在返回的结果集中。
- GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error): 根据指定的key查询所有的历史纪录信息。
- CreateCompositeKey(objectType string, attributes[]string) (string, error): 创建一个复合键。
- SplitCompositeKey(compositeKey string) (string, []string, error): 对指定的复合键进行分割。
- GetQueryResult(query string) (StateQueryIteratorInterface, error): 对(支持富查询功能的)状态数据库进行富查询
交易信息API
- GetTxID() string: 返回交易提案中指定的交易ID
- GetChannelID() string: 返回交易提案中指定的通道ID
- GetTxTimestamp() (*timestamp.Timestamp, error): 返回交易创建的时间戳,这个时间戳时Peer接收到交易的具体时间
- GetBinding() ([]byte, error): 返回交易的绑定信息,如一些临时信息,以避免重复性攻击
- GetSignedProposal() (*pb.SignedProposal, error): 返回与交易提案相关的签名身份信息
- GetCreator() ([]byte, error): 返回该交易提交者的身份信息
- GetTransient() (map[string][]byte, error): 返回交易中不会被写至账本中的一些临时信息
事件处理API
- SetEvent(name string, payload[]byte) error: 设置事件,包括事件名称及内容
对PrivateData操作的API
- GetPrivateData(collection string , key string) ([]byte, error): 根据指定的key,从指定的私有数据集中查询对应的私有数据
- PutPrivateData(collection string, key string, value[]byte) error: 将指定的key与value保存到私有数据集中
- DelPrivateData(collection string, key string) error: 根据指定的key从私有数据集中删除相应的数据
- GetPrivateDataByRange(collection string, startKey, endKey string) (State-QueryIteratorInterface, error): 根据指定的开始key与结束key查询范围(不包含结束key)内的私有数据
- GetPrivateDataByPartialCompositeKey(collection string, objectType string, keys[]string) (StateQueryIteratorInterface, error): 根据给定的部分组合键的集合,查询给定的私有状态
- GetPrivateDataQueryResult(collection string, query string) (StateQueryIteratorInterface, error): 根据指定的查询字符串执行富查询
链码开发 Hello world
先进入fabric-samples/chaincode目录下创建一个hello的目录
创建链码文件:vim hello.go
// hello.go
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
type HelloChaincode struct{}
// 实例化/升级链码时被自动调用
// -c '{"Args": ["Hello", "World"]}'
func (t *HelloChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
fmt.Println("开始实例化链码......")
// 获取参数
// args := stub.GetStringArgs()
_, args := stub.GetFunctionAndParameters()
// 判断参数长度是否为2个
if len(args) != 2 {
return shim.Error("指定了错误的参数个数")
}
fmt.Println("保存数据......")
// 通过调用PutState函数将数据保存在账本中
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error("保存数据时发生错误")
}
fmt.Println("实例化链码成功")
return shim.Success(nil)
}
// 对账本数据进行操作时被自动调用(query, invoke)
func (t *HelloChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 获取调用链码时传递的参数内容(包括要调用的函数名及参数)
fun, args := stub.GetFunctionAndParameters()
// 客户意图
if fun == "query" {
return query(stub, args)
}
return shim.Error("非法操作, 指定功能不能实现")
}
func query(stub shim.ChaincodeStubInterface, args []string) peer.Response {
// 检查传递的参数是否为1
if len(args) != 1 {
return shim.Error("指定的参数错误, 必须且只能指定相应的Key")
}
// 根据指定的Key调用GetState方法查询数据
result, err := stub.GetState(args[0])
if err != nil {
return shim.Error("根据指定的 " + args[0] + " 查询数据时发生错误")
}
if result == nil {
return shim.Error("根据指定的 " + args[0] + " 没有查询到相应的数据")
}
// 返回查询结果
return shim.Success(result)
}
func main() {
err := shim.Start(new(HelloChaincode))
if err != nil {
fmt.Printf("chaincode start failed: %v", err)
}
}
链码测试
先进入github.com/hyperledger/fabric-samples/chaincode-docker-devmode
目录,启动链码的Dev开发测试模式
先清空一下docker的环境,使用上一篇文章中的清空命令
设置下docker-compose-simple.yaml
文件,将yaml文件的tag改成自己使用镜像tag,所有用到的image都改一下,不然又要从docker-hub拉取镜像了
services:
orderer:
container_name: orderer
image: hyperledger/fabric-orderer:1.4 # 就是这里
environment:
- FABRIC_LOGGING_SPEC=debug
- ORDERER_GENERAL_LISTENADDRESS=orderer
- ORDERER_GENERAL_GENESISMETHOD=file
- ORDERER_GENERAL_GENESISFILE=orderer.block
- ORDERER_GENERAL_LOCALMSPID=DEFAULT
- ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp
- GRPC_TRACE=all=true,
- GRPC_VERBOSITY=debug
working_dir: /opt/gopath/src/github.com/hyperledger/fabric
command: orderer
volumes:
- ./msp:/etc/hyperledger/msp
- ./orderer.block:/etc/hyperledger/fabric/orderer.block
ports:
- 7050:7050
启动docker容器
docker-compose -f docker-compose-simple.yaml up -d
docker ps
看一下环境的状态
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
073c62bb15d5 hyperledger/fabric-tools:1.4 "/bin/bash -c ./scri…" 6 seconds ago Up 3 seconds cli
6e78e11d6b1f hyperledger/fabric-ccenv:1.4 "/bin/sh -c 'sleep 6…" 6 seconds ago Up 2 seconds chaincode
eeb2dd0e8509 hyperledger/fabric-peer:1.4 "peer node start --p…" 7 seconds ago Up 5 seconds 0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp peer
c9cdcf50b042 hyperledger/fabric-orderer:1.4 "orderer" 8 seconds ago Up 6 seconds 0.0.0.0:7050->7050/tcp orderer
这样就正常启动容器了
- Chaincode容器,用于链码环境
- CLI容器,用于与链码进行交互
接着进入chaincode容器
docker exec -it chaincode bash
# 进入chaincode容器后, 进入存有chaincode的目录编译链码
cd hello
go build
启动链码:
CORE_PEER_ADDRESS=peer:7052
CORE_CHAINCODE_ID_NAME=hellocc:0 ./hello
- CORE_PEER_ADDRESS: 用于指定Peer
- CORE_CHAINCODE_ID_NAME: 用于注册到Peer的链码
- hellocc: 指定链码名称
- 0: 指定链码初始版本号
- ./hello: 指定链码文件
CORE_PEER_ADDRESS=peer:7052CORE_PEER_ADDRESS=peer:7052中的7052端口指的是什么,为什么不是7051?
7052是用于指定链码的专用监听地址及端口号,而7051是Peer节点监听的网络端口
正常启动,显示如下内容:
root@6e78e11d6b1f:/opt/gopath/src/chaincode/hello# CORE_CHAINCODE_ID_NAME=hellocc:0 ./hello
2020-02-25 16:51:41.309 UTC [shim] setupChaincodeLogging -> INFO 001 Chaincode log level not provided; defaulting to: INFO
2020-02-25 16:51:41.310 UTC [shim] setupChaincodeLogging -> INFO 002 Chaincode (build level: ) starting up ...
新开一个terminal,进入cli容器:
docker exec -it cli bash
# 安装链码时指定的链码名称与版本号必须与在之前chaincode容器中注册的链码名称及版本号相同
peer chaincode install -p chaincodedev/chiancode/hello -n hellocc -v 0
实例化链码:
peer chaincode instantiate -n hellocc -v 0 -c '{"Args":["init","Hello","World"]}' -C myc
调用链码:
peer chaincode query -n hellocc -c '{"Args":["query","Hello"]}' -C myc
最后返回World
,调用成功