深入探索以太坊世界状态
zmhg799417
发表于 2022-12-4 05:28:33
138
0
0
面向实验和测试来快速搭建一个以太坊私有网络! P6 x) ?" `- i# Q
” 的文章提供了指引你安装和配置以太坊私有网络的手把手教程。一旦搭建好你的以太坊私有网络,你就能执行交易并探究以太坊的“状态”是如何根据交易、合约和挖矿来进行改变的。如果你并没有准备好来配置一个以太坊私有网络,也没关系,下文将提供我们的范例代码和以太坊私有网络运行时的截图。分析以太坊数据库正如我们上文中提到的那样,以太坊区块链中有众多的 Merkle Patricia 前缀树(在每一个区块中都有引用):状态前缀树存储前缀树交易前缀树收据前缀树
接下来的章节中我们将假设你已经/ c( |' z1 u0 o5 }+ k8 R: J* E
安装并配置好了你自己的以太坊私有网络$ V L2 j3 J2 m- Q' D/ n$ n- |
,或者你愿意跟着看看我们运行代码并对以太坊 LevelDB 数据库进行的讨论。为了找到某一个区块中特定的默克尔帕特里夏树,我们需要获得它的根哈希作为索引。下图中的三条指令分别可以使我们得到创世区块中状态前缀树、交易前缀树和收据前缀树的根哈希值。web3.eth.getBlock(0).stateRoot
web3.eth.getBlock(0).transactionsRoot
web3.eth.getBlock(0).receiptsRoot& r8 N0 h, U$ C3 i; o. a8 S
注意:如果你想得到最近的一个区块的根哈希值时(而不是创世区块),你可以使用如下指令。web3.eth.getBlock(web3.eth.blockNumber).stateRoot/ F- W& B6 s- u6 t S6 |0 k; g
安装 npm、node、level 以及 ethereumjs我们将使用 nodejs、level 以及0 v; M1 E9 g% m+ d5 v
ehereumjs0 y; A. H+ n: C, Q3 s. d4 z
一系列工具来探查 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( T5 i; l+ J$ O
sudo apt-get install nodejs
npm -v' h3 w6 [0 z* M+ f2 Y1 Q
nodejs -v
npm install levelup leveldown rlp merkle-patricia-tree --save# S+ O: ^3 H0 m6 d1 Q' s
git clone https://github.com/ethereumjs/ethereumjs-vm.git4 x) C0 ?0 L# l/ p) {) y
cd ethereumjs-vm
npm install ethereumjs-account ethereumjs-util --save
在这一步运行如下代码可以得到以太坊账户的公钥(存储于你以太坊私有网络的状态根之中)。这个代码连接了以太坊的 LevelDB 数据库 ,进入到了以太坊世界状态中(使用区块链中一个区块的 stateRoot 值),然后接入了以太坊私有网络中所有账户的键索引。//Just importing the requirements
var Trie = require('merkle-patricia-tree/secure');6 s, w" ~9 M! r( T. T3 l
var levelup = require('levelup');* Q$ }9 L) C* c& F" u
var leveldown = require('leveldown');/ A0 M, \7 @+ M6 [5 v9 i" Z
var RLP = require('rlp');
var assert = require('assert');$ {# B) W+ |- K2 ^7 i/ A t* k+ \' _' Z
//Connecting to the leveldb database
var db = levelup(leveldown('/home/timothymccallum/gethDataDir/geth/chaindata'));7 p# v7 n9 i2 ^
//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 library3 X+ I- y! R+ G( O j5 h
var trie = new Trie(db, root);) Q7 y8 X: |0 S7 W
//Creating a nodejs stream object so that we can access the data0 a+ b% E* p) |6 P
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);; I: N; ]7 q! P/ C
});& s7 n# m: B5 ~) [5 ~3 n" G
有趣的是,以太坊的账户中只在(与该账户有关的)交易完成后才加入到状态前缀树中。举例来说,仅仅使用 “geth account new” 指令在本地生成一个以太坊账户并不会改变状态前缀树,即使这之后网络又挖矿验证了数个区块也不会。然而一旦一个和该账户有关的交易成功(交易执行消耗了 gas *并且*被挖矿验证)执行了,当且仅当此时这个账户会加入到状态前缀树中。这种做法机智地避免了不怀好意的攻击者通过不断制造新账户来使状态前缀树过度膨胀的后果。解码数据你现在应当注意到了 LevelDB 所返回的是加密后的结果。这是因为以太坊事实上使用了自己独特的 “默克尔帕特里夏树” 来与 LevelDB 进行交互。以太坊维基百科提供了改良
“默克尔帕特里夏树”
和
递归长度前缀(RLP)编码# w" T) o. U! V1 ~. q: h
的设计和实现方法。简单来说,以太坊通过上述的方法来对前缀树数据结构进行了拓展。比如说改良默克尔帕特里夏树通过使用“拓展”节点找到路径捷径(顺着前缀树)的方法。在以太坊中,一个改良默克尔帕特里夏树节点只可能是以下的某一种:一个空字符串(记作 NULL)一个包含17个元素的数组(记作一个分支)一个包含两个元素的数组(记作一个叶子节点)一个包含两个元素的数组(记作一个拓展节点)
以太坊前缀树依循严格的规则设计并构建,而理解他们的最好方式则是通过使用计算机代码。如下的例子中用到了 ethereumjs 。ethereumjs 库的安装和使用都十分简便,是我们快速了解以太坊 LevelDB 数据库的绝佳工具。以下代码(在有了某特定区块的 stateRoot 以及以太坊账户地址的情况下)将返回可直接阅读的账户余额。-代码的输出结果(地址 0xccc6b46fa5606826ce8c18fece6f519064e6130b 的账户余额)-//Mozilla Public License 2.0% v+ a8 o1 f' n, o- Y
//As per https://github.com/ethereumjs/ethereumjs-vm/blob/master/LICENSE; U6 X$ ?7 m9 t3 H* K
//Requires the following packages to run as nodejs file https://gist.github.com/tpmccall ... a2e634b7a877e60143a
//Getting the requirements
var Trie = require('merkle-patricia-tree/secure');6 `" J+ M& f; k& E: h, u
var levelup = require('levelup');: X4 t3 p- Y @, x8 p! m8 e5 y
var leveldown = require('leveldown');/ r4 v3 r5 Q! j! x) ?' \0 P; S
var utils = require('ethereumjs-util');5 b( O5 o2 o. g5 u$ s' l# s. ?
var BN = utils.BN;# ~1 r F) @6 O) O
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.# ~% ]: D; f2 d& n5 t
var root = '0x9369577baeb7c4e971ebe76f5d5daddba44c2aa42193248245cf686d20a73028';* m* e: N. |+ i2 d
//Creating a trie object of the merkle-patricia-tree library7 k6 s- i/ h: G* l
var trie = new Trie(db, root);0 W( P& _2 Y, s2 y' N
var address = '0xccc6b46fa5606826ce8c18fece6f519064e6130b';
trie.get(address, function (err, raw) {/ G$ S9 b ?, J2 G) W
if (err) return cb(err)7 ^9 y4 d t8 O
//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());
}); n" U( j3 ?+ [0 r
结论在上文中我们阐述了以太坊能够良好地管理其“状态”。这种聪明的预先设计有许多好处。移动端亲和性随着移动设备和物联网设备的无处不在,未来的电子商务必将依赖于安全、强健且快速的移动应用。既然我们承认了移动性上的优势,我们也必须认识到区块链数据大小不可避免地不断增加。在每一个移动设备上存储完整的区块链网络显然是不切实际的。高速而不降低安全性以太坊世界状态的设计和使用改良默克尔帕特里夏树的做法在这一领域提供很多优势。在前缀树上操作的每一种函数(增删改)都有一个确定的密码学哈希,进一步来说,前缀树根节点独一无二的密码学哈希能作为证据,保证前缀树未被篡改。举例而言,任何对前缀树进行的任何程度的改变(例如在 LevelDB 数据库种增加某一账户的余额),都将会完全颠覆根哈希。这样的密码学特性使得轻客户端(不存储整条区块链的设备)成为可能,用户可以很迅速且可信地查询区块类似某某账户 “0x … 4857” 是否有足够余额在 “5044866” 高度完成购买的问题。“默克尔证明关于所存储的数据量大小是对数复杂的。这就意味着即使整个状态前缀树有数 Gb 大小,如果一个节点能从可信源得到状态根,那它就可以在仅仅下载数 Kb 证明数据的情况下百分百验证树上的任何信息。”[2]限制消费在以太坊白皮书 [3] 中提到了储蓄账户这样一种有意思的想法。在这种场景下,两个用户(也许是丈夫和妻子,或者是商业伙伴)可以每天从余额中提走 1% 的存款。这个创意仅仅在白皮书中的 “未来应用” 中被提及,但它的意义在于理论上这样的做法可以作为以太坊的基础协议层(作为第二层解决方案或是第三方钱包的必要支持方案)。你可能会想到了本文开篇中对 UTXOs 的讨论。正如我们所说 UTXOs 在区块链上是黑箱的数据,比特币区块链实际上不存储用户的账户余额。因此比特币网络很难(也许不可能)实现任何一种限制消费的应用。消费者信心我们可以看到在这一领域的许多工作都离不开轻客户端的发展。更确切地说,离不开安全、稳健且快速的,能与区块链科技交互的移动应用。一个能支撑电子商务的成功区块链必须做到高速、安全和易用。提供更好用、更安全和性能更强的设计聪明的产品往往能增加消费者的信心,并且使普罗大众得以认可。CyberMiles 团队正在向着这个目标努力迈进着!
成为第一个吐槽的人