Hyperledger Fabric 架構梳理
區塊鏈的數據結構
State數據結構 由peer維護,key/value store
Ledger 記錄了所有成功和不成功的狀態更新交易。Ledger被ordering service構造,是一個全排序的交易區塊(有效的和無效的)哈希鏈。
Ledger存儲在peer節點和orderer的一個子集裏。存儲在peer上的Ledger和存儲在Orderer上的Ledger不同地方在於peer上的Ledger在本地維護一個位掩碼(比特位)用來區分有效交易和無效交易。
PeerLedger在v1後續版本會被裁減。Orderers維護OrdererLedger用來保證peerLedger的容錯和有效性。V1
Ledger允許peer重放歷史所有交易並重新構建state狀態。這樣上文提及的state結構是可選擇的。
Nodes Nodes是指區塊鏈中的通信實體。一個Node是指一個邏輯功能,因此不同類型的Nodes能夠運行在同一臺物理服務器上。關鍵在於Nodes實例怎樣被組合在一個信任域裏以及和管控他們的邏輯實例關聯。
存在三種類型的Node:
Client:client提交交易調用到endorsers,廣播交易建議到ordering service(client應該先提交交易到網絡中的背書節點,獲取背書簽名後廣播交易到
Peer: peer發起交易,維護state狀態和ledger的副本。另外,peer可以擁有endorser的角色。
Orderer:orderer節點運行通信服務完成分發保證,類如原子的或者全部的order廣播。
Client client作為終端用戶必須連接到peer才能與區塊鏈通信。Client可能連接到任何peer節點。Client創建並調用交易。Client與peer和ordering service都會通信。
Peer peer接收排序後的狀態更新(區塊形式),維護狀態和賬本。
Peer可以同時實現endorsing peer
Ordering service nodes
Orderers 提供ordering service,用來保證交付過程。Ordering service 能夠通過不同方式實現:針對不同的網絡和節點故障模型包括中心化的服務和分布式的協議。
Ordering service提供一個共享的通信channel至clients和peers,提供廣播交易消息服務。Clients 連接到channel後,可以在channel上廣播消息,這些消息然後被分發到所有peers。
這個channel提供了原子分發消息方式(可以實現total-order分發和可靠性的消息通信)。換句話說,channel對所有連接的peer輸出同樣的消息,並且按照同樣的排序分發給所有peer。原子通信保障也被成為total-order廣播或者在分布式系統中成為共識。廣播的消息內容是納入到區塊鏈狀態中的候選交易。
分區(ordering service channels)。Ordering service能夠支持多channel,類似公有/訂閱消息系統。Clients連接到一個給定channel,然後發送和接收消息。Channels可以被理解為分區-clients連接到一個channel,對於其他channel的存在是無感知的,但是clients可以連接到多個channel。盡管Hyperledger Fabric有的ordering service實現支持多channels,為了簡單起見,下面的文檔中,我們假定ordering service包含一個獨立channel/topic。
Ordering service API。Peers通過ordering service提供的接口連接到ordering service提供的channel。Ordering service API包含兩個基本操作(一般為異步事件):
Booadcast(blob):client調用這個接口廣播任意的消息到channel中。在BFT中檔發送請求到一個服務也被稱為request(blob)。
Deliver(seqno, prevhash, blob):ordering service調用這個接口分發消息到peers,分發過程指定一個非負整形序列號、最近分發blob的hash值。也就是,這是一個ordering service的輸出事件。Deliver()在公共-訂閱系統中也被稱為notify()或者在BFT系統中被稱為commit()。
賬本和區塊構造。賬本包含ordering service的所有輸出數據。概要來說,這時一系列deliver(seqno,prevhash,blob)事件。通過prevhash構造一個hash鏈。
大多數時候,因為效率原因,ordering service會在一個deliver事件中組合多個blobs和輸出多個區塊而不是一個交易。這種情況下,ordering service必須強制確認一個每一個區塊中blob的排序。區塊中blob的個數會根據ordering service的實現動態選擇。
下面,為便於描述,我們定義ordering service屬性並解釋交易背書的工作流(假定一個deliver事件只包括一個blob)。這些內容很容易的可以拓展到區塊同理到一系列的deliver事件(根據上文描述的一個區塊中包括明確的blobs排序)。
Ordering service屬性
Ordering service的保障(或者原子廣播channel)規定了廣播消息做了什麽以及所有分發消息中存在的關系。這些保障如下:
1、安全性(一致性保障):只要peers連接到channel足夠長的時間(peers會斷開連接或crash,但是會重啟和重新連接),那麽他們就會收到完全相同的deliver(seqno, prehash, blov)消息。這意味著在所有peer節點上接收的輸出都按照同樣的順序。根據消息序列號以及同一序列號包含完全一樣的內容(blob和prevhash)可以實現peer節點排序的一致。註意這只是一個邏輯排序,一個peer節點接收到的deliver(seqno,prehash,blob)並不需要其他的peer節點實時的接收到相同的消息。但是,對於一個seqno,兩個正確的peer節點都會收到有同樣prevhash和blob的deliver。另外,除非有client(peer)調用broadcast(blob),否則是不會deliver任何blob的。每個broadcast的blob只會deliver一次。
Deliver()事件包含之前deliver事件中所包含數據的hash值(prevhash)(類似merkel值)。當ordering service完成原子廣播後,prevhash就是seqno-1序號的deliver event參數的hash值。這樣所有的deliver事件構成了一個哈希鏈,可以用這個哈希鏈去驗證ordering service的完整性。
2、活躍度(分發保障)ordering service的活躍度保障特指ordering service的一種實現方式。準確的保障能夠可能會依靠網絡或者節點錯誤模型。
原則上,如果提交的client沒有fail,那麽ordering service需要保證每一個連接到ordering service的正常peer最終可以接收到所有提交的交易。
總結來說,ordering service確保了一下屬性:
1、合約。對於任何不同的正常peer節點收到的deliver(seqno, prevhash0, blob0)和deliver(seqno, prevhash1, blob1)事件存在prehash0=prehash1和blob0=blob1。
2、哈希鏈的完整性。對於正常peer節點上接收到的deliver(seqno-1, prehash0, blob0)和deliver(seqno, prevhash, blob)存在prevhash=HASH(seqno-1||prevhash||blob0)。
3、連續性。如果一個正常的peer節點已經收到ordering service的輸出deliver(seqno, prevhash, blob),那麽這個節點已經收到事件deliver(seqno-1, prehash0,blob0)。
4、非創造性。Peer節點收到任何deliver(seqno, prevhash, blob)前肯定存在某個peer節點發送了broadcast(blob)事件。
5、不可復制性(可選)。對於正常peers節點收到的任何兩個事件broadcast(blob)和broadcast(blob’),如果blob=blob’,那麽seqno0=seqno1並且prevhash0=prevhash1。
6、活躍度。如果一個正確的client調用了broadcast(blob)事件,那麽每個正確的peer最終都會收到一個deliver(*, *, blob)事件。
交易背書的工作過程
下面,我們描述一個交易的更深層次的請求流程。
Client創建一個交易並發送交易到client所選擇的背書節點。
為了調用一個交易,client發送一個PROPOSE消息到一系列的背書節點(可能不是同時發送)。對於一個給定chaincodeID對應的背書peer,client通過peer節點(client必須連接到peer節點,client發送交易消息到背書節點也是通過連接的peer節點發送的)使用這些endorsing peers。Client所連接的peer節點通過endorsement policy可以知道endorsing peer序列。舉例來說,交易可以被發送到一個chaincodeID對應的所有endorsers。即便這樣,有些endorsers可以離線,其他的endorsers可能會反對或者選擇不對這個交易背書。提交交易的client試圖滿足可用的endorsers的策略。
下面,我們首先詳細描述PROPOSE消息的格式,然後我們討論client和endorsers可能的交互模式。
PROPOSE交易格式
PROPOSE消息的格式是<PROPOSE, tx, [anchor]>,其中tx是必須的,anchor可選參數在下面描述。
Tx=<clientID, chaincodeID, txPayload, timestamp, clientSig>。其中
clientID是提交交易client的ID
chaincodeID指的是交易所屬於的chaincode對應的ID
txPayload包含提交的交易本身
Timestamp對於一個新的交易單調遞增的整形值,值由client維護。
clientSig是client上tx其他字段的簽名
調用交易和deploy交易(引用特定用來部署的系統chaincode的invoke交易)的txPayload的詳細信息是不同的。對於invoke交易,txPayload包含兩個字段:
txPayload = <operation, metadata>
其中operation表示chaincode操作(function)和參數
Metadata表示和調用相關聯的屬性
對於deploy交易,txPayload包含如下三個字段:
txPayload = <source, metadata, policies>
其中source表示chaincode的源碼
Metadata表示與chaincode和application相關的屬性
Policies包含所有peer節點都可以獲取到的與chaincode相關的策略。例如背書策略。註意deploy交易背書策略並不在txPayload裏,deploy交易的txPayload僅包含了背書策略的ID和它的參數。
Anchor包含read version依賴,更具體的說是key-version對(anchor是K*N的子集),這把PROPOSE請求和特定version的keys(存儲在KVS)綁定在一起。如果client制定了anchor參數,那麽endorser僅當本地KVS中相應keys的version和anchor中匹配時才會背書。
tx加密的哈希值對於所有節點用來作為一個唯一的交易標識tid(tid-HASH(tx))。Client把tid存儲在內存中,等待endorsing peer的響應。
消息模式
Client決定和endorsers交互次序。例如一個client可以發送<PROPOSE,tx>(沒有anchor參數)到一個單獨的endorser,這個endorser然後生成了version dependencies(anchor)。Client然後可以使用上述生成的anchor作為參數發送PROPOSE消息到其他endorsers。同樣,client可以直接發送<PROPOSE, tx>(不包括anchor)到所有背書節點。不同的通信方式都有可能,client可以靈活地選擇不同方式。
Endorsing peer模擬交易,生成背書簽名
當從client收到一個<PROPOSE,tx,[anchor]>消息後,endorsing peer epID 首先驗證client的簽名clientSig,然後模擬這個交易。如果PROPOSE消息指定了anchor,那麽模擬交易僅僅依賴read version numbers(readset會在下文定義)。
模擬交易包括背書節點暫時執行交易(txPayload),通過交易所引用的chaincode(chaincodeID)和本地存儲的狀態副本。
作為運行的結果,endorsing peer計算read version依賴(readset)和狀態更新(writeset),在DB語言中也被稱為MVCC+postimage信息。
之前我們提到狀態包含key/value對,所有的k/v實例都是有版本的,每個實例包含有序的版本信息,這個版本信息會在key所對應的value被更新時遞增。Endorsing peer節點保存有所有k/v對,能夠被chaincode獲取,可以讀和寫,但是peer模擬交易時不會更新這個狀態。特別是:
1、在endorsing peer執行一個交易之前假定狀態為s,對於交易讀取的每個key值k,鍵值對(k,s(k).version)保存在readset中。
2、另外,對於交易要更新key k的值為新的值value v’時,鍵值對(k, v’)被添加到writeset中。作為可選,v’可以是新的值相對於原值的delta值。
如果client在PROPOSE消息中指定了anchor的值,那麽指定的anchor信息必須和endorsing peer生成的readset一致時才能模擬交易。(anchor值應該在有些交易依賴於之前交易的完成情況或者對當前狀態的條件有要求,只有狀態滿足一定要求或者某些交易完成後,才能模擬當前交易,這個時候anchor具有體現依賴關系的意義)。
然後peer節點把tran-proposal(也可能是tx)發送至peer的背書交易邏輯,稱為endorsing logic。默認情況下,endorsing logic接收tran-proposal然後簽名這個tran-proposal。但是,endorsing logic可能有很多功能,例如通過tran-proposal和legacy系統交互以及使用tx作為輸入來做決定是否背書一個交易。
如果endorsing logic決定背書這個交易,那麽會發送<TRANSACTION-ENDORSED, tid, tran-proposal,epSig>消息給提交交易的client(tx.clientID),其中:
tran-proposal := (epID,tid,chaincodeID,txContentBlob,readset,writeset)
這裏txContentBlob是是chaincode/transaction的特定消息,這樣做是為了可以使用txContentBlob在一些情況下代替Tx(例如:txContentBlob=tx.txPayload)。
epSig是endorsing peer對tran-proposal的簽名。
如果endorsing logic拒絕背書交易,endorser會發送消息(TRANSACTION-INVALID, tid, REJECTED)到提交交易的client。
註意在這一步endorser不會改變他的狀態,endorsement在模擬交易時產生的狀態更新不會影響到狀態。
Submitting client收集一個交易的endorsement並且通過ordering service廣播這個endorsement。
Submitting client直到接收到足夠的(TRANSACTION-ENDORSED, tid, *, *) 消息和簽名後,表明這個transaction proposal已經被背書。這個過程並不一定一次性完成,可能會分多次發送至背書節點完成背書。
足夠的數量多少由背書的策略決定。如果背書策略滿足了,那麽這個交易就已經被背書了;註意這是交易還沒有被提交。Client收到的用來表示一個交易已被背書的簽名消息TRANSACTION-ENDORSED集合被稱為endorsement。如果提交交易的client沒有收到transaction proposal的endorsement,那麽client會丟棄這個交易,稍後重試。
對於正常接收到endorsement的交易,我們現在開始運行ordering service。Client通過boroadcast(blob)調用ordering service,其中blob=endorsement。如果client沒有直接調用ordering service的能力,那麽client可能會通過client所連接的代理peer調用ordering service。這裏的peer必須是client所信任的,不會從endorsement中刪掉任何消息,否則更改後的endorsement會被認為無效。
Ordering service分發交易信息到peer節點
當peer收到deliver(seqno, prevhash, blob)事件後,同時peer已經完成所有序列號小於seqno的deliver事件的更新。Peer節點會做以下操作:
1、根據chaincode的背書策略確認blob.endorsement是否有效。
2、一般情況,peer驗證依賴關系blob.endorsement.tran-proposal.readset沒有同時被violated。復雜情況是,endorsement中的tran-proposals字段可能會不相同(endorsement中有多個背書節點返回的tran-proposal),這時endorsement策略指定了狀態怎樣更新。
根據狀態更新選擇一致性屬性還是“isolation guarantee”,依賴的驗證有不同的實現方式。串行是默認的“isolation guarantee”,除非chaincode endorsement策略指定了不同方式。
串行方式要求readset中每個key所對應的的version和sate中同樣key的version值相同,拒絕不滿足該條件的交易。
3、所有上述驗證都通過後,交易被認為有效的或者提交的。這時peer標記把PeerLedger上這個交易對應的bit位標記為1,同時應用blob.endorsement.tran-proposal.writeset更新到區塊鏈狀態(如果tran-proposal都是一樣的,否則endorsement 策略定義了方法選擇更新方式)。
4、如果blob.endorsement的背書策略驗證失敗,交易變為無效。Peer在peerLedger上的標記為上標記為0。無效的交易不會更改狀態。
註意這已經足夠滿足對所有正常的peer,執行一個給定序列號的deliver事件後有同樣的狀態。也就是說,在ordering service的保證下,所有的正常peer將會受到一致的序列deliver事件。當endorsement策略是規範的,readset中的版本依賴是確定的,所有的正常peer節點最終都會是一樣的結局,無論交易是否在一個有效的區塊裏。因此所有peer節點按照同樣的方式提交、應用同樣序列的交易。
背書策略
背書策略是背書一個交易的條件。區塊鏈peer節點有一系列預先設置的策略,用來背書安裝指定chaincode的deploy交易引用。背書策略可以參數化,這些參數能夠用deploy交易指定(安裝chaincode時設定背書策略)。
動態添加背書策略(例如通過在部署chaincode時使用deploy transaction添加)是非常敏感的,由於有限的策略驗證時間、決定性、執行和安全保證。因此動態添加背書策略是不允許的,但是未來會支持。
通過背書策略驗證交易
一個交易只有根據背書策略簽名後才會有效。一個invoke交易首先需要獲得滿足chaincode背書策略的endorsement,否則不會被提交。這個過程通過client和endorsing peer的交互完成。
背書策略的例子
背書策略可能會包含邏輯表達式,典型條件是讓endorsing peer對交易請求進行數字簽名。
假設chaincode指定背書節點集合為:
E = {Alice, Bob, Charlie, Dave, Eve, Frank, George}
那麽背書策略可能如下面示例:
1、獲取所有E中節點對tran-proposal的有效簽名
2、獲取E中任何一個節點的簽名
3、獲取E中節點對同一tran-proposal的簽名,選擇E中節點的條件是:(Alice OR Bob) AND (any two of: Charlie, Dave, Eve, Frank, George).
4、從7個endorsers中任意選擇5個進行簽名
5、假設endorses有金額或者權重,比如{Alice=49, Bob=15, Charlie=15, Dave=10, Eve=7, Frank=3, George=1},權重綜合為100。背書策略要求獲得具有絕大多數金額的背書節點簽名(簽名節點的集合金額加起來超過50)
6、5中金額的賦值可以是靜態的(在chaincode中固定),也可以是動態的(依賴chaincode的狀態並且能夠在運行中更改)
7、獲取(Alice Or Bob)對tran-proposal1的簽名以及對tran-proposal2進行簽名(any two of: Charlie, Dave, Eve, Frank, George)。Tran-proposal1和tran-proposal2不同點在於endorsing peer和狀態更新。
具體怎樣使用背書策略需要根據應用,對endorsers失敗和失效的希望彈性,還有其他因素。
Hyperledger Fabric 架構梳理