http://blog.talkingdata.com/?p=6172
在前一篇文章(Fabric和Sawtooth技術分析(上))中,我們着重跟大家分享了 Fabric 相關的內容,在本篇文章中,我們將圍繞着 Sawtooth 進行一些分析和探討。
Sawtooth 結構及分析Sawtooth 是 Intel 公司推出的企業級區塊鏈,2018年 Intel 將其貢獻給 Hypherlegder 項目。本文中筆者主要從 Sawtooth 的存儲結構、數據結構、網絡結構方面做簡要介紹。
Sawtooth

Sawtooth 的存儲結構
Sawtooth 使用名為 Radix Merkle Tree 的存儲結構,它融合了 Radix Tree 和 Merkle Hash Tree 的功能,先看看這兩種結構:
- Radix Tree
Radix Tree 概念比較拗口,簡單地說就記住在這個樹上,任何一個葉子節點的位置和一個 01 串唯一對應,因此我們可以根據一個 01 串組成的地址確定葉節點是誰。
下圖是一個 Sawtooth 所使用的 Radix Tree 對應的字符串,由70個16進制字符組成,前6位稱為命名空間前綴(Namespace prefix),后邊的是該前綴所對應的空間內可分配的地址范圍。

我們以 Sawtooth 預定義的一個 Transaction Family-IntegerKey 為例,注意 Sawtooth中 的 Transaction Family 相當於 Fabric 中的 chaincode 或者 Ethereum 中的 Smart Contract 。IntegerKey 的前綴(prefix)計算法方法是:hashlib.sha512(‘intkey’.encode(‘utf-8’)).hexdigest()[0:6],運算結果是 ‘1cf126’。那么,存儲該 transaction family 中一個 block 的地址就是:
address = “1cf126” + hashlib.sha512(‘name’.encode(‘utf-8’)).hexdigest()[-64:]
當然,地址的構成也可以更復雜一些,比如,有個自定義 Transaction Family 的前綴是 prefix = “my-transaction-family-namespace-example”。命名空間可以進一步划分為 object-type 和 unique-object-identifier 。其中,object-type = “widget-type”,unique-object-identifier = ”unique-widget-identifier”。那么,對應的字符串就可以計算如下:
hashlib.sha256(“my-transaction-family-namespace-example”.encode(‘utf-8’)).hexdigest()[:6] + hashlib.sha256(“widget-type”.encode(‘utf-8’)).hexdigest()[:4]
+ hashlib.sha256(“unique-widget-identifier”.encode(‘utf-8’)).hexdigest()[:60]
最后得到下面的地址:
‘4ae1df0ad3ac05fdc7342c50d909d2331e296badb661416896f727131207db276a908e’
眾所周知,2的10次方是1K,20次方是1M,30次方是1G,40次方是1T,對於一個名字空間,Sawtooth 為其保留64位,從存儲空間需求上講,即使有一些位被用來做子空間划分,應該也夠用了。在查找時,完全可以根據 Transaction 的名字找到它的存儲位置,所以檢索速度也會非常快。
我們可以認為,Sawtooth 就像一個原始的區塊鏈,每向后一層都可以分叉,以樹的形式組織數據存儲,而不再是以線性的方式來組織數據存儲。也可以把原始的比特幣區塊鏈理解為只有一個Transaction Family 的 Sawtooth。在完整的分析過 Sawtooth 以后,最應該記住的就是 Sawtooth 的存儲結構,它其后的所有設計都是基於這一結構。
- Merkle Hash Tree
Merkle Hash Tree 的特點是所有節點的值都是哈希值,每個哈希值是根據其子節點的哈希值計算出來的,所以任何一個節點和哈希值出現變化,它的上層節點的哈希值都會跟着變。

Sawtooth 采用 Radix Merkle Tree 結構做數據存儲的好處就是給定 Block 名及類別,直接計算哈希值,就找到它的存儲位置了。而且存儲空間是隔離的,每個 transaction family 的存儲空間和其它的都是分開的,互不影響。所以,從存儲結構來看,基於 Sawtooth 的區塊鏈天生是多條連的(Forked),很容易去解析它的分叉。
Sawtooth 的數據結構
Fabric 沒有去嚴格定義數據結構,Sawtooth 的數據結構也沒有什么值得特別提出的亮點。只要知道 Sawtooth 定義了 Transaction 包括Header 和 Payload 兩部分即可,而 Sawtooth 要求不管是一個還是多個 Transaction 必須被封裝在 Batch 中才可以提交給 Transactio n的 Processor ,或者說 Transaction Processor 只接受以 Batch 為最小單位的 Transactions 。同樣地,Batch 也包括 Header 和 Payload 兩部分,其關系如下圖所示:

Sawtooth 的網絡結構
如下圖所示,Sawtooth 的一個節點可能由如下幾個部件組成:Validator、Transaction Proc essor、REST API、以及 Client。Validator 是 Sawtooth 的核心部件,主要功能包括接收 Transaction 請求,並將其轉發給相應的 Transaction Processor 來處理,根據 Transaction Processor 的處理結果,決定如何生存新的區塊,如何給 Client 回顯。Validator 還要與其他的 Validator 協同,以保持 Sawtooth 網絡的全局狀態一致。Transaction Processor 顧名思義就是用來專門處理某一類型 Transaction Family 的 Transactions 的 Processor 。
Client 需要按照 Transaction 和 Batch 規定的數據結構生成請求,REST API 則是標准化的網絡傳輸數據格式。之所以說可能由這幾部分組成,是因為對 Sawtooth 來說,只有 Validator 屬於其固定結構,比如圖中有 Validator1 和 Validator2 ,而 Validator2 就沒有連接其他部件,而是只與 Validator1 相連。從構成角度看,一個 Sawtooth 網絡可以只由一個 Validator 構成。從網絡方面看,其他的 Validator 可以動態加入網絡。從部件方面看,Transaction Processor 可以動態注冊到 Validator ,然后 Client 提交相應 Transaction 就有對應的 Processor 來進行處理。網絡節點和部件可以分別使用不同的端口來區分。這樣,Sawtooth 網絡就變成完全動態的了,每個組成部分都可以動態插拔。

接下來,我們先看看 Validator 的組成結構,Validator 的軟件實現部分稱為 Journal,如下圖。從功能上看,Journal 包括 Completer、ChainController 和 BlockPublisher 三個主要部分。

當 Batch 被提交給 Validator 后,先被交付到 Completer ,它先檢查是否 Batch 的所有依賴項都得到了滿足。完整且滿足依賴的 Batch 會被提交給 ChainController 。Sawtooth 的這種設計可以支持串行和並行的 Batch,注意這已經不是進程級並行了,而是線程級並行。接下來再看看 ChainController 是干什么的:

ChainController 需要完成4個工作:
- 1)確定塊的頭在哪
- 2)確定當前塊在哪-先去 BlockCache 里找,再去 BlockStore 里找。
- 3)驗證塊有沒有損壞
- 4)把新塊寫進區塊鏈
寫入新區塊鏈后的發布工作則是由 BlockPublisher 完成。從圖中可以看到,ChainController 和 BlockPublisher 本質上都是接口,具體的實現由更下層的共識(Consensus)機制完成,共識機制向上提供 BlockVerifier,ForkResolver 和 BlockPublisher 三個功能。
從整體上看,Validator 的結構比較簡單。接下來再看看 Validator 之間是怎么連接起來的。Sawtooth 的 Validator 的網絡連接方式如下圖,可能會有點亂,同時解釋地也不是很清楚,這里筆者的理解是:把它看做一個 Ad Hoc 網絡,組網的過程完全就是模擬 Ad Hoc 網絡路由節點發現的。開始的時候有初始(生成 Genesis block 的)節點,它可以發出廣播包,問誰在它邊上,可以按照設定的規則加入網絡,如果有人應答就可以加入,然后這些節點繼續廣播,每個廣播包只傳播給距離自己1跳的 Validator 節點,這樣網絡很快就組成了。有新節點想加入也是這樣,發廣播包,看看自己周圍有沒有可以直連的節點,退出就無所謂了,反正少一個節點不影響網絡。我們知道 Ad Hoc 網絡的健壯性和靈活性都是非常高的,所以 Sawtooth 的 Validator 網絡中任何節點都是可以動態加入或退出的,只要網絡規模足夠大,理論上,網絡的健壯性是有保障的。

這里有兩個關鍵問題其實沒有說清。
- 1)哪些 Validator 可以加入該網絡
- 2)Validator 接受哪些 client 提交的 Batch
這兩個問題就構成了訪問控制和隱私保護功能的核心,而 Fabric 花大力氣實現的體系結構也正是為了回答這兩個問題,稍后會詳細說明,核心網絡介紹完畢以后,會想到 Client 提交了 Transaction,那Transaction 執行與否?所以,還差一個事件機制來實現消息傳遞和回顯功能。這里的事件機制要確定這么幾件事:
- 1)誰應該被告知事件—(廣播?還是根據注冊情況組播單播)
- 2)事件應該包括什么—(消息格式-收據(Transaction Receipt))
- 3)事件在什么情況下,或什么時候才算有效—(放在 Transaction Receipt Store 中,通知發起 Transaction 的 client 來拿)
下圖是 Sawtooth 的事件機制示意圖,它把技術實現和組件名稱混到一起,看起來也比較亂。大體的意思是左上部是事件子系統用 Zero message queue 的技術實現,其特點是在需要的時候可以隨時創建,右上部是寫好的類庫,注冊后,只要滿足約束就可以調用它。下部說的是 Transaction Processor 調用具體的 Handler 處理 Transaction 后會告訴 ChainController 的 Scheduler 和 Executor 執行 Transaction 的結果情況,ChainController 除了把新的 block 寫到它應該在的地方之外,還會把 Transaction 的 Receipt 放到一個叫 Transaction Receipt Store 的地方,這時候 ChainObserver (Client 注冊后產生的一個部件)就會告訴 Client, Transaction 執行完了,來拿收據吧。

