ETH

以太坊交易树和收据树

交易树和收据树介绍

Posted by Emo on June 20, 2022

ETH

EMO’s Blog

交易树和收据树是一一对应的,而且和状态树一样都是MPT结构,这三棵树的根hash值都是保存在块头里面的。

主要是为了统一方便,而且MPT支持查找操作。查找的键值就是这个交易在发布的区块里面的序号。

状态树和交易树以及收据树的区别

  • 交易树和收据树都是只把当前区块里面发布的交易组织起来。所以每一个区块的交易树和收据树都是独立的。
  • 状态树要把系统中所有账户的状态都要包含进去。多个区块的状态树是共享节点的,每次发布新的区块的时候,只有被改变了状态的节点需要新建一个分支。

交易树和收据树的作用

提供Merkel Proof。交易树用来证明某个交易被记录到区块中了。同样每个交易的执行结果也可以在收据树中去进行Merkel Proof。

除此之外,以太坊还提供一些更加复杂的查询操作:

比如说,你想查询到10天当中和某个智能合约相关的交易。

一种方法是把过去十天产生的所有区块都扫描一遍,看看那些交易和这个智能合约相关,缺点是复杂度过高,还有就是轻节点只有区块头,无法进行这些操作。

Bloom filter

以太坊提出了Bloom filter,这个数据结构可以支持高效的查找某个元素是否在集合里。

第一种方式是线性查找,第一复杂度是O(n),第二你需要有一种方法来保存集合中的所有的元素。对于轻节点来说,这种方法就用不了

Bloom filter提出的方法提出了一个巧妙的方法,对这样一个元素的集合提出一个很紧凑的摘要,比如一个128位的向量。

对集合中的元素取Hash,然后根据Hash值将其映射到一个指定的位置,并标志为1。如果要判断元素d是存在,只需要对d取hash值后,去验证对应的位置是否是0,如果是0则不存在。但是存在一种问题,如果对d取hash后发现对用的结果是1,这时候我们也无法确定d是否存在,因为两个不同的元素可能被映射到同一个位置,也就是hash碰撞。这个时候我们说,这种方法可能发生False Positive,但不会发生False Negative(会发生误报,但不会发生漏报)。这其实跟Merkel Tree的功能是正好相反的。Bloom Filter还有一个特征就是不支持删除操作,因为有可能多个元素可能映射到同一个位置,你如果把这个位置归0了,那么未被删除的元素同样无法检测到了。这就违背了其不会发生漏报的特性。

Bloom Filter存在于哪儿?

每个交易执行完过后会形成一个收据,这个收据里面就带有一个Bloom Filter记录这个交易的类型,地址等信息。发布的区块,在他的块头里也有一个总的Bloom Filter,这个总的Bloom Filter是这个区块里所有的Bloom Filter的并集。

实际作用

当我们要查询某一个类型的交易的时候,我们就会先去每个区块的块头,判断一下这个类型的交易是否存在于这个区块当中,如果存在,那我们再去查找区块里面的交易所对应的收据树里面的Bloom Filter是否包含这个类型的数据。

好处是什么?

因为Bloom Filter的特性,误报但不会漏报,这一点保证了我们一定能锁定所有的满足这个类型条件的收据,虽然可能里面会有假阳性的,会导致我们额外消耗一些精力,但是总归我们是筛选掉了大量无关的区块。

感想

我们可以把以太坊看作是一个交易驱动的状态机。状态指的就是状态树里面的状态,交易就是指的交易树里面的交易。通过执行这些交易,会驱动系统从当前的状态转移到下一个状态。比特币也可以认为是一个交易驱动的状态机,比特币里面状态就是UTXO,每次发布新的区块,会删除UTXO里面的一些数据,同时也会新增一些数据。这两个状态机都有一个特点,这两个的状态转移都得是确定性的。

问题

  1. 一笔交易里面包含的账户,是否可能不存在于状态树上?

是的,可能的。比如转账的时候的收款人是一个刚新建的从来没有发生过交易的账户,这个时候他也是可以收账的。这个时候我们就需要将这个新的账户添加到状态树上了。

  1. 能否每个区块都维护一个状态树,而这个状态树只涉及和本区块上的交易相关的账户?

如果这样设计的话其实就跟比特币比较类似了。其实这里带来的问题是,账户状态的查询困难,如果一个账户只在1w个区块前发生过交易,那么现在要涉及到这个账户,你就需要往前不断查询很多次,才能锁定到这个账户,很浪费时间。但其实这里也可以用一些设计去解决这个问题。比如上面提到的布隆过滤器,这样我们就可以快速地判断当前区块是否包含我们想要查找的账户了,可以过滤掉很多不必要的查询,提高查询速度。还有就是根据第一个问题中提到的第一次出现的新账户,我们就可能需要遍历完所有的区块才能知道他是第一次出现的新账户。即使使用布隆过滤器,也比较费时。

代码

Receipt收据的数据结构

块头的数据结构

Bloom Filter相关函数


{ % if page.mermaid % } { % endif % }