执行组件
概要
Libra区块链是一个复制的状态机,那么每个验证机中都存在一个副本,从创世区块状态 S0, 每一笔交易 Ti 将先前状态 Si-1 更新为 Si. 每 Si 是从帐户(由32字节地址表示)映射到 与每个帐户关联的一些数据。
执行组件根据完全有序的交易,通过Move虚拟机来计算每个交易输出,然后将输出应用于先前的状态,并生成一个新的状态,执行组件和共识算法 — HotStuff 一种基于领导者的算法 — 协同工作, 帮助它提议对一组拟议的交易及其执行达成一致。这样一组交易就是一个块。与其他区块链系统不同,块除了作为一组交易之外没有任何意义 - 每一个交易由其在分布账中的位置来识别,这也是被称为“版本”。 每个共识参与者构建一个区块树,如下:
┌-- C
┌-- B <--┤
| └-- D
<--- A <--┤ (A 是最后已提交的块: committed)
| ┌-- F <--- G
└-- E <--┤
└-- H
↓ 之后提交(commiting)块E
┌-- F <--- G
<--- A <--- E <--┤ (E是最后已提交的块)
└-- H
一个块是一组交易,它会应用到已经提交的、确定顺序的区块上。
从上次已提交块到未提交的块的路径形成一个有效的链。无论共识算法的提交规则如何, 此树上有两种可能的操作:
- 使用给定的父节点向树中添加块,并扩展特定的链 (例如,用块
G
扩展块F
)。当我们用一个新块去扩展一个链,该区块应该包含正确的(块中)交易执行结果,就像它的所有祖先也都是按照同样的规则提交。然而,所有未提交的块和它们的执行结果保存在某个临时位置,对外部客户端不可见。 - 提交一个块(commiting)。随着共识收集到越来越多关于块的选票,按规则决定提交一个块,其所有祖先也是同样的规则提交的。然后我们将所有这些块保存到永久存储中,并在同一时间丢弃所有冲突的块。
因此,执行组件提供了两个主要API — execute_block
和 commit_block
- 以支持上述操作。
实现细节
每个版本的状态都表示为存储中的稀疏Merkle树。当一个交易提交修改一个帐户时,该帐户和此树根下的兄弟帐户将加载到内存中。例如,如果我们确定的已提交的状态上执行一个交易 Ti ,它会修改账户 A
, 我们最终会得到如下的树:
S_i
/ \
o y
/ \
x A
其中 A
具有帐户的新状态,而 y
和 x
是从树根到 A
路径上的兄弟节点。 如果下一个交易 Ti+1修改了另一个帐户 B
,而B
位于 y
的子树中, 一棵新的树将会
构建,结构将如下所示:
S_i S_{i+1}
/ \ / \
/ y / \
/ _______/ \
// \
o y'
/ \ / \
x A z B
使用这个结构,我们可以查询全局状态,同时考虑到未提交交易的输出。例如,如果我们想执行另一个交易 Ti+1', 我们可以使用树
Si. 如果我们查找帐户A,我们可以在树中找到它的新值。
否则,我们知道该帐户不存在于树中,我们可以回退到存储。另一个例子,如果我们想执行交易 Ti+2, 我们可以使用树 Si+1 它更新了帐户 A
和 B
的值.
这个组件是怎样组织的?
execution
└── execution_client # A Rust wrapper on top of GRPC clients.
└── execution_proto # All interfaces provided by the execution component.
└── execution_service # Execution component as a GRPC service.
└── executor # The main implementation of execution component.