在Fabric ChainCode中使用状态机

在企业级应用开发中,经常会涉及到流程和状态,而有限状态机(FSM)则是对应的一种简单实现,如果复杂化,就上升到Workflow和BPM了。我们在Fabric ChainCode的开发过程中,也很可能涉及到状态机,这里我们就举一个例子,用FSM实现一个二级审批的状态转移。

我们有一个表单,员工填写表单是可以保存为Draft状态,提交后变成Submitted状态,然后在一级审批的时候,可以Approve或者Reject,同意了改为L1Approved,进入下一级审批,拒绝了那么就以Reject状态打回给起草人,二级审批人员也是有Approve和Reject两个操作,同意了状态就改为Complete,拒绝了就改为Reject。这是一个很常见的审批例子。

我们使用Go来开发ChainCode,那么可以采用https://github.com/looplab/fsm 这个FSM库。这个库也是Fabric官方采用的状态机库。下面是我的操作过程:

1.新建ChainCode项目并引入fsm库

我们新建一个项目fsmtest,并在其中建立住ChainCode文件:main.go,然后新建vendor文件夹,将https://github.com/looplab/fsm从GitHub clone下来,并放在vendor/github.com/looplab/fsm文件夹中,最终项目个文件结构如下:

2.定义FSM初始化函数

接下来打开main.go文件,除了编写ChainCode所必须使用的函数外,最主要的就是编写定义状态机转移的初始化函数了,我们根据前面流程图中的流程状态定义,我们可以写出如下的FSM初始化函数:

func InitFSM(initStatus string) *fsm.FSM{
   f := fsm.NewFSM(
      initStatus,
      fsm.Events{
         {Name: "Submit", Src: []string{"Draft"}, Dst: "Submited"},
         {Name: "Approve", Src: []string{"Submited"}, Dst: "L1Approved"},
         {Name: "Reject", Src: []string{"Submited"}, Dst: "Reject"},
         {Name: "Approve", Src: []string{"L1Approved"}, Dst: "Complete"},
         {Name: "Reject", Src: []string{"L1Approved"}, Dst: "Reject"},
      },
      fsm.Callbacks{},
   )
   return f;
}

3.在ChainCode中调用FSM Event

接下来我们在ChainCode重定义了4个函数,

  • Draft
  • Submit
  • Approve
  • Reject
于是我们可以在Invoke函数中定义4中情况:
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
   function, args := stub.GetFunctionAndParameters()
   fmt.Println("invoke is running " + function)
   if function == "Draft" { //自定义函数名称
      return t.Draft(stub, args) //定义调用的函数
   } else if function == "Submit" {
      return FsmEvent(stub,args,"Submit")
   }  else if function == "Approve" {
      return FsmEvent(stub,args,"Approve")
   }  else if function == "Reject" {
      return FsmEvent(stub,args,"Reject")
   }
   return shim.Error("Received unknown function invocation")
}
其中Draft函数就是把表单状态初始化为Draft并保存到数据库,并不涉及状态的修改:
func (t *SimpleChaincode) Draft(stub shim.ChaincodeStubInterface, args []string) pb.Response{
   formNumber:=args[0]
   status:="Draft"
   stub.PutState(formNumber,[]byte(status))//初始化Draft状态的表单保存到StateDB
   return shim.Success([]byte(status))
}
而其他操作都涉及状态的修改,由于我们引入了状态机,所以我们只需要初始化状态机,并发送对应的Event即可,而最新的状态是由状态机根据我们的定义而获得的。所以我们虽然有3个操作,去只需要一个函数就能完成,并没有冗余的if else判断,这就是状态机的优势!
func  FsmEvent(stub shim.ChaincodeStubInterface, args []string,event string) pb.Response{
   formNumber:=args[0]
   bstatus,err:=stub.GetState(formNumber)//从StateDB中读取对应表单的状态
   if err!=nil{
      return shim.Error("Query form status fail, form number:"+formNumber)
   }
   status:=string(bstatus)
   fmt.Println("Form["+formNumber+"] status:"+status)
   f:=InitFSM(status)//初始化状态机,并设置当前状态为表单的状态
   err=f.Event(event)//触发状态机的事件
   if err!=nil{
      return shim.Error("Current status is "+status+" does not support envent:"+event)
   }
   status=f.Current()
   fmt.Println("New status:"+status)
   stub.PutState(formNumber,[]byte(status))//更新表单的状态
   return shim.Success([]byte(status));//返回新状态
}

4.部署并测试ChainCode

现在状态写完了,我们需要进行测试,我们可以git push到GitHub,然后到Ubuntu中git clone下来,也可以通过rz命令,把Windows中开发好的ChainCode上传到Ubuntu中,不管什么方法,最终我们整个ChainCode项目放在了~/go/src/github.com/hyperledger/fabric/examples/chaincode/go/fsmtest这个文件夹下。

然后使用e2e_cli下面的network_setup.sh up命令启动整个Fabric网络。启动Fabric网络后,我们需要进入CLI进行部署和合适fsmtest:

docker exec -it cli bash

然后安装并初始化我们的ChainCode:

peer chaincode install -n fsmtest -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/fsmtest
ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -v 1.0 -c ‘{"Args":[]}‘

现在安装完毕后,我们可以起草一个报销单EXP1:

peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -c ‘{"Args":["Draft","EXP1"]}‘

我们可以看到系统返回的结果:

现在状态是Draft,然后我们试一试提交报销单EXP1:

peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -c ‘{"Args":["Submit","EXP1"]}‘

我们看到状态已经改为Submitted了。接下来我们进一步一级审批通过,二级审批通过,都是执行相同的命令:

peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile $ORDERER_CA -C mychannel -n fsmtest -c ‘{"Args":["Approve","EXP1"]}‘

这个时候,状态已经是Complete了,如果我们再次调用Approve函数会怎么样?因为我们在状态机中并没有定义这么一个流转事件,所以肯定是报错,无法正常执行的:

大家如果也在做这个实验,也可以去测试Reject函数,会得到想要的结果的。

5.总结

总的来说,在Fabric的ChainCode开发中,引入第三方的库可以方便我们编写更强大的链上代码。而这个FSM虽然简单,但是也可以很好的将状态流转的逻辑进行集中,避免了在状态流转时编写大量的Ugly的代码,让我们在每个函数中更专注于业务逻辑,而不是麻烦的状态转移。最后直接粘贴出我的完整ChainCode 源码,方便大家直接使用。

时间: 2024-08-10 23:22:26

在Fabric ChainCode中使用状态机的相关文章

PLC状态机编程-如何在STL中使用状态机

搞PLC编程多年,一直不知道状态机,学习matlab后,发现状态机编程异常方便,过去很多编程时的疑惑豁然开朗起来.今天跟大家分享一下如何在STL中使用状态机. 下面是用状态机描述的控制任务. 这个状态机较简单,那如何在STL中把它描述出来呢? 这里我们选择用JL指令...我就直接上代码了,一看便知.但JL程序只能用来表示单步状态,遇到并行状态就处理不了了..下次我们用ST语言来描述更复杂的状态机.. L     #state     //  把#state的值载入累加器       JL   

Hyperledger Fabric --- Chaincode Operator 解读和测试(二)

本文接上一节是测试部分 搭建一个模拟测试环境 作者将fabric release1.2工程中的 example-e2e进行了改造来进行本次实验: (1)首先我们将examples/e2e_cli/scripts/script.sh中的安装智能合约部分注释掉,或者从此处下载替换原有的脚本 (2)然后再写一个用于安装signcd的脚本 script_chaincode.sh ,放在examples/e2e_cli/scripts/ 目录下面 (3)启动测试网络: cd examples/e2e_cl

Hyperledger fabric网络中transaction产生以及流转过程

一.发起transaction 当client想要发起一个transaction时,它会首先发送一个PROPOSE消息到它选择的一组endorser节点,消息模式有以下两种,节点可以自由选择(可能有更多种): client首先将<PROPOSE, tx>消息发送给某个单个的endorser,该endorser会产生相应的版本依赖(anchor),以供client稍后作为PROPOSE消息的参数发送给其它endorser节点: client直接将<PROPOSE, tx>消息发送给它

fabric --- Python中的批量远程管理和部署工具

Fabric是Python中一个非常强大的批量远程管理和部署工具,常用于在多个远程PC上批量执行SSH任务. 常见的使用方法大概总结如下: 1, 首先,要将批量执行的任务写入到一个fabfile.py中, # -*- coding:utf-8 -*- from fabric.api import run, local, roles, env, cd env.hosts=[ '192.168.1.110', '192.168.1.111', '192.168.1.112' ] env.user="

Fabric chaincode开发调试

由于chaincode开发调试步骤稍多,每次都要查看官方doc有些不便,且偶尔还会遇到官方doc无法访问的情况,故整理一份chaincode开发步骤(环境已经配置好的前提),自用还ok: 首先下载官方提供的fabric-samples: git clone https://github.com/hyperledger/fabric-samples.git 终端1 启动网络: cd fabric-samples/chaincode-docker-devmode sudo docker-compos

简述游戏开发中的状态机

为什么我们需要状态机 实行较多状态的角色,把动作全写在一个部分中会导致维护成本高,拓展性低 例如:走路,跳跃,射击,躲避的相互转换,有些可以转换,有些不能,实现逻辑复杂 (满屏幕都是if - else) 状态模式switch实现 //包含着所有的状态 enum class State{StateA, StateB, StateC, ...} activeState; ... //通过switch语句切换状态,根据具体情况实现细节 switch (activeState) { case State

6_State 游戏开发中使用状态机

### State 不好的代码 ``` //处理玩家输入的代码 void Heroine::handleInput(Input input) { if (input == PRESS_B) { if (!isJumping_ && !isDucking_) { // Jump... } } else if (input == PRESS_DOWN) { if (!isJumping_) { isDucking_ = true; setGraphics(IMAGE_DUCK); } else

Lua中使用状态机FSM简单例子

FSM 有限状态机: 一个有限状态机是一个设备,或者是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得一个状态变换到另一个状态,或者是使一个输入或者一种行为的发生.一个有限状态机在任何瞬间只能处在一种状态. 进入动作 退出动作 更新动作 FsmMachine.lua FsmMachine = {} function FsmMachine:New() self.__index = self o = setmetatable({}, self) o.states = {

[整理]JS中的状态机

/*StateMachine*/ var StateMachine = (function(){ function StateMachine(opts){ this.current = opts.initial||'none' this.events = opts.events||[] for(var i=0,length=this.events.length;i<length;i++){ var self = this var evt = self.events[i] self[evt.nam