Solana - 分片包

本文档详细介绍了Solana区块链中Shred packets的结构、协议、以及生成和验证过程。Shred packets是block数据分割后的片段,用于在点对点网络中传播交易数据。文档涵盖了shred packets的多个版本,包括数据布局、头部信息、分片方法、纠删码、签名以及Merkle认证机制等关键技术细节。

分片数据包

分片数据包是区块(交易数据)的片段,用于促进在点对点网络中的传播。

数据分片通过分割区块数据来创建。 代码分片通过对分组到前向纠错(FEC)集合中的数据分片构建纠删码来创建。

所有分片都由区块生产者签名。

版本

存在分片结构的多个版本。 分片版本没有被显式编码。

v1:创世版本

Solana 主网 beta 创世启动时,支持具有传统认证机制的代码和数据分片。

v2:显式数据大小

数据分片头的重大更改,在偏移量 0x56 处添加了一个新的大小字段。

分片有效载荷从偏移量 0x56 移动到 0x58

v3:Merkle 认证机制

引入了两种具有 Merkle 认证方案的附加分片变体。

协议

数据布局

分片按顺序包含以下部分:

每个字段都是字节对齐的。整数字节顺序为小端。

数据包大小

SHRED_SZ_MAX 常量定义为 1228。

如果使用传统认证, 则每个分片在序列化时占用 SHRED_SZ_MAX 字节。 如果使用 Merkle 认证,则编码分片占用 SHRED_SZ_MAX 字节, 而数据分片占用 1203 字节。

这源于 IPv6 的最小链路 MTU 为 1280 字节,减去为 IPv6 和 UDP 头部保留的 48 字节。 另外 4 字节保留给可选的 nonce 字段。

通用头部

通用头部大小为 0x53(83 字节)。

偏移量 大小 类型 名称 目的
0x00 64B Ed25519 签名 signature 区块生产者签名
0x40 1B u8 variant 分片变体
0x41 8B u64 slot Slot 编号
0x49 4B u32 shred_index 分片索引
0x4d 2B u16 shred_version 分片版本
0x4f 4B u32 fec_set_index FEC 集合索引
字段:区块生产者签名

该签名验证了分片源自该 slot 的当前区块生产者。

区块生产者的公钥从外部获取。

用于创建签名的已签名消息的内容取决于使用的认证方案:

  • 在传统认证方案中,区块生产者对每个分片的内容进行签名。 要签名的消息从通用分片头部开始的 64 字节之后开始(即跳过签名字段),并跨越分片的其余部分,包括任何零填充。 生成的签名被放入 区块生产者签名 字段中。

  • 在 Merkle 认证方案中,区块生产者对分片 FEC 集合的 Merkle 根进行签名。有关更多详细信息,请参见 Merkle 证明。 因此,签名字段对于同一 FEC 集合中的所有分片都具有相同的内容。 Merkle 节点大小被截断为 20 个字节。

字段:分片变体

分片变体标识分片类型(数据、代码)和认证机制(传统、Merkle)。

该字段被编码为两个 4 位无符号整数。

高 4 位字段位于位范围 4:8。 低 4 位字段位于位范围 0:4

高 4 位 低 4 位 分片类型 认证
0x5 0xa 代码 传统
0xa 0x5 数据 传统
0x4 任何 代码 Merkle
0x8 任何 数据 Merkle

当使用 Merkle 认证时, 低 4 位指示 Merkle 树的高度。 此数字定义如下:$h$。

字段:Slot 编号

设置为此分片所属的区块的 slot 编号。

字段:分片索引

对于数据分片,设置为此分片在 slot 中所有数据分片中的索引。 对于编码分片,设置为此分片在 slot 中所有编码分片中的索引。

字段:分片版本

标识此分片所属区块的网络分支。

字段:FEC 集合索引

设置为与此分片相同的 FEC 集合中第一个分片的分片索引。 所有具有相同 FEC 集合索引的分片都是同一 FEC 集合的一部分。

数据分片头部(修订版 v2)

数据分片头部

偏移量相对于通用头部的开始。

偏移量 大小 类型 名称 目的
0x53 2B u16 parent_offset 到父区块的 slot 距离
0x55 1B u8 data_flags 数据标志
0x56 2B u16 size 总大小

数据有效载荷从通用头部开始的 0x58 偏移量处开始。

