深入探索以太坊世界狀態,Part-2


深入探索以太坊世界狀態,Part-2


以太坊前綴樹的實際示例

以太坊的各個主流客戶端使用兩種不同的數據庫軟件來存儲前綴樹,其中用 Rust 寫成的 Parity 客戶端使用 RocksDB ,而以太坊的 Go 、C++ 以及 Python 客戶端使用 LevelDB 。

以太坊和 RocksDB

Rocksdb 不在本文的討論範圍之內,可能在以後我們會推出相關的文章,但是現在,讓我們一起看看使用 LevelDB 的三種主流以太坊客戶端吧。

以太坊和 LevelDB

LevelDB 是谷歌開源的一個鍵值存儲庫,除開其他方面,它提供了對數據的前向和後向迭代,從字符串類型鍵到字符串類型值的有向圖,自定義比較算法以及自動壓縮等功能。數據會自動地使用 “Snappy” 進行壓縮,那是谷歌的一個開源壓縮/解壓縮庫。

雖然 Snappy 並不致力於高的壓縮比率,但具備極高的壓縮速率。LevelDB 是管理以太坊網絡狀態的重要存儲和檢索手段。因此,LevelDB也成為了最流行的幾種以太坊客戶端(節點)的必要依賴環境,例如 go-ethereum, cpp-ethereum 和 pyethereum 。

雖然前綴樹數據結構的生成能在硬盤上完成(使用像 levelDB 一樣的數據庫軟件),但需要明白在前綴樹中增刪改和在單純的鍵值對數據庫中進行操作是截然不同的。

為了更深入的理解,我們必須使用合適的帕特里夏樹庫來在 LevelDB 中進行數據存取操作。這個練習要求我們安裝以太坊。

關於安裝以太坊的操作我們已經寫了一篇額外的教程(和本文配套)。另一篇名為 “面向實驗和測試來快速搭建一個以太坊私有網絡” 的文章提供了指引你安裝和配置以太坊私有網絡的手把手教程。

一旦搭建好你的以太坊私有網絡,你就能執行交易並探究以太坊的“狀態”是如何根據交易、合約和挖礦來進行改變的。如果你並沒有準備好來配置一個以太坊私有網絡,也沒關係,下文將提供我們的範例代碼和以太坊私有網絡運行時的截圖。

分析以太坊數據庫

正如我們上文中提到的那樣,以太坊區塊鏈中有眾多的 Merkle Patricia 前綴樹(在每一個

區塊中都有引用):

  • 狀態前綴樹
  • 存儲前綴樹
  • 交易前綴樹
  • 收據前綴樹

接下來的章節中我們將假設你已經安裝並配置好了你自己的以太坊私有網絡,或者你願意跟著看看我們運行代碼並對以太坊 LevelDB 數據庫進行的討論。

為了找到某一個區塊中特定的默克爾帕特里夏樹,我們需要獲得它的根哈希作為索引。下圖中的三條指令分別可以使我們得到創世區塊中狀態前綴樹、交易前綴樹和收據前綴樹的根哈希值。

web3.eth.getBlock(0).stateRoot
web3.eth.getBlock(0).transactionsRoot
web3.eth.getBlock(0).receiptsRoot


深入探索以太坊世界狀態,Part-2


注意:如果你想得到最近的一個區塊的根哈希值時(而不是創世區塊),你可以使用如下指令。

web3.eth.getBlock(web3.eth.blockNumber).stateRoot

安裝 npm、node、level 以及 ethereumjs

我們將使用 nodejs、level 以及 ehereumjs 一系列工具來探查 LevelDB 數據庫的變化。下面的指令用於進一步搭建實驗環境。

cd ~
sudo apt-get update
sudo apt-get upgrade
curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash - sudo apt-get install -y nodejs
sudo apt-get install nodejs
npm -v
nodejs -v
npm install levelup leveldown rlp merkle-patricia-tree --save
git clone https://github.com/ethereumjs/ethereumjs-vm.git
cd ethereumjs-vm
npm install ethereumjs-account ethereumjs-util --save

