Ethereum 在分散式系統設計的取捨

fcamel
fcamel的程式開發心得
9 min readMar 21, 2020

先前看了《Designing Data-Intensive Applications》對分散式系統有了概念,想來隨筆寫一下 Ethereum 設計上的取捨。

注意,本文著重於目前的 Ethereum,在 Ethereum 2.0 會改善一些問題。但是一來它還沒上線,二來我也不熟,所以本文是用現有的 Ethereum 分析。

需求分析

和一般分散式系統不同,像 Ethereum 這樣的公有鏈,多考慮了 Byzantine Fault Tolerance (BFT),也就是連線的節點可能會騙人。這表示不能相信拿到的任何資料,需要驗證核心資料,通訊協定要假設可能受騙 (例如對方可能故意不回應或給假資料)。這個需求帶來許多複雜和計算、儲存、通訊成本。

不過若有適合的應用,其它分散式系統用事後補強驗證的作法,很難作到一樣的安全保證。舉例來說,Ethereum 用 Merkle Patricia Tree 表示狀態,每次更新都能立即算出全部狀態合起來的 hash,兩個節點比對 hash 即可判別雙方狀態是否一致,每次更新區塊都會比對狀態是否一致。

至於其它需求:

  • Reliable: 天生支援 replication,提供同步資料的協定。採用 Proof of Work (POW),任何人都能出塊,讀寫的 reliability 都很好。
  • Scalable: 讀的部份沒問題,但為了保證 POW 安全性,10s 左右出一個區塊 (帶有新的 transactions),寫入沒有效率。Ethereum 2.0 會有 sharding 等方法提升寫的效率,不過不知何時會作完。而且cross shards transaction 相當困難,不知要怎麼做到可靠又有效率。
  • Maintainable: 依需求而定,不好判定。

區塊、狀態、時間

Ethereum 有一個 canonical chain,表示目前相信的歷史軌跡。每個區塊都帶有零到多個 transactions (後述),執行 transactions 後會更新狀態。

每個 account address 有自己的狀態,包含有多少錢和額外資料。可以是一般帳號或是 smart contract。轉帳給 smart contract 就是呼叫 smart contract 的 API。

由於時間不可信,在區塊鏈的世界裡,是以區塊高度表示時間。大家在同一高度預期會有一樣的狀態。即使目前不同,隨著區塊不斷成長,最後會有一樣的狀態。

Data Model

類似 Document database,每個 account address 是 key,value 可以存金額和資料,甚至包含可執行的 byte codes。

沒有提供 query language,但有提供多種查詢狀態的 API,使用格式是 JSON RPC,可以作批次處理。沒有 query language 表示要作 OLAP 會比較麻煩,簡單的分析也得寫程式處理。

每個 smart contract 類似一個 table,有自己定的 API 和內部儲存格式。”schema migration” 是需要考慮的問題,我沒有研究這部份,不知是否成熟易用。

Transactions

Ethereum 區塊內存的資料就叫做 transaction,每個 transaction 是一個轉帳或和 smart contract 相關的操作。smart contract 像是 database 的 stored procedure,可用事先寫好的程式作複雜的操作。

雖然 Ethereum 沒有直接支援 database 的 transaction,但可透過 smart contract 做到 database transaction 最重要的 atomicity (abortability),在一個 smart contract call 裡做多筆轉帳,並保證全部一起成功或一起失敗。

其它一些相關知識:

  • 每個 account address 是由 public key 做些計算得出的。之所以要另算出 address 而不用 public key,我猜是因為 address 比 public key 短,藉此減少儲存空間 (address 長度會影響 merkle tree 高度)。
  • Transactions 必須用 source address 對應的 private key 簽名才能使用。這有個巧妙的應用: 可以將 private key 放在離線裝置 (冷錢包),在上面簽好 transactions 再傳回有網路的電腦使用。確保 private key 不會外流。
  • Ethereum 有 snapshot isolation (MVCC),可以取得不同區塊 (即不同時間) 的狀態,但對寫入效能沒幫助,因為 transactions 的執行是 serializable。犧牲效率換來資料的一致性。

Replications

Replication logs

Ethereum 的 replication logs 是原始的 transactions,不同於其它分散式系統採用 logical logs/write-ahead logs。這樣可以保證狀態是由原始資料 (transactions) 算出來的,而 transactions 有簽名認證,整個區塊包含 transactions 也有 hash 保證 integrity。

