Bitcoin Core 私有广播:每笔交易 1000 次发送尝试上限与 attempts_remaining 观测字段
本篇基于 Bitcoin Core 的发布说明 release-notes-35680.md(PR #35680)展开,讲清 -privatebroadcast 私有广播机制中两个具体变化:每笔交易被限制为最多 1000 次发送尝试、达到上限后的停播与重试方式,以及 getprivatebroadcastinfo 新增的 attempts_remaining 字段。读完后你将能理解私有广播队列的发送预算如何计算、耗尽后交易的状态如何查询与恢复,并能用仓库中的单元测试和功能测试自行验证这些行为。
发布说明原文的两项核心变更
release-notes-35680.md 记录了两点:
- P2P 与网络变更:每笔通过私有广播(
-privatebroadcast)发送的交易限制为 1,000 次发送尝试。达到上限后停止广播;再次调用sendrawtransaction即可重试。达到上限的交易仍然可以通过getprivatebroadcastinfo查询,也可以被abortprivatebroadcast移除。 - RPC 更新:
getprivatebroadcastinfo现在为每笔交易额外报告一个attempts_remaining字段(该交易剩余可用的发送尝试次数)。
下面结合当前仓库源码逐项核实其实现。
私有广播是什么、何时生效
私有广播由启动参数 -privatebroadcast 控制。参数在 src/init.cpp 中的定义说明其语义:
通过 Tor 或 I2P 网络,使用短生命周期连接广播经
sendrawtransactionRPC 提交的交易,且不先进入 mempool;通过钱包提交的交易不受此选项影响。
因此该功能有两个前提,均可在 sendrawtransaction 的 RPC 实现 中直接看到校验:
- 必须开启
-privatebroadcast,且 Tor(NET_ONION)或 I2P 网络可达,否则sendrawtransaction会直接报错并提示检查 Tor daemon、-torcontrol、-torpassword、-i2psam配置; - 开启私有广播后,
sendrawtransaction走NO_MEMPOOL_PRIVATE_BROADCAST广播路径;未开启时走常规的MEMPOOL_AND_BROADCAST_TO_ALL路径(src/rpc/mempool.cpp)。
启动阶段还有一组兼容性检查:若 Tor 与 I2P 都不可达则初始化失败;若同时配置了 -connect(禁用 addrman 出站连接)也会报错,因为私有广播需要向随机选择的 Tor/I2P 节点建立新连接;另外若未开启 -proxyrandomize,会给出隐私相关警告(src/init.cpp)。
变更一:每笔交易 1000 次发送尝试上限
上限常量与"一次尝试"的定义
上限常量定义在 src/private_broadcast.h:
/// Maximum number of send attempts for a transaction. Once this limit is
/// reached, the transaction remains tracked but is not sent again unless
/// explicitly re-added.
static constexpr size_t MAX_SEND_ATTEMPTS{1'000};
从源码结构看,"一次发送尝试"对应 PrivateBroadcast::PickTxForSend() 每被调用一次并为某个节点选中的交易追加一条 SendStatus(即向一个对等节点发一次)。是否还有尝试剩余由 IsPending() 判定:
bool PrivateBroadcast::IsPending(const TxSendStatus& status) const
{
return status.send_statuses.size() < m_max_send_attempts;
}
选取下一个待发送交易时,PickTxForSend() 先按 IsPending 过滤掉已耗尽交易,再从剩余交易中按优先级(被选次数最少、确认数最少、时间最早者优先,见 Priority 比较逻辑)选出最紧急的一笔。这意味着某笔交易耗尽预算后,不会阻塞队列中其他交易的发送——这一点在单元测试中有专门断言(见下文"行为验证"一节)。
达到上限后发生了什么
与"直接丢弃"不同,达到上限的交易仍保留在私有广播队列中,只是不再被发送:
HavePendingTransactions()返回 false,PickTxForSend()不会返回它;- 它仍然出现在
getprivatebroadcastinfo的结果中,且attempts_remaining为 0; - 可以随时用
abortprivatebroadcast将其从队列中移除(RPC 定义见 src/rpc/mempool.cpp,说明为 "Abort private broadcast attempts for a transaction currently being privately broadcast. The transaction will be removed from the private broadcast queue.")。
发布说明中"call sendrawtransaction again to retry"对应的是 Add() 的重置语义。PrivateBroadcast::Add() 对"已存在且已耗尽"的交易不是简单拒绝,而是重置其状态:
// An exhausted transaction can be explicitly retried by adding it again.
it->second.time_added = NodeClock::now();
it->second.send_statuses.clear();
return AddResult::Added;
即重新提交同一笔交易(wtxid 相同)会清空全部发送历史、刷新入队时间,让它获得全新的 1000 次发送预算;若该交易尚未耗尽预算,则返回 AddResult::AlreadyPresent 不做任何改动。返回值的三种语义定义在 AddResult 枚举:Added(新加入或耗尽后重置)、AlreadyPresent(仍在预算内)、QueueFull(队列达到 MAX_TRANSACTIONS,即同时最多跟踪 10,000 笔,见 src/private_broadcast.h)。
需要说明的是,1,000 是主线路径上的编译期常量;构造函数虽然接受 max_send_attempts 参数(默认即 MAX_SEND_ATTEMPTS,见 构造函数),但该可配置性目前仅被测试代码利用,用户侧并无对应的启动参数。
变更二:getprivatebroadcastinfo 报告 attempts_remaining
attempts_remaining 字段在 TxBroadcastInfo 结构中声明(src/private_broadcast.h),其值在 GetBroadcastInfo() 中计算:
const size_t attempts_remaining{m_max_send_attempts - std::min(state.send_statuses.size(), m_max_send_attempts)};
即"上限减去已向节点发送的次数",且用 std::min 钳制保证结果不会为负。RPC 侧在 getprivatebroadcastinfo 方法定义 中将该字段加入结果描述,并把文档说明一并更新为"达到发送尝试上限的交易会以 attempts_remaining=0 留在结果中"(src/rpc/mempool.cpp)。
该 RPC 仅在以 -privatebroadcast 运行时可用,否则抛出 Method not found(src/rpc/mempool.cpp)。其完整返回结构为:
{
"transactions": [
{
"txid": "0a1b…",
"wtxid": "1c2d…",
"hex": "020000000001…",
"time_added": 1730000000,
"attempts_remaining": 997,
"peers": [
{ "address": "onion:…:8333", "sent": 1730000010 },
{ "address": "i2p:…:11337", "sent": 1730000020, "received": 1730000021 }
]
}
]
}
(以上为按 RPC 字段定义 组织的示例结构。)其中 peers 数组逐项记录每个接收节点:address(发送目标地址)、sent(被选中发送的时间)、received(可选,对端以 PONG 回应 PING 确认收到的时间,见 NodeConfirmedReception 的注释)。达到上限时该交易 attempts_remaining 为 0、peers 中最多有 1000 条记录。
对运维者的实际意义是:可以持续轮询该 RPC,观察 attempts_remaining 是否单调下降到 0,从而判断私有广播是否长期无法触达对端(例如 Tor/I2P 路由异常),并据此决定重试(重新 sendrawtransaction)还是放弃(abortprivatebroadcast)。
行为验证:单元测试、模糊测试与功能测试
仓库中三处测试与本次变更直接对应,可在本地构建后运行核对行为。
- 单元测试 src/test/private_broadcast_tests.cpp 的
send_attempt_limit用例用max_attempts=5构造队列,验证了完整生命周期:- 连续 5 次
PickTxForSend都能选中该交易; - 第 6 次起
HavePendingTransactions()为 false、PickTxForSend()返回 nullopt; GetBroadcastInfo()中peers.size()==5、attempts_remaining==0;- 耗尽的交易不会使队列过期(
GetStale()为空),也不会阻止其他新交易被发送; - 再次
Add同一交易后peers被清空、attempts_remaining恢复为 5。
- 连续 5 次
- 模糊测试 src/test/fuzz/private_broadcast.cpp 在 1~12 的随机
max_send_attempts范围内构造队列,并断言attempts_remaining == max_send_attempts - 已发送次数(src/test/fuzz/private_broadcast.cpp),保证该字段在各种乱序操作下始终与内部计数一致。 - 功能测试 test/functional/p2p_private_broadcast.py 在真实 P2P 环境下断言
attempts_remaining == MAX_PRIVATE_BROADCAST_ATTEMPTS - len(peers),并与实际收到的peers条目、确认收到的数量做交叉校验。
另外 test/functional/p2p_private_broadcast_retry_v1.py 覆盖了重试相关路径,可作为"重新提交后重新开始发送预算"这一行为的端到端佐证。
小结
PR #35680 为私有广播增加了"发送预算"这一确定性边界:每笔交易最多 1000 次逐节点发送尝试(MAX_SEND_ATTEMPTS,src/private_broadcast.h),耗尽后交易滞留队列、attempts_remaining 归零(src/rpc/mempool.cpp 上报),既不自动消失也不阻塞其他交易;用户重新调用 sendrawtransaction 即通过 Add() 的重置分支(src/private_broadcast.cpp)恢复预算,或用 abortprivatebroadcast 明确放弃。适用前提与限制:功能仅对 sendrawtransaction 提交的交易生效,要求 Tor 或 I2P 可达,且与 -connect 配置互斥;上限值在主线路径上是固定的 1000,没有运行时配置项。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00