1. 程式人生 > >在Fabric ChainCode中使用狀態機

在Fabric ChainCode中使用狀態機

安裝 for spl 返回 狀態 沒有 interface 文件 ret

在企業級應用開發中,經常會涉及到流程和狀態,而有限狀態機(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 源碼,方便大家直接使用。

在Fabric ChainCode中使用狀態機