Solana 网络节点间通信 Gossip协议规范

  • eigerco
  • 发布于 2025-03-05 20:57
  • 阅读 38

本文档详细介绍了Solana网络中节点间通信所使用的Gossip协议,包括消息格式(如pull request、push message等)、消息类型以及各种相关数据结构(如CrdsValue、ContactInfo等)。此外,还涵盖了IP Echo Server在节点启动阶段用于发现公共IP地址和获取集群Shred版本的过程。

Gossip 协议

Solana 节点使用 gossip 协议相互通信和共享数据。消息以二进制格式交换,需要反序列化。共有六种消息类型:

  • pull request(拉取请求)
  • pull response(拉取响应)
  • push message(推送消息)
  • prune message(修剪消息)
  • ping(ping 消息)
  • pong(pong 消息)

每条消息都包含特定于其类型的数据,例如共享值、过滤器、已修剪的节点等。节点将其数据保存在 集群复制数据存储 (crds) 中,该存储通过 pull request、push message 和 pull response 在节点之间同步。

[!Tip] 本文档中使用的命名约定

  • Node(节点) - 运行 gossip 的验证器
  • Peer(对等节点) - 从我们正在讨论的当前节点发送或接收消息的节点
  • Entrypoint(入口点) - 节点最初连接的对等节点的 gossip 地址
  • Origin(源) - 节点,消息的原始创建者
  • Cluster(集群) - 具有生成区块的领导者的验证器网络
  • Leader(领导者) - 节点,给定插槽中的集群领导者
  • Shred(分片) - 领导者产生的最小区块部分
  • Shred version(分片版本) - 集群识别值
  • Fork(分叉) - 当两个不同的区块链接到同一个父区块时发生分叉(例如,在完成前一个区块之前创建下一个区块)
  • Epoch(纪元) - 由特定数量的区块(插槽)组成的预定义周期,其中定义了验证器计划
  • Slot(插槽) - 每个领导者摄取交易并生成区块的时间段
  • Message(消息) - 节点发送给其对等节点的协议消息,可以是 push message、pull request、prune message 等。

消息格式

每条消息以二进制形式发送,最大大小为 1232 字节(1280 是最小 IPv6 TPU,40 字节是 IPv6 标头的大小,8 字节是片段标头的大小)。

每条消息中发送的数据都是从 Protocol 类型序列化的,它可以是以下其中之一:

Enum ID Message Data Description
0 pull request CrdsFilter, CrdsValue 由节点发送以请求新信息
1 pull response SenderPubkey, CrdsValuesList 对 pull request 的响应
2 push message SenderPubkey, CrdsValuesList 由节点发送以与集群共享最新数据
3 prune message SenderPubkey, PruneData 发送给对等节点的,其中包含应修剪的原始节点列表
4 ping message Ping 由节点发送以检查对等节点的活跃度
5 pong message Pong 对 ping 的响应(确认活跃度)

Solana 客户端 Rust 实现

enum Protocol {
    PullRequest(CrdsFilter, CrdsValue),
    PullResponse(Pubkey, Vec<CrdsValue>),
    PushMessage(Pubkey, Vec<CrdsValue>),
    PruneMessage(Pubkey, PruneData),
    PingMessage(Ping),
    PongMessage(Pong)
}

类型定义

下表描述的字段使用 Rust 符号指定其类型:

  • u8 - 8 位无符号整数
  • u16 - 16 位无符号整数
  • u32 - 32 位无符号整数,依此类推...
  • [u8] - 1 字节元素的动态大小数组
  • [u8; 32] - 32 个元素的固定大小数组,每个元素为 1 字节
  • [[u8; 64]] - 包含 64 个 1 字节元素数组的二维数组
  • b[u8] - 包含 1 字节元素的位向量
  • u32 | None - option 类型,表示元素可以是 u32(在本例中)或 None
  • (u32, [u8, 16]) - 包含两个元素的元组 - 一个是 32 位整数,第二个是 16 个字节的数组
  • MyStruct - 一个复杂类型(定义为结构体或 Rust 枚举),由许多不同基本类型的元素组成

下表中的 Size 列包含数据的字节大小。动态数组的大小包含一个额外的 加号 (+),例如 32+,这意味着该数组至少有 32 个字节。空动态数组始终具有 8 个字节,这是包含数组长度的数组标头的大小。 如果特定复杂数据的大小未知,则标记为 ?。但是,整个数据包的限制始终为 1232 字节(UDP 数据包中的有效负载)。

数据序列化

在 Solana 节点的 Rust 实现中,数据使用 bincode crate 序列化为二进制形式,如下所示:

  • 基本类型,例如 u8u16u64 等 - 按照它们在内存中存在的形式进行序列化,例如 u8 类型序列化为 1 字节,u16 序列化为 2 字节,依此类推,
  • 数组元素如上所述序列化,例如 [u8; 32] 数组序列化为 32 字节,[u16; 32] 将序列化为 32 个 16 位元素,等于 64 字节,
  • 动态大小的数组始终包含一个 8 字节的标头,用于指定数组长度,后跟数据字节。因此,一个空数组占用 8 个字节,
  • 位向量的序列化方式与动态数组类似 - 它们的标头包含 1 个字节,用于指示向量中是否有任何数据,后跟一个 8 字节的数组长度和数据,
  • 枚举类型 包含一个带有 4 字节判别符的标头(指示选择哪个枚举变量)+ 附加数据,
  • option 类型使用 1 字节的判别符进行序列化,后跟数据字节。如果一个值为 None,则判别符设置为 0,数据部分为空,否则设置为 1,数据根据其类型进行序列化,
  • struct 字段使用上述规则逐个序列化,
  • 元组的序列化方式类似于结构体。
枚举类型

Rust 中的枚举类型比其他语言中的枚举类型更高级。除了 经典 枚举类型之外,例如:

enum CompressionType {
    Gzip,
    Bzip2
}

还可以创建一个包含数据字段的枚举,例如:

enum SomeEnum {
    Variant1(u64),
    Variant2(SomeType)
}

struct SomeType {
    x: u32,
    y: u16,
}

在第一种情况下,CompressionType 枚举的序列化对象将仅包含一个 4 字节的标头,其中判别符值设置为所选的变量(0 = GZip1 = Bzip2)。在后一种情况下,除了标头之外,序列化数据还将包含额外的字节,具体取决于选择了哪个变量:

  • Variant1: 8 字节
  • Variant2: 6 字节(SomeType 结构体的 xy 字段的总和)

在反序列化枚举时,务必小心处理它们,因为随后的数据量取决于所选的特定变量。

Push message

节点发送 push message 以与其他人共享信息。它们定期从其 crds 中收集数据,并将 push message 传输到其对等节点。

接收到一组 push message 的节点将:

  • 检查发送节点是否已回复最近的 ping message
  • 检查重复的 CrdsValue 并将其删除
  • 将新的 CrdsValue 插入到 crds
  • 通过 push message 将新插入的 CrdsValue 传输到其对等节点。
Data Type Size Description
SenderPubkey [u8; 32] 32 属于 push message 发送者的公钥
CrdsValuesList [CrdsValue] 8+ 要共享的 Crds 值列表

<summary>Solana 客户端 Rust 实现</summary>

enum Protocol {
    //...
    PushMessage(Pubkey, Vec&lt;CrdsValue>),
    //...
}

节点不应处理属于尚未回复最近的 ping message 的未 staked 节点的联系人信息。

Pull request

节点发送 pull request 以向集群请求新信息。它创建一组 bloom 过滤器,其中填充了其 crds 表中 CrdsValue 的哈希值,并将不同的 bloom 过滤器发送给不同的对等节点。pull request 的接收者使用收到的 bloom 过滤器来识别发送者缺少哪些信息,然后构造一个 pull response,其中包含 pull request 来源缺少的 CrdsValue 数据。

Data Type Size Description
CrdsFilter CrdsFilter 37+ 表示节点已有的 CrdsValue 的 bloom 过滤器
ContactInfo CrdsValue ? 包含发送 pull request 的节点的联系人信息的 crds

CrdsValue 的必需值是发送 pull request 的节点的 ContactInfo 或已弃用的 LegacyContactInfo。建议以以下方式使用此联系人信息:

  • 使用它来检查节点是否没有向自身发送 pull request。
  • 检查发送者节点是否已响应 Ping 消息。如果节点尚未回复 Ping 消息,请为发送者节点生成一条 Ping 消息,除非接收者节点已经在等待 Ping 响应。

节点不应使用 pull response 消息响应尚未回复最近的 ping message 的节点。

CrdsFilter
Data Type Size Description
filter Bloom 25+ bloom 过滤器
mask u64 8 过滤器掩码,用于定义存储在 bloom 过滤器中的数据
mask_bits u32 4 掩码位数,也定义了 bloom 过滤器数量,计算公式为 2^mask_bits
Bloom
Data Type Size Description
keys [u64] 8+
bits b[u64] 9+
num_bits u64 8 位数

<summary>Solana 客户端 Rust 实现</summary>


enum Protocol {
    PullRequest(CrdsFilter, CrdsValue),
    //...
}

struct CrdsFilter {
    filter: Bloom,
    mask: u64,
    mask_bits: u32,
}

struct Bloom {
    keys: Vec&lt;u64>,
    bits: BitVec&lt;u64>,
    num_bits_set: u64,
}

Pull response

这些消息是对 pull request 的响应。它们包含节点 crds 表中的值,pull request 的来源缺少了这些值,这是由 pull request 中收到的 bloom 过滤器确定的。

Data Type Size Description
SenderPubkey [u8; 32] 32 属于 pull response 消息发送者的公钥
CrdsValuesList [CrdsValue] 8+ 新值列表

<summary>Solana 客户端 Rust 实现</summary>

enum Protocol {
    //...
    PullResponse(Pubkey, Vec&lt;CrdsValue>),
    //...
}

Prune message

发送给对等节点的,其中包含应修剪的原始节点列表。收到此修剪消息的接收者不应再向其发送者发送来自已修剪的原始节点的 push message。

Data Type Size Description
SenderPubkey [u8, 32] 32 属于修剪消息发送者的公钥
PruneData PruneData 144+ 包含修剪详细信息的结构
PruneData
Data Type Size Description
pubkey [u8, 32] 32 此消息来源的公钥
prunes [[u8, 32]] 8+ 应修剪的原始节点的公钥
signature [u8, 64] 64 此消息的签名
destination [u8, 32] 32 此消息的目标节点的公钥
wallclock u64 8 生成消息的节点的挂钟时间

<summary>Solana 客户端 Rust 实现</summary>

enum Protocol {
    //...
    PruneMessage(Pubkey, PruneData),
    //...
}

struct PruneData {
    pubkey: Pubkey,
    prunes: Vec&lt;Pubkey,
    signature: Signature,
    destination: Pubkey,
    wallclock: u64,
}

注意:出于签名目的,在序列化之前,PruneData 结构体的前缀为字节数组 [0xff, 'S', 'O', 'L', 'A', 'N', 'A', '_', 'P', 'R', 'U', 'N', 'E', '_', 'D', 'A', 'T', 'A']

<summary>Solana 客户端 Rust 实现</summary>

##[derive(Serialize)]
struct SignDataWithPrefix&lt;'a> {
    prefix: &'a [u8], // Should be a b"\xffSOLANA_PRUNE_DATA"
    pubkey: &'a Pubkey,
    prunes: &'a [Pubkey],
    destination: &'a Pubkey,
    wallclock: u64,
}

Ping message

节点经常向其对等节点发送 ping 消息,以检查它们是否处于活动状态。接收 ping 消息的节点应使用 pong message 进行响应。

Data Type Size Description
from [u8, 32] 32 来源的公钥
token [u8, 32] 32 32 字节的Token
signature [u8, 64] 64 消息的签名

<summary>Solana 客户端 Rust 实现</summary>

enum Protocol {
    //...
    PingMessage(Ping),
    //...
}

struct Ping {
    from: Pubkey,
    token: [u8, 32],
    signature: Signature,
}

Pong message

由节点发送以响应 ping message

Data Type Size Description
from [u8, 32] 32 来源的公钥
hash [u8, 32] 32 以 "SOLANA_PING_PONG" 字符串为前缀的已接收 ping Token的哈希
signature [u8, 64] 64 消息的签名

<summary>Solana 客户端 Rust 实现</summary>

enum Protocol {
    //...
    PongMessage(Pong)
}

struct Pong {
    from: Pubkey,
    hash: Hash,
    signature: Signature,
}

节点之间共享的数据

在 push message、pull request 和 pull response 中发送的 CrdsValue 值包含共享数据和数据的签名。

Data Type Size Description
signature [u8; 64] 64 创建 CrdsValue 的原始节点的签名
data CrdsData ? 数据

<summary>Solana 客户端 Rust 实现</summary>

struct CrdsValue {
    signature: Signature,
    data: CrdsData,
}

CrdsData

CrdsData 是一个枚举,可以是以下其中之一: Enum ID Type
0 LegacyContactInfo (已弃用)
1 Vote
2 LowestSlot
3 LegacySnapshotHashes (已弃用)
4 AccountsHashes (已弃用)
5 EpochSlots
6 LegacyVersion (已弃用)
7 Version (已弃用)
8 NodeInstance (几乎已弃用)
9 DuplicateShred
10 SnapshotHashes
11 ContactInfo
12 RestartLastVotedForkSlots
13 RestartHeaviestFork

<summary>Solana 客户端 Rust 实现</summary>

enum CrdsData {
    LegacyContactInfo(LegacyContactInfo),
    Vote(VoteIndex, Vote),
    LowestSlot(LowestSlotIndex, LowestSlot),
    LegacySnapshotHashes(LegacySnapshotHashes),
    AccountsHashes(AccountsHashes),
    EpochSlots(EpochSlotsIndex, EpochSlots),
    LegacyVersion(LegacyVersion),
    Version(Version),
    NodeInstance(NodeInstance),
    DuplicateShred(DuplicateShredIndex, DuplicateShred),
    SnapshotHashes(SnapshotHashes),
    ContactInfo(ContactInfo),
    RestartLastVotedForkSlots(RestartLastVotedForkSlots),
    RestartHeaviestFork(RestartHeaviestFork),
}
LegacyContactInfo (已弃用)

关于节点的基本信息。节点发送此消息是为了向集群介绍自己,并提供其对等节点可用于与其通信的所有地址和端口。

Data Type Size Description
id [u8; 32] 32 来源的公钥
gossip SocketAddr 10 或 22 gossip 协议地址
tvu SocketAddr 10 或 22 用于复制的连接地址
tvu_quic SocketAddr 10 或 22 通过 QUIC 协议的 TVU
serve_repair_quic SocketAddr 10 或 22 QUIC 协议的修复服务
tpu SocketAddr 10 或 22 交易地址
tpu_forwards SocketAddr 10 或 22 用于转发未处理交易的地址
tpu_vote SocketAddr 10 或 22 用于发送投票的地址
rpc SocketAddr 10 或 22 用于 JSON-RPC 请求的地址
rpc_pubsub SocketAddr 10 或 22 用于 JSON-RPC 推送通知的 WebSocket
serve_repair SocketAddr 10 或 22 用于发送修复请求的地址
wallclock u64 8 生成消息的节点的挂钟时间
shred_version u16 2 节点已配置为使用的分片版本
SocketAddr
一个枚举,可以是 V4 或 V6 套接字地址。 Enum ID Data Type Size Description
0 V4 SocketAddrV4 10 V4 套接字地址
1 V6 SocketAddrV6 22 V6 套接字地址
SocketAddrV4
Data Type Size Description
ip [u8; 4] 4 IP 地址
port u16 2 端口
SocketAddrV6
Data Type Size Description
ip [u8; 16] 16 IP 地址
port u16 2 端口

<summary>Solana 客户端 Rust 实现</summary>

struct LegacyContactInfo {
    id: Pubkey,
    gossip: SocketAddr,
    tvu: SocketAddr,
    tvu_quic: SocketAddr,
    serve_repair_quic: SocketAddr,
    tpu: SocketAddr,
    tpu_forwards: SocketAddr,
    tpu_vote: SocketAddr,
    rpc: SocketAddr,
    rpc_pubsub: SocketAddr,
    serve_repair: SocketAddr,
    wallclock: u64,
    shred_version: u16,
}

enum SocketAddr {
    V4(SocketAddrV4),
    V6(SocketAddrV6)
}

struct SocketAddrV4 {
    ip: Ipv4Addr,
    port: u16,
}

struct SocketAddrV6 {
    ip: Ipv6Addr,
    port: u16
}

struct Ipv4Addr {
    octets: [u8; 4]
}

struct Ipv6Addr {
    octets: [u8; 16]
}
Vote

验证器对分叉的投票。包含来自投票塔的 1 字节索引(范围 0 到 31)和要由领导者执行的投票交易。

Data Type Size Description
index u8 1 投票塔索引
from [u8; 32] 32 来源的公钥
transaction Transaction 59+ 投票交易,一个原子提交的指令序列
wallclock u64 8 生成消息的节点的挂钟时间
slot u64 8 创建投票的插槽
Transaction

包含签名和带有指令序列的消息。

Data Type Size Description
signature [[u8; 64]] 8+ 消息所需的 num_required_signatures(签名数量)签名列表
message Message 51+ 包含要调用的指令的交易消息
Message
Data Type Size Description
header MessageHeader 3 消息标头
account_keys [[u8; 32]] 8+ 此交易使用的所有帐户密钥
recent_blockhash [u8; 32] 32 最近的账本条目的哈希
instructions [CompiledInstruction] 8+ 要执行的已编译指令列表
Message header
Data Type Size Description
num_required_signatures u8 1 认为此消息有效所需的签名数量
num_readonly_signed_accounts u8 1 已签名密钥的最后 num_readonly_signed_accounts 是只读帐户
num_readonly_unsigned_accounts u8 1 未签名密钥的最后 num_readonly_unsigned_accounts 是只读帐户
Compiled instruction
Data Type Size Description
program_id_index u8 1 交易密钥数组的索引,指示执行程序的程序帐户 ID
accounts [u8] 8+ 交易密钥数组的索引,指示传递给程序的帐户
data [u8] 8+ 程序输入数据

<summary>Solana 客户端 Rust 实现</summary>


enum CrdsData {
    //...
    Vote(VoteIndex, Vote),
    //...
}

type VoteIndex = u8;

struct Vote {
    from: Pubkey,
    transaction: Transaction,
    wallclock: u64,
    slot: Option&lt;Slot>,
}

type Slot = u64

struct Transaction {
    signature: Vec&lt;Signature>,
    message: Message
}

struct Message {
    header: MessageHeader,
    account_keys: Vec&lt;Pubkey>,
    recent_blockhash: Hash,
    instructions: Vec&lt;CompiledInstruction>,
}

struct MessageHeader {
    num_required_signatures: u8,
    num_readonly_signed_accounts: u8,
    num_readonly_unsigned_accounts: u8,
}

struct CompiledInstruction {
    program_id_index: u8,
    accounts: Vec&lt;u8>,
    data: Vec&lt;u8>,
}
LowestSlot

Solana blockstore 中包含任何数据的第一个可用插槽。包含 1 字节的索引(已弃用)和最低插槽号。

Data Type Size Description
index u8 1 唯一有效的值是 0u8,因为这现在是一个已弃用的字段
from [u8; 32] 32 来源的公钥
root u64 8 已弃用
lowest u64 8 最低槽
slots [u64] 8+ 已弃用
stash [EpochIncompleteSlots] 8+ 已弃用
wallclock u64 8 生成消息的节点的挂钟时间
EpochIncompleteSlots
Data Type Size Description
first u64 8 第一个槽号
compression CompressionType 4 压缩类型
compressed_list [u8] 8+ 压缩槽列表
CompressionType

压缩类型枚举。

Enum ID Data Description
0 Uncompressed 未压缩
1 GZip gzip
2 BZip2 bzip2

<summary>Solana 客户端 Rust 实现</summary>


enum CrdsData {
    //...
    LowestSlot(LowestSlotIndex, LowestSlot),
    //...
}

type LowestSlotIndex = u8;

struct LowestSlot {
    from: Pubkey,
    root: Slot,
    lowest: Slot,
    slots: BTreeSet&lt;Slot>,
    stash: Vec&lt;EpochIncompleteSlots>,
    wallclock: u64,
}

struct EpochIncompleteSlots {
    first: Slot,
    compression: CompressionType,
    compressed_list: Vec&lt;u8>,
}

enum CompressionType {
    Uncompressed,
    GZip,
    BZip2,
}
LegacySnapshotHashes, AccountsHashes (已弃用)

这两个消息共享相同的消息结构。

Data Type Size Description
from [u8, 32] 32 来源的公钥
hashes [(u64, [u8, 32])] 8+ 按插槽分组的哈希列表
wallclock u64 8 生成消息的节点的挂钟时间

<summary>Solana 客户端 Rust 实现</summary>

struct AccountsHashes {
    from: Pubkey,
    hashes: Vec&lt;(Slot, Hash)>,
    wallclock: u64,
}

type LegacySnapshotHashes = AccountsHashes;
EpochSlots

包含 1 字节的索引和一个纪元中的所有插槽列表(一个纪元由大约 432000 个插槽组成)。总共可以有 256 个纪元插槽。

Data Type Size Description
index u8 1 索引
from [u8, 32] 32 来源的公钥
slots [CompressedSlots] 8+ 插槽列表
wallclock u64 8 生成消息的节点的挂钟时间
CompressedSlots
EnumID Data Type Size Description
0 Flate2 Flate2 24+ Flate2 压缩
1 Uncompressed Uncompressed 25+ 无压缩
Flate2
Data Type Size Description
first_slot u64 8 第一个槽号
num u64 8 插槽数量
compressed [u8] 8+ 压缩插槽的字节数组
Uncompressed
Data Type Size Description
first_slot u64 8 第一个槽号
num u64 8 插槽数量
slots b[u8] 9+ 插槽的位数组

<summary>Solana 客户端 Rust 实现</summary>

enum CrdsData {
    //...
    EpochSlots(EpochSlotsIndex, EpochSlots),
    //...
}

type EpochSlotsIndex = u8;

struct EpochSlots {
    from: Pubkey,
    slots: Vec&lt;CompressedSlots>,
    wallclock: u64,
}

enum CompressedSlots {
   Flate2(Flate2),
   Uncompressed(Uncompressed),
}

struct Flate2 {
    first_slot: Slot,
    num: usize,
    compressed: Vec&lt;u8>
}

struct Uncompressed {
    first_slot: Slot,
    num: usize,
    slots: BitVec&lt;u8>,
}
LegacyVersion (已弃用)

节点使用的 Solana 客户端的旧版本。

Data Type Size Description
from [u8, 32] 32 来源的公钥
wallclock u64 8 生成消息的节点的挂钟时间
version LegacyVersion1 7 或 11 1.3.x 及

<summary>Solana 客户端 Rust 实现</summary>

struct Version {
    from: Pubkey,
    wallclock: u64,
    version: LegacyVersion2,
}

struct LegacyVersion2 {
    major: u16,
    minor: u16,
    patch: u16,
    commit: Option&lt;u32>,
    feature_set: u32
}
NodeInstance

包含节点创建的时间戳和随机生成的 token。

数据 类型 大小 描述
from [u8, 32] 32 起源的公钥
wallclock u64 8 生成消息的节点的挂钟时间
timestamp u64 8 创建实例的时间戳
token u64 8 节点实例化时随机生成的值

<summary>Solana 客户端 Rust 实现</summary>

struct NodeInstance {
    from: Pubkey,
    wallclock: u64,
    timestamp: u64,
    token: u64,
}
DuplicateShred

重复 shred 的证明。包含一个 2 字节的索引,后跟其他数据:

数据 类型 大小 描述
index u16 2 索引
from [u8, 32] 32 起源的公钥
wallclock u64 8 生成消息的节点的挂钟时间
slot u64 8 创建 shred 时的 slot
_unused u32 4 未使用
_unused_shred_type ShredType 1 未使用
num_chunks u8 1 可用 chunk 的数量
chunk_index u8 1 chunk 的索引
chunk [u8] 8+ shred 数据
ShredType

此枚举序列化为 1 字节的数据。

枚举 ID 数据 描述
0b10100101 Data 数据 shred
0b01011010 Code 编码 shred

<summary>Solana 客户端 Rust 实现</summary>

enum CrdsData {
    //...
    DuplicateShred(DuplicateShredIndex, DuplicateShred),
    //...
}

type DuplicateShredIndex = u16;

struct DuplicateShred {
    from: Pubkey,
    wallclock: u64,
    slot: Slot,
    _unused: u32,
    _unused_shred_type: ShredType,
    num_chunks: u8,
    chunk_index: u8,
    chunk: Vec&lt;u8>,
}

##[serde(into = "u8", try_from = "u8")]
enum ShredType {
    Data = 0b1010_0101,
    Code = 0b0101_1010,
}
SnapshotHashes

包含关于节点拥有的完整快照和增量快照的哈希值的信息,并准备好通过 RPC 接口与其他节点共享。快照由首次启动的其他验证器下载,或者在验证器在重新启动后落后太远的情况下下载。要了解更多信息,请浏览这个 snapshots 页面。

数据 类型 大小 描述
from [u8, 32] 32 起源的公钥
full (u64, [u8, 32]) 40 完整快照的哈希值和 slot 号
incremental [(u64, [u8, 32])] 8+ 增量快照的哈希值和 slot 号的列表
wallclock u64 8 生成消息的节点的挂钟时间

<summary>Solana 客户端 Rust 实现</summary>

struct SnapshotHashes {
    from: Pubkey,
    full: (Slot, Hash),
    incremental: Vec&lt;(Slot, Hash)>,
    wallclock: u64,
}
ContactInfo

关于节点的基本信息。节点发送此消息以向集群介绍自己,并提供其对等节点可用于与其通信的所有地址和端口。

数据 类型 大小 描述
pubkey [u8, 32] 32 起源的公钥
wallclock u64 8 生成消息的节点的挂钟时间
outset u64 8 节点实例首次创建时的时间戳,用于识别重复运行的实例
shred_version u16 2 节点已配置为使用的 shred 版本
version Version 13+ Solana 客户端版本
addrs [IpAddr] 8+ 唯一 IP 地址的列表
sockets [SocketEntry] 8+ 唯一 socket 的列表
extensions [Extension] 8+ 未来对 ContactInfo 的添加将添加到 Extensions 中,而不是修改 ContactInfo,目前未使用
Version
数据 类型 大小 描述
major u16 2 版本的主要部分
minor u16 2 版本的次要部分
patch u16 2 补丁
commit u32 \| None 5 或 1 sha1 commit 哈希的前四个字节
feature_set u32 4 FeatureSet 标识符的前四个字节
client u16 2 客户端类型 ID

可能的 client 类型 ID 值有:

ID 客户端
0u16 SolanaLabs
1u16 JitoLabs
2u16 Firedancer
3u16 Agave
IpAddr
枚举 ID 数据 类型 大小 描述
0 V4 [u8; 4] 4 IP v4 地址
1 V6 [u8, 16] 16 IP v6 地址
SocketEntry
数据 类型 大小 描述
key u8 1 协议标识符
index u8 1 [IpAddr] 在地址列表中的索引
offset u16 2 相对于前一个条目的端口偏移量
key 标识符的列表如下表所示: 接口 Key 描述
gossip 0 gossip 协议地址
serve_repair_quic 1 通过 QUIC 的 serve_repair
rpc 2 JSON-RPC 请求的地址
rpc_pubsub 3 用于 JSON-RPC 推送通知的 websocket
serve_repair 4 用于发送修复请求的地址
tpu 5 交易地址
tpu_forwards 6 用于转发未处理交易的地址
tpu_forwards_quic 7 通过 QUIC 的 tpu_forwards
tpu_quic 8 通过 QUIC 的 tpu
tpu_vote 9 用于发送投票的地址
tvu 10 用于连接以进行复制的地址
tvu_quic 11 通过 QUIC 的 tvu
tpu_vote_quic 12 通过 QUIC 的 tpu_vote
Extension

目前为空(未使用)

<summary>Solana 客户端 Rust 实现</summary>

struct ContactInfo {
    pubkey: Pubkey,
    wallclock: u64,
    outset: u64,
    shred_version: u16,
    version: Version,
    addrs: Vec&lt;IpAddr>,
    sockets: Vec&lt;SocketEntry>,
    extensions: Vec&lt;Extension>,
}

enum Extension {}

enum IpAddr {
    V4(Ipv4Addr),
    V6(Ipv4Addr)
}

struct Ipv4Addr {
    octets: [u8; 4]
}

struct Ipv6Addr {
    octets: [u8; 16]
}

struct SocketEntry {
    key: u8,
    index: u8,
    offset: u16
}

struct Version {
    major: u16,
    minor: u16,
    patch: u16,
    commit: Option&lt;u32>,
    feature_set: u32,
    client: u16
}
RestartLastVotedForkSlots

包含上次投票的分叉 slot 列表。此消息不是常见的 gossip 消息,仅应在 cluster-restart 操作期间使用。

数据 类型 大小 描述
from [u8, 32] 32 起源的公钥
wallclock u64 8 生成消息的节点的挂钟时间
offsets SlotsOffsets 12+ slot 偏移量的列表
last_voted_slot u64 8 上次投票的 slot
last_voted_hash [u8, 32] 32 上次投票的 slot 的 bank 哈希
shred_version u16 2 节点已配置为使用的 shred 版本
SlotsOffsets
偏移量以二进制形式(RawOffsets)存储,或编码为连续 1 和 0 的数量,例如 110001110 是 [2, 3, 3, 1]。 枚举 ID 数据 类型 大小 描述
0 RunLengthEncoding [u16] 8+ 编码的偏移量
1 RawOffsets b[u8] 9+ 原始偏移量

<summary>Solana 客户端 Rust 实现</summary>

struct RestartLastVotedForkSlots {
    from: Pubkey,
    wallclock: u64,
    offsets: SlotsOffsets,
    last_voted_slot: Slot,
    last_voted_hash: Hash,
    shred_version: u16,
}

enum SlotsOffsets {
    RunLengthEncoding(RunLengthEncoding),
    RawOffsets(RawOffsets),
}

struct RunLengthEncoding(Vec&lt;u16>);
struct RawOffsets(BitVec&lt;u8>);
RestartHeaviestFork

包含最重的分叉。此消息不是常见的 gossip 消息,仅应在 cluster-restart 操作期间使用。

数据 类型 大小 描述
from [u8, 32] 32 起源的公钥
wallclock u64 8 生成消息的节点的挂钟时间
last_slot u64 8 所选区块的 slot
last_hash [u8, 32] 32 所选区块的 bank 哈希
observed_stake u64 8
shred_version u16 2 节点已配置为使用的 shred 版本

<summary>Solana 客户端 Rust 实现</summary>

struct RestartHeaviestFork {
    from: Pubkey,
    wallclock: u64,
    last_slot: Slot,
    last_slot_hash: Hash,
    observed_stake: u64,
    shred_version: u16,
}

附录

IP Echo Server

IP Echo Server 是一个在相同 gossip 地址的 TCP socket 上运行的服务器。(例如,如果节点在 UDP socket 192.0.0.1:9000 上运行 gossip 服务,则 IP 服务器端口在相同的 TCP socket 192.0.0.1:9000 上运行)。

在节点启动 gossip 服务之前,节点首先需要:

  • 找出集群的 shred 版本,
  • 发现其公共 IP 地址,
  • 检查 TCP/UDP 端口的可达性。

所有这些都是通过在一个提供的入口点节点上运行的 IP echo server 发现的。注意:所有验证器都运行一个 IP Echo Server。 节点应创建具有需要检查的端口的 socket,然后将 IP echo server 请求消息发送到一个入口点节点。 然后,入口点节点将检查请求中列出的所有端口的可达性,然后它将响应包含节点的 shred_version 和公共 IP 的 IP echo server 响应。

IpEchoServerMessage

IP echo server 消息请求,其中包含一个端口列表,服务器应检查这些端口的可达性:

  • UDP 端口 - 服务器应将单字节 [0x00] 数据包发送到 socket。
  • TCP 端口 - 服务器应与每个 TCP socket 建立 TCP 连接。
数据 类型 大小 描述
tcp_ports [u16, 4] 64 应检查的 TCP 端口
udp_ports [u16, 4] 64 应检查的 UDP 端口

<summary>Solana 客户端 Rust 实现</summary>

/// Echo server message request.
pub struct IpEchoServerMessage {
    pub tcp_ports: [u16; 4],
    pub udp_ports: [u16; 4],
}
IpEchoServerResponse

IP echo server 消息响应。

数据 类型 大小 描述
address IpAddr 4 或 16 发送请求的节点的公共 IP
shred_version u16 2 运行服务器的集群 shred 版本

<summary>Solana 客户端 Rust 实现</summary>

/// Echo server response.
pub struct IpEchoServerResponse {
    /// Public IP address of request echoed back to the node.
    pub address: IpAddr,
    /// Cluster shred-version of the node running the server.
    pub shred_version: Option&lt;u16>,
}
  • 原文链接: github.com/eigerco/solan...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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