區塊鏈100講:10分鐘教會你深挖以太坊數據層

image

在當下數據爆炸的信息時代,憑藉區塊鏈去中心化、點對點和防篡改的特性,「區塊鏈+大數據」已成爲研究的熱門,能夠說,區塊鏈與大數據的結合爲從此區塊鏈應用的大規模落地奠基了基礎。node

那麼,區塊鏈中的數據如何存儲?不一樣區塊鏈數據存儲機制有何異同?以以太坊爲例,在本文中,MIT 孵化初創公司 TowardsBlockChain 聯合創始人 vasa 詳細闡述了以太坊的數據存儲機制、以太坊如何存儲區塊鏈狀態與交易以及以太坊和比特幣在存儲機制上的異同。git

此外,本文將帶你深刻了解 「Patricia 字典樹」數據結構背後的理論基礎,並經過使用 Google 的 levelDB 數據庫演示以太坊字典樹的具體實現。github

字字行文皆重點,行行代碼皆乾貨,請往下看!web

從架構設計上來講,區塊鏈能夠簡單的分爲三個層次,協議層、擴展層和應用層。其中,協議層又能夠分爲存儲層和網絡層,它們相互獨立但又不可分割。數據庫

1

數據存儲層中存儲了什麼?

首先了解下區塊鏈的數據存儲層,什麼是區塊鏈數據存儲層?它存儲了什麼?它須要存儲哪些數據才能保障區塊鏈系統正常工做?npm

image

好比Alice向Bob轉帳10美圓。從上圖能夠看出,能夠經過向區塊鏈中加入一筆交易來改變區塊鏈當前的狀態。數組

在跟蹤不一樣用戶(狀態)的帳戶餘額和其餘相關的細節的同時,也要跟蹤不一樣用戶經過區塊鏈(交易)所引發的區塊鏈狀態轉變的細節。安全

不一樣的區塊鏈,好比比特幣和以太坊,實現上述功能所使用的方法是不一樣的。bash

1.1 比特幣的「狀態」微信

比特幣的「狀態」由其全網絡未使用的交易輸出UTXO(Unspent Transaction Output)來表示。比特幣的價值轉移是經過交易來實現的。更具體地說,比特幣用戶能夠經過建立一筆交易並將其一個或多個UTXO添加爲交易的輸入來花掉這一個或多個UTXO。

比特幣的UTXO模型,是其區別於以太坊的主要特徵,爲更好地理解兩者之間的差別,先來看一些例子。

首先,比特幣中的UTXO不能只花費一部分,必須所有花完

若是一個比特幣用戶要花費0.5個比特幣,而他只有一個價值1比特幣的UTXO,那麼在交易時他必須將本身的比特幣地址也加入到交易的輸出中,即發給本身0.5個比特幣做爲找零

若是他不給本身發送找零,他將失去這0.5個比特幣,這0.5個比特幣將會被看成交易費付給挖出此區塊的礦工。

image

UTXO交易

其次,從本質上講,比特幣的區塊鏈並不會存儲和更新用戶的帳戶餘額。在比特幣網絡中,用戶只需持有一個或多個 UTXO 的私鑰。

數字錢包的使用使得比特幣的區塊鏈看起來像是在自動存儲和更新用戶的賬戶餘額,但其實並非這樣。

image

圖解比特幣錢包工做過程

比特幣的 UTXO 模型運行良好,一部分緣由是數字錢包可以執行與交易相關的大多數任務,包括但不侷限於:

  • 處理 UTXO

  • 存儲密鑰

  • 設定交易費用

  • 提供交易找零地址

  • 彙總 UTXO(顯示可用的、交易進行中的和總餘額)

如何來描述 UTXO 模型中的交易行爲?鈔票是一個絕佳的類比。

用戶經過將錢包(類比比特幣地址或者數字錢包)中的鈔票(類比 UTXO)相加來計算本身的資金,想要花錢時,就使用一張或者多張鈔票。

每張鈔票只能使用一次,由於一旦花費,它就不屬於你了。

所以,能夠得出這樣的結論:

  • 比特幣區塊鏈並不存儲和更新帳戶餘額

  • 比特幣錢包持有UTXO對應的私鑰

  • 若是UTXO包含在交易中,那麼它會被所有花完(在 UTXO 大於支出金額時,會收到一個全新 UTXO 的「找零」)

1.2 以太坊的「狀態」

與上述比特幣的區塊鏈不一樣,以太坊區塊鏈中的狀態可以存儲和更新用戶的帳戶餘額等信息

以太坊的狀態不是一個抽象的概念,它是以太坊底層協議的一部分。

正如以太坊黃皮書所提到的,以太坊是一個基於交易的「狀態機」,是一個能夠構建全部基於交易的「狀態機」的技術。

與全部其餘區塊鏈同樣,以太坊的區塊鏈由創世區塊開始延伸。

從創世區塊開始,諸如交易,部署智能合約和挖礦等行爲將不斷改變以太坊區塊鏈的狀態。在以太坊中,每當有與該賬戶相關的交易發生時,賬戶餘額(存儲在狀態字典樹中)就會發生變化。

賬戶餘額等數據並不直接存儲在以太坊區塊鏈的區塊中, 只有交易字典樹、狀態字典樹和收款字典樹的根節點哈希直接存儲在區塊鏈中。以下圖:

image

存儲字典樹(保存全部智能合約數據的地方)的根節點哈希實際上指向狀態字典樹,而狀態字典樹又指向區塊鏈。

以太坊中存儲着兩種大相徑庭的數據:永久數據和臨時數據。

交易信息爲永久數據,一筆交易在獲得徹底確認後,將被記錄在交易字典樹中,它永遠不會改變;帳戶餘額則爲臨時數據,地址對應的帳戶餘額存儲在狀態字典樹中,而且每當出現與該指定賬戶相關的交易時帳戶餘額就會更改。

所以,永久數據和臨時數據應單獨、分別存儲,以太坊使用字典樹的數據結構來管理數據。

以太坊的記錄保存機制與銀行同樣,一個類比就是使用ATM /借記卡。

銀行跟蹤每張借記卡的餘額,當用戶須要花錢時,銀行會檢查交易記錄,以判斷用戶是否有足夠的餘額來進行交易。

1.3 比特幣 UTXO 模型與以太坊帳戶/餘額模型的比較

比特幣 UTXO 模型的優勢:

  • 可擴展性:因爲能夠同時處理多個 UTXO,所以能夠實現並行交易並可促進在可擴展性上的創新。

  • 隱私保護:即便比特幣不是一個徹底匿名的系統,但只要用戶每筆交易都使用新地址,UTXO 模型就能提供更高級別的隱私保護。若是須要加強隱私保護,能夠考慮使用更復雜的方案,例如環簽名。

以太坊帳戶/餘額模型的優勢:

  • 簡單性:以太坊選擇了更簡單直觀的模型,便於開發人員實現複雜的智能合約,特別是那些須要以太坊網絡狀態信息或涉及多個參與方的智能合約。

    好比基於以太坊網絡的不一樣狀態執行不一樣任務的智能合約,若使用 UTXO 的無狀態模型,須要強制在每筆交易中加入狀態信息,這會使智能合約的設計複雜化。

  • 高效性:除了簡單性以外,以太坊帳戶/餘額模型更加高效,由於每筆交易只須要驗證發送方帳戶是否有足夠的餘額來支付交易。

爲防止以太坊帳戶/餘額模型遭到雙重支付攻擊,能夠用一個遞增的隨機數來防範這種類型的攻擊。

在以太坊中,每一個賬戶都有一個公共可見的隨機數,每次進行交易時,這個隨機數增長1,這種機制能夠防止同一筆交易被屢次提交。

這個隨機數與以太坊工做量證實的隨機數不一樣,後者是一個挖礦過程的隨機值

**在計算機體系架構中,有時須要在不一樣模型之間進行折衷。**一些區塊鏈技術,好比 Hyperledger,就採用了 UTXO 機制,由於這樣能夠從比特幣區塊鏈所衍生的創新中受益。

接下來簡要分析更多基於這兩種記錄保存模型構建的技術。

2

以太坊字典樹數據結構

以太坊字典樹數據結構主要包括狀態字典樹、存儲字典樹和交易字典樹。

2.1 狀態字典樹——獨一無二的存在

在以太坊網絡中有一個惟一的全網絡狀態字典樹

這個全網絡狀態字典樹不斷在更新。

這個全網絡狀態字典樹中包含以太坊網絡中每一個帳戶所對應的鍵值對(key and value pair)。

全網絡狀態字典樹中的「鍵」是一個的160位標識符(以太坊賬戶的地址)。

全網絡狀態字典樹中的「值」是經過對以太坊帳戶的如下詳細信息進行編碼(使用遞歸長度字典編碼(Recursive-Length Prefix encoding,RLP)方法)生成的:

  • Nonce:一個公共可見的隨機數。若是賬戶是一個外部賬戶,這個數字表明從賬戶地址發送的交易數量;若是賬戶是一個合約賬戶,Nonce 是賬戶建立的合約數量。

  • balance:這個地址擁有的 Wei(以太坊貨幣單位)數量,每一個以太幣有1e+18 Wei。

  • storageRoot :一個Merkle Patricia 樹根節點的哈希,它對賬戶的存儲內容的哈希值進行編碼,並默認爲空。

  • codeHash:EVM(以太坊虛擬機)的哈希值代碼。 對於合約賬戶,這是一個被哈希計算後並存儲爲codeHash的代碼;對於外部賬戶,codeHash字段是空字符串的哈希值。

狀態字典樹的根節點(在給定時間點整個狀態字典樹的哈希值)被用做狀態字典樹的安全且惟一的標識符;狀態字典樹的根節點在密碼學上取決於狀態字典樹全部內部的數據。

image

狀態字典樹(Merkle Patricia 字典樹的levelDB實現)和以太坊區塊之間的關係

image

狀態字典樹:在給定的區塊中,狀態字典樹根節點的 Keccak-256位哈希值被存儲爲「stateRoot」值

stateRoot: ‘0x8c77785e3e9171715dd34117b047dffe44575c32ede59bde39fbf5dc074f2976’

2.2 存儲字典樹——存儲智能合約數據的地方

存儲字典樹存儲全部智能合約數據,每一個以太坊賬戶都有本身的存儲字典樹。存儲字典樹根節點的256位哈希值做爲「storageRoot」值存儲在全局狀態字典樹中。

image

2.3 交易字典樹——每一個區塊一個

每一個以太坊區塊都有本身獨立的交易字典樹。

一個區塊中包含許多交易,區塊中交易的順序由挖出該區塊的礦工決定。

交易字典樹中到特定交易的路徑經RLP編碼後獲得交易在區塊中的索引。

因爲區塊鏈的防篡改性,已經被挖出的區塊不會再改變,因此區塊中交易的位置永遠不會改變。

一旦在區塊的交易字典樹中找到這筆交易,即便你反覆返回相同的路徑,檢索的結果也是相同的。

image

3

以太坊字典樹實例分析

主流的以太坊客戶端使用兩種不一樣的數據庫軟件解決方案來存儲字典樹。以太坊的 Rust 語言客戶端 Parity 使用 rocksDB 數據庫,而以太坊的 Go 語言,C ++ 語言和 Python 語言客戶端都使用 levelDB 數據庫。

本文中,主要帶你瞭解 levelDB 數據庫。

以太坊和 levelDB 數據庫

LevelDB 是一個開源的谷歌鍵值存儲程序庫,除了常規功能外,它還提供對數據的前向和後向迭代,從字符串鍵到字符串值的有序映射,自定義比較函數和自動壓縮。

自動壓縮功能使用開源 Google 壓縮/解壓縮程序庫 「Snappy」。Snappy 程序庫的設計目標並非追求最大壓縮率,而是追求很是高的壓縮速度。

**LevelDB 數據庫是一種重要的存儲和檢索機制,用於管理以太坊網絡的狀態。**所以,levelDB 是主流以太坊客戶端(節點),好比 go-ethereum,cpp-ethereum 和 pyethereum 的底層數據庫。

雖然能夠在磁盤上完成字典樹數據結構的實現(使用諸如 levelDB 之類的數據庫軟件),但重要的是要注意遍歷字典樹和簡單地查看鍵/值數據庫之間存在的差別。

爲了更詳細說明這些差別,可使用 Patricia 字典樹的程序庫來訪問數據庫levelDB 中的數據。

在以太坊客戶端上,執行交易、部署智能合約和挖礦等網絡操做,並觀察它們如何影響以太坊的「狀態」。

4

分析以太坊數據庫

以太坊區塊鏈中每一個區塊都包含許多 Merkle Patricia 字典樹:

  • 狀態字典樹

  • 存儲字典樹

  • 交易字典樹

  • 收款字典樹

要在特定區塊中引用特定的 Merkle Patricia 字典樹,須要獲取其根節點哈希值做爲索引。

使用如下命令,獲取創世區塊中狀態字典樹、交易字典樹和收款字典樹的根節點哈希值:

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

image

若是想獲得最新挖出區塊的根節點哈希(而不是創世區塊),使用如下命令:

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

獲取根節點哈希值以後,須要配置網絡環境。

4.1 安裝npm、Node、Level 和 EthereumJS

使用 Node.js,Level 和 EthereumJS(使用 JavaScript 語言編寫的以太坊虛擬機)三個程序來進行 levelDB 數據庫的實驗。

經過如下命令配置實驗環境。

cd ~

3sudo apt-get update

5sudo 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);

});

image

以上代碼的輸出

以太坊網絡中的帳戶只有在交易(與該特定帳戶相關的交易)發生時纔會被加入到狀態字典樹中。