在這一步運行如下代碼可以得到以太坊賬戶的公鑰(存儲於你以太坊私有網絡的狀態根之中)。這個代碼連接了以太坊的 LevelDB 數據庫 ,進入到了以太坊世界狀態中(使用區塊鏈中一個區塊的 stateRoot 值),然後接入了以太坊私有網絡中所有賬戶的鍵索引。

//Just importing the requirements
var Trie = require('merkle-patricia-tree/secure');
var levelup = require('levelup');
var leveldown = require('leveldown');
var RLP = require('rlp');
var assert = require('assert');
//Connecting to the leveldb database
var db = levelup(leveldown('/home/timothymccallum/gethDataDir/geth/chaindata'));
//Adding the "stateRoot" value from the block so that we can inspect the state root at that block height.
var root = '0x8c77785e3e9171715dd34117b047dffe44575c32ede59bde39fbf5dc074f2976';
//Creating a trie object of the merkle-patricia-tree library
var trie = new Trie(db, root);
//Creating a nodejs stream object so that we can access the data
var stream = trie.createReadStream()
//Turning on the stream (because the node js stream is set to pause by default)
stream.on('data', function (data){
//printing out the keys of the "state trie"
console.log(data.key);
});


深入探索以太坊世界狀態,Part-2


有趣的是,以太坊的賬戶中只在(與該賬戶有關的)交易完成後才加入到狀態前綴樹中。舉例來說,僅僅使用 “geth account new” 指令在本地生成一個以太坊賬戶並不會改變狀態前綴樹,即使這之後網絡又挖礦驗證了數個區塊也不會。然而一旦一個和該賬戶有關的交易成功(交易執行消耗了 gas *並且*被挖礦驗證)執行了,當且僅當此時這個賬戶會加入到狀態前綴樹中。這種做法機智地避免了不懷好意的攻擊者通過不斷製造新賬戶來使狀態前綴樹過度膨脹的後果。

解碼數據

你現在應當注意到了 LevelDB 所返回的是加密後的結果。這是因為以太坊事實上使用了自己獨特的 “默克爾帕特里夏樹” 來與 LevelDB 進行交互。以太坊維基百科提供了改良 “默克爾帕特里夏樹” 和 遞歸長度前綴(RLP)編碼 的設計和實現方法。簡單來說,以太坊通過上述的方法來對前綴樹數據結構進行了拓展。比如說改良默克爾帕特里夏樹通過使用“拓展”節點找到路徑捷徑(順著前綴樹)的方法。

在以太坊中,一個改良默克爾帕特里夏樹節點只可能是以下的某一種:

  • 一個空字符串(記作 NULL)
  • 一個包含17個元素的數組(記作一個分支)
  • 一個包含兩個元素的數組(記作一個葉子節點)
  • 一個包含兩個元素的數組(記作一個拓展節點)

以太坊前綴樹依循嚴格的規則設計並構建,而理解他們的最好方式則是通過使用計算機代碼。如下的例子中用到了 ethereumjs 。ethereumjs 庫的安裝和使用都十分簡便,是我們快速瞭解以太坊 LevelDB 數據庫的絕佳工具。

以下代碼(在有了某特定區塊的 stateRoot 以及以太坊賬戶地址的情況下)將返回可直接閱讀的賬戶餘額。


深入探索以太坊世界狀態,Part-2


-代碼的輸出結果(地址 0xccc6b46fa5606826ce8c18fece6f519064e6130b 的賬戶餘額)-