数据标志包含以下字段。(LSB 0 编号)

类型 名称 目的
7 bool block_complete 区块完成位
6 bool batch_complete 批处理完成位
0:6 u6 batch_tick 批处理 tick 编号
字段:区块完成位

如果此数据分片是此区块的最后一个分片,则设置为 1。 否则,设置为零。

区块中的最终分片也是其各自批处理中的最终分片, 因此如果区块完成位设置为 1,则批处理完成位也必须设置为 1。

字段:批处理完成位

如果此数据分片是此 entry 批处理的最后一个分片,则设置为 1。 否则,设置为零以指示下一个数据分片是同一 entry 批处理的一部分。

字段:总大小

此数据包的大小,包括通用分片头部、数据分片头部和数据有效载荷。 大小不包括零填充(如果有) 和 Merkle 证明(如果使用 Merkle 认证)。

数据分片头部(修订版 v1)

修订版 v1 数据分片。

总大小字段假定为 1228 字节,因此未包含在数据包中。

偏移量 大小 类型 名称 目的
0x53 2B u16 parent_offset 到父区块的 slot 距离
0x55 1B u8 data_flags 数据标志

数据有效载荷从通用头部开始的 0x56 偏移量处开始。

代码分片头部

偏移量 大小 类型 名称 目的
0x53 2B u16 num_data_shreds 数据分片的数量
0x55 2B u16 num_coding_shreds 编码分片的数量
0x57 2B u16 position 此分片在 FEC 集合中的位置

Reed-Solomon 编码有效载荷从通用头部开始的 0x59 偏移量处开始。

字段:数据分片的数量

设置为此数据包所属的 FEC 集合中数据分片的数量。 集合中的每个编码分片在此字段中必须具有相同的值。

字段:编码分片的数量

设置为此数据包所属的 FEC 集合中编码分片的数量。 集合中的每个编码分片在此字段中必须具有相同的值。

字段:FEC 集合位置

标识此数据包包含哪个 Reed-Solomon 分片。 必须在范围 $[0, \texttt{num}\textunderscore\texttt{coding}\textunderscore\texttt{shreds})$ 内。 在 https://github.com/solana-labs/solana/pull/27136 之前,此字段不存在并且设置为 0。 集合中的每个编码分片在此字段中必须具有唯一的值。

分片有效载荷构造

区块生产者在活动时创建分片并将其广播到验证器网络。

这以流式传输过程发生,其中分片以最早的可能机会构建。

分片的构造需要以下步骤:

  1. 区块 Entry 批处理
  2. 数据分片
  3. 纠删码
  4. 签名

区块 Entry 批处理

批处理创建区块 entry 的子组,每个子组被序列化为一个字节数组。

批处理的序列化是所有序列化 entry 的串联,前缀为 entry 计数作为 u64 整数(8 字节)。

分片

分片过程将序列化的 entry 批处理转换为数据分片向量。 在本节中,序列化的 entry 批处理被分成固定大小的块,这些块构成每个数据分片的有效载荷, 并计算相关的元数据。

首先,我们必须计算 $S$,每个数据分片的有效载荷的大小。

  • 对于使用传统认证方案的分片,$S$ 具有硬编码值 1051。 这个值足够小,可以确保代码分片的有效载荷可以覆盖数据分片的头部,同时仍然可以适应 1228 字节。
  • 对于使用 Merkle 认证方案的分片,$S = 1115 - 20 \lceil \log_2 (N+K) \rceil$, 其中 $N+K$ 是 FEC 集合中数据和代码分片的总数 (有关详细信息,请参见下一节)。 不幸的是,这意味着定义有些循环, 并且 $S$ 仅对于特定的 FEC 集合是恒定的, 而不是对于整个 entry 批处理。 这意味着,当使用 Merkle 认证时, 分片和纠删码步骤并不完全独立。 为了保持演示的清晰性,我们将分别描述它们。)

令 $\ell$ 为序列化的 entry 批处理的字节长度,并且 $x_0, x1, \ldots, x{\ell-1}$ 为序列化的 entry 批处理字节。

