在企业级应用开发中,经常会涉及到流程和状态,而有限状态机(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状态的表单保存到StateDBreturn 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 源码,方便大家直接使用。

【本文章出自博客园深蓝居,转载请注明作者出处,如果您觉得博主的文章对您有很大帮助,欢迎点击右侧打赏按钮对博主进行打赏。】
打个招聘广告,博主正在主导开发一个跨链区块链项目:PalletOne,一直在招Go程序员,待遇丰厚,坐标北京酒仙桥,希望有识之士加入!

在Fabric ChainCode中导入第三方包(以状态机为例)相关推荐

  1. JAVA第三方包导入但找不到类,解决:导入第三方包报错java.lang.NoClassDefFoundError:XXX.XXX,XXXXXX...

    报错信息如下,主要是导入androidpn测试client端,结果报错 04-02 16:28:34.325: E/dalvikvm(485): Could not find class 'org.a ...

  2. php导包,Thinkphp5.1 导入第三方包的问题

    一般刚接触tp5.1的,会很不适应,虽然版本号只是比5.0多了0.1,但是差别挺大,废弃了不少方法,官方的教程又很简单,很多东西没说全,在此鄙视一下框架作者,最起码体谅一下小白嘛,搞了好多天才把5.1 ...

  3. Android Studio中导入第三方库

    之前开发Android都是使用的eclipse,近期因为和外国朋友Timothy一起开发一款应用,他是从WP平台刚切换使用Android的,使用的开发环境时Android Studio,为了便于项目的 ...

  4. python 导入第三方包_Python第三方包的导入

    我们知道,定义在环境变量PYTHONPATH里是可以直接用import导入的.下面我们来操作如何从非pythonpath目录导入第三方包或者自己定义的包. 假定我们的包有如下的目录结构:person ...

  5. Eclipse中导入第三方源码的问题和备用解决方案

    Eclipse中导入第三方源码的问题和备用解决方案 参考文章: (1)Eclipse中导入第三方源码的问题和备用解决方案 (2)https://www.cnblogs.com/fjdingsd/p/4 ...

  6. ironpython使用opencv_如何在Dynamo(PythonScript中导入第三方模块)

    欢迎加入BIM行业开发交流1群 群号:711844216 一.背景 小伙伴们在使用dynamo中的PythonScript节点时,为了丰富PythonScript节点的功能,那么就需要引入第三方模块. ...

  7. python 导入第三方包_python 导入第三方包---

    python导入第三方包的方法: 最重要的四件事: 1 import sys sys.path.append("..") 2要引入的包所在文件添加:__init__.py 3 fr ...

  8. Mac IDEA中导入jar包

    Mac IDEA中导入jar包 Source Distribution vs Binary Distribution 导入下载好的jar包 1. 在IDEA中打开File,选择Project Stru ...

  9. VsCode,GO语言,go mod 下导入第三方包飘红,但是可以执行

    问题:使用VScode编写的Go语言代码,开启了go mod,导入第三方包 import "github.com/360EntSecGroup-Skylar/excelize" 飘 ...

最新文章

  1. linux存储--文件描述符以及file结构体(一)
  2. windows哪些版本可以免费升级win10
  3. STL 之replace,replace_if,replace_copy,replace_copy_if
  4. python功能选择模块_python – 组合功能和功能模块
  5. JDBC编程可能遇到的错误:java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or ..
  6. MySQL 8小时空闲后连接失效的解决
  7. android nds模拟器窗口,安卓NDS模拟器drastic模拟器使用经验分享
  8. 拓端tecdat|R语言用极大似然和梯度下降算法估计GARCH(p)过程
  9. JAVA从入门到放弃之JVM内存高占用问题排查
  10. vue-tippy优秀悬浮框插件的两种使用方式(解决elementui的tooltip不及时消失及定位出错问题,使用el-progress进度条模仿柱状图,tippy模仿echarts悬浮框)
  11. 用antd实现番茄钟
  12. js的validate插件异步效验
  13. MIT推出3D全息图生成新方法,可在智能手机上实时运行
  14. 关于母亲节的c语言程序设计教程课后答案,《我的母亲》习题及参考答案
  15. MySQL的查询及删除重复记录
  16. 【Linux】创建、修改和删除用户(useradd | usermod | userdel)
  17. 【中秋佳节】CSDN卷王们内卷--中秋节要不要休息呢?
  18. DBlink 创建 删除 脚本
  19. 菜鸟在学编程__LSJ
  20. linux下mkdir和windows下mkdir的对比

热门文章

  1. VS2012+EF6+Mysq
  2. 兼容低版本迅雷的js调用
  3. One Switch for Mac 一键切换系统各项功能
  4. 一天超2000次,阿里如何打响音视频超时空战役?
  5. 使用HTML5的canvas做一个会动的时钟
  6. 坐标和变换的数学基础(2)
  7. [BZOJ 2002][Hnoi2010]Bounce 弹飞绵羊(分块)
  8. Yahoo网站性能优化的34条规则
  9. Unity3D教程:自定义mesh做指向信息面板效果
  10. 预测 “疯狂三月” 冠军的办法,我只告诉你!