//Mozilla Public License 2.0 
//As per https://github.com/ethereumjs/ethereumjs-vm/blob/master/LICENSE
//Requires the following packages to run as nodejs file https://gist.github.com/tpmccallum/0e58fc4ba9061a2e634b7a877e60143a
//Getting the requirements
var Trie = require('merkle-patricia-tree/secure');
var levelup = require('levelup');
var leveldown = require('leveldown');
var utils = require('ethereumjs-util');
var BN = utils.BN;
var Account = require('ethereumjs-account');
//Connecting to the leveldb database
var db = levelup(leveldown('/home/timothymccallum/gethDataDir/geth/chaindata'));
//Adding the "stateRoot" value from the block so that we can inspect the state root at that block height.
var root = '0x9369577baeb7c4e971ebe76f5d5daddba44c2aa42193248245cf686d20a73028';
//Creating a trie object of the merkle-patricia-tree library
var trie = new Trie(db, root);
var address = '0xccc6b46fa5606826ce8c18fece6f519064e6130b';
trie.get(address, function (err, raw) {
if (err) return cb(err)
//Using ethereumjs-account to create an instance of an account
var account = new Account(raw)
console.log('Account Address: ' + address);
//Using ethereumjs-util to decode and present the account balance
console.log('Balance: ' + (new BN(account.balance)).toString());
})

結論

在上文中我們闡述了以太坊能夠良好地管理其“狀態”。這種聰明的預先設計有許多好處。

移動端親和性

隨著移動設備和物聯網設備的無處不在,未來的電子商務必將依賴於安全、強健且快速的移動應用。

既然我們承認了移動性上的優勢,我們也必須認識到區塊鏈數據大小不可避免地不斷增加。在每一個移動設備上存儲完整的區塊鏈網絡顯然是不切實際的。

高速而不降低安全性

以太坊世界狀態的設計和使用改良默克爾帕特里夏樹的做法在這一領域提供很多優勢。在前綴樹上操作的每一種函數(增刪改)都有一個確定的密碼學哈希,進一步來說,前綴樹根節點獨一無二的密碼學哈希能作為證據,保證前綴樹未被篡改。

舉例而言,任何對前綴樹進行的任何程度的改變(例如在 LevelDB 數據庫種增加某一賬戶的餘額),都將會完全顛覆根哈希。這樣的密碼學特性使得輕客戶端(不存儲整條區塊鏈的設備)成為可能,用戶可以很迅速且可信地查詢區塊類似某某賬戶 “0x … 4857” 是否有足夠餘額在 “5044866” 高度完成購買的問題。

“默克爾證明關於所存儲的數據量大小是對數複雜的。這就意味著即使整個狀態前綴樹有數 Gb 大小,如果一個節點能從可信源得到狀態根,那它就可以在僅僅下載數 Kb 證明數據的情況下百分百驗證樹上的任何信息。”[2]

限制消費

在以太坊白皮書 [3] 中提到了儲蓄賬戶這樣一種有意思的想法。在這種場景下,兩個用戶(也許是丈夫和妻子,或者是商業夥伴)可以每天從餘額中提走 1% 的存款。這個創意僅僅在白皮書中的 “未來應用” 中被提及,但它的意義在於理論上這樣的做法可以作為以太坊的基礎協議層(作為第二層解決方案或是第三方錢包的必要支持方案)。你可能會想到了本文開篇中對 UTXOs 的討論。正如我們所說 UTXOs 在區塊鏈上是黑箱的數據,比特幣區塊鏈實際上不存儲用戶的賬戶餘額。

因此比特幣網絡很難(也許不可能)實現任何一種限制消費的應用。

消費者信心

我們可以看到在這一領域的許多工作都離不開輕客戶端的發展。更確切地說,離不開安全、穩健且快速的,能與區塊鏈科技交互的移動應用。

一個能支撐電子商務的成功區塊鏈必須做到高速、安全和易用。提供更好用、更安全和性能更強的設計聰明的產品往往能增加消費者的信心,並且使普羅大眾得以認可。CyberMiles 團隊正在向著這個目標努力邁進著!

參考文獻

[1] Wood, G., 2014. Ethereum: A secure decentralised generalised transaction ledger. Ethereum Project Yellow Paper, 151.

[2] https://github.com/ethereum/wiki/wiki/Light-client-protocol

[3] https://github.com/ethereum/wiki/wiki/White-Paper#further-applications

原文鏈接: https://medium.com/cybermiles/diving-into-ethereums-world-state-c893102030ed

作者: Timothy McCallum

翻譯&校對: 安仔 Clint & Elisa

稿源:以太坊愛好者(https://ethfans.org/posts/diving-into-ethereums-world-state-part-2)


分享到:


相關文章: