天天看點

開發Fabric鍊碼的8個基本準則

我相信智能合約(鍊碼)是Hyperledger Fabric區塊鍊網絡的核心。正确開發鍊碼可以真正發揮一個安全區塊鍊的優勢,反之則會帶來災難性的後果。在這篇文章裡我不打算探讨Hyperledger Fabric鍊碼設計的特定模式的好與壞,而是希望分享我在開發若幹Hyperledger Fabric概念驗證應用過程中總結的一些基本準則。

Hyperledger Fabric區塊鍊開發教程: Node.js |
Java Golang

1、啟用peer節點的開發模式

使用開發模式開啟你的Hyperledger Fabric鍊碼開發流程。這一點無論怎麼強調都不過分,這會節省你大量的時間和精力,因為你可以自由地修改代碼而無需重新部署并激活鍊碼,也無需一遍遍地重新開機網絡。

參考文檔:

https://gist.github.com/arnabkaycee/d4c10a7f5c01f349632b42b67cee46db

2、使用Fabric鍊碼的日志

這可能是能幫助你調試Hyperledger Fabric鍊碼并快速找對外連結碼bug的第一個有用的技能。鍊碼日志很簡單易用,使用Fabric内建的logger即可。

3、避免在Fabric鍊碼中使用全局鍵

在開發Hyperledger Fabric鍊碼時,我們經常會發現在搜尋資料方面限制很多,是以要跟蹤在鍵值庫中注冊的鍵,我們有時會嘗試使用某些全局資料。

例如,當你再Hyperledger Fabric應用中跟蹤注冊的彈珠時,可能想建立一個全局的計數器以便生成彈珠的下一個ID。但是這麼做的時候,你就引入了對這個變量的依賴。在開始的時候這看起來不是個問題,但是當你送出并發交易時就會出錯。為什麼?讓我解釋一下。

看一下鍊碼:

package main
import (
    //other imports
    "github.com/hyperledger/fabric/core/chaincode/shim"
       pb "github.com/hyperledger/fabric/protos/peer"
)

//不要這麼做!    
totalNumberOfMarbles := 0

func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    var err error
    
    marbleId := fmt.Sprintf("MARBLE_%06d",totalNumberOfMarbles)
    marbleName := args[0]
    color := strings.ToLower(args[1])
    owner := strings.ToLower(args[3])
    size, err := strconv.Atoi(args[2])
    
    //other code to initialize
    objectType := "marble"
    marble := &marble{objectType, marbleId, marbleName, color, size, owner}
    
    //--------------CODE SMELL----------------
    //BIG source of Non-determinism as well as performance hit.
    totalNumberOfMarbles = totalNumberOfMarbles + 1 
    //--------------CODE SMELL----------------
    

    //regular stuff...        
    err = stub.PutState(marbleId, marbleJSONasBytes)
    if err != nil {
        return shim.Error(err.Error())
    }
}           

那麼,為什麼我不喜歡這樣?

第一個原因。假設你已經完成這個Fabric鍊碼,一切都很正常,直到有一天,某個運作這個鍊碼的peer節點,崩潰了。雖然賬本資料還在,但是内部有些可怕的事情已經發生了。你可能重新啟動peer節點,起初一切看起來都正常。但是突然,這個節點背書的所有交易都開始失敗了。為什麼?就是因為那個全局計數變量已經不能正确跟蹤真實的值了。其他的peer節點都計數到比如15K了,而這個節點突然從零開始計數,你的彈珠的ID又從零開始了。是以,當你将這個交易發送給排序節點(Orderer)并到達送出節點(Peer)時,送出節點上的驗證系統(Validation System Chaincode)會比較所有背書交易的提議響應,同時檢查是否有足夠的簽名存在,隻要有一個提議響應不比對,送出節點就會抛出一個ENDORSEMENT_POLICY_FAILURE異常。

第二個原因。現在讓我們嘗試解決上面的問題,在Fabric鍊碼的最後添加如下的代碼:

stub.PutState("marble_count", totalNumberOfMarbles)           

這樣會好一些嗎?Noooooooooooooooooo!

想象一下,有兩個并發交易都試圖插入新的彈珠。

例如,一個交易要将marble_count的值更新為34,marble_count狀态的新版本為10。而另一個交易則要将marble_count的值更新為35, 它也認為marble_count的新版本為10。記住,由于這兩個交易是并發的,兩個交易看到的都是current_version(marble_count) = 09。

現在其中一個交易将在另一個交易之前到達Fabric的排序節點,marble_count鍵已經更新到新的值,這時marble_count的版本已經是10,是以後到的交易将失敗,因為marble_count的版本已經是10 ,而後續交易還認為它讀的是版本09并且将更新到版本10。這是區塊鍊中經典的雙花問題(double spending)。

Hyperledger Fabric在送出交易時使用一種優化的鎖模型。正如我已經解釋過的,提議響應由用戶端從背書節點采集,然後發送給排序節點并最終由排序節點将其分發給送出節點。着這個兩步過程中,如果有些在背書階段讀取的鍵的版本發生了變化,你就會得到MVCC_READ_CONFLICT錯誤。當存在并發交易同時更新相同的鍵時,就有可能出現這個問題。

關于這一點的詳細說明,可以參考

這篇文章

4、聰明地使用CouchDB查詢

Couch DB查詢(又稱為Mongo查詢)在搜尋Fabric節點的鍵值庫中的資料時非常有用,但是有一些坑你需要注意。

  • Couch DB查詢不會修改交易的READ SET

Mongo查詢僅用來查詢節點的鍵值庫也就是狀态庫。它不會修改交易的read set。這可能會在交易中

導緻幻讀(phantom reads)。

  • 隻能搜尋已經存入CouchDB的資料

不要試圖用Mongo查詢按鍵名搜尋。雖然你可以通路CouchDB的Fauxton控制台,但你無法按鍵查詢。例如,不允許查詢

channelName\0000KeyName

。更好的方法時将鍵作為你自己資料的屬性儲存。

5、編寫确定性的Fabric鍊碼

永遠不要編寫不确定的鍊碼。意思是說如果我在多個不同的時間、不同的環境下執行鍊碼,總應該得到相同的結果。例如,避免使用像

rand.New(...)

t := time.Now()

這樣的代碼,或者依賴于某個沒有在賬本中持久化的變量。

這是因為,如果生成的讀寫集不一樣,Hyperledger Fabric的驗證系統鍊碼(Validation System Chaincode)會拒絕交易并抛出ENDORSEMENT_POLICY_FAILURE異常。

6、調用其他通道的Fabric鍊碼時要小心

在鍊碼中調用同一個通道中的另一個鍊碼沒問題,但是要了解的是,如果是要調用另一個通道的鍊碼,你隻能得到鍊碼方法的傳回結果,而不會在另一個通道賬本中有任何送出。目前,跨通道的鍊碼調用不會修改資料,是以,一個交易一次隻能寫入一個通道。

7、記得設定Fabric鍊碼的執行逾時時間

在高負載的情況下,你的Hyperledger Fabric鍊碼可能不會在30s内完成。是以一個好的實踐是根據需求定制鍊碼執行逾時值。這是由core.yaml中的參數決定的。你可以在docker compose 檔案中如下設定:

Example: CORE_CHAINCODE_EXECUTETIMEOUT=60s           

8、避免從Fabric鍊碼中通路外部資源

通路外部資源可能會暴露系統漏洞并給你的Hyperledger Fabric鍊碼引入安全威脅。無論如何你不會希望外部資源中的惡意代碼影響你的鍊碼邏輯。是以請盡可能的避免再Fabric鍊碼中通路區塊鍊外部的資源。

原文連結:

Hyperledger Fabric鍊碼開發的8條軍規 — 彙智網