我相信智能合約(鍊碼)是Hyperledger Fabric區塊鍊網絡的核心。正确開發鍊碼可以真正發揮一個安全區塊鍊的優勢,反之則會帶來災難性的後果。在這篇文章裡我不打算探讨Hyperledger Fabric鍊碼設計的特定模式的好與壞,而是希望分享我在開發若幹Hyperledger Fabric概念驗證應用過程中總結的一些基本準則。
Hyperledger Fabric區塊鍊開發教程: Node.js |Java Golang
1、啟用peer節點的開發模式
使用開發模式開啟你的Hyperledger Fabric鍊碼開發流程。這一點無論怎麼強調都不過分,這會節省你大量的時間和精力,因為你可以自由地修改代碼而無需重新部署并激活鍊碼,也無需一遍遍地重新開機網絡。
參考文檔:
https://gist.github.com/arnabkaycee/d4c10a7f5c01f349632b42b67cee46db2、使用Fabric鍊碼的日志
這可能是能幫助你調試Hyperledger Fabric鍊碼并快速找對外連結碼bug的第一個有用的技能。鍊碼日志很簡單易用,使用Fabric内建的logger即可。
- Golang: shim ChaincodeLogger
- NodeJS: shim newLogger
- Java:可以使用任何标準的日志架構,例如log4j
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條軍規 — 彙智網