下面是一些事件的例子,可以幫助我們理解事件的格式:
1Example events generated by BlockEventExtractor: 2Event { 3 event_type = "sawtooth/block-commit", 4 attributes = [ 5 Attribute { key = "block_id", value = "abc...123" }, 6 Attribute { key = "block_num", value = "523" }, 7 Attribute { key = "state_root_hash", value = "def...456" }, 8 Attribute { key = "previous_block_id", value = "acf...146" }, 9 ], 10} 11 12 13Example events generated by ReceiptEventExtractor: 14// Example transaction family specific event 15Event { 16 event_type = "xo/create", 17 attributes = [Attribute { key = "game_name", value = "game0" }], 18} 19 20// Example sawtooth/block-commit event 21Event { 22 event_type = "sawtooth/state-delta", 23 attributes = [Attribute { key = "address", value = "abc...def" }], 24 event_data = <bytes> 25}
Sawtooth的訪問權限控制
Sawtooth 的權限(Permissions)機制應該參考過 Fabric。主要功能包括網絡權限的設置(哪些 Validator 可以加入這個網絡),和 Transaction 權限設置(哪些 client 提交的 Batch 可以被 Validator 執行)兩種。和 Fabric 一樣的是,Sawtooth 也需要配置文件,如果所有功能全部用配置文件完成則稱為 Off-Chain transactor Permission,通常來說小規模網絡,極限情況下,只有一個節點的網絡完全可以使用 Off-Chain 的方式實現。在 Off-Chain Permission 中,權限是靜態設置的。在配置文件 validator.toml 中,直接配置:
[permissions]
ROLE = POLICY_NAME
Policy file:
PERMIT_KEY <key>
DENY_KEY <key>
這里的 ROLE,POLICY_NAME 暫不解釋,key 一般是一個公鑰,PERMIT_KEY 和 DENY_KEY 就將它理解為一個 bool 值,一個是1,一個是0,含義就是允許不允許。
和 Fabric 不一樣的地方是,Sawtooth 還有一種配置方式叫 On-Chain transactor Permission 。就是在區塊鏈上直接設置訪問權限,還專門為此設置了一個叫 Identity 的 Transaction Family 。這樣 transactor Permission 就有自己的存儲空間,其當前值也好,變化也罷,所有節點都可以同步過去,不會存在各個節點配置文件不一樣導致系統出錯的可能性。
接下來具體看下 Identity。Identity Namespace 以 key-value 對的形式存儲 roles,key 是 role name,value 是 policy。所有的 role 都以 transactor 為前綴。比如下面:
transactor.SUB_ROLE = POLICY_NAME
首先,第一個問題是開始誰可以設置訪問權限。和 Fabric 例子中類似,機構 R4 通過網絡配置文件設置訪問權限一樣。在 Sawtooth 中,理所當然的應該由創始區塊的生成者來設置初始權限。它執行如下命令來設定允許給別人授權的人的公鑰:
sawset proposal create sawtooth.identity.allowed_keys=00000
這里的00000是創始區塊的生成者自己的公鑰,那現在就它自己可以給別人授權。這個類似於 Fabric 里設定,公鑰設定以后可以利用 identity-tp 進行授權,也可以繼續用 sawset proposal create 命令讓其他 Validator 有權做網絡或者 Transaction 授權。proposal 這個子命令其實就能猜到 Sawtooth 設計訪問權限的時候肯定是參考了 Fabric 的。
具體的 Transaction 授權命令的例子如下:
sawtooth identity policy create policy_1 “PERMIT_KEY *”,這個的意思是創建一個叫 policy_1 的策略,對所有人都是允許的,也就是誰都可以提交 Transaction ,注意這僅僅是個策略,還可以定義其他的策略,相當於 Fabric 里的 Deploy 而不是 Invoke 。可以調用 sawtooth identity policy list,查詢當前有哪些策略,比如在執行了剛才的命令后,會得到如下回顯:
$ sawtooth identity policy list
policy_1:
Entries:
PERMIT_KEY *
接下來執行如下命令:
$ sawtooth identity role create transactor policy_1
就會把 transactor 的策略設置為 policy_1。換句話說,這時,就真正讓 policy_1 生效了,類似於 Fabric 里的 Invoke。然后可以調用sawtooth identity role list,查詢當前角色的狀態:
$ sawtooth identity role list
transactor: policy_1
上邊我們都用 transactor 為例子,其實 role 可以有如下幾種:
default、transactor、transactor.transaction_signer、transactor.transaction_signer.{tp_name}、transactor.batch_signer。
意思其實從字面上能看出來,這里 transactor 可以是 organization,一個 transactor.batch_signer 可以是一個 organization 下邊的部門,transactor.transaction_signer 可以是該部門的一個用戶,如果有好多 tp 的話,該用戶可以只具有其中某些 tp 的執行權限。
比如,我們現在自己寫了一個新的 tp 叫 supply_chain,新定義了兩個用戶,一個的公鑰是12345,另一個的公鑰是23456,我們希望這個 tp 只有這兩個新用戶可以運行,這時我們就可以執行命令:
sawtooth identity policy create policy_2 “PERMIT_KEY 12345” “PERMIT_KEY 23456”
sawtooth identity role create transactor.transaction_signer.supply_chain policy_2
網絡權限設置和 tp 執行權限設置差不多,比如有個 Validator 對外的公鑰是00001,然后執行如下命令,它就不能加入網絡了:
$sawtooth identity policy create policy_3 “DENY_KEY 00001”
$ sawtooth identity role create network policy_3
$ sawtooth identity role list
network: policy_3
Fabric 和 Sawtooth 的比較
相信看完了 Fabric 和 Sawtooth 的介紹,大家對這兩個項目都有了自己的認識。筆者再談一下個人對這兩個項目的理解。
首先,從背景上看,Fabric 是脫胎於 Ethereum 的,或者說能在 Fabric 上看到 Ethereum 的影子,而 Sawtooth 已經看不到 Ethereum 的任何痕跡了。反而,個人覺得 Sawtooth 更加純粹一點,它就是一個有共同起點的 比特幣區塊鏈,只是比特幣沒有不同 Transaction 的概念,而 Sawtooth 把 Transaction 按照用途分類,且允許用戶根據需要自己定義新的 Transaction (這個概念是 Ethereum 提出來的)。
從體系結構角度看,我認為 Sawtooth 明顯優於 Fabric 。Fabric 像是針對特定問題的一個專用解決方案,而不像一個通用架構。Sawtooth 可以認為是一個通用架構,然后根據需求變為一個專用解決方案。從訪問控制角度看,Fabric 像是根據實際情況建設了一套管網,讓涉及隱私的數據只能在其中運行。Sawtooth 則更像是在一套管網上加裝了閥門,通過閥門來控制數據的流向。這可能和 IBM 以及Intel 的業務特點和公司文化息息相關。我的印象中,IBM 是一家咨詢公司,專門針對企業級用戶設計解決方案。Intel 是處理器公司,不管你的業務是什么樣的,它提供通用中央處理器,讓你能根據自己的需要配置成自己的專用解決方案。
當然,二者也有一些具體的差異:Fabric 中,多個節點收到相同的輸入后分別獨立執行,以期得到一致的結果。Sawtooth 的 SGX 和基於 SGX 的 PoET 驗證的是在一台機器上的執行結果,沒有再讓每個節點都去執行一遍,而是一個執行完了以后去和別的同步結果。二者的假設不同,效率上也有差別。Fabric 的權限控制依賴形成 channel 這種體系結構,Sawtooth 的權限控制通過 Transaction 本身進行設置。或者說,Fabric 只有特定的人能看,但能看到特定范圍內的全部。Sawtooth 所有人都可以看,但 Transaction 的 Permissions 限制了能看什么。Fabric 的 orderer 完成 Transaction 排序,可以實現進程級並行。Sawtooth 的排序是在 batch 里指明依賴關系,通過Completer 排序實現線程級並行。
對 Fabric 來說,要設計它時,大家認可的網絡結構就是 Ethereum 了,怎么能稍作修改,實現隱私保護和訪問控制是它的設計目標。Sawtooth 沒這種包袱,可以重新設計網絡。所以,我感覺 Fabric 更像是一個過渡性的產品。從我的角度看,Sawtooth 的結構設計的比較精巧,可擴展性強,這是它比 Fabric 強的地方。但它還可以借鑒 Fabric,增加類似 CA 的機制確保用戶可以被識別,也應該增加權限配置的靈活性,比如引入 ABAC,等等。不過,現在這樣就已經非常不錯了。
