块连线
有态度/有深度/有温度
关注
文 |Timothy McCallum
翻译&校对 | 安仔 Clint & Elisa
来源 | 区块链兄弟
以太坊是由多个组成部分构成的。这篇文章旨在解构以太坊,使你能更深入理解它的数据存储层。我们将介绍区块链中“状态”的概念,并探究帕特里夏前缀树(Patricia Trie)数据结构的理论依据,利用 Google 的 leveldb 数据库来阐释以太坊中前缀树的应用。本文同时和一篇手把手学习指引相关联,它能指导你安装并配置好自己的以太坊私有网络(包括挖矿)。学习之后你将能够执行交易,并且探索以太坊的“状态”是如何根据诸如交易这样的活动而改变的。
什么是区块链“状态”?
比特币的“状态”是通过网络中全局的未使用交易输出(UTXO)来刻画的。比特币网络中价值的传递通过交易来进行,更准确地说,一个比特币用户能使用一个或多个 UTXO 来创建一笔交易,所消耗的 UTXO 将作为交易的输入。
对 UTXO 更详尽的介绍不在本文的讨论范围内,然而在接下来的数个段落中,我们将不断提及这一概念,来指出比特币和以太坊之间基础实现上的差异。
接下来的两个比特币例子将指出比特币 UTXO 模型和以太坊世界状态概念之间的不同之处。
首先,比特币的 UTXO 不能被拆分消耗。如果一个比特币用户想要花费 0.5 个比特币(他手上只有一个价值 1 比特币的 UTXO),他必须显式地将赋予 0.5 个比特币转账地址(转给自己)作为找零。如果他们没有设置自我找零,那么 0.5 个比特币就会作为矿工打包交易的奖励转给矿工。
第二点,在一个最底层的角度来说,比特币并不保存用户的账户余额。在比特币中,用户仅仅在某一给定时间点掌握着一到多个 UTXO 的私钥。尽管数字钱包设计得好像比特币区块链能自动保存和管理用户的余额等等,但实际上不是这样的。
用户的账户余额在比特币网络中是一种抽象的概念,事实上一个用户的余额是他所控制的每一个 UTXO (用户保管着对应的私钥)价值的和。用户所使用的私钥能对每一个 UTXO 进行签名和消费。
UTXO 系统在比特币网络中运行良好,某种程度上要归功于数字钱包能完成绝大多数与交易相关的工作,包括但不限于以下几点:
a) 操作 UTXO
b) 保存私钥
c) 设置交易费用
d) 提供找零地址
e) 统计 UTXO (显示可用余额、转帐中的数额以及总余额)
有趣的是,一个非确定性钱包(如上图中的比特币核心钱包)的备份仅仅提供 UTXO 的快照(在该时间点)。只要用户执行了任何交易(发送或接收),他们所生成的原始备份就过期了。
总结来说,我们知道:
比特币区块链不保存账户余额
比特币钱包保存 UTXO 的私钥
当被包含在某一条交易中时,整个 UTXO 就被使用掉了(在有些找零场景中,原来 UTXO 中的价值会被新的 UTXO 承载)
和上述比特币网络不同,以太坊世界状态已经能管理账户余额以及更多信息了。以太坊中的状态并不是一种抽象的概念,它是以太坊的基础层协议。正如黄皮书 [1] 中所提及的,以太坊是一个基于交易的“状态”机。基于所有交易的状态机概念就这样被构建出来。
让我们从头开始捋一捋,和其他区块链一样,以太坊区块链是由创世区块开始的。从这个起点开始(创世状态在 0 区块高度),诸如交易、合约以及挖矿的活动会持续不断地改变以太坊区块链的状态。在以太坊中,账户余额(存储在状态树中)随每一次交易进行所发生的改变就是这样一个例子。
值得留意的是,像账户余额这样的数据并不直接保存在以太坊区块链的区块中。区块中只保存交易树、状态树和收据树的根节点哈希值。其存储结构如下图所示。
从上图中可以注意到,存储树(所有智能合约数据存储的位置)的根节点哈希实际上指向了状态树,从而间接指向了区块链。接下来我们将深入探讨这一部分更细节的内容。
以太坊中存在两种截然不同的数据类型:永久数据和暂存数据。交易就是永久数据的一个例子。一旦一个交易被确认,它就将永久地被记录在交易树结构中,并且无法篡改。而某一个特定以太坊账户的余额则是暂存数据的例子。账户地址的余额存储在状态树中,并且每当接收到和该账户有关的交易时,该余额都会改变。将挖矿确认后的交易这样的永久数据和账户余额这样的暂存数据分开管理是有意义的。以太坊使用前缀树这种数据结构(上图所示结构)来管理数据,那么接下来我们介绍一下什么是前缀树。
前缀树
前缀树是众所周知用于存储有序字符串的一种数据结构。以太坊特别采用了一种被称为“检索用文字数字编码的信息的实用算法”(Practical algorithm to retrieve information coded in alphanumeric,缩写为 PATRICIA,下文音译“帕特里夏”)。帕特里夏树的主要优势在于它简缩的存储。接下来我们对比标准(传统)前缀树和帕特里夏前缀树之间工作原理的不同。
- 表示空指针-
在前缀树中添加单词的规则
我们跟随着所加入单词的搜索路径来看。如果我们(在搜索过程中)遇到了一个空指针,就构建一个新节点;当成功将单词加入到前缀树中后,我们再创建一个空指针(终止符)。
当需要加入一个被其他长单词所包含的短单词时,我们就直接把所有的字母放进去并加入一个空指针(终止符)。
从前缀树中删除单词的规则
我们从前缀树中搜索一个表示字符串(我们所要删除的单词)的叶子(分支末端节点)。紧接着删除从叶子末端节点开始直到根节点的所有节点。除非我们遇到了一个有超过一个子节点的节点,否则删完为止。
前缀树中搜索单词的规则
我们依次检索所搜索字符串中的各个字母,直到得到一条完整的路径才停止搜索(在正确的顺序下)。如果在检索完字符串(检索目标)中的所有字母之前就遇到了空指针,那就可以说该字符串并不在该前缀树中。另一方面,如果我们随着检索到达了一个叶子节点(分支末端节点),那么该路径就代表着目标字符串,可以认为目标字符串在前缀树之中。
帕特里夏树
向帕特里夏树添加单词的规则
帕特里夏树将所有的常用字符集合为一个分支。
所有非常用字符都会成为路径上的一个新分支。当要向帕特里夏树添加一个新单词时,我们依次加入所有的字母,然后加入空指针(终止符)。
从帕特里夏树中删除单词的规则
从帕特里夏树中删除单词的规则整体上和传统前缀树一样,不同之处在于删除节点(从叶子节点到根节点)时,我们必须保证所有的父节点都至少有两个子节点。单个子节点只包含字符和一个空指针是允许的(如上图所示,每一个单词末尾都是这种情况)。同样允许一个节点只包含一个空指针(这种情况发生在短单词包含于长单词内)。上图中就体现了“wood”和“wooden”在同一个前缀树中的情形。
值得留意的是,当要从一个前缀树种删除时,路径上不能保留任何只有一个子节点的父节点。如果在删除过程中发生了这种情况,我们需要把合适的字符重新连接起来以解决该问题。这种例子在下图中呈现(从前缀树种删除单词 “word” )。
-从前缀树中删除 “word” 之前-
-删除并重组前缀树之后-
帕特里夏树中的单词搜索规则
在 Patricia 前缀树中搜索单词和标准前缀树一致。
标准前缀树和帕特里夏树之间的相似性
假设 “m” 是我们要添加的字符串的长度, “N” 是可用字母表的大小,将该字符串加入到前缀树的时间复杂度 “O” 为 O(mN)
假设 “m” 是我们要删除的字符串的长度, “N” 是可用字母表的大小,在前缀树中删除该字符串的时间复杂度 “O” 为 O(mN)
假设 “m” 是我们要搜索的字符串的长度,在前缀树中搜索该字符串的时间复杂度 “O” 为 O(m)
标准前缀树和 Patricia 前缀树之间的主要区别
使用帕特里夏树最大的优势在于存储方面。
使用标准前缀树来存储总长度为“M”的字符串的空间复杂度为 O(MN) ,其中 “N” 为可用字母表的大小。
使用帕特里夏树来存储总长度为“M”的字符串的空间复杂度为 O(nN+M) ,其中 “n” 是前缀树中存储字符串的数量,“N” 为可用字母表的大小。
直观来看能发现,两种树的深度显著不同(如上述两图所示)。帕特里夏树的深度更小(更浅),这归因于帕特里夏树将常用的字符组合的能力(并把空指针和叶子节点连接)更强。
以太坊前缀树的深入探究
让我们更深入地探究一下状态、存储以及交易前缀树。
状态前缀树 —— 独一无二
以太坊中有且只有一个全局状态前缀树。
这个全局状态树在不断地更新着。
这个状态前缀树包含了以太坊网络中每一个账户的一组键值对。
这个 “键” 是一个 160 位的标识符(以太坊账户的地址)。
全局状态前缀树中的 “值” 是通过编码以太坊账户中的如下细节来得到的(使用递归长度前缀编码 (RLP) 的方法):
nonce 值
余额
存储前缀树根节点哈希
代码哈希
状态前缀树的根节点(在给定时间点整个状态树的哈希值)是用来确保状态前缀树安全的唯一标志符,状态前缀树的根节点是由整个内部状态树的全部数据来通过密码学手段得到的。
-状态前缀树(用 leveldb 实现的默克尔帕特里夏树(缩写 MPT))和一个以太坊区块之间的关系-
-给定区块中存储的“stateRoot”,这是用 Keccak 256 位哈希算法计算状态前缀树根节点得到的。“stateRoot”:‘0x8c77785e3e9171715dd34117b047dffe44575c32ede59bde39fbf5dc074f2976’-
存储前缀树 —— 智能合约数据的存储
存储前缀树是智能合约数据存储的位置。每一个以太坊账户都有自己的存储前缀树。在全局状态前缀树中保存着存储前缀树根节点的 256 位哈希 storageRoot 值(正如我们刚刚讨论的那样)。
交易前缀树 —— 一个区块一个树
每一个以太坊区块都有着自己的独立的交易前缀树。一个区块往往包括多笔交易,而交易的顺序当然由打包交易的矿工来决定。在交易前缀树中找到一笔交易的路径是通过(RLP 编码方法)检索交易在区块中的索引来得到的。已经被挖矿验证过的区块将永远不会再更新,所以区块中的交易顺序也将固定下来。这就意味着一旦你从区块的交易前缀树中定位到了某一笔交易,你日后就可以通过相同的路径找回它。
阿剑评:账户模式可以说是以太坊设计中的核心部分了。而这一部分又要跟区块链本身结合起来,同时兼顾各方面的效率要求(存储、调用,等等)。话说回来,听过状态树、这棵树、那棵树,现在终于搞清楚它们是怎么来的了。不过这篇文章应该还有一部分没谈到,就是帕特里夏树如何跟默克尔树相结合。
(以上观点不代表 EthFans 看法,EthFans 期待听见不同的声音~)
文章发布只为分享区块链技术内容,版权归原作者所有,观点仅代表作者本人。
END
块连线推荐
领取专属 10元无门槛券
私享最新 技术干货