9.6 KiB
title
title |
---|
简单支付和状态验证 |
让资源不足的客户端轻松加入Solana集群通常很有帮助。 无论是参与经济活动还是合约执行活动,验证客户活动已经被网络接受通常都很昂贵。 该提案提出了一种机制,使此类客户可以以最小的资源支出和信任第三方来确认其行为已提交到账本状态。
一种简单的方法
验证节点会在短时间内存储最近确认的交易的签名,以确保不会多次处理它们。 验证节点提供了一个JSONRPC端点,客户端可以使用它来查询集群(如果最近处理了事务)。 验证节点还提供PubSub通知,从而当验证节点观察到给定签名时,客户端将进行注册并进行通知。 虽然这两种机制允许客户验证付款,但它们并不是证明,并且完全依赖验证节点。
我们将描述一种使用Merkle证明将验证节点的响应锚定在账本中的方式来最小化这种信任的方法,使客户端可以自己确认足够数量的验证节点已确认交易。 它要求多个验证节点证明进一步降低了对验证节点的信任,因为这个过程增加了折衷其他几个网络参与者的技术和经济难度。
轻量级客户端
“轻量级客户端”是群集参与者,它本身并不运行验证程序。 该轻客户端提供比信任远程验证节点更高的安全级别,而它本身无需花费大量资源来验证账本。
验证程序不是直接向轻量客户端提供交易签名,而是从感兴趣的交易到包含区块中所有交易的Merkle树的根生成Merkle证明。 此Merkle根存储在账本条目中,该条目由验证节点投票,提供共识合法性。 轻客户端的额外安全性级别取决于轻客户端认为是集群涉及的一组初始规范验证节点。 随着该设置的更改,客户端可以使用收据更新其内部的已知验证节点集。 对于大量的委托质押,这可能比较具有挑战性。
出于性能原因,验证节点本身可能希望使用轻客户端API。 例如,在验证节点的初始启动期间,验证节点可以使用状态提供的集群提供的检查点,并通过收据进行验证。
收据(Receipts)
收据是证明的最低限度的证明;交易已包含在一个区块中,该区块已由客户的一组首选验证程序进行了投票,并且投票已达到所需的确认深度。
包含证明的交易
包含证明的交易是一种数据结构,其中它包含了从 Entry-Merkle 到 Block-Merkle 的交易的 Merkle 路径,该路径包含在带有所需验证节点票证的 Bank-Hash中。 来自 Bank-Hash 的包含后续验证节点投票的 PoH 条目的区块链是确认的证明。
交易 Merkle
Entry-Merkle 是 Merkle根,包括了给定条目中按签名排序的所有交易。 条目中的每个交易都已经在这里进行了合并:b6bfed64cb/ledger/src/entry.rs (L205)
。 这意味着我们可以显示条目E
中包含了交易T
。
Block-Merkle 是在该块中排序的所有 Entry-Merkles 的 Merkle 根。
这两个 Merkle 证明结合起来证明了交易T
包含在具有银行哈希B
的区块中。
帐户哈希是在当前插槽内修改的每个帐户的状态哈希的组合哈希。
收据必须具有交易状态,因为状态收据是针对该区块构造的。 处于同一状态的两笔交易可能会出现在这个区块中,因此,无法仅从该状态推断出提交到账本的交易修改状态是成功还是失败了。 可能不需要对完整的状态代码进行编码,而只需对单个状态位进行编码即可指示交易成功。
当前,尚未实现 Block-Merkle,因此要验证 E
是具有银行哈希 B
区块中的条目,我们需要在该区块中提供所有条目哈希。 理想情况下,可以采用 Block-Merkle 来实现,但这种方法效率很低。
区块头
为了验证包含证明的交易,轻客户端需要能够推断网络中分支的拓扑。
更具体地说,轻客户端将需要跟踪传入的区块头,以便给定块A
和B
的两个银行哈希,他们可以确定A
是否为B
的祖先(下文的乐观确认证明
章节有详细解释)。 区块头的内容是计算银行哈希值所必需的字段。
Bank-Hash 是上面 Transaction Merkle
章节所述的 Block-Merkle 和 Accounts-Hash 串联的哈希。
代码:
b6bfed64cb/runtime/src/bank.rs (L3468-L3473)
let mut hash = hashv(&[
// 父块的 bank hash
self.parent_hash.as_ref(),
// 所有修改账号的哈希值
accounts_delta_hash.hash.as_ref(),
// 该区块处理的所有签名数量
&signature_count_buf,
// 该区块的上一次 PoH 哈希值
self.last_blockhash().as_ref(),
]);
在验证程序的重播逻辑中沿现有流逻辑来实现是一种好方法:b6bfed64cb/core/src/replay_stage.rs (L1092-L1096)
乐观确认证明
目前前,通过监听八卦和重播管道的侦听器可以检测到投票的乐观确认:b6bfed64cb/core/src/cluster_info_vote_listener.rs (L604-L614)
。
每次投票都是一项签名交易,其中包括验证节点投票的区块银行哈希值,即上面的Transaction Merkle
部分的B
。 一旦网络的某个阈值T
已对一个区块进行投票,就认为该区块是乐观确定的。 需要由这组T
验证节点的投票才能证明乐观确认了带有银行哈希B
的区块。
但是,除了某些元数据以外,已签名的投票本身当前未存储在任何地方,因此无法按需检索它们。 这些投票可能需要保留在Rocksdb数据库中,并通过(Slot, Hash, Pubkey)
键进行索引,该键代表投票的位置,投票的银行哈希值以及负责投票的投票帐户pubkey。
这样就可以通过扩展现有的签名订阅逻辑,通过 RPC 向订户提供交易处理和乐观确认证明。 当检测到乐观确认时,已经通知了订阅“SingleGossip”确认级别的客户,可以提供一个标志来指示上述两个证明也应该返回。
要注意的一点是,对B
进行乐观确认还意味着对B
的所有祖先区块进行了乐观确认,而且并不是所有的区块都被乐观确认。
B -> B'
因此在上面的示例中,如果乐观确认了区块B'
,那么B
也一样。 因此,如果交易在区块B
中,则证明中的交易合并将针对区块B
,但是在证明中的投票将针对区块B'
。 这就是上文所说的区块头
章节中的头部非常重要的原因,客户端需要验证B
确实是B'
的祖先。
质押分配证明
一旦获得了上面的交易提示和乐观确认证明,客户就可以验证交易T
是否在具有银行哈希的区块B
被乐观确认。 最后一个遗漏的点是如何验证上述乐观证明中的票数实际上构成了为维护“乐观确认”的安全保证所必需的质押有效百分比T
。
解决此问题的一种方法可能是,在每个质押设置发生变化的 epoch,将所有质押写入系统帐户,然后让验证程序订阅该系统帐户。 接着完整的节点可以提供Merkle证明系统帐户状态在某个区块B
中已更新,然后表明该区块B
被乐观确认/扎根。
账户状态验证
可以通过向集群提交带有 TBD 指令的交易来验证帐户的状态(余额或其他数据)。 然后,客户可以使用包含证明的交易来验证群集是否同意该帐户已达到预期状态。
验证节点投票
领导者应将验证节点的投票按质押权重合并为一个条目。 这将减少创建收据所需的条目数量。
条目链
一个收据具有从付款或状态 Merkle Path 根到连续验证票列表的 PoH 链接。
它包含以下内容:
- Transaction -> Entry-Merkle -> Block-Merkle -> Bank-Hash
以及 PoH 条目的矢量:
- 验证节点投票条目
- 滴答
- 轻量级条目
/// 此条目定义跳过交易,仅包含
/// 用于修改 PoH 交易的哈希。
LightEntry {
/// 从上一个 Entry ID 以来的哈希数量。
pub num_hashes: u64,
/// 从上一个 Entry ID 以来的 SHA-256 hash `num_hashes`。
hash: Hash,
/// 编码到条目中的交易的 Merkle 根。
entry_hash: Hash,
}
轻量级条目是从 Entries 重构的,它仅显示混合到 PoH 哈希中的 Merkle Root 条目,而不是完整的交易集。
客户端不需要开始投票状态。 分叉选择算法的定义为,只有在交易之后出现的投票才能为交易提供最终性,并且最终性与起始状态无关。
验证
轻客户端知道绝大多数设置验证节点的轻客户端可以通过遵循 PoH 链的 Merkle 路径来验证收据。 Block-Merkle 是 Merkle 根,将出现在条目中包含的投票中。 轻客户端可以模拟连续投票的分叉选择,并验证收据已确认为所需的锁定阈值。
综合状态
综合状态应与银行生成的状态一起计算到银行哈希中。
例如:
- Epoch验证节点帐户及其质押和权重。
- 计算费率。
这些值应在银行哈希中有一个条目。 它们在已知帐户中,因此具有一个哈希连接的索引。