比特币和以太坊的数据结构
众所周知,比特币作为区块链最早最成功的应用,其不可篡改性和隐私保护性是基于哈希算法的。为了迎合哈希算法的使用,Merkle-Tree应运而生。Merkle-Tree就是一颗将指针换为哈希指针的特殊的树。然而,比特币的记录是基于交易的,这样就不能很方便地查询某个时刻某节点的余额(balance)。虽然,比特币中的全节点也维护一个UTXO(即未支出的交易),轻节点也可以向全节点查询UTXO中的内容从而得到某节点的余额。但是,这耗费的成本和时间是不必要的,同时也与我们的日常习惯相悖。
于是,以太坊提出了基于账户的区块,即以太坊中的区块体中是包括每一个区块的余额的。这时,问题来了。我们是否还能采用比特币中的Merkle-Tree结构进行账户余额的存储呢?后面我们将会揭晓。
比特币的数据结构
以上便是区块体中交易的存储结构。首先,各个交易Di生成一个哈希值,然后逐层向上两两合并,最后得到一颗树结构。最后,将树的根哈希值存放在区块头中作为所有交易的唯一标识。只要Merkle-Tree上的任何一笔交易被篡改,对应的哈希也会逐层变更,正所谓牵一发而动全身,最后根哈希也会被改变。所以,基于Merkle-Tree的数据结构是可以保证交易数据的不可篡改性的。其次,在区块链网络中存在着许多轻节点,即只存放区块链头信息的节点。那么当这些轻节点想检验某个交易是否被记录在区块上时,它需要怎么做呢?
难道它需要向节点请求下载树上的完整信息吗?当然不是。它只需要全节点提供该交易到根的路径上其他有关生成哈希的必要哈希值,通过沿着路径生成哈希,最后轻节点会计算出一个根哈希值。最后只需要比较计算出的根哈希和区块上记录的根哈希是否一致,若一致则说明该交易确实被记录在区块里,否则则说明该交易不存在。
这样,每个区块都把10分钟左右的交易记录在案,所有交易都将可追溯。通过交易的验证,双花问题就可以得到解决。通常,在一笔交易中,比如A需要向B支付10个比特币。那么A就得证明它在之前的区块中有得到一定量比特币的交易,这个叫做output script。比如,之前C向A支付了8个比特币,D向A支付了5个比特币。这样A在给B转账的时候,需要明确的指出C和D是曾经以一共给了他13个比特币的,这样才能顺利完成交易。同时,剩余的3个比特币需要A自己建立一个新的找零账户进行存储,否则就会被矿工当成交易费拿走(doge)。
大家有没有觉得这样很麻烦?每次给别人钱,都得去证明自己手头上的钱是合法的。于是,以太坊建立了基于账户的区块,这样显式地记录某账户的余额,就无需像比特币一般进行多次验证。
以太坊的数据结构
以太坊中的区块含有三棵树,他们都采用同一个数据结构。这里对数据结构的决定起主要作用的当然就是状态树(state tree),也就是显示某个账户有多少余额的树。这里需要维护的好像就是一个(key,value)对,key就是账户的地址,而value就是余额(balance)。
那么,不如试一下哈希表?它的查询、更新速度很快。有什么问题呢?如何生成根哈希呢?所有哈希同时一起算出一个根哈希可行吗?那么这就无法提供快速的Merkle-proof,如果采用哈希表,只有获得区块体中其余所有账户的哈希才能证明某个账户的余额是正确的。这对轻节点是十分不友好的。
那么,可否采用比特币的Merkle-Tree依葫芦画瓢呢?这里是不行的。我们需要认清比特币和以太坊的一个很大的区别。在比特币中,被记录在Merkle-Tree上的交易都是有效交易;但在以太坊中,Merkle-Tree上记录的是全部节点的余额。但在一段时间中,可能只有很少一部分账户的余额发生了变化,这对存储造成了巨大的浪费。同时,Merkle-Tree也没有快速查找和更新的方法,也没有唯一性保证。
那么,能不能只将当前发生余额变化的账户记录到Merkle-Tree上呢?这个想法不错。但可惜,仍然是不可行的。举个例子,如果B要收取A一笔费用。B就需要从当前区块逐个向前寻找包含A账户余额的区块,假若A很长一段时间没有参与交易,那么对B而言就会造成巨大的时间成本。更有甚者,如果A压根未出现过,B就需要一直搜索到创始区块(genisis block)才能发现。
现在已经否决了hash table和Merkle- Tree了。我们再次进行升级——Sorted Merkle Tree。会排序的Merkle-Tree,这就解决了快速查找和更新的问题。不幸的是,SMT的插入成本是很大的,它需要重构整颗树,代价很大。
所以,我们目前面临的一个情况是,少量的账户的余额如何进行证明?这里需要说明的是,账户的地址是由40位的16进制组成,地址之间是十分稀疏的。当然,我们还是需要对所有节点的余额信息进行存储。
想想其他数据结构。既然地址的每一位都是0~f,有没有一点思路?没错!就是trie,传说中的前缀树。它在这里简直合适不过了。其一,它的查找效率取决于地址的长度,这个我们都可以接受。其二,无论输入顺序如何,它的结构都是唯一的。其三,如果更新局部信息,只需沿着一条路径进行访问即可,无需重构数据结构。
trie在这里看似已经不错了,但其实还是有一定的缺陷。例如存在一定的存储浪费,可能地址中间的某些信息对区分地址是无用的,这就需要将其合并。这就引出了我们最终的结果:Patricia tree(路径压缩trie)。再与哈希结合,它就成了Merkle-Patricia-tree,也就是MPT。
总结
比特币和以太坊的工作机制固然是空前的,但我们不仅应该熟悉其工作原理,更重要的是我们应该尝试去揣测整个思考过程。正是这种批判性思维,使得区块链应用一步步趋于更加合理化。正是这种批判性思维,推动着科技发展走向新的台阶!