但要執行每筆 transaction 的代價也滿高的:

  • 同步效率不佳。包含檢驗 transaction 簽名和執行 transaction 都需花 CPU。在目前 transaction per second 不高的情況,還不是問題。但會拖慢新節點同步大量區塊的速度。
  • smart contract 無法使用 now()、rand() 這類函式,因為無法保證所有節點執行時有一樣結果。分散式系統要取得一致、不可事先預測、有效率的亂數,不是簡單的事。大致上是 commit & reveal 的形式,有興趣的人可以看看 Ethereum 的研究,還滿有趣的。

這些都是為了安全性而必要的代價。

Leaderless

現行 Ethereum 採用 leaderless replications,但沒有用 quorum。作法是人人都可出塊,之後再用最長鏈判斷那些資料會留下來。不用擔心 failover 的問題,但要多考慮 finality的問題 (何時資料會永久留存)。

實務作法是等 K 個區塊後,可以放心使用前 K 個區塊的狀態。愈重要的交易等愈多個區塊。也可以用歷史記錄判斷,比方說過去 X 年沒有超過 K 個區塊被 rollback 的記錄,所以用 K 滿安全的。

Blockchain 沒提供 read consistency 的保證,應用程式要自己處理。比方說發出 transaction T 得到 T 的 hash H。接著會作:

  1. 持續查詢 H,確認 H 已上鏈後,記錄 H 所在的區塊高度 N 和該區塊的 hash B。
  2. 等區塊成長到 N + K 的高度時,查詢區塊高度 N 的區塊,hash 是否仍為 B (表示是同一區塊)。

至此,應用程式才能放心資料不會消失。但應用程式不能假設所有節點會提供一樣新的資料,仍要小心處理 read-after-write consistency。

相較之下,leader-based replications 只要強迫從 leader 讀資料,即可保證取得最新資料。或是使用 quorum 的 leaderless 也可保證有 read-after-write consistency。相關說明,可見先前筆記

相較於 BFT-style POS,POW 簡單且容易處理 incentive design,計算時間是很大的成本。在 POS 要作出相似強度的 incentive design,不是容易的事。

同步資料的協定

為了支援 BFT,需要特別的同步協定,一方面透過 P2P 從不同來源取得資料,另一方面要驗證資訊正確性,避免受假資料影響。要作到有效率需要一些巧思。

Partitioning (Sharding)

還在開發中的 Ethereum 2.0 會支援 sharding,但沒作完前不好評論。cross shard transaction 很難作到可靠,要 atomic commit 就會很慢且有卡死的潛在風險 (參見先前 distributed transactions 的筆記)。

若有任何區塊鏈聲稱有作好 partitioning/sharding,可以先看他們怎麼處理 cross-shard transaction,勢必得在 reliability、efficiency、finality 之間作取捨。

Linearizability 和 Idempotence

Ethereum 和任何 Blockchain 一樣具 linearizability,只是 finality 並不明確。所以應用程式還是要自己小心處理讀到舊資料或資料「消失」的問題。

另一方面,Ethereum 為避免 double-spending attack,在每個 transaction 內有帶上 nonce (number used only once),這個 nonce 必須是從零開始的遞增整數。對 address A 來說,每次要產生 A 轉錢出去的 transaction 時,附帶 nonce 的值等於 A 先前有上鏈的 transaction 數量。因此,Ethereum 區塊鏈可以用 nonce 判別是否已執行過此 transaction。這樣具 idempotence 的特性,減輕應用程式負擔,不用擔心重覆執行會重覆扣款。

Message Broker

Ethereum 保有從第一天開始全部的 transactions,並提供 API 可以讀取全部 transactions 執行的記錄。smart contract 可以在執行過程產生 logs,供外部觀察者讀 logs 得知變化。可以想成 Ethereum 本身是 producer 兼 message broker。

這種永久性儲存、可移動 read offset (即區塊高度) 的設計,和 Kafka 這類 log-based message broker 滿像的,對開發者比較方便。有新需求時,可以寫新的 ETL 從頭取出關心的資料。我滿喜歡這種 log-based message broker 的設計。

為了安全性而要求保留全部資料 (也可設不同模式只保留最近的資料),在此前提下。提供 log-based message broker 不需額外的成本。

--

--