首先先表述结论:Substrate 的数据模型与以太坊一致,是基于 MPT (Merkle Patricia Tree)的“全历史世界状态”模型。这里展开讲一点:当前区块链用于对业务进行建模的模型主要有两类:
UTXO 模型,即比特币及其分支的模型
状态模型,即以以太坊为代表,包含 eos 及其他区块链等以记录状态为主的模型
因为当前主流描述业务的方式主要还是以状态迁移的模型去建模业务,因此状态模型的区块链能更容易支持更广泛的场景。本系列只介绍状态模型,UTXO 模型请参照笔者之前关于比特币的相关文章。
对于没有接触过状态区块链的开发者而言,首先请记住以下一些基础概念:
当前状态是从 genesis (即第 0 块,初始状态)开始,通过交易或其他方式产生了状态变更,不断累计出来的。
状态并不是储存块中,而是节点自身独立维护的。
块中记录的是“状态迁移”的方式
如上图所示即是一个链的状态变化的过程。比如在 genesis 的时候,状态为
A:1 B:2
而经过块 1 的过程后,通过交易(tx)或其他因素,将 A 的状态修改成了 2,因此在块 1 下的“世界状态”就变为了 A:2 B:2
。块 2 以此类推。因此实际上块中并不是记录当前的状态,而是节点自己本地维护了一个“世界状态”。这个状态就是存在本地的数据库中。而块中保存的是 “状态迁移”,“状态迁移” 就是交易(或其他因素),节点同步 / 执行了一个区块后,通过区块中含有的迁移状态的方式(即是在执行这个区块),修改自己的本地状态,从而当一个块执行完毕后,本地当前的状态即为这个块下的状态。而对于当前状态模型的链而言,一般情况下会具备如下的特性:
最新块(最高块)下的状态即为当前的状态,在这个状态下可以获取当前所有对象的状态。
块中含有对这个块下的状态的证明(即状态的统一性经过了节点间共识)
可以通过任意一个历史的区块,取到在这个区块下的状态(如当前最高块已经是 2,而通过 1 块中的相关信息可以取到 A:2
而不是最高块下的 A:4
)
这 3 个条件中只有 1 是必须满足的,往后越达成一个条件,需要付出的代价就要更多。若能满足这 3 个条件,即是“全历史世界状态”。当然这里的词“全历史世界状态”是笔者自己造的,因为在以太坊的那个时代,还只有“世界状态”的概念。而实际上以太坊的“世界状态”是将每一个块的那个时刻的状态都做了“快照”,可以恢复到任意块的时候下的状态,也就是将全部的“状态变化历史”都保存下来的方式,因此笔者对这种模型命名为“全历史状态”。但是显然,这种方式将会将所有过去的历史都存下来,因此会造成数据量十分庞大。因此这种方式是状态区块链的一个极端。而牺牲第 3 个条件,保留状态证明与只保留最新状态是一种权衡。而第 3 点就不再交由链来维护,而交给第三方的附属设施维护,如中心化数据库,区块链浏览器等等。若连第 2 个条件也不用,则是状态模型的另一种极端,这种极端一定情况下牺牲了共识状态安全性。这种方式带来的好处是实现上比较简单。这即是 eos 的模型。
由于 Substrate 采用了和以太坊一样的模型,因此满足上述的 3 个条件。在 Substrate 中 MPT 简称为 trie
。对于 MPT 实现的原理这里不进行详细描述。简单来说 MPT 的实现和 git
,IPFS 中的 IPLD 模型等原理上都是一致的,用一句话描述就是:使用 DAG 的方式,只记录每次变更后的索引(hash)。如下图所示:例如在上文提到的状态
A:1 B:2
,将 A,B 分别看做两个 key,在 MPT 中 key 就是树的路径,而 1,2
是 key 对应的值,在 MPT 中就是叶子节点。因此 A:1 B:2
变更到 A:2 B:2
的这个过程中,对应到上图相当于:
从 root -> 2 -> value2 相当于记录了 A:1
,从 root->3->value3 的过程相当于记录了 B:2
从 genesis 到 block1 的过程中,A 的状态发生了变化,从 1 变成了 2
在 MPT 中由于 A 的值发生了变化,因此 MPT 生成了一个新的叶子节点代表 A 的新状态,然后从叶子节点重新生成一个新的索引路径,即图中的 value2' -> 2'-> root'
的过程
而在生成新路径的过程中,由于 B 的状态没有发生变化,因此在生成新的路径的过程中,直接索引了 B 的老路径(即图中的虚线)。
因此如上图所示,通过新的树根 root'
索引到的 A 和 B 的值分别是 A:2
(即新的 A 的状态)与 B:2
(即老的 B 的状态)
如以上过程所示,每一个新的树跟记录的这个树根下状态的索引,因此每一个树跟即是每一个状态的 DAG 的起点,通过这个起点,可以获取到所有的状态。每个区块都会含有状态变更,而每次变更即是通过以上类似过程生成一个新的树根,这个根 root 在以太坊及 Substrate 中被称为状态根 state_root
,放入每一个区块的区块头中,作为当前这个区块进行共识的状态证明。而通过以上方式也可以看出,只要从任意区块中取出状态根,那么就可以获取到这个状态根下那个时刻的所有状态的值。(对于相同的 key A,通过 root 索引出 value2,通过 root' 索引出 value2')因此,MPT 这种数据结构描述的世界状态,满足上述所说的 3 个条件。
抛开以上原理不谈,我们可以将 trie 看成一个 K-V 数据库,这个数据库通过给予的 Key 能够获取到对应的 Value。只不过这个 k-v 数据库通过给予一个 root 可以索引出在这个 root 的那个时刻下的对应数据。也就是说 trie 实现的链上状态就是一个带快照的 k/v 数据库。每一个块就是对当前数据库全部数据的快照,块的时间戳代表了那个时刻下的数据状态,通过块中的状态根可以获取那个时刻下的数据。而在打包执行当前区块时使用的 root 即是上一个区块的 root,也就是打包区块时取当前最新的状态。在 Substrate 中对于 Runtime 层而言,提供的接口即是
get(key) -> value
set(key, value) / remove(key)
这样的接口对于 Runtime 而言,可以直接将 trie 树看做一个 key/value 数据库即可,屏蔽了所有的 trie 树细节。因此对于初学者而言,若目前还不是很容易搞清楚 trie 树的实现细节,那么就不用关心,只需要记住 Substrate 的读写数据模型是 key/value 数据库即可。
实际上状态模型区块链即使通过各种实现方式,能够在每个块下记录这个块当前的状态(key/value)。只不过使用 mpt 树实现的“全历史世界状态”是包含上文提到的 3 种性质的一种实现,这是一种极端,包含了证明与历史,但对应的也会带来数据的膨胀。Substrate 采用的是这种 MPT 树实现的状态区块链模型。
更多该系列内容:
扫码关注公众号,回复 “1” 加入波卡群
关注 PolkaWorld
发现 Web 3.0 时代新机遇
点个 “在看” 再走吧!
声明:本内容为作者独立观点,不代表 CoinVoice 立场,且不构成投资建议,请谨慎对待,如需报道或加入交流群,请联系微信:VOICE-V。
简介:波卡(Polkadot)第一中文社区,带你寻找 Web 3.0 时代新机遇!
评论0条