Solana 乐观交易传播信号(Optimistic Transaction Propagation Signal)提案解析:确定性 Turbine 重传树的设计、接收端验证与攻击模型
2026/9/14 14:19:58 网站建设 项目流程

Solana 乐观交易传播信号(Optimistic Transaction Propagation Signal)提案解析:确定性 Turbine 重传树的设计、接收端验证与攻击模型

【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana

本篇技术指南基于 Solana 官方设计提案文档 optimistic-transaction-propagation-signal.md 撰写。该提案着眼于提升 Solana 交易的乐观确认效率:通过将 Turbine 重传树从"随机生成"改为"确定性生成",让每个接收节点都能仅凭本地信息推算出自己在整棵重传树中的位置、已获得多少质押权重的"覆盖",从而向共识层发出"交易传播进度"的乐观信号。读完本文,你将掌握 Turbine 重传树的现状算法、确定性种子与 weighted_shuffle 的实现原理、接收端层级与质押占比的计算方法,以及该提案面临的攻击模型与开放问题。

一、背景:Turbine 重传树的现状

Solana 的区块数据(shred)通过名为 Turbine 的多层树形广播结构在网络中传播:leader 生成 shred 后,由根节点(root)逐层向叶子节点转发。当前实现中,每个节点重传 shred 时,其转发目标列表的构建方式记录在提案文档的 "Current Retransmit behavior" 一节中,共分三步:

  1. 候选集合构建:依次考虑 epoch 质押节点(epoch staked nodes)、TVU 对等节点(tvu peers,按联系信息与 shred 版本过滤)、以及当前验证节点自身,将三者拼接(concatenating);
  2. 去重与过滤:按 pubkey 去重,去重时优先保留带联系信息(contact info)的条目,随后仅保留具有联系信息的条目;
  3. 加权随机打乱:将该列表按质押权重(stake weight)随机打乱,然后向最多FANOUT个邻居(neighbors)和最多FANOUT个子节点(children)重传 shred。

上述逻辑在源码中均有对应实现。在 turbine/src/cluster_nodes.rs 的get_nodes函数中,候选节点集合由"本节点自身 + gossip 表可见的全部 TVU 对等节点 + 全部质押节点"三部分拼接而成,随后通过sorted_by_key(|node| Reverse((node.stake, node.pubkey)))稳定排序并按 pubkey 去重,从而保证"去重时优先保留带联系信息的条目"。NodeId枚举(ContactInfo(ContactInfo)Pubkey(Pubkey))正是对"有联系信息"与"仅有公钥(无联系信息)"两类节点的建模。

随机打乱与邻居/子节点选取则体现在ClusterNodes<RetransmitStage>::get_retransmit_peers(turbine/src/cluster_nodes.rs):它基于get_seeded_rng(slot_leader, shred)构造随机源,对weighted_shuffle进行shuffle(&mut rng),再调用get_retransmit_peers(fanout, self_index, &nodes)计算当前节点应当转发的子节点列表。树形布局由注释给出(turbine/src/cluster_nodes.rs):

root : [0] 1st layer: [1, 2, ..., fanout] 2nd layer: [[fanout + 1, ..., fanout * 2], [fanout * 2 + 1, ..., fanout * 3], ... [fanout * fanout + 1, ..., fanout * (fanout + 1)]] 3rd layer: ...

即:leader 将 shred 广播给 root 节点,root 转发给第 1 层全部节点,第 1 层每个节点再各自转发给第 2 层中属于自己邻域(neighborhood)的fanout个节点,依此类推。get_retransmit_peers通过offset = (index - 1) % fanoutanchor = index - offset等索引运算精确定位邻域,get_retransmit_parent则逆向计算父节点;turbine/src/cluster_nodes.rs 中的test_get_retransmit_nodesfanout = 2fanout = 3的显式节点编号对父子关系进行了逐对断言验证。

二、核心思路:确定性重传树