那么第 $i$ 个数据分片的有效载荷为 $x{iS}, x{iS+1}, \ldots, x{(i+1)S-1}$ 对于 $0\le i < \lfloor{\ell/S}\rfloor$。 如果 $\ell$ 不能被 $S$ 整除,那么最终的有效载荷将 0 填充到 $S$ 字节,即 $x{\lfloor{\ell/S}\rfloor S}, x{\lfloor{\ell/S}\rfloor S+1}, \ldots, x{\ell-1}, \underbrace{0, 0, \; \ldots\;, 0}_{\ell-S\lfloor{\ell/S}\rfloor bytes}$。

该区块中的第一个批处理的第一个分片的分片索引为 0。 每个后续分片的分片索引比前一个分片的分片索引大 1。 后续批处理中的第一个分片的分片索引比前一个批处理中的最后一个分片的分片索引大 1。 也就是说,分片索引单调递增,不会在每个批处理中重置。

每个批处理的最后一个数据分片设置了“批处理完成”位。 可以使用数据标志位 0x40 提取此字段。

该区块的最后一个数据分片设置了“区块完成”位。 可以使用数据标志位 0x80 提取此字段。 由于一个区块包含整数个 entry 批处理, 因此该区块的最后一个数据分片也必须是批处理的最后一个数据分片。

批处理中所有分片的“批处理 tick 编号”设置为 自该 slot 开始以来,该批处理中第一个 entry 经过的 PoH tick 数, 由于 Solana 每个 slot 有 64 个 tick,因此该字段不会溢出。 可以使用掩码为 0x3f 的数据标志位字段来提取此字段。 当“区块完成”标志设置为 1 时,“批处理 tick 编号”可以设置为 0。

分片过程(deshredding)的逆过程是从数据分片流中重建序列化的批处理。

纠删码

数据分片被分组在一起以形成前向纠错(FEC)集合。 区块生产者可以选择 $N$, 要包含在 FEC 集合中的连续数据分片的数量。 但是,一个 FEC 集合必须至少有 1 个且不超过 67 个数据分片, 建议使用 $N=32$。

给定选择的值 $N$, 一个兼容的区块生产者必须生成 $K$ 个代码分片,如下表所示:

$N$ $K$ 总分片数 ($N+K$) $N$ $K$ 总分片数 ($N+K$)
1 17 18 17 26 43
2 18 20 18 27 45
3 19 22 19 27 46
4 19 23 20 28 48
5 20 25 21 28 49
6 21 27 22 29 51
7 21 28 23 29 52
8 22 30 24 29 53
9 23 32 25 30 55
10 23 33 26 30 56
11 24 35 27 31 58
12 24 36 28 31 59
13 25 38 29 31 60
14 25 39 30 32 62
15 26 41 31 32 63
16 26 42 32 32 64

对于 $N>32$,使用 $K=N$。

但是,只要 $1\le K, N \le 67$,兼容的实现也可以接受具有不同数量的代码分片的 FEC 集合。

使用 Reed-Solomon 编码从数据分片生成代码分片。 当使用传统认证时, 用于进行纠删码的“数据分片”的解释从数据分片的通用头部的第一个字节开始 并包括签名字段。 当使用 Merkle 认证时, 用于进行纠删码的“数据分片”的解释紧随签名字段之后开始, 并在 Merkle 证明部分之前立即结束。

令 $x_{i,b}$ 为 FEC 集合(编号为 $0, 1, \ldots, N-1$)的第 $i$ 个数据分片的第 $b$ 个字节,解释为有限域 $GF(2^8)$(即 $\mathbb{F}_2[\gamma] / (\gamma^8 + \gamma^4 + \gamma^3 + \gamma^2 + 1)$)的元素。

一次取一个 $b$,定义阶数小于 $N$ 的多项式 $P_b(x)$,使得对于所有 $0\le i < N$, $P_b(i) = x_i$(将 $i$ 的字节值解释为 $GF(2^8)$ 的元素)。 这个多项式是唯一的。

然后,每个代码分片的第 $b$ 个字节来自评估 $Pb$ 作为后续点。 更准确地说,令 $y{j,b}$ 为 $0\le j < K$ 的第 $j$ 个代码分片的第 $b$ 个字节。 那么 $y_{j,b} = P_b(N+j)$,其中 $N+j$ 被计算为整数,然后解释为 $GF(2^8)$ 的元素。

等效地,这是一个线性运算,因此也可以描述为 $GF(2^8)$ 上的矩阵-向量积:

$$ M \left( \begin{array}{c} x{0,b} \ x{1,b} \ \vdots \ x{N-1,b} \end{array} \right) = \left( \begin{array}{c} y{0,b} \ y{1,b} \ \vdots \ y{K-1,b} \end{array} \right).$$

