在Fabric ChainCode中導入第三方包(以狀態機為例)


在企業級應用開發中,經常會涉及到流程和狀態,而有限狀態機(FSM)則是對應的一種簡單實現,如果復雜化,就上升到Workflow和BPM了。我們在Fabric ChainCode的開發過程中,也很可能涉及到狀態機,這里我們就舉一個例子,用FSM實現一個二級審批的狀態轉移。

我們有一個表單,員工填寫表單是可以保存為Draft狀態,提交后變成Submitted狀態,然后在一級審批的時候,可以Approve或者Reject,同意了改為L1Approved,進入下一級審批,拒絕了那么就以Reject狀態打回給起草人,二級審批人員也是有Approve和Reject兩個操作,同意了狀態就改為Complete,拒絕了就改為Reject。這是一個很常見的審批例子。

image

我們使用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文件夾中,最終項目個文件結構如下:

image

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"]}'

我們可以看到系統返回的結果:

image

現在狀態是Draft,然后我們試一試提交報銷單EXP1:

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

image

我們看到狀態已經改為Submitted了。接下來我們進一步一級審批通過,二級審批通過,都是執行相同的命令:

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

image

這個時候,狀態已經是Complete了,如果我們再次調用Approve函數會怎么樣?因為我們在狀態機中並沒有定義這么一個流轉事件,所以肯定是報錯,無法正常執行的:

image

大家如果也在做這個實驗,也可以去測試Reject函數,會得到想要的結果的。

5.總結

總的來說,在Fabric的ChainCode開發中,引入第三方的庫可以方便我們編寫更強大的鏈上代碼。而這個FSM雖然簡單,但是也可以很好的將狀態流轉的邏輯進行集中,避免了在狀態流轉時編寫大量的Ugly的代碼,讓我們在每個函數中更專注於業務邏輯,而不是麻煩的狀態轉移。最后直接粘貼出我的完整ChainCode 源碼,方便大家直接使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM