本文深入探讨了简单序列化(SSZ)在以太坊信标链中的应用,以及其与RLP序列化的比较。SSZ旨在提高以太坊共识层的效率、安全性和可扩展性,详细介绍了SSZ的基本类型、向量、列表、位向量、容器等序列化和反序列化过程,并提供了相关示例代码和图示,以帮助读者更好地理解SSZ的操作及其在以太坊中的重要性。
Simple Serialize (SSZ) 是一个序列化和 Merkle化 方案,专为以太坊的 Beacon Chain 设计。SSZ 替代了执行层 (EL) 中使用的 RLP 序列化,在共识层 (CL) 的各个地方使用,除了 对等发现协议。它的开发和应用旨在增强以太坊 CL 的效率、安全性和可扩展性。
本文档是关于 SSZ 序列化的。你可以通过 merkleization 了解更多关于 SSZ Merkle 化的信息。
有许多工具可用于 SSZ。以下是 SSZ 工具的完整列表。下面是一些流行的工具:
标准 | 紧凑 | 表达能力 | 哈希化 | 索引 |
---|---|---|---|---|
RLP | 是 | 灵活 | 可行 | 否 |
SSZ | 否 | 是 | 是 | 较差 |
表格:根据 Piper Merriam 的 SSZ 与 RLP 比较。
表达能力:
哈希化:
索引:
O(N)
,这对大型网络的性能可能是显著的缺陷。数据类型兼容性:
确定性序列化:
基于这些原因,以太坊在全力推动全面迁移到 SSZ 序列化并停止使用 RLP 序列化。
以下是 SSZ 如何处理基本类型的序列化和反序列化:
flowchart TD
A[开始序列化] --> B[选择数据类型]
B --> C[无符号整数]
B --> D[布尔值]
C --> E[将整数转换为\n小端字节数组]
E --> F[整数的序列化输出]
D --> G["将布尔值转换为字节\n(True 转为 0x01, False 转为 0x00)"]
G --> H[布尔值的序列化输出]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E,G process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class F,H output;
图:基本类型的序列化过程。
flowchart TD
A[开始反序列化] --> B[确定数据类型]
B --> C[无符号整数]
B --> D[布尔值]
C --> E[读入小端字节数组]
E --> F[重建原始整数值]
F --> G[反序列化的整数输出]
D --> H[读入字节]
H --> I["将字节转为布尔值\n(0x01 转为 True, 0x00 转为 False)"]
I --> J[反序列化的布尔值输出]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E,H,I process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class G,J output;
图:基本类型的反序列化过程。
无符号整数(uintN
)在 SSZ 中表示为 N
可以是 8、16、32、64、128 或 256 位。这些整数直接序列化为其小端字节表示形式,适用于大多数现代计算机架构并便于按字节级别进行操作。
无符号整数的序列化过程:
uintN
类型的无符号整数。N/8
的字节数组。例如,uint16
表示 2 个字节。示例:
1025
作为 uint16
将序列化为 01 04
(十六进制)。先将 1025
转换为十六进制,得到 0x0401
。在小端格式中,最低有效字节 (LSB) 优先。因此,0x0401
在小端格式为 01 04
。字节数组 [01, 04]
就是序列化的输出。无符号整数的反序列化过程:
uintN
的字节数组。示例:
01 04
(十六进制)被反序列化为整数 1025
。读取第一个字节 01
为整数的低位部分,04
为高位部分。重组时以大端格式可读,再转化为十六进制为 0401
,即十进制的 1025
。SSZ 中的布尔值非常简单,每个布尔值用一个字节表示。
布尔值的序列化过程:
True
或 False
)。True
,序列化为 01
(十六进制)。False
,序列化为 00
。示例:
True
变为 01
。False
变为 00
。布尔值的反序列化过程:
01
表示 True
。00
表示 False
。示例:
01
被反序列化为 True
。00
被反序列化为 False
。我们可以使用指定的 python Eth2 规范运行 SSZ 序列化和反序列化命令,如下所示,并验证上述字节数组。
>>> from eth2spec.utils.ssz.ssz_typing import uint64, boolean
## 序列化
>>> uint64(1025).encode_bytes().hex()
'0104000000000000'
>>> boolean(True).encode_bytes().hex()
'01'
>>> boolean(False).encode_bytes().hex()
'00'
## 反序列化
>>> print(uint64.decode_bytes(bytes.fromhex('0104000000000000')))
1025
>>> print(boolean.decode_bytes(bytes.fromhex('01')))
1
>>> print(boolean.decode_bytes(bytes.fromhex('00')))
0
SSZ 中的向量用于处理固定长度的同质元素集合。以下是 SSZ 如何处理向量的序列化和反序列化的详细分解。
SSZ 向量的序列化
flowchart TD
A[开始序列化] --> B[定义类型和长度的向量]
B --> C[序列化每个元素]
C --> D["将每个元素转换为\n字节数组(小端)"]
D --> E[将所有字节数组连接]
E --> F[输出序列化向量]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class F output;
图:SSZ 向量的序列化。
固定长度定义:向量在定义时指定可以包含的元素的具体长度和类型,例如 Vector[uint64, 4]
表示包含四个 64 位无符号整数的向量。
元素序列化:
拼接:
示例:
对于具有元素 [256, 512, 768]
的 Vector[uint64, 3]
,每个元素占 64 位或 8 个字节。序列化过程如下:
将每个整数转换为小端字节数组:
256
作为 uint64
变为 00 01 00 00 00 00 00 00
。512
作为 uint64
变为 00 02 00 00 00 00 00 00
。768
作为 uint64
变为 00 03 00 00 00 00 00 00
。将这些字节数组连接:
00 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 03 00 00 00 00 00 00
。序列化输出:
00 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 03 00 00 00 00 00 00
。SSZ 向量的反序列化
flowchart TD
A[开始反序列化] --> B[接收序列化字节流]
B --> C[根据元素大小识别并拆分字节流]
C --> D[将每个字节段反序列化为其原始类型]
D --> E[将元素重新组合为向量]
E --> F[输出反序列化向量]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class F output;
图:SSZ 向量的反序列化。
固定长度利用:
元素反序列化:
重建:
示例:
给定 Vector[uint64, 3]
的序列化数据:
00 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 03 00 00 00 00 00 00
。将数据解析为段:
00 01 00 00 00 00 00 00
→ 代表整数 256。00 02 00 00 00 00 00 00
→ 代表整数 512。00 03 00 00 00 00 00 00
→ 代表整数 768。将每个段从小端字节数组转换回整数:
uint64
整数。重建:
[256, 512, 768]
。我们可以在 python 中运行并验证如下示例:
>>> from eth2spec.utils.ssz.ssz_typing import uint8, uint16, Vector
>>> Vector[uint16, 3](256, 512, 768).encode_bytes().hex()
'000100000000000000020000000000000003000000000000'
>>> print(Vector[uint64, 3].decode_bytes(bytes.fromhex('000100000000000000020000000000000003000000000000')))
Vector[uint64, 3]<<len=3>>(256, 512, 768)
>>>
SSZ 中的列表对于管理具有指定最大长度 (N
) 的变长同质元素集合至关重要。这种灵活性允许动态管理数据结构,如交易集合或变更状态组件,以适应网络的变化需求。
SSZ 列表的序列化
flowchart TD
A[开始序列化] --> B[定义类型和最大长度的列表]
B --> C[序列化每个元素]
C --> D[将每个元素转换为字节数组 - 小端]
D --> E[将所有字节数组连接]
E --> F[可选: 包含长度元数据]
F --> G[输出序列化列表]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E,F process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class G output;
图:SSZ 列表的序列化。
定义列表:SSZ 中的列表以特定的元素类型和最大长度进行定义,记为 List[type, N]
。此定义不仅限制了列表的最大容量,还告知如何进行序列化操作。
元素序列化:
uint64
元素,序列化过程包含将每个整数转换为字节数组。拼接序列化元素:
包含长度元数据(可选):
示例:
对于包含元素 [1024, 2048, 3072]
的 List[uint64, 5]
,序列化过程将包括:
00 04 00 00 00 00 00 00
,00 08 00 00 00 00 00 00
,00 0C 00 00 00 00 00 00
。00 04 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 0C 00 00 00 00 00 00
。SSZ 列表的反序列化
flowchart TD
A[开始反序列化] --> B[接收序列化字节流]
B --> C["识别并根据元素大小分割字节流"]
C --> D[将每个字节段反序列化为 uint64]
D --> E[将元素重新组合为列表]
E --> F[输出反序列化列表]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class F output;
图:SSZ 列表的反序列化。
接收序列化数据:序列化字节流作为输入,包含为每个元素编码的字节数组。
解析并反序列化每个元素:
uint64
),将序列化流按 8 字节的段进行解析。重建列表:
示例:
给定序列化数据 00 04 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 0C 00 00 00 00 00 00
表示的 List[uint64, 5]
:
00 04 00 00 00 00 00 00
、00 08 00 00 00 00 00 00
、00 0C 00 00 00 00 00 00
。1024
、2048
、3072
。[1024, 2048, 3072]
。我们可以在上面示例中这样运行并验证:
>>> from eth2spec.utils.ssz.ssz_typing import uint8, List, Vector
>>> List[uint64, 5](1024, 2048, 3072).encode_bytes().hex()
'00040000000000000008000000000000000c000000000000'
>>> print(List[uint64, 5].decode_bytes(bytes.fromhex('00040000000000000008000000000000000c000000000000')))
List[uint64, 5]<<len=3>>(1024, 2048, 3072)
>>>
列表是在 SSZ 中的变长对象,它们在包含另一对象时以不同的方式编码。因此,有一些小的开销。例如,下面的 Alice
和 Bob
对象有不同的编码。
>>> from eth2spec.utils.ssz.ssz_typing import uint8, Vector, List, Container
>>> class Alice(Container):
... x: List[uint8, 3] # 变长
>>> class Bob(Container):
... x: Vector[uint8, 3] # 固定长度
>>> Alice(x = [1, 2, 3]).encode_bytes().hex()
'04000000010203'
>>> Bob(x = [1, 2, 3]).encode_bytes().hex()
'010203'
>>>
在 SSZ 中,位向量被用于管理固定长度的布尔值序列,通常表示为位。该数据结构对于紧凑存储二进制数据或标志非常高效,这是以太坊应用中常见的用于指示状态条件、权限或其他二进制设置的应用。
SSZ 位向量的序列化
flowchart TD
A[开始序列化] --> B[定义大小为 N 的位向量]
B --> C[将位打包到字节中]
C --> D[从 LSB 到 MSB 的位顺序]
D --> E[如果 N % 8 != 0 添加填充]
E --> F[输出序列化字节数组]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class F output;
图:SSZ 位向量的序列化。
定义位向量:SSZ 中的位向量由它的长度 N
定义,指定可包含的位数。例如,Bitvector[256]
表示包含 256 位的位向量。
将位转换为字节:
0
对应 False
,1
对应 True
。字节数组形成:
N
不是 8 的倍数,最后一个字节中会包含少于 8 位的数据,并在最高有效位位置填充零。示例:
对于 Bitvector[10]
的模式 1011010010
:
10110100
) 形成第一个字节。10
) 用六个零填充形成第二个字节:10000000
。B4 80
(十六进制)。SSZ 位向量的反序列化
flowchart TD
A[开始反序列化] --> B[接收序列化字节数组]
B --> C[读取每个字节]
C --> D[将字节转换为位]
D --> E[尊重字节中的 LSB 到 MSB 的顺序]
E --> F[去掉任何填充位]
F --> G[重构位向量]
G --> H[输出反序列化的位向量]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E,F,G process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class H output;
图:SSZ 位向量的反序列化。
读取序列化字节数组:以作为输入的字节数组开始。
从字节提取位:
N
不是 8 的倍数,则去除最后一个字节中的多余填充的位。重建位向量:
N
。示例:
给定序列化数据 B4 80
表示 Bitvector[10]
:
B4
(二进制10110100
)和 80
(二进制10000000
)转换回位。1011010010
。1011010010
。你可以像下面这样在 python 中运行并验证:
>>> from eth2spec.utils.ssz.ssz_typing import Bitvector
>>> Bitvector[8](0,0,1,0,1,1,0,1).encode_bytes().hex()
'b4'
>>> Bitvector[8](0,0,0,0,0,0,0,1).encode_bytes().hex()
'80'
实际上,我们可以使用Vector[boolean, N]
或Bitvector[N]
来表示布尔值的列表。然而,后者在实际使用中序列化占用的字节上最大减少了八倍,因为前者将会对每个位一整字节。
>>> from eth2spec.utils.ssz.ssz_typing import Vector, Bitvector, boolean
>>> Bitvector[5](1,0,1,0,1).encode_bytes().hex()
'15'
>>> Vector[boolean,5](1,0,1,0,1).encode_bytes().hex()
'0100010001'
位列表在 SSZ 中类似于位向量,但它们旨在处理带有指定最大长度 (N
) 的变长布尔值序列。
SSZ 位列表的序列化
flowchart TD
A[开始序列化] --> B[定义大小为 N 的位列表]
B --> C[将位打包为字节]
C --> D[添加哨兵位]
D --> E[如有必要对最后一个字节填充]
E --> F[输出序列化字节数组]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class F output;
图:SSZ 位列表的序列化。
定义位列表:位列表通过其最大长度 N
定义,该长度指定可以包含的位的最大数量。实际的位数可以小于 N
。
已位打包为字节:
0
对应 False
,1
对应 True
。添加哨兵位:
1
)。这一点对于确保反序列化过程准确识别位列表的长度很重要。字节数组形成和填充:
SSZ 位列表的反序列化
flowchart TD
A[开始反序列化] --> B[接收序列化字节数组]
B --> C[将字节转换为位]
C --> D[识别并移除哨兵位]
D --> E[去掉填充位]
E --> F[重建原始位列表]
F --> G[输出反序列化位列表]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E,F process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class G output;
图:SSZ 位列表的反序列化。
接收序列化字节数组:开始时以包含哨兵位的字节数组为输入。
从字节提取位:
识别并移除哨兵位:
1
(哨兵位)以确定位列表数据的实际结束。重建位列表:
可以像下面这样运行位列表的编码:
>>> from eth2spec.utils.ssz.ssz_typing import Bitlist
>>> Bitlist[100](0,0,0).encode_bytes().hex()
'08'
由于哨兵对于数量的要求,如果其实际长度是 8 的整数倍,则我们需要一个额外的字节来序列化一个位列表(与最大长度 N
无关)。固定长度的位向量则不会这样。
>>> Bitlist[8](0,0,0,0,0,0,0,0).encode_bytes().hex()
'0001'
>>> Bitvector[8](0,0,0,0,0,0,0,0).encode_bytes().hex()
'00'
在 SSZ 中,容器是将多个字段分组为单个复合类型的基本结构。容器内的每个字段可以是任何 SSZ 支持的类型,包括基本类型如 uint64
、其他容器、向量或列表等复合类型。容器类似于编程语言中的结构或对象,使它们在以太坊中表示复杂和嵌套数据结构方面至关重要。
SSZ 容器的序列化
flowchart TD
A[开始序列化] --> B[定义容器模式]
B --> C[根据类型序列化每个字段]
C --> D["序列化基本类型\n(uint64、布尔值等)"]
C --> E["序列化复合类型\n(其他容器、列表、向量)"]
D --> F[拼接字段的序列化输出]
E --> F
F --> G[输出序列化容器]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E,F process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class G output;
图:SSZ 容器的序列化。
定义容器:SSZ 中的容器由其模式定义,模式指定字段的类型和顺序。该模式至关重要,因为它决定了如何序列化和反序列化数据。
序列化每个字段:
拼接序列化字段:
SSZ 容器的反序列化
flowchart TD
A[开始反序列化] --> B[接收序列化的容器数据]
B --> C[根据容器模式解析数据]
C --> D[根据类型反序列化字段]
D --> E[反序列化基本类型]
D --> F[反序列化复合类型]
E --> G[用反序列化的字段重建容器]
F --> G
G --> H[输出反序列化的容器]
classDef startEnd fill:#f9f,stroke:#333,stroke-width:4px;
class A startEnd;
classDef process fill:#ccf,stroke:#f66,stroke-width:2px;
class B,C,D,E,F,G process;
classDef output fill:#cfc,stroke:#393,stroke-width:2px;
class H output;
图:SSZ 容器的反序列化。
读取序列化数据:以表示容器的序列化字节流作为输入。
根据模式解析序列化数据:
反序列化每个字段:
重建容器:
示例:
让我们通过以太坊 Beacon Chain 中的 IndexedAttestation
容器深入了解 SSZ 序列化和反序列化过程。此示例将概述如何处理复杂的嵌套容器,尤其是那些涉及固定大小和变长数据类型的容器。
IndexedAttestation
容器的结构如下:
class IndexedAttestation(Container):
attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
signature: BLSSignature
它包含一个 AttestationData
容器:
class AttestationData(Container):
slot: Slot
index: CommitteeIndex
beacon_block_root: Root
source: Checkpoint
target: Checkpoint
而 Checkpoint
容器则有两个:
class Checkpoint(Container):
epoch: Epoch
root: Root
IndexedAttestation 容器结构
IndexedAttestation
容器包括多个字段,其中一些字段为固定大小的基本类型,另一些为复合类型,包括另一个容器(AttestationData
)和列表(如 attesting_indices
)。
结构如下:
List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
(变长)AttestationData
(复合容器)BLSSignature
(固定大小)
``- **插槽**:
Slot`(固定大小)CommitteeIndex
(固定大小)Root
(固定大小)Checkpoint
(复合容器)Checkpoint
(复合容器)检查点容器结构
Epoch
(固定大小)Root
(固定大小)序列化过程
IndexedAttestation
的序列化涉及根据其类型序列化每个组件:序列化固定大小元素
Slot
,CommitteeIndex
,Epoch
,Root
,BLSSignature
)被序列化为其对应的字节格式,通常对数字类型使用小端格式。序列化可变大小元素
List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
通过先记录列表的长度,再序列化每个索引的形式进行序列化。示例序列化输出
from eth2spec.utils.ssz.ssz_typing import *
from eth2spec.capella import mainnet
from eth2spec.capella.mainnet import *
attestation = IndexedAttestation(
attesting_indices = [33652, 59750, 92360],
data = AttestationData(
slot = 3080829,
index = 9,
beacon_block_root = '0x4f4250c05956f5c2b87129cf7372f14dd576fc152543bf7042e963196b843fe6',
source = Checkpoint (
epoch = 96274,
root = '0xd24639f2e661bc1adcbe7157280776cf76670fff0fee0691f146ab827f4f1ade'
),
target = Checkpoint(
epoch = 96275,
root = '0x9bcd31881817ddeab686f878c8619d664e8bfa4f8948707cba5bc25c8d74915d'
)
),
signature = '0xaaf504503ff15ae86723c906b4b6bac91ad728e4431aea3be2e8e3acc888d8af'
+ '5dffbbcf53b234ea8e3fde67fbb09120027335ec63cf23f0213cc439e8d1b856'
+ 'c2ddfc1a78ed3326fb9b4fe333af4ad3702159dbf9caeb1a4633b752991ac437'
)
print(attestation.encode_bytes().hex())
表示该 IndexedAttestation
对象的序列化数据 blob 为(十六进制):
e40000007d022f000000000009000000000000004f4250c05956f5c2b87129cf7372f14dd576fc15
2543bf7042e963196b843fe61278010000000000d24639f2e661bc1adcbe7157280776cf76670fff
0fee0691f146ab827f4f1ade13780100000000009bcd31881817ddeab686f878c8619d664e8bfa4f
8948707cba5bc25c8d74915daaf504503ff15ae86723c906b4b6bac91ad728e4431aea3be2e8e3ac
c888d8af5dffbbcf53b234ea8e3fde67fbb09120027335ec63cf23f0213cc439e8d1b856c2ddfc1a
78ed3326fb9b4fe333af4ad3702159dbf9caeb1a4633b752991ac437748300000000000066e90000
00000000c868010000000000
序列化输出的分解
为了清晰地解释序列化过程和示例中 IndexedAttestation
容器的序列化数据结构,我们将序列化分解为其各个组成部分,并理解每个部分如何在字节流中表示。这种拆解有助于说明 SSZ 格式如何管理复杂的数据结构。
第1部分:固定大小元素
可变大小列表 (attesting_indices
) 的 4 字节偏移:
00
e4000000
attesting_indices
列表在序列化字节流中的开始位置。十六进制值 e4
转换为十进制是 228
,意味着列表从字节 228
开始。插槽 (uint64):
04
7d022f0000000000
slot
字段序列化为一个 64 位无符号整数。十六进制 7d022f00
在小端格式下转化为十进制的 3080829
,即插槽编号。委员会索引 (uint64):
0c
0900000000000000
index
字段,表示一个委员会索引,作为 64 位无符号整数。值 09
表示委员会索引 9
。信标区块根 (Bytes32):
14
4f4250c05956f5c2b87129cf7372f14dd576fc152543bf7042e963196b843fe6
Bytes32
存储的 256 位哈希,表示信标区块的根哈希。源检查点纪元 (uint64) 和根 (Bytes32):
34
1278010000000000
3c
d24639f2e661bc1adcbe7157280776cf76670fff0fee0691f146ab827f4f1ade
epoch
(96274) 和一个 root
。根是另一个 256 位哈希。目标检查点纪元 (uint64) 和根 (Bytes32):
5c
1378010000000000
64
9bcd31881817ddeab686f878c8619d664e8bfa4f8948707cba5bc25c8d74915d
epoch
(96275) 和一个 root
,详细说明了之前的状态。签名 (BLSSignature/Bytes96):
84
第2部分:可变大小元素
e4
748300000000000066e9000000000000c868010000000000
228
开始,并包含索引 33652
,59750
,和 92360
。
- 原文链接: github.com/thogiti/thogi...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!