脊链
每个 ZooBC 节点都维护两条链。主链每 15 秒产出一个区块,承载交易。与之并行运行的是脊链:在 mainnet 上每天一个区块,完全不承载交易。脊链区块记录注册表作为一个整体所担保的内容,即主链的一个检查点、加入或退出的节点密钥,以及最新快照的清单,并且需要三分之二席位的签名。这使脊链成为最终性的锚,也是每个新节点的起点。
脊链区块包含什么
- 一个检查点。 每个脊链区块指定一个足够深、已可视为定案的主链参考区块:位于该脊链区块产出时主链链顶以下 760 个区块(3 小时 10 分钟)处,且永远不低于上一个脊链区块的参考区块。它复制该区块的默克尔根,其载荷哈希承诺了参考区块自身的哈希和累计难度,因此检查点锁定的是一个确切的区块,而不仅仅是一个高度。
- 公钥变更。 自上一个脊链区块以来加入或移出注册表的节点密钥。
- 快照清单。 对于主链状态的每一份近期快照:整个文件的哈希、每个分块的哈希、参考高度、有效期和主链的创世哈希(见 快照与快速同步)。
脊链每个 spine_smithing_period 产出一个区块:在 mainnet 和 testnet 上为 86,400 秒,即每天一个;在 devnet 上为 3,600 秒,即每小时一个,devnet 的检查点深度为 80 个区块(20 分钟)。
由三分之二的席位联署
脊链区块携带一个聚合签名:先是一个 4 字节的计数,然后对每个签名者依次跟着其 32 字节的节点密钥和 64 字节的 Ed25519 签名,所有签名都针对相同的 204 个区块头字节。区块的哈希是对区块头字节和整个签名字段计算的 SHA3-256。
所需的签名数量为:
required = max(spine_min_signatures, ceil(n × 67 / 100))
其中 n 是签名者名单的大小。有 11 个席位时,11 × 67 / 100 = 7.37,向上取整为 8。向上取整是有意为之:7 会少于 11 的三分之二。
脊链计入其签名者名单上的每一个席位,无论是否被暂停。这是唯一一处被暂停席位仍然计入的三分之二计数,因此检查点始终代表全部席位的三分之二(见 三分之二规则)。
如果太多席位消失、脊链停滞,门槛会一步放宽:在 3 个脊链周期都没有存储任何脊链区块之后(mainnet 上为 3 天),超过半数即可,即 11 个中的 6 个。两种门槛都不得低于 spine_min_signatures,它在每条链上都是 5。若要求全部 11 个签名,一个缺席的席位就能让脊链停摆。
签名者先检查再签名
席位从不盲目签名。在联署一个提案之前,每个签名者都会检查:
- 密钥变更与它自己的注册表在同一区间内的变更相同;
- 本链的每份清单都与它自己的快照逐个哈希相同;没有该快照的签名者弃权;
- 参考区块足够深,且没有倒退;
- 它尚未在该脊链高度上联署过另一个不同的区块。
节点在存储脊链区块时,会再次检查其内容,同时检查出块者抽签和区块的难度。
谁来提议,何时提议
脊链的出块者从签名者名单中抽签产生:即索引为 LE64(SHA3-256(种子 ‖ "spine" ‖ 轮次)) mod n 的签名者,其中种子是上一个脊链区块参考高度处主链区块的种子,因此每个验证者都能重复这次抽签。在 mainnet 上,一个脊链轮次持续 100 秒,创建窗口为 300 秒,容差为 150 秒;签名者会拒绝超出其 60 秒收集窗口的旧提案。如果一个脊链区块到来的时间早于上一个区块的时间加上周期再减去容差,就会被拒绝。
脊链区块从不为空。在相互竞争的脊链之间,签名更多的一方获胜。节点在回滚任何东西之前会先衡量竞争脊链的权重,永远不会把自己的脊链回滚到版本锚点以下,并且在另一条脊链应用失败时会恢复自己的脊链。
脊链的用途
- 最终性。 任何节点都不会在最近存储的脊链检查点以下对主链进行重组(见 最终性与回滚)。
- 快速同步。 新节点先同步脊链:其中的清单说明要获取哪个快照,其中的密钥变更说明要信任哪些节点密钥(见 快照与快速同步)。
- 归档。 保存从区块 0 起每一份快照的,是去中心化存储,而不是每个节点,因此任何人都可以沿脊链回溯到过去的任何快照,并重建当时的状态(见 归档与读取过去)。
脊链由每个节点维护,而不是由 ZooBC Foundation 维护,其上也不进行任何买卖。