随机打乱意味着同一节点在不同时刻、不同网络状态下对同一 shred 的重传目标并不一致,任何单个节点都无法预测"还有哪些节点尚未收到数据",也就无法对传播进度形成可靠判断。本提案的核心改动是:将重传树的构建改为确定性(deterministic)计算

具体地,weighted_shuffle将改用确定性种子:当enable_deterministic_seed功能开启时,种子由三元组(shred slot、shred index、leader pubkey)派生:

if enable_deterministic_seed(self.slot(), root_bank) { hashv(&[ &self.slot().to_le_bytes(), &self.index().to_le_bytes(), &leader_pubkey.to_bytes(), ])

这样,任何持有相同输入(slot、index、leader)的节点,都能独立复现出完全一致的打乱顺序与树结构。注意enable_deterministic_seed目前仅存在于本提案的设想中,尚未在仓库源码中实现(全文搜索仅在 docs/src/proposals/optimistic-transaction-propagation-signal.md 中出现),因此以下描述均属提案设计。

在确定性树中,节点选取规则被重新定义:

  • 首先,只考虑 epoch 质押节点(无论其是否具有联系信息,甚至可能包含验证节点自身);
  • 基于确定性 shred 种子,对 epoch 质押节点做weighted_shuffle,得到确定性的排列顺序;
  • 定义neighbor_set:从当前节点的邻居中最多选取FANOUT个;
  • 定义child_set:从当前节点的子节点中最多选取FANOUT个;
  • neighbor_setchild_set均需按联系信息过滤(无法联系到的节点不参与实际转发);
  • 定义epoch_setneighbor_setchild_set的并集;
  • 定义remaining_set为其余所有具有联系信息且不在epoch_set中的节点;
  • epoch_set的大小不足2 * FANOUT,则从remaining_set随机补选最多2 * FANOUT - epoch_set.len个节点参与重传。

这一设计的精妙之处在于"确定性为主、随机为补":确定性部分保证每个节点可复现、可推演,而随机补选部分只负责在质押节点数量不足时兜底(例如 epoch 初期质押节点较少),不会破坏树主体结构的可预测性。

三、确定性种子与 weighted_shuffle 的底层实现

3.1 种子如何生成

提案中的种子三元组(slot、index、leader pubkey)与仓库现有实现高度吻合。在 ledger/src/shred.rs 中,ShredId::seed已经实现了几乎相同的哈希逻辑:

pub fn seed(&self, leader: &Pubkey) -> [u8; 32] { let ShredId(slot, index, shred_type) = self; hashv(&[ &slot.to_le_bytes(), &u8::from(*shred_type).to_le_bytes(), &index.to_le_bytes(), AsRef::<[u8]>::as_ref(leader), ]) .to_bytes() }

ShredId(slot, index, shred_type)三元组构成,与提案的(slot, index, leader)仅差一个shred_type字节。这一种子随后被 turbine/src/cluster_nodes.rs 的get_seeded_rng用于初始化ChaChaRng

fn get_seeded_rng(leader: &Pubkey, shred: &ShredId) -> ChaChaRng { let seed = shred.seed(leader); ChaChaRng::from_seed(seed) }

也就是说,"每个 shred 一个确定性随机源"的机制已经存在于生产代码中,提案只是把同样的思路从"打乱所有节点"推广到"仅对质押节点做确定性排序",并将其结果暴露给接收端用于进度推断。

3.2 WeightedShuffle:二叉索引树上的加权抽样

weighted_shuffle模块位于 gossip/src/weighted_shuffle.rs,其实现基于二叉索引树(Fenwick Tree,见文件头部注释)维护"未选中索引的质押权重前缀和",核心保证有三条(gossip/src/weighted_shuffle.rs):

  • 返回的索引在[0, weights.len())内互不重复;
  • 权重越高的索引越倾向于靠前出现,且出现概率与其权重成正比;
  • 零权重索引被单独打乱并排在最后。

关键 API 包括:

  • WeightedShuffle::new(name, weights):构建二叉索引树,负权重与溢出权重按零处理并上报指标(gossip/src/weighted_shuffle.rs);
  • first(&self, rng):等价于shuffle(&mut rng).next(),即加权抽取第一个索引(gossip/src/weighted_shuffle.rs);
  • shuffle(&mut self, rng):返回按权重依次抽样的迭代器,每次通过search定位累积权重区间内的抽样点,再用remove从树中移除已选权重(gossip/src/weighted_shuffle.rs);
  • remove_index(&mut self, index):在打乱前显式剔除某索引(例如 slot leader 自身,见 turbine/src/cluster_nodes.rs)。

由于WeightedShuffle对同一个确定性 RNG 输入会给出完全确定性的输出序列,因此"同一 (slot, index, leader) → 同一排序"在数学上是严格可复现的——这正是接收端能够逆向推算自身位置的前提。

四、接收端:如何从确定性树中推算传播进度

当某个验证节点收到一条被重传的 shred 时,它可以按如下步骤判断自己处于整棵树的哪个位置,以及"多少质押权重已经在它之前完成了分发":

  1. 前置条件判断:若当前验证节点不属于该 shred 所在 epoch 的质押节点集合,则无法获得任何早期重传信息(因为它根本不在确定性树中,见下文"信号桶"一节);
  2. 计算确定性种子:用 (slot, index, leader pubkey) 计算 deterministic shred seed;
  3. 重放确定性打乱:对 epoch 质押节点集合运行确定性的epoch_stakesshuffle,得到与发送端完全一致的排列;
  4. 定位自身:在该排列中查找自身位于neighbor_set还是child_set,确定自己在树中的层级;
  5. 累加前置质押:计算"当前及此前所有分发层级"(current and prior distribution levels)中所有节点的质押总和——这个总和占 epoch 总质押的比例,即"在收到该 shred 时,树中已有多少比例的质押权重被覆盖"。

4.1 层级与 root_distance 的对应关系

仓库现有实现已经为"层级判断"提供了现成逻辑。在 turbine/src/cluster_nodes.rs 中,root_distance的计算方式是:

let root_distance = if self_index == 0 { 0 } else if self_index <= fanout { 1 } else if self_index <= fanout.saturating_add(1).saturating_mul(fanout) { 2 } else { 3 // If changed, update MAX_NUM_TURBINE_HOPS. };

即:排列首位为 root(距离 0),随后fanout个为第 1 层,再往后fanout * (fanout + 1)个为第 2 层,其余为第 3 层;全局跳数上限由 turbine/src/cluster_nodes.rs 的MAX_NUM_TURBINE_HOPS = 4约束。在 turbine/src/retransmit_stage.rs 中,num_shreds_receivednum_shreds_sent均为按MAX_NUM_TURBINE_HOPS长度统计的数组,record函数(turbine/src/retransmit_stage.rs)分别对"在距离 k 处收到"与"在距离 k 处转发"的 shred 计数——说明距离度量已经贯穿于重传阶段的指标体系。

提案中的neighbor_set/child_set即对应这一层级体系:节点在排列中的索引越小(越靠前),距离 root 越近,其收到数据意味着树中越多的前置分发已经完成。

4.2 质押求和的边界考量

在累加质押权重时,提案专门指出两个容易出错的边界情况:

  • 被跳过的节点:质押总和可能包含"此前分发层级中因缺少联系信息而被跳过的节点"的质押。这些节点在确定性排列中占位,但实际未收到数据,若将其质押计入"已覆盖"会产生乐观偏差;
  • 被过滤的自身:当前节点原本位于发送端确定性打乱的排列中,但因为缺少联系信息而被过滤、未实际收到转发;随后它又从随机补选路径收到重传,此时节点会"误以为"这次重传来自确定性树计算,而非后续随机补选。提案认为这种情况良性的(benign)——因为当前节点会**低估(underestimate)**重传树中已处理的前置质押权重,即信号偏保守,不会导致过度乐观的确认。

五、攻击模型分析

确定性重传树让接收节点能够推断传播进度,但也引入了一种新的信号滥用面:恶意节点可以伪造"传播进度"信号。提案分别考虑了两种攻击:

leader 攻击(第 0 层):leader 除了按正常流程将 shred 分发到树中,还额外把 shred(或伪造的假 shred)直接发送给第 2 层及以上的节点,使这些节点误以为树中已有更大比例的转发已被处理。由于 leader 在系统内天然处于最可信的"源头"位置,这种攻击最难防御。

第 n 层节点攻击:位于第 n 层的恶意节点,将 shred 重传给第 n+2 层及以上的节点,同样会让接收者高估树的处理进度。

这两类攻击的共同点是:接收节点无法仅凭"收到了数据"就判断数据来自确定性路径还是恶意直传路径。这也直接引出了提案的开放问题:

  • 接收节点是否应该尝试验证 shred 的来源确实是"预期中的转发节点"?如果需要验证,如何考虑 IP 欺骗(spoofing)成本与可行性?
  • 该传播进度信息最终应如何被消费?(例如:是作为乐观确认的辅助信号,还是参与共识超时/确认阈值的计算?)

从源码角度看,turbine/src/cluster_nodes.rs 已经实现了get_retransmit_parent——它能在确定性树中逆向查出任一节点的父节点(同时明确注释:非质押节点位置不确定,因此返回None)。这一函数正是"验证来源是否来自预期父节点"所需的基础能力,但提案将其作为"是否应当验证"的开放问题提出,说明在工程取舍上尚未定论。

六、信号桶:传播信号的实践分层

提案在 Notes 一节给出信号的分桶设计,明确不同位置的节点能够发出不同粒度的信号

  1. 当前 leader:在发出广播(broadcast)时即可发出"第 1 层已覆盖"的信号;
  2. 第 1 层节点
    • 收到 shred 时,可发出"第 1 层已覆盖"的信号;
    • 发出重传(retransmit)时,可发出"第 1 层 + 第 2 层子集已覆盖"的信号;
  3. 第 2 层节点
    • 收到 shred 时,可发出"第 2 层已覆盖"的信号;
    • 发出重传时,可发出"第 2 层 + 第 3 层子集已覆盖"的信号;
  4. 非质押节点:不属于 epoch 质押节点集合,无法发出任何信号。

可以看到,信号粒度随节点在树中的深度增加而变粗:层级越深,节点能够确认的传播范围越大,但信号到达共识决策点的时间也越晚。这一分层设计让共识层可以根据信号来源的层级,对不同强度的"乐观传播确认"加以区分利用。

七、小结与展望

本提案通过将 Turbine 重传树从随机化改造为"以 (slot, index, leader pubkey) 为种子的确定性加权打乱",赋予网络中每个质押节点一项新能力:仅凭收到的 shred 与本地 epoch 质押数据,即可推算出自己在重传树中的位置以及已被覆盖的质押权重比例,从而为交易的乐观确认提供可验证、可分层的传播进度信号。

从仓库现有实现看,提案的诸多基础设施已经就绪:确定性种子生成(ledger/src/shred.rs)、ChaChaRng 种子化(turbine/src/cluster_nodes.rs)、加权打乱的二叉索引树实现(gossip/src/weighted_shuffle.rs)、树形层级与父/子节点计算(turbine/src/cluster_nodes.rs)以及按距离分桶的统计指标(turbine/src/retransmit_stage.rs)。enable_deterministic_seed功能开关本身尚未落地,攻击防护与信号消费方式也仍是开放问题——但作为设计蓝图,它为 Solana 进一步压缩乐观确认延迟、提升交易传播的可观测性,指明了一条清晰的技术路径。

【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询