Skip to main content

Blockstore(区块存储)

当一个区块到达最终状态时,从那个区块到创世区块之间所有区块形成了一条链,就是大家都熟悉区块链。 然而,在此之前,验证节点必须维护所有可能有效的链,称为分叉(forks)。 领导节点从验证节点中通过算法轮换选举,这个过程会自然的产生分叉。 本节描述了验证节点的 区块存储(blockstore) 数据结构是如何复制并处理这些分叉,直到确定最终的区块。

验证节点记录它在网络上观察到的每一个区块分片(无论任何顺序,只要区块分片是由给定插槽的领导者签名的)。

碎片被移动到一个可分叉的区间领导者插槽 + 碎片索引 (在一个 slot 区间) 。 这允许Solana中的跳表数据结构完整的存储碎片,而无需事先选择跟随哪个分叉或者等待区块已经达到最终状态。

可以从内存中或者最近的文件中重组最新的区块碎片,较晚的区块碎片可以从磁盘的旧文件中重组,这取决于BlockStore的实现。

Blockstore 功能#

  1. 持久性:Blockstore和节点的验证流水线打交道

    接收输入流和签名验证。 如果

    收到的区块碎片和领导节点的签名是一致的(

    例如领导节点的分配插槽内接收的区块),它需要立即存储。

  2. 重组:重组和上面提到的一样

    但是它能够为任何接收的区块碎片进行重组。 Blockstore存储带有签名的区块碎片,

    保留区块链的历史记录。

  3. 分叉:Blockstore支持区块碎片的随机访问,

    因此可以支持验证节点回滚并且重放记录点后的交易。

  4. 重启:通过适当的修剪/剔除,

    区块储藏可以通过少数交易条目回放到 slot 0。 回放的逻辑就是

    ()通过最新的Blockstore状态

    来处理的,例如处理分叉。

Blockstore 设计#

  1. 每条记录都是通过键值对来存储的,键就是插槽索引加上区块碎片索引,值就是条目数据。 值得注意的是每个碎片索引都是重新从零开始的(按照每个插槽)。

  2. Blockstore 中每个插槽中的元组 SlotMeta 数据结构包含:

    • slot_index - 插槽索引

    • num_blocks - 插槽的区块数量 (用于区块链中前一个插槽)

    • consumed - 碎片索引的连续最高值 n,对于所有的 m < n,,总会有一个碎片索引的值等于 n (例如连续最高的碎片索引)。

    • received - 碎片索引的最高值

    • next slots - 下一个插槽的列表 可以用于重建到

      一个记录点的回放

    • last _index - 插槽的最后一个碎片标识 当领导节点在传输插槽的最后一个碎片索引时会带上这个标识。

    • is_root - 如果为True那么当前插槽所有区块碎片都是收集完整的。 我们可以按照以下的规则进行。 假设slot(n)是索引为n的插槽,并且slot(n).is_full()为真,如果索引为n的插槽都收集到了所有的碎片。 假设 is_rooted(n) 表示“slot(n).is_root 为真”。 那么:

      is_rooted(0) is_rooted(n+1) iff(is_rooted(n) and slot(n).is_full()

  3. Chaining - 当x插槽的碎片到达时,我们检查新插槽中的(num_blocks)(这个信息在碎片中会有编码)。 我们就知道这个新插槽链是 x - num_blocks 插槽。

  4. Subscriptions - Blockstore记录一组已“订阅”到的插槽。 这意味着这些插槽的条目将被发送到Blockstore通道,供ReplayStage使用。 详情请查看 Blockstore APIs

  5. Update notifications - 对于任意一个 n,Blockstore 更新 slot(n).is_rooted 这个值从false 变成true。

Blockstore 接口#

Blockstore提供了一个基于订阅的API,ReplayStage使用它来请求所需要的条目。 Blockstore有暴露相关的接口让请求方获取这些条目。 这些订阅API如下所示: 1. fn get_slots_slots_slouts(slot_indexes: &[u64]) -> Vec<SlotMeta>:返回slot_indexes中对应的区块碎片数据。

  1. fn get_slot_entries(slot_index: u64, entry_start_index: usize, max_entries: Option<u64>) -> Vec<Entry>:返回以 entry_start_index 开头的插槽入口向量,如果 max_entries == Some(max),则将结果限制在 max,对返回向量的长度施加限制。

请注意:这意味着重播阶段现在必须知道一个slot何时结束,并订阅它感兴趣的下一个slot以获得下一组条目。 较旧的slot将会存储到Blockstore当中。

与Bank交互#

Bank 暴露以下字段用于交易重放:

  1. prev_hash:PoH链上的最后一个区块哈希

    它处理过的条目

  2. tick_head:PoH链中的滴答,由 Bank

    进行验证

  3. votes:包含以下内容的一堆记录: 1. prev_hashes: 上一次PoH的哈希值 2. tick_high:此投票的高度 3. lockout period:这个投票的最大截止时间

    能够在这次投票中被引用

在这个voteReplay阶段会建立分叉点,用Blockstore API找到它可以挂起的最长链。 如果当前链没有最新的投票,那么重放阶段将回滚到投票的分叉点,并从那里重新同步。

Blockstore 剪枝#

一旦Blockstore中记录太旧了,表示所有可能的分叉就变得不那么有用了,甚至可能在重启时重放出现问题。 一旦验证节点的投票达到最大截止时间限制,任何不在PoH链上的区块都可以被剪枝、删除。