区块链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

相关文章
相关标签/搜索