以太坊前缀树的实际示例
以太坊的各个主流客户端使用两种不同的数据库软件来存储前缀树,其中用 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
注意:如果你想得到最近的一个区块的根哈希值时(而不是创世区块),你可以使用如下指令。
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);
});
有趣的是,以太坊的账户中只在(与该账户有关的)交易完成后才加入到状态前缀树中。举例来说,仅仅使用 “geth account new” 指令在本地生成一个以太坊账户并不会改变状态前缀树,即使这之后网络又挖矿验证了数个区块也不会。然而一旦一个和该账户有关的交易成功(交易执行消耗了 gas 并且被挖矿验证)执行了,当且仅当此时这个账户会加入到状态前缀树中。这种做法机智地避免了不怀好意的攻击者通过不断制造新账户来使状态前缀树过度膨胀的后果。
解码数据
你现在应当注意到了 LevelDB 所返回的是加密后的结果。这是因为以太坊事实上使用了自己独特的 “默克尔帕特里夏树” 来与 LevelDB 进行交互。以太坊维基百科提供了改良 “默克尔帕特里夏树” 和 递归长度前缀(RLP)编码 的设计和实现方法。简单来说,以太坊通过上述的方法来对前缀树数据结构进行了拓展。比如说改良默克尔帕特里夏树通过使用“拓展”节点找到路径捷径(顺着前缀树)的方法。
在以太坊中,一个改良默克尔帕特里夏树节点只可能是以下的某一种:
一个空字符串(记作 NULL)
一个包含17个元素的数组(记作一个分支)
一个包含两个元素的数组(记作一个叶子节点)
一个包含两个元素的数组(记作一个拓展节点)
以太坊前缀树依循严格的规则设计并构建,而理解他们的最好方式则是通过使用计算机代码。如下的例子中用到了 ethereumjs 。ethereumjs 库的安装和使用都十分简便,是我们快速了解以太坊 LevelDB 数据库的绝佳工具。
以下代码(在有了某特定区块的 stateRoot 以及以太坊账户地址的情况下)将返回可直接阅读的账户余额。
-代码的输出结果(地址 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)