BSN智能合约开发培训-Hyperledger Fabric(一)

原文标题:

Hyperledger Fabric开发实战-03编写智能合约

Hyperledger Fabric开发实战-04编写智能合约

本文是在阅读《区块链开发实战-Hyperledger Fabric关键技术与案例分析》一书的同时,在实践中记录的一些实践步骤与经验分享。

Hyperledger Fabric的智能合约叫做Chaincode,是业务的成载体,负责具体的业务逻辑

Chaincode代码

Fabric的Chaincode运行在容器中,可以使用Go,Java,Node.js语言开发,Golang是目前为主比较成熟稳定的。

Chaincode组成

Chaincode主要由下面几个组成:

  • 必须在main包下
  • 引用必要的依赖
  • 定义一个结构体,并为结构体绑定Init和Invoke方法
  • shim.ChaincodeStubInterface和pb.Response
  • main方法

下面以一个例子进行说明

<code>// 1.在main包下
package main
// 2.引入必要的依赖
import(
   "fmt"
   "github.com/hyperledger/fabric/core/chaincode/shim"
   pb "github.com/hyperledger/fabric/protos/peer"
)
// 3.定义一个结构体
type mychaincode struct {
}
// 4.为结构体绑定init和invoke方法
func (t *mychaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{
    fmt.Println(" << ====[Init] success init it is view in docker ======")
    return shim.Success([]byte("success init"))
}

func (t *mychaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{
    fmt.Println(" << ====[Invoke] success init it is view in docker ======")
    return shim.Success([]byte("success init"))
}
// 5.主方法
func main(){
    err := shim.Start(new(mychaincode))
    if err != nil{
        fmt.Println("Error starting Simple chaincode : %s",err)
    }
}/<code>

Chaincode部署

Chaincode的运行分为:install,instantiate和invoke三步

编写以上的代码后,我们将其放入一个文件夹,例如: /home/ssj234/fabricwksp/05-chaincode/下,在部署的时候,会去$GOPATH的src下面寻找文件夹,因此,我们在如下路径编写Chaincode

<code>cd /home/ssj234/fabricwksp/05-chaincode/
mkdir -p src/firstchaincode
cd src/firstchaincode
# 编写代码
vim firstchaincode.go
go build/<code>

安装

<code># 设置GOPATH,让其能够找到Chaincode代码
# 设置要链接的Peer节点和Peer的Admin证书
export set GOPATH=/home/ssj234/fabricwksp/05-chaincode
export set FABRIC_CFG_PATH=/home/ssj234/fabricwksp/02-helloworld/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.cmbc.com:7051
export set CORE_PEER_MSPCONFIGPATH=/home/ssj234/fabricwksp/02-helloworld/fabricconfig/crypto-config/peerOrganizations/org1.cmbc.com/users/[email protected]/msp

# 部署chaincode 代码,-n后面是名字 -v 是Chaincode的版本  -p后面是在$GOPAT/src下的相对路径
peer chaincode install -n hellochaincode -v 1.1 -p hellochaincode/<code>

实例化

实例化的时候会调用init方法

<code># 设置GOPATH
# 设置要连接的Peer节点和其Admin证书
export set GOPATH=/home/ssj234/fabricwksp/05-chaincode
export set FABRIC_CFG_PATH=/home/ssj234/fabricwksp/02-helloworld/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.cmbc.com:7051
export set CORE_PEER_MSPCONFIGPATH=/home/ssj234/fabricwksp/02-helloworld/fabricconfig/crypto-config/peerOrganizations/org1.cmbc.com/users/[email protected]/msp

# 初始化chaincode 代码,需要指定channel的名字,-c是传入的参数
peer chaincode instantiate -o orderer.cmbc.com:7050 -C cmbcchannel666 -n firstchaincode -v 1.1 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"/<code>

instantiate实例化命令运行的时间较长,银行会启动一个docker容器来执行chaincode,我们可以通过docker ps命令观察,会看到下面的一个容器正在运行

<code>STATUS                  PORTS                       NAMES
bee440516894        dev-peer0.org1.cmbc.com-firstchaincode-1.2-876a5b6466d4eeac14cf940b95e3538a8ace73b133fad7c70041ea17d7cf4b8e   "chaincode -peer.a..."   2 minutes ago       Up 2 minutes/<code>

使用docker logs bee440516894查看日志,会输出Init方法中打印的内容

<code><< ====[Init] success init it is view in docker ======/<code>

调用

使用peer chaincode invoke可以调用chaincode的invoke方法

<code># 设置$GOPATH
# 设置要连接的Peer和Peer的Admin证书
export set GOPATH=/home/ssj234/fabricwksp/05-chaincode
export set FABRIC_CFG_PATH=/home/ssj234/fabricwksp/02-helloworld/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.cmbc.com:7051
export set CORE_PEER_MSPCONFIGPATH=/home/ssj234/fabricwksp/02-helloworld/fabricconfig/crypto-config/peerOrganizations/org1.cmbc.com/users/[email protected]/msp

# 调用chaincode 代码,需要制定orderer服务器,channel的名称和Chaincode的名称和版本号
peer chaincode invoke -o orderer.cmbc.com:7050 -C cmbcchannel666 -n firstchaincode -v 1.1 -c '{"Args":["invoke","a","100","b","200"]}'/<code>

执行完成,通过docker logs命令可以查看到Invoke方法的打印内容。


在上一节演示了一个简单的Chaincode示例,可以看到,Chaincode中主要是Init和Invoke方法的实现。两个方法的原型如下:

<code>func (t *mychaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{
}
func (t *mychaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{
}/<code>

可以看到,主要是对shim.ChaincodeStubInterface的使用。

shim接口

shim.ChaincodeStubInterface

<code>// 获取传入的参数,args是一个数组
function,args = stub.GetFunctionAndParameters()
// 将数据保存到fabric中
PutState(key string, value []byte) error
// 将数据保存到fabric中
GetState(key string) ([]byte,error)
// 获取区间内的key
GetStateByRange(startKey,endKey string) (StateQueryInterface,error)
// 获取key的历史
GetHistoryForKey(key string) (HistoryQueryIteratorInterface,error)
// 删除key
DelState(key string) error
// 创建一个组合对象,类似hash对象
CreateCompositeKey(objectType string,attributes [] string) (string,error)
// 获取hash对象的属性内容
GetStateByPartialCompositeKey(objectType string,keys []string) (StateQueryInterface,error)
// 拆分复合对象的key
SplitCompositeKey(compositeKey string) (string,[]string,error)/<code>

pb.Response

shim提供了一组方法,用来包装返回的信息

返回成功

<code>// 原型
func Success(payload []byte) pb.Response
// 使用
shim.Success([]byte("success"))/<code>

返回失败

<code>// 原型
func Error(msg string) pb.Response
// 使用
shim.Error("success"))/<code>

Chaincode存取数据

通过上面的shim接口,我们可以看到如何将数据存放到账本中,如何从账本取出,下面编写一个例子来演示,对上一节的代码进行改动。

Init方法中,接收两个名字和金额,如["init","Alice","100","Bob","200"]

<code>func (t *mychaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{
    var args []string
    fmt.Println(" << ====[Init] success init it is view in docker ======")
    _,args = stub.GetFunctionAndParameters()
    var a ,b string
    var aAmt,bAmt int
    var err error
    a = args[0]
    aAmt,err = strconv.Atoi(args[1])
    b = args[2]
    bAmt,err = strconv.Atoi(args[3])
    fmt.Println("aAmt is ",aAmt)
    fmt.Println("bAmt is ",bAmt)
    stub.PutState(a,[]byte(args[1]))
    stub.PutState(b,[]byte(args[3]))
    if err != nil{
       fmt.Println("error")
    } 
    return shim.Success([]byte("success init"))
}/<code>

Invoke方法中,接受A到B的转账,如["invoke","Alice","Bob","10"]

<code>func (t *mychaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{
   var args []string 
   var aAmtBytes,bAmtBytes []byte
   fmt.Println(" << ====[Invoke] success init it is view in docker ======")
   _,args = stub.GetFunctionAndParameters()
   var a ,b string
   var aAmt,bAmt int
   var transAmt int
   //var err error
   a = args[0]
   b = args[1]
   transAmt,_ = strconv.Atoi(args[2])
   aAmtBytes,_ = stub.GetState(a)
   bAmtBytes,_ = stub.GetState(b)
   aAmt, _ = strconv.Atoi(string(aAmtBytes))
   bAmt, _ = strconv.Atoi(string(bAmtBytes))
   fmt.Println("aAmt form fabric is ",aAmt)
   fmt.Println("bAmt form fabric is ",bAmt)
   aAmt = aAmt - transAmt
   bAmt = bAmt +  transAmt
   stub.PutState(a, []byte(strconv.Itoa(aAmt)))
   stub.PutState(b, []byte(strconv.Itoa(bAmt)))
    return shim.Success([]byte("success init"))
}/<code>

编写完成后,还需要编写main方法

<code>func main(){
        err := shim.Start(new(mychaincode))
        if err != nil{
                fmt.Println("Error starting Simple chaincode : %s",err)
        }
}/<code>

之后,使用go build构建,再按照上一节的方法,安装,实例化,调用测试,使用docker logs查询日志

<code><< ====[Init] success init it is view in docker ======
aAmt is  100
bAmt is  200

 << ====[Invoke] success init it is view in docker ======
aAmt form fabric is  100
bAmt form fabric is  200
 << ====[Invoke] success init it is view in docker ======
aAmt form fabric is  90
bAmt form fabric is  210
<< ====[Invoke] success init it is view in docker ======
aAmt form fabric is  80
bAmt form fabric is  220/<code>

编写完成Chaincode后,可以执行go build查看是否编译成功,如果安装和实例化之后更改了Chaincode程序,需要重新build,然后使用peer chaincode upgrade命令,设置最新的版本号,更新chaincode

链接:https://www.jianshu.com/p/2e389823f556

https://www.jianshu.com/p/6b7049b4bdba


分享到:


相關文章: