广播交易时最常见的报错之一是 bad-txns-inputs-missingorspent。这个代号把两种完全不同的事实压成了一行字:你引用的输出还没出现在我这儿,或者它已经被别人花掉了。节点选择不区分,而这个”不区分”是有代价、也有配套机制的。
代号出现在哪一步
比特币核心 v31.0 的交易校验里,处理完格式与脚本之外的一致性检查后,会逐个输入去查未花费输出集合。查不到时,节点先做一次”是不是我自己已经有这笔了”的快判:拿这笔交易自己的交易哈希配上输出序号,问一句内存缓存里有没有。有,就说明这是重复提交,返回冲突类的 txn-already-known。没有,才落到本尊:内部结果 TX_MISSING_INPUTS,代号 bad-txns-inputs-missingorspent。
源码在这一点上的注释相当坦率:否则就当它是一笔我们还没见到父交易的孤儿交易。换句话说,这个代号在节点内部承担的语义更接近”暂时没法判断”,而不是”我判定你双花了”。
为什么不告诉你到底是哪种
要区分”父交易还没到”和”输出已经被花掉”,节点必须回到磁盘上的完整状态库去查这个输出点的历史。而对一笔刚广播来的交易,节点默认只查内存里的未花费输出缓存。
这条路径的取舍很直白。缓存查得到,皆大欢喜;查不到时,两种可能都还没被排除。既然其中一种可能——父交易在路上——是完全正常且常见的网络时序,节点就不该在这一步把交易一棒子打死。用一个合并代号收尾,把判定推迟到真正拿到父交易、或者确定超时之后再下结论,是更经济的做法。
对使用方来说,这条实现细节有个实用含义:拿到这个报错,不能直接推断自己遭遇了双花。要下双花的结论,需要另外确认那个输出点确实已被某笔交易引用,而这要靠对状态的显式查询,不是靠重发交易撞报错。
配套的孤儿机制
TX_MISSING_INPUTS 这个内部结果在网络上还有一个特殊待遇。节点在处理孤儿交易的重新校验时会写一句条件:只要结果不是 TX_MISSING_INPUTS,就当作明确无效处理掉。言下之意,父交易迟到时,交易有资格留在原地等一等。
这套等待机制就是通常说的孤儿交易库:一笔交易进来,父交易不在内存池也不在链上,节点先把它按来源对端存着,等父交易到了再回炉重验。等的是时序,不是宽恕——一旦回炉后查出别的拒绝理由,比如脚本不合法或者手续费太低,它同样会被立刻丢弃。
需要注意容量口径的变化:孤儿库的记账方式在新版本里做过重写,早先那个用来限制孤儿数量的命令行参数在 v30 起已经无效、v31 里被移除,现在约束的是按对端计的条目数与总体积,不是简单的条数上限。写脚本时如果还在读那个老参数,它已经不起作用了。
排查顺序建议
第一步,确认父交易存在。用父交易的哈希去查它是否已被收录、在哪个高度。父在链上但状态显示已被花费,说明你引用的那一路输出确实花掉了,这笔交易是真的双花,重发多少次都不会变。
第二步,父存在且未被花费,说明是节点侧的可见性问题。此时父交易可能还没被这个节点收录,也可能你自己引用的是一笔尚未确认的输出——后者需要靠子交易链条解决,前者的解法一般是等同步推进或换个节点试试。
第三步,父根本没被网络看到。这时你手里这笔交易是真的孤儿,任何节点都会给出同一个合并代号。要么先广播父交易,要么用钱包侧的弃用手段把这笔交易彻底丢掉。
第四步,反复出现这个代号且金额不小,检查钱包是否在你不知情的情况下重复创建了找零支出,例如助记词在两个设备上同时用同一条未花输出发起支付。合并代号在这种场景下会把两笔真实竞争的交易都推回给你,看似报错刷屏,实际是同一个输出点在打架。
风险提示:本文依据比特币核心 v31.0 源码撰写,拒绝代号与孤儿库口径可能随版本变化;涉及资产状态判断请以你自己节点或可信数据源的查询结果为准,本文不构成任何投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。