例如,僅使用命令 「geth account new」 建立的新賬戶將不會被加入到狀態字典樹中;若是一筆成功的交易(一筆消耗了以太坊燃料並被加入到已挖出的區塊中的交易)與這個帳戶產生關聯,那麼這時該帳戶纔會出如今狀態字典樹裏。

這能夠防止惡意攻擊者不斷建立新賬戶,從而維持狀態字典樹的正常數據量。

4.2 解碼數據

以太坊在與 levelDB 數據庫交互時使用了**「改進的 Merkle Patricia 字典樹(Modified Merkle Patricia Trie)**」,擴展了字典樹數據結構。

例如,改進的 Merkle Patricia 包含一種方法,該方法能夠經過使用**「擴展」節點**來實現快速遍歷。

在以太坊中,一個改進的的 Merkle Patricia trie 節點能夠是:

  • 一個空字符串(NULL)

  • 一個包含17個項目的數組(分支)

  • 一個包含2個項目的數組(葉節點)

  • 一個包含2個項目的數組(擴展名)

因爲以太坊的字典樹是根據嚴格的規則進行設計和構建的,所以檢查它們的最佳方法是使用計算機代碼進行測試。

如下示例使用了 EthereumJS,當提供特定區塊的 stateRoot 以及以太坊賬戶地址時,運行下面代碼返回該賬戶的餘額。

image

如下代碼的輸出(以太坊地址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());

})

5

獨特的設計帶來了哪些優勢?

5.1 移動性

現在移動設備和物聯網(IoT)設備無處不在,而電子商務的將來創建在安全、強大和快速的移動應用上。

能夠說區塊鏈在移動性上取得了巨大進步,但咱們也必須認可區塊鏈大小的不斷增長是不可避免的。所以在平常移動設備上存儲整個區塊鏈是不切實際的。

5.2 速度快,不會影響安全性

以太坊網絡狀態的設計及其對改進的 Merkle Patricia 字典樹的使用爲其應用提供了更多的可能性。

在以太坊中字典樹上執行的每一個操做(添加、更新或刪除)都使用了肯定性的密碼學哈希值。

此外,字典樹根節點的密碼學哈希值能夠用做字典樹未被篡改的證據。例如,對字典樹數據的任何更改(例如增長 levelDB 數據庫中的賬戶餘額)都將徹底改變根節點哈希值。

這種密碼學特性爲輕客戶端(不存儲整個區塊鏈的設備)帶來了快速、可靠查詢的可能性,好比查詢帳戶 「0x ... 4857」 在區塊高度爲 「5044866」 的區塊上是否有足夠的資金完成這次交易等?

「Merkle 證實的空間複雜度與存儲數據量呈對數關係。這意味着,即便整個狀態字典樹的大小爲幾千兆字節,若是一個節點從受信任的源接收一個狀態,該節點只需下載一個幾千字節的證實數據就可以徹底肯定該字典樹上任何信息的有效性。」

5.3 額度限制

**在以太坊白皮書中,有一個關於活期儲蓄帳戶的概念。**在這種情景下,兩個用戶(多是丈夫和妻子,或着商業夥伴之間)每人天天最多隻能提取賬戶總餘額的1%。

雖然這個想法僅僅在白皮書中「進一步發展方向」的部分中被提到,但無疑它會引發人們普遍的興趣,由於理論上它能夠做爲以太坊底層協議的一部分(而不是被看成爲第二層協議或者是第三方錢包的一部分)。

UTXO對區塊鏈數據是不可見的,實際上比特幣區塊鏈並不存儲用戶的帳戶餘額。所以,比特幣的底層協議不太可能實現任何類型的每日額度限制。

5.4 消費者信心

相信隨着區塊鏈開發者的不斷努力,咱們會目擊輕量級客戶端的快速發展,目擊能夠與區塊鏈技術交互的安全、強大和快速的移動應用程序的大規模落地。

在電子商務領域想要實現區塊鏈技術的落地必須提升速度、安全性和可用性。經過巧妙的設計提供優異的可用性,安全性和性能,這將提升消費者信心同時增長大衆的採用率。

數據的存儲機制也是當下區塊鏈應用落地面臨的一大問題,它決定了區塊鏈的運行效率。

只有解決有關區塊鏈應用落地的痛點,區塊鏈才能真正走進人們的生活,給人們帶來便利!

內容來源:區塊鏈大本營

微信ID:blockchain_camp

做者 | Vasa  企業家、TowardsBlockChain 聯合創始人

編譯 | kou、Guoxi

image

相關文章
相關標籤/搜索