把一笔交易广播出去却没人收,最理想的网络会回一张”回执”:拒了,理由是 txn-replace-expired 或 bad-txns-inputs-spent。比特币网络偏偏不是这样——现代节点对失败的默认态度是沉默。这不是疏忽,而是一场持续了三年的主动拆除,主角是编号 BIP 61 的 reject 消息。
reject 消息当年的样子
reject 是比特币 P2P 网络最早期软件里就存在的消息之一,后来在 2014 年被整理成编号 61 的提案(BIP 仓库里它的状态至今是 Deployed——这个 BIP 流程术语表示它曾被实际部署生效)。它的规则很简单:节点在拒绝 inv、tx 或 block 这类消息时,追加一条 reject 消息,带上被拒消息的命令名、一个错误码和一句人类可读的理由。早期排障围绕这套码展开:RPC 返回与节点日志都复用了 reject 消息同一套码与原因的词汇,sendrawtransaction 被拒时的原因文本就带有它的印记。
为什么它必须死
问题出在信任模型上。reject 是一张由对方自愿填写的回执——它没有密码学强度,任何人可以给你寄假回执,真正的对端也可以根本不寄。于是”没收到 reject”从来不等于”被接受了”,而”收到了 reject”也可能只是某个不守规矩节点的随口一说。更糟的是它增加带宽、暴露行为(谁在广播什么、被谁拒了),还制造了实现差异:不同客户端发不发、发什么码各不相同。官方在 v0.18 发布说明里把话挑明:reject 消息在 P2P 网络上没有实际用途,多数节点只是拿它记调试日志,而且对隐私和安全有害。
拆除时间线
比特币核心用四个版本走完这段路:
- v0.17(#13134):加入
-enablebip61选项,reject 发送从”默认行为”降级为”可关功能”; - v0.18:官方宣布 BIP 61 弃用,预告将改为默认关闭;
- v0.19(#14054):默认关闭,需要
-enablebip61=1才发;同期还把 BIP 37 布隆过滤器服务的默认开关一并收紧,堵掉了另一类磁盘 DoS 面; - v0.20(#17004):彻底删除,
-enablebip61选项本身也不复存在。
v0.20 的删除说明写得很完整,同时给出了替代品清单:调试协议要看节点自己的日志(-debug 类别);验证区块用 submitblock 或 getblocktemplate 的 proposal 模式;验证交易用 sendrawtransaction 和 testmempoolaccept 的返回值;钱包不应再靠”没收到 reject”判断交易已广播或费率足够,而应改用费率估算和 RBF。附带影响是 testmempoolaccept 与 sendrawtransaction 从此不再回传 P2P 层的拒绝码,只保留 RPC 自己的原因文本。
今天的正确姿势
到了 v31.1,reject 这个字符串在协议常量表和消息处理层都已查无此名——收发两侧都被清空,它真正成了历史消息。对使用者的含义很直接:
- 广播交易后没有回应,是正常现象,不代表成功;真正的信号是它是否被邻居继续转告(inv)以及最终进没进块。
- 想知道为什么被拒,第一时间查本地节点的 debug.log——交易没进内存池时,节点会把验证失败的原因文本记在日志里;用
testmempoolaccept也可以在不广播的情况下先问一遍原因。 - 交易所钱包、自建收款系统里”广播即失败”的判断必须走 RPC 返回值,不能对等网络沉默做文章。
一条协议回执的退场走完了十余年:它从网络诞生之初就是默认行为,2014 年被正式立成标准,2020 年从主流实现里被物理删除。这条时间线本身,就是理解比特币”升级靠淘汰、不靠修补”的一个小标本。
别把沉默读成接受
顺手纠正三个流传很广的口头禅。第一条:广播后没报错就是成功了——sendrawtransaction 只对你连的那台节点负责,它对全网不做任何承诺。第二条:几分钟没到账就是被拒了——广播与到账是两个通道,后者慢不代表前者败。第三条:日志里没出现 REJECT 字样就是没问题——现代节点根本收发不了这条消息,它退出协议已经多年。三条的共同点,是把判断依据从对端是否口头同意,换成本地验证结果加链上事实,这正是 v0.20 删除说明反复强调的立场:网络里没人欠你一个解释,能欠你的只有你自己的节点。
风险提示:拒绝原因的日志字段和 RPC 错误码可能随版本调整,排障时以你所运行版本的文档为准;本文不构成投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。