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'))此背书策略有两种方法使它生效:

  1. 组织Org1MSP中的某个用户对交易进行验证
  2. 组织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 <输入参数>
posted @ 2020-03-08 22:36  NykuvL  阅读(775)  评论(0编辑  收藏  举报