本節介紹從最底層的賬本開始,逐一講解賬本的結構和存儲、智能合約的編寫和部署、通道的操作、節點的背書和提交、排序的共識和客戶端SDK的接口調用,與交易流程順序相反,由里及表的說明Fabric最關鍵的技術,通過學習了這六種關鍵技術知識,能初步掌握Fabric的核心,了解Fabric運作原理。
視頻教程:https://study.163.com/course/introduction/1210196297.htm
6.3.1 帳本(Ledger)
Fabric帳本(Ledger)是一系列有序和防篡改的狀態轉換的記錄,結構由一個區塊鏈構成,並將不可變的、有序的記錄存放在區塊中;同時包含一個狀態數據庫來記錄當前的狀態,賬本的當前狀態信息是鏈交易日志中記錄過的所有鍵的最新值,由於當前狀態表示的是通道已知的所有鍵的最新值,由此也被稱為世界狀態。區塊鏈結構保存在本地的文件系統中;世界狀態由數據庫來維護,以鍵值對(k-v)的方式保存在數據庫,默認數據庫為LevelDB,數據庫版本可替換為KV類型的數據庫,如CouchDB等。
帳本結構圖如下:
圖:帳本結構圖
區塊鏈(Blockchain):
區塊鏈(Blockchain)是基於本地文件系統,將區塊存儲於文件系統的硬盤中,每個區塊中保存有區塊頭(Block header)、區塊數據(Block data)、區塊元數據(Block metadata),通過區塊頭中的前一個區塊的哈希值(Previous Block Hash)指向前一個區塊的當前哈希值(Current Block Hash),連接成區塊鏈。
上圖中區塊圖示說明:
B:Blockchain 區塊鏈
B1:Block 區塊
H3:Block header 區塊頭
D1:Block data 區塊數據
T5:Transaction 交易數據,保存在區塊數據中
M3:Block metadata區塊元數據
H1-H2:H2 is chained to H1 區塊2指向區塊1
狀態數據庫(State Database):
狀態數據庫是存儲所有在交易中出現的鍵值對的最新值,調用鏈碼執行交易可以改變狀態數據,為了高效的執行鏈碼調用,所有數據的最新值都被存放在狀態數據庫中;狀態數據庫被設計為組件,可以通過配置替換數據庫,目前有LevelDB和CouchDB兩種數據庫, LevelDB是默認的內置的數據庫。
LevelDB:適用於簡單的鏈值對k-v場景,LevelDB嵌入在peer進程中。
CouchDB:適用於支持豐富的查詢和數據類型的場景,應用系統作為JSON文檔存儲時,CouchDB是一個特別合適的選擇,支持對chaincode數據進行豐富的查詢。
賬本索引數據庫(Block index):
區塊鏈保存到文件系統時,會在LevelDB 中存儲區塊交易對應的文件塊及其偏移,也就是將 LevelDB 作為賬本數據庫的索引。文件形式的區塊存儲方式如果沒有快速定位的索引,那么查詢區塊交易信息會非常的慢。
歷史狀態數據庫(History index)
歷史狀態數據庫用於查詢某個 key 的歷史修改記錄,歷史數據庫並不存儲 key 具體的值,而只記錄在某個區塊的某個交易里,某 key 變動了一次,后續需要查詢的時候,根據變動歷史去查詢實際變動的值,雖然減少了數據的存儲,當然也增加了查詢邏輯的復雜度。
6.3.2 智能合約(Smart contract)
智能合約又稱為鏈碼,是在區塊鏈上運行的一段代碼,是應用系統與區塊鏈底層交互的中間件,通過智能合約可以實現各種復雜的應用。
目前使用Go語言來開發智能合約以外,還可以使用java、node.js開發,支持最好的還是Go語言。
智能合約安裝在節點(Peer)上,運行在Docker容器中,通過gRPC與Peer進行數據交互,交互步驟如下:
圖:鏈碼與節點消息交互
1) 鏈碼(chaincode)調用 shim.Start()方法后,給節點(Peer)發送 ChaincodeMessage_REGISTER 消息嘗試進行注冊。客戶端鏈碼等待接收來自節點(Peer)的消息。此時鏈碼(chaincode)和節點(Peer)的狀態為初始的created。
2) 節點(Peer)在收到來自鏈碼容器的 ChaincodeMessage_REGISTER 消息后,會注冊到本地的一個handle結構,返回 ChaincodeMessage_REGISTERED 消息給鏈碼容器。並且更新狀態為 established ,然后會自動發出 ChaincodeMessage_READY 消息給鏈碼(chaincode),並且更新狀態為ready。
3) 鏈碼(chaincode)在收到 ChaincodeMessage_REGISTERED 消息之后,先不進行任何的操作,完成注冊步驟。更新狀態為 established 。在收到 ChaincodeMessage_READY 消息之后再更新狀態為 ready 。
4) 節點(Peer)發出 ChaincodeMessage_INIT 消息給鏈碼容器,對鏈碼進行初始化。
5) 鏈碼容器收到 ChaincodeMessage_INIT 消息之后,調用用戶代碼 Init()方法進行初始化,成功之后,返回 ChaincodeMessage_COMPLETED 消息。到此,鏈碼容器可以被調用了。
6) 鏈碼(chaincode)被調用的時候,節點(Peer)發出 ChaincodeMessage_TRANSACTION 消息給鏈碼。
7) 鏈碼在收到 ChaincodeMessage_TRANSACTION 消息之后,會調用 Invoke()方法,根據Invoke方法中用戶的邏輯,可以發送如下的消息給節點(Peer):
ChaincodeMessage_GET_HISTORY_FOR_KEY
ChaincodeMessage_GET_QUERY_RESULT
ChaincodeMessage_GET_STATE
ChaincodeMessage_GET_STATE_BY_RANGE
ChaincodeMessage_QUERY_STATE_CLOSE
ChaincodeMessage_QUERY_STATE_NEXT
ChaincodeMessage_INVOKE_CHAINCODE
8)節點(Peer)在收到這些消息之后,會進行相應的處理,並回復 ChaincodeMessage_RESPONSE 消息。最后,鏈碼(chaincode)會回復調用完成的消息 ChaincodeMessage_COMPLETE至節點(Peer)。
9) 上述消息交互過程完成后,節點(Peer)和鏈碼(chaincode)會定期相互發送 ChaincodeMessage_KEEPALIVE 消息給對方,以確保彼此是在線狀態。
6.3.3 通道(Channel)
通道是兩個節點(Peer)或多個節點之間信息通信的私有空間,在通道內的交易的數據與通道外隔絕,保證通道內數據的安全。在網絡上的交易都要在某個通道(Channel)上執行,參互交易的每個成員都需要進行身份驗證和授權,才能在通道(Channel)上進行處理。
Fabric是多通道設計,系統可以創建多條通道,某個節點(Peer)可以加入到不同的通道中,在每個通道中有自身的創世區塊和實例化智能合約(Smart contract)。
每個通道都有屬於自己的錨節點,通過錨節點可以與其它通道進行信息交互,但本身通道內的賬本不會通過一個通道傳到另一個通道上,通道對賬本是分離的。
通道結構圖:
圖:通道結構圖
上圖中通道圖示說明:
N:Blockchain Network區塊鏈網絡
C:Channel 通道
P:Peer 節點
S:Chaincode 鏈碼
L:Ledger 賬本
A:Application 客戶端
PA-C:Principal PA(e.g. A,P1)communicaties via channel C 客戶端A與節點P(Peer)通過通道C(channel)進行通信。
H1-H2:H2 is chained to H1 區塊2指向區塊1
6.3.4 節點(Peer)
節點(Peer)是區塊鏈的交易處理和賬本維護的主體,主要負責參與共識過程和通過執行鏈碼(chaincode)實現對賬本的讀寫操作。節點(Peer)根據功能不同分為背書節點(Endorser peer)和提交節點(Committer peer);根據通訊不同分為錨節點(Anchor peer)和主節點(Leading peer)。
背書節點(Endorser peer):背書節點(Endorser peer)負責對交易根據事先設定策略進行簽名背書,背書節點(Endorser peer)根據鏈碼在實例化的時候設置背書策略,指定哪些節點用於背書。當客戶端向節點發起交易背書時,該Peer節點才雎有背書功能,其它時間只是普通的記賬節點。
記賬節點(Committer peer):記賬節點(Committer)負責維護狀態數據和賬本的副本。
錨節點(Anchor peer):錨節點(Anchor peer)是隨通道(Channel)存在,是能被其它通道發現的的節點(peer),每個通道(channel)上有一個或多個錨節點(Anchor peer)。
主節點(Leading peer):主節點(Leading peer)負責與排序(Orderer)通信,把共識后的區塊傳輸到其他節點。
應用程序與節點交互圖:
圖:客戶端與節點交互
上圖中通道圖示說明:
N:Blockchain Network區塊鏈網絡
A:Application 應用程序
P:Peer 節點
S:Chaincode 鏈碼
L:Ledger 賬本
O:Orderer 排序
交互流程:
1)應用程序成功連接到節點(Peer)后,調用鏈碼向節點(Peer)進行提案;
2)節點(Peer)根據提案信息調用鏈碼;
3)鏈碼進行查詢和更新,然后返回提案信息給應用程序;
4)應用程序發送交易信息給排序(Orderer);
5)排序(Orderer)把含有交易信息的區塊發送給節點(Peer);
6)節點(Peer)把交易區塊更新到賬本中,最終完成處理;
6.3.5 排序(Orderer)
排序(Orderer)指對區塊鏈網絡中不同通道產生的交易進行排序,並廣播給節點(Peer)。排序(Orderer)是以可插拔組件的方式實現,目前分為SOLO和Kafka兩種類型。
SOLO:僅有一個Orderer服務節點負責接收交易信息進行排序,是最簡單的排序算法,一般用於測試環境。
Kafka:是由Apache軟件基金會開發的一個開源流處理平台,一種高吞吐量的分布式發布訂閱消息系統,它可以處理消費者規模的網站中的所有動作流數據,可以配置多個排序節點集群方式,以便使用在生成環境。Hyperledger Fabric利用kafka的高吞吐、低延時的特性,對交易信息進行排序處理,實現在集群內部支持節點故障容錯。
正式環境中需要使用Kafka搭建,保證數據可靠性和安全性,以下介紹基於Kafka集群和ZooKeeper集群的排序服務的原理。
1.Kafka處理概述
Kafka處理流程示意圖:
圖:Kafka處理流程示意圖
說明:
排序(Orderer)節點(Ordering Service Node)簡稱為OSN,Orderer集群連接到Kafka集群和Zookeeper集群,利用Kafka的共識功能,實現網絡中交易的排序和生成區塊的任務。
當排序(Orderer)節點通過RPC廣播(Broadcast)接收到從節點(Peer)來的交易數據時,先判斷發送交易的客戶端是否有權限處理該通道(Channel)數據,如果有權限,排序(Orderer)節點會把交易保存到Kafka的分區(partition)中,每個通道(Channel)對應Kafka中的單獨的單分區(partition)的類別中(topic)。
排序(Orderer)節點使用該分區(partition),將接收到交易分組寫入到本地區塊中,並加入到本地的區塊鏈中,最后通過RPC傳輸(Deliver)給需要接收的客戶端。
2. Kafka集群和ZooKeeper集群的節點數量規定
kafka集群節點的最小值為4,是故障容錯所需要的最小數值。四個kafka結點可以容許一個節點崩潰后,所有的通道(Channel)還可以繼續讀寫且創建通道。
zookeeper可以為3,5或7。它必須是一個奇數來避免分裂(split-brain)情景,同時選擇大於1是為了避免單點故障,超過7個ZooKeeper節點是多余的。
3. 排序(Orderer)操作步驟
1)在網絡的創世塊中寫入Kafka相關的信息
在生成創世區塊時,需要在configtx.yaml文件中配置Kafka相關的信息,如Orderer.OrdererType設置為kafka、Orderer.Kafka.Brokers設置Kafka集群中的節點IP地址和端口;
2)設置區塊最大容量
區塊最大容量在configtx.yaml文件中設置Orderer.AbsoluteMaxBytes項的值,以字節為位置,不包括區塊頭信息大小。
3)創建創世區塊
使用 configtxgen 工具,根據步驟1和2中配置生成創世區塊。
4) 配置Kafka集群
設置unclean.leader.election.enable為false;
設置min.insync.replicas為M(數字),數據提交時會寫入至少M個副本,值的范圍為1<M<N(N為default.replication.factor值);
設置default.replication.factor為N(數字),N表示在Kafka節點上每個channel都保存N個副本的數據;值的范圍為1<K(K為Kafak集群數量);
設置message.max.bytes值,message.max.bytes應該嚴格小於socket.request.max.bytes的值,socket.request.max.bytes的值默認被設置為100MB;
設置replica.fetch.max.bytes值,每個通道獲取的消息的字節數;
設置log.retention.ms為-1,關閉基於時間的日志保留方式。
5) 所有排序(Orderer)節點都指向創世區塊
在 orderer.yaml 文件中配置 General.GenesisFile參數, 讓排序(Orderer)節點指向步驟3中所創建的初始區塊。
6)調整輪詢間隔和超時時間
在orderer.yaml 文件中配置Kafka.Retry參數, 調整 metadata/producer/consumer 請求的頻率以及socket的超時時間。
7) 設置排序節點和 Kafka 集群間為SSL 通訊
在orderer.yaml文件中配置Kafka.TLS參數,確定是否通過TLS(安全傳輸層協議)進行通信。
8) 集群啟動順序
先啟動ZooKeeper 集群,然后啟動 Kafka 集群,最后啟動排序(Orderer)節點。
6.3.6 接口(SDK)
Fabric SDK提供調用賬本(Ledger)、智能合約(Smart contract)、通道(Channel)、節點(Peer)、排序(Orderer)等接口,方便用第三方應用程序的開發,大大擴展了Fabric的應用場景。
Hyperledger Fabric提供了許多SDK來支持各種編程語言,目前正式發布了Node.js和Java兩種版本的SDK。將來還會發布Python、Go、REST版本的SDK,還在測階段。
Fabric SDK應該可以為開發人員提供編寫應用程序的多種操作區塊鏈網絡的方式。應用程序可以部署/執行chaincode,監聽網絡中產生的事件,接收塊信息,把交易存儲到賬本中等。
接口模塊如下:
1) Package: Hyperledger Fabric Client
模塊 |
等級 |
功能 |
Client |
0 |
主要的入口模塊。它必須允許用戶創建需要的任何對象來執行所有支持的操作,例如直接連接網絡,chaincode部署,交易執行,多種查詢。另外,基於編碼規范和普遍的社區練習,每一種語言的實現也能決定是否添加方便的方法,如sendTransaction(chain, tx) |
Chain |
1 |
一個鏈代表一些節點特別形成的一個網絡,啟動一個共識的通道,在通道中交易可以被獨立的處理。一個網絡可能有一個或多個鏈。鏈上的節點維護一個單獨的賬本包含交易在鏈上派發,包括成員關系的任何配置。所有的交易都是在鏈上發送,一個應用可能操作多個鏈。 |
Peer |
2 |
代表網絡上的計算節點。節點的角色有背書節點和提交節點,它們都在維護着賬本。應用可能連接到一定數量的可用的節點 |
Orderer |
2 |
類似節點,不同的是它代表排序服務的終端,可能是一個單獨的節點(開發時本地安裝)或者一個網絡排序者的代理節點。基於區塊鏈網絡的fabric會有一個由多個排序者節點組成的單獨的排序服務。應用可以選擇信任特定的排序者,或者一部分排序者,或者設置代理去給排序者節點廣播交易。 |
User |
2 |
代表在網絡上交易的用戶。用戶實例可以基於登記證書被初始化。證書可以從成員服務或者外部CA獲取。理論上,這種用戶也能代表網絡上的節點成員。然而,這與應用程序無關(這更像是網絡管理方面的問題),所以在這個設計中沒有開放。 |
Proposal |
3 |
登記的用戶可以向節點列表提出交易提案來背書交易。一旦接收到背書響應,應用程序可以決定是否已經獲取背書簽名,是否需要執行提交交易到共識服務。這是關於提案原始的GRPC消息的包裝類,它提供了便利的創建方法。 |
ProposalResponse |
3 |
提案調用背書節點的響應,打包背書結果(是或否),簽名,等等。這是關於提案響應原始的GRPC消息包裝類,它提供了便利的方法來利用它自己的內容(背書,簽名,等等)。 |
Transaction |
3 |
登記用戶收集了背書之后可以提交交易。交易請求包含背書簽名和MVCC+post-image,並且使用排序服務API。交易有兩種類型:部署和執行。這是交易有關原始GRPC消息的包裝類,它提供了便利的創建方法。 |
CryptoSuite |
3 |
加密模塊打包了數字簽名算法,非對稱加密的密鑰對,對稱加密的密鑰消息,安全的hash和MAC。 |
2) Package: Member Service
模塊 |
等級 |
功能 |
MemberService |
0 |
這是fabric可選模塊的客戶端,成員服務。本模塊的主要功能是從成員服務獲取用戶登記證書。另外,這個模塊本身或它的擴展類也應該能在fabric默認的成員服務的實現中提供可用的額外的功能,如用戶注冊功能。 |