矩阵 $M$ 仅取决于 $N$ 和 $K$。 有多种计算 $M$ 的方法,但一种描述是

$$ M = \left( \begin{array}{c} N^0 & N^1 & \cdots & N^{N-1} \ (N+1)^0 & (N+1)^1 & \cdots & (N+1)^{N-1} \ \vdots & \vdots & \ddots & \vdots \ (N+K-1)^0 & (N+K-1)^1 & \cdots & (N+K-1)^{N-1} \end{array} \right) * \left( \begin{array}{ccccc} 1 & 0 & 0 & \cdots & 0 \ 1^0 & 1^1 & 1^2 & \cdots & 1^{N-1} \ 2^0 & 2^1 & 2^2 & \cdots & 2^{N-1} \ \vdots & \vdots & \vdots & \ddots & \vdots \ (N-1)^0 & (N-1)^1 & (N-1)^2 & \cdots & (N-1)^{N-1} \end{array} \right)^{-1} $$

其中基数和指数计算是整数运算, 但指数运算、矩阵逆运算和矩阵乘法是有限域运算。 也就是说,$M$ 是 Vandermonde 矩阵的一部分与另一个 Vandermonde 矩阵的逆的乘积。

零填充

当分片过程产生的数据 没有填充有效载荷字段时, 必须在分片数据之后插入额外的零字节。 这确保了所有数据分片都具有相同的长度。

Reed-Solomon 编码过程自然会产生大小相同的编码分片, 因此编码分片不需要零填充。

签名

传统

当使用传统认证方法时, 区块生产者使用数据分片和代码分片的 Ed25519 签名填充数据包中签名字段后面的字节, 包括任何零填充。

Merkle

当使用 Merkle 认证方法时, 区块生产者从 FEC 集合中的每个分片构建 规范 Merkle 树, 其中数据分片按顺序排列,然后是编码分片按顺序排列。 两种分片类型的叶节点都是字节 从签名字段之后立即开始 到 Merkle 证明部分之前立即结束。 所有哈希值都被截断为 20 个字节, 每个 SHA256 哈希值的最后 12 个字节都会立即被丢弃。 叶节点使用前缀 \x00SOLANA_MERKLE_SHREDS_LEAF,内部节点使用前缀 \x01SOLANA_MERKLE_SHREDS_NODE。 这两个前缀都是 26 个字节,并且没有以 \0 结尾。

区块生产者计算 Merkle 树的根的 Ed25519 签名, 并将签名存储在通用头部的签名字段中。 由于 FEC 集合中的所有数据包都是同一 Merkle 树的一部分 因此具有相同的 Merkle 根, 因此,在此方案中,同一 FEC 集合中的所有分片(代码和数据)都具有相同的签名。

Merkle 证明

当使用 Merkle 认证时, 数据包的最后字节包含 Merkle 证明,证明有效载荷属于签名字段覆盖的 Merkle 树。

令 $h=\lceil \log_2 (N+K) \rceil$ 为 FEC 集合的 Merkle 树的高度 (包括叶节点但不包括根节点)。 Merkle 证明部分由以下内容组成:

偏移量 大小 类型 描述
end- $20h$ 20B 截断的 Merkle 哈希 兄弟叶节点的 Merkle 哈希
end- $20h$ + 20 20B 截断的 Merkle 哈希 叶节点的父节点的兄弟节点的 Merkle 哈希
... ... ... ...
end-20 20B 截断的 Merkle 哈希 根的子节点的 Merkle 哈希

Merkle 证明包含计算从数据包中的叶节点到根节点的完整分支所需的其他信息。 例如,在 规范 Merkle 树图 2 中, L0 的证明包含哈希值 L1(兄弟叶节点)、 。 它不包括 ,因为这些可以从包含的信息中计算出来。 L3 的证明包含 L2

如前所述,两种分片类型的叶节点都是字节 从签名字段之后立即开始 到 Merkle 证明部分之前立即结束。

一个符合规范的实现必须验证 签名是根哈希的有效签名 并且 Merkle 树在 FEC 集合中的所有分片中是一致的。

  • 原文链接: github.com/solana-founda...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
solana-foundation
solana-foundation
江湖只有他的大名,没有他的介绍。