Hyperledger fabric入门 day2
Hyperledger fabric入门 day2
Fabric链码规范
/*
simplechaincode可以自己进行命名
chaincode结构体是chaincode的主体结构。chaincode结构体需要实现Fabric提供 的接口"github.com/hyperledger/fabric/protos/peer",其中必须实现以下的 Init和Invoke方法
*/
type simplechiancode struct {
}
/*
系统初始化方法,在部署chaincode的过程中当执行命令的时候会调用该方法
peer chaincode instantiate -o orderer.example.com:7050 -C <通道名> -n <链码名称> -v <版本号> -c <传入参数> -P <策略>
*/
func (t *simplechaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
// 获取参数
_, args := stub.GetFunctionAndParameters()
}
/*
主业务逻辑,在执行命令的时候会调用该方法并传入相关的参数,注意“invoke”之后的参数是需要传入的参数
peer chaincode invoke -o 192.168.23.212:7050 -C <通道名> -n <链码名称> -c <传入参数>
*/
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
}
func main() {
err := shim.Start(new(simplechiancode))
if err != nil {
fmt.Printf("error: %s", err)
}
}
链码的安装和调用
在Chaincode安装之前首先要确保已经成功编译安装了Fabric的相关子模块,并且要保证已经正确启动Orderer节点和Peer节点
在部署前查看当前Peer节点已经加入了哪些Channel
export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp
peer channel list
部署
export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp
peer chaincode install -n <链码名称> -v <版本号> -p <链码所在目录>
实例化
export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp
peer chaincode instantiate -o orderer.example.com:7050 -C <通道名称> -n <链码名称> -v <版本号> -c <输入参数> -P <背书策略>
Chaincode实例化成功之后运行Chaincode的Docker容器会被启动,这个时候执行命令docker ps,会发现一个包含chaincode名字的进程以及被运行。Chaincode如果被成功实例化,在当前的Peer节点被重新启动之后,已经被实例化的Chaincode不会自动重新启动,这个时候如果客户端对Chaincode发起请求(比如请求query方法),系统会自动运行Chaincode的Docker进程。
还有一点需要注意,如果Chaincode某个方法发生异常导致Docker容器关闭,若此时客户端重新对Chaincode发起访问请求,只要请求的方法没有异常,系统会自动启动Chaincode的容器。
关于Chaincode的示例还有一点需要注意,如果在一个channel中已经加入了多个peer节点,并且这些peer节点需要安装相同的chaincode。这个时候只需要在第一个部署Chaincode的Peer节点中执行peer chaincode instantiate,其余的Peer节点部署Chaincode的时候只需要执行peer chaincode install命令,然后执行invoke或者query命令,系统会自动启动Chaincode相关的Docker镜像。当需要在多个Peer节点部署同一个Chaincode的时候instantiate命令只需要执行一次。
调用
export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp
peer chaincode invoke -o orderer.example.com:7050 -C <通道名> -n <链码名> -c <输入参数>
shim包的核心方法
shim包主要负责和客户端进行通信。shim提供了一组核心方法和客户端进行交互,这些方法如下所示
Success
Success方法负责将正确的消息返回给调用Chaincode的客户端,Success方法的定义和调用示例如下:
// 方法定义
func Success(payload []byte) peer.Response
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChiancodeStubInterface) peer.Response {
return shim.Success([]byte("success invoke"))
}
Error
Error方法负责将错误的信息返回给调用Chaincode的客户端,Error方法的定义和调用示例如下:
// 方法定义
func Error(msg string) peer.Response
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
shim.Error(error_str)
}
LogLevel
LogLevel方法负责修改Chaincode中运行日志的级别,LogLevel方法的定义个调用示例如下:
// 方法定义
func LogLevel(levelString string) (LoggingLevel, error)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
loglevel, _ := shim.Loglevel("debug")
shim.SetLoggingLevel(loglevel)
return shim.Success([]byte("success invoke"))
}
shim包中ChaincodeStubInterface接口的核心方法
ChaincodeStubInterface接口的核心方法大概可以分四个大类:系统管理、存储管理、交易管理、调用外部chaincode。
ChaincodeStubInterface接口的系统管理相关方法
GetFunctionAndParameters:该方法负责接收调用Chaincode的客户端传递过来的参数,它的定义和调用示例:
// 方法定义
GetFunctionAndParameters() (string, []string)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
_, args := stub.GetFunctionAndParameters()
var a_parm = args[0]
var b_parm = args[1]
var c_parm = args[2]
}
-c '{"Args":["invoke", "set", "abc", "def"]}'
根据Invoke子命令的格式定义,Args不是参数而是格式关键字,后面的参数数组中第一个是方法名,后面三个是参数
ChaincodeStubInterface接口的存储管理相关方法
PutState:PutState方法负责把客户端传递过来的数据保存到Fabric中,它的定义和调用示例如下:
// 方法定义
PutState(key string, value []byte) error
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
stub.PutState(b_parm, []byte("putvalue"))
return shim.Success([]byte("Success invoke"))
}
GetState:GetState方法负责从Fabric中取出数据,然后把这些数据交给Chiancode处理,它的定义和调用示例如下:
// 方法定义
GetState(key string) ([]byte, error)
// 代码示例
func (t *simplechaincode) Invoke(stub *shim.ChaincodeStubInterface) peer.Response {
var keyvalue []byte
var err error
keyvalue, err = stub.GetState("getkey")
if (err != nil) {
return shim.Error("error")
}
return shim.Success(keyvalue)
}
GetStateByRange:GetStateByRange方法根据key的范围来查询相关的数据。它的定义和调用示例如下:
// 方法定义
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
startkey := "startkey"
endkey := "endkey"
keysIter, err := stub.GetStateByRange(startkey, endkey)
if err != nil {
return shim.Error(fmt.Sprintf("GetStateByRange find err: %S", err))
}
defer keyIter.Close()
var keys []string
for keysIter.HasNext(){
response, iterErr := keyIter.Next()
if iterErr != nil {
return shim.Error(fmt.Sprintf("find an error %s", iterErr))
}
keys = append(keys, response.Key)
}
for key, value := range keys {
fmt.Printf("key %d contains %s\n", key, value)
}
jsonKeys, err := json.Marshal(keys)
if err != nil {
return shim.Error(fmt.Sprintf("find error on Marshaling JSON: %S", err))
}
return shim.Success(jsonKeys)
}
GetHistoryForKey:GetHistoryForKey方法负责查询某个键的历史记录,它的定义和调用示例如下:
// 方法定义
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)
// 代码示例
func (t *simplechiancode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
KeysIter, err := stub.GetHistoryForKey(b_parm)
if err != nil {
return shim.Error(fmt.Sprintf("GetHistoryForKey failed. Error accessing state: %s", err))
}
defer keysIter.Close()
var keys []string
for keysIter.HasNext() {
response, iterErr := keysIter.Next()
if iterErr != nil {
return shim.Error(fmt.Sprintf("GetHsitoryForKey operation failed. Error accessing state: %s", err))
}
// 交易编号
txid := response.TxId
// 交易的值
txvalue := response.Value
// 当前交易的状态
txstatus := response.IsDelete
// 交易发生的时间戳
txtimestamp := response.Timestamp
tm := time.Unix(txtimestamp.Seconds, 0)
datestr := tm.Format("2006-01-02 03:04:05 PM")
fmt.Printf("Tx info - txid : %s value : %s if delete: %t datatime: %s\n", txid, string(txvalue), txstatus, datestr)
keys = qppend(keys, rxid)
}
jsonKeys, err := json.Marshal(keys)
if err != nil {
return shim.Error(fmt.Sprintf("Query operation failed. Error marshaling JSON: %s", err))
}
return shim.Success(jsonKeys)
}
DelState:DelState方法用来删除一个key,它的定义和调用示例如下:
// 方法定义
DelState(key string) error
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
err := DelState("deletekey")
if err != nil {
return shim.Error("delete error")
}
return shim.Success([]byte("delete success"))
}
CreateCompositeKey:CreateCompositeKey方法负责创建一个组合键,它的定义和调用示例如下:
// 方法定义
CreateCompositeKey(objectType string, attributes []string) (string, error)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
parms := []string{"c_1", "d_1", "e_1", "f_1", "g_1", "h_1"}
ckey, _ := stub.CreateCompositeKey("testkey", parms)
err := stub.PutState(ckey, []byte(c_parm))
if err := nil {
fmt.Println("find error %s", err)
}
return shim.Success([]byte(ckey))
}
GetStateByPartialCompositeKey:GetStateByPartialCompositeKey方法用来查询复合键的值,它的定义和调用示例如下:
// 方法定义
GetStateByPartialCompositeKey(objectType string, key []string) (StateQueryIteratorInterface, error)
//代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
parms := []string{"c_1"}
rs, err := stub.GetStateByPartialCompositeKey("testkey", parms)
if err := nil {
error_str := fmt.Sprintf("find error: %s", err)
return shim.Error(error_str)
}
defer rs.Close()
var i int
var tlist []string
for i = 0; rs.HasNext(); i++ {
responseRange, err := rs.Next()
if err != nil {
error_str := fmt.Sprintf("find error: %s", err)
fmt.Printfln(error_str)
return shim.Error(error_str)
}
value1, compositeKeyParts, _ := stub.SplitCompositeKey(responseRange.Key)
value2 := compositeKeyParts[0]
value3 := compositeKeyParts[1]
fmt.Printf("find value v1:%s v2:%s v3:%s\n", value1, value2, value3)
}
return shim.Success("success")
}
SplitCompositeKey:SplitCompositeKey方法用来拆分复合键的属性,它的定义示例如下:
// 方法定义
SplitCompositeKey(compositeKey string) (string, []string, error)
ChaincodeStubInterface接口中交易管理相关的方法
GetTxTimestamp:该方法负责获取当前客户端发送的交易时间戳,它的定义和调用示例如下:
// 方法定义
GetTxTimestamp() (*timestamp.Timestamp, error)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
txtime, err := stub.GetTxTimestamp()
if err != nil {
fmt.Printf("Error getting transaction timestamp: %s", err)
return shim.Error(fmt.Sprintf("Error getting transaction timsstamp:%s", err))
}
tm := time.Unix(txtime.Seconds, 0)
fmt.Printf("Transaction Time: %v\n", tm.Format(2006-01-02 03:04:05 PM))
return shim.Success([]byte(fmt.Sprint("time is: %s", tm.Format("2006-01-02 15:04:05"))))
}
调用其他Chaincode的方法
InvokeChaincode
InvokeChaincode方法的定义和调用示例如下:
// 方法定义
InvokeChaincode(chaincodeName string, args [][]byte, channel string) peer.Response
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 设置参数
parms1 := []string{"query", "a"}
queryArgs := make([][]byte, len(parms1))
for i, arg := range parms1 {
queryArgs[i] = []byte(arg)
}
// 调用Chaincode
response := stub.InvokeChaincode("cc_endfinlshed", queryArgs, "roberttestchannel12")
if response.Status != shim.OK {
errStr := fmt.Sprintf("Failed to query chaincode. Got error: %s", reponse.Payload)
fmt.Printf(errStr)
return shim.Error(errStr)
}
result := string(response.Payload)
fmt.Printf("invoke chaincode %s", result)
return shim.Success([]byte("Success InvokeChaincode and Not opter" + result))
}
GetTxID
该方法可以获取客户端发送的交易的编号,GetTxID方法的定义和调用示例如下:
// 方法定义
GetTxID() string
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.response {
txid := stub.GetTxID()
fmt.Println("===== GetTxID ===== %s", txid)
return shim.Success([]byte(txid))
}
如何通过Chaincode进行交易的endorse
在Fabric中有一个非常重要的概念称为Endorsement,中文名为背书。背书的过程是一笔交易被确认的过程。而背书策略被用来指示对相关的参与方如何对交易进行确认。当一个节点接收到一个交易请求的时候,会调用VSCC(系统Chaincode,专门负责处理背书相关的操作)与交易的Chaincode共同来验证交易的合法性。在VSCC和交易的Chaincode共同对交易的确认中,通常会做以下的校验
- 所有的背书是否有效(参与的背书的签名是否有效)。
- 参与背书的数量是否满足要求。
- 所有背书参与方是否满足要求。
背书策略是指定第二和第三点的一种方式。
背书策略的设置通过Chaincode部署时instantiate命令中-P参数来设置的。
peer chiancode instantiate -o oerderer.example.com:7050 -C <链码名称> -n <链码名> -v <版本号> -c <输入参数> -P "AND ('Org1MSP.member', 'Org2MSP.member')"
"AND ('Org1MSP.member', 'Org2MSP.member')"
这个参数包说明的是当前Chaincode发起的交易,需要组织编号为Org1MSP和组织编号为Org2MSP的组织中的任何一个用户共同参与交易的确认并且同意,这样交易才能生效并被记录到区块中。
在cryptogen的配置文件中有一个节点Orgnizations->ID,该节点的值就是该组织的编号,也是在配置背书策略时需要用到的组织的编号。
OR('Org1MSP.member', AND('Org2MSP.member','Org3MSP.member'))
此背书策略有两种方法使它生效:
- 组织Org1MSP中的某个用户对交易进行验证
- 组织Org2MSP和Org3MSP中的成员共同对交易进行验证
有一点需要注意的是,背书规则只针对Chiancode中写入数据的操作进行校验(PutState、DelState),对于查询类的操作不需要背书
Fabric中的背书是发生在客户端的,需要进行相关的代码的编写才能完成整个背书的操作。
Chaincode的调试方式
Chaincode在Docker容器之外的运行
注册需要调试的chaincode
如果需要在Docker容器之外运行Chaincode,首先需要把需要运行的Chaincode注册到Fabric中,注册完成后就可以借用已注册成功的chaincode的名字和版本号在Docker容器之外运行Chaincode。有一点需要强调,在注册的时候只需要向Fabric提交Chaincode的名字和版本号,实际注册的Chaincode的代码并不重要
设置Peer节点的运行模式
可以通过修改配置文件或者添加环境变量的方式修改Peer节点的启动模式。配置文件可以通过修改core.yaml中的chaincode节点的mode子节点的值,将mode的值修改为net,修改后如下:
chaincode:
mode: net
或者通过环境变量修改Peer运行模式
export set CORE_CHAINCODE_MODE=net
注册Chaincode
export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp
peer chaincode install -n <链码名称> -v <版本号> -p <链码所在目录>
peer chaincode instantiate -o orderer.example.com:7050 -C <通道名称> -n <链码名称> -v <版本号> -c <输入参数> -P <背书策略>
注册成功之后一定要记住install命令执行时参数-n和-v的值,这一点非常重要,后面的配置中需要用到这两个参数的值。
将Peer节点设置为调试模式
要想在Docker外面运行Chaincode,首先需要调整Peer节点的运行模式。可以通过修改配置文件或者添加环境变量的方式修改Peer节点的启动模式。配置文件可以通过修改配置文件core.yaml中的chaincode节点的mode子节点的值,将mode的值修改为dev,修改后如下所示:
chiancode:
mode: dev
mode子节点有两个属性dev和net,net表示chaincode运行在Docker容器中,dev表示Chaincode运行在容器之外,通常用于开发模式。
设置环境变量的方式让Chaincode可以运行在Docker容器之外:
export set CORE_CHAINCODE_MODE=dev
如果设置的dev模式,那么当前Peer模块不能执行peer chaincode instantiate命令
编译chaincode
进入链码所在目录进行go build
运行Chaincode
要运行Chaincode需要设置相应的环境变量和系统参数
export set CORE_PEER_ADDRESS=192.168.23.212:7051
export set CORE_CHAINCODE_ID_NAME=mycc:1.1
export set CORE_CHIANCODE_LOGGING_LEVEL=debug
export set CORE_CHAINCODE_LOGGING_SHIM=debug
./simplechaincode -peer.address=192.168.23.212:7052
上述的环境变量和命令选项作用如下:
- CORE_PEER_ADDRESS:Peer节点的IP地址
- CORE_CHAINCODE_ID_NAME:chaincode的名字和版本号,冒号前面的是chaincode的名字,后面是chaincode的版本。这两个值必须和第一步install命令中-n和-v的参数完全一致,否则Chaincode无法执行
- CORE_CHAINCODE_LOGGING_LEVEL:chaincode系统的日志级别
- CORE_CHAINCODE_LOGGING_SHIM:shim的日志级别
- -peer.address=:Peer节点中的Chiancode的监听端口,该端口在core.yaml文件中的peer节点下面的chaincodeListenAddress子节点
调动Chaincode
上面启动的Chaincode通过下面的命令调用:
export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp
peer chaincode invoke -o 192.168.23.212:7050 -C <通道名> -n <链码名> -c <输入参数>