烧掉的币为什么不占账本:IsUnspendable 输出在节点各层的遭遇
往 OP_RETURN 里刻一段铭文、再把若干聪转进一个谁都花不掉的地址,俗称”烧币”。烧掉的币其实还在链上,但它们在很多地方”不存在”:UTXO 账本里没有它们的格子、统计数字跳过它们、钱包也不给它们建收款记录。这条线在比特币核心源码里由一个函数裁决:IsUnspendable。本文全部以 Bitcoin Core v31.0 源码为准,看这个判定怎么下、下游哪些模块跟着它行动。
一个函数两种情形
src/script/script.h 里的定义只有一行:脚本非空且第一个字节是 OP_RETURN,或者脚本长度超过 MAX_SCRIPT_SIZE(一万字节)——任一成立即判”不可花费”。前半句对应 2014 年软分叉后生效的共识规则:以 OP_RETURN 开头的输出脚本,验证器在解锁时直接判失败,谁也无法引用它做输入。后半句常被忽略:脚本长度超过一万字节的输出虽然可能编码得合法,但没有任何解锁方案能让它通过执行前的尺寸检查(脚本本身要压进栈,栈元素上限 520、脚本验证的初始脚本不能超一万),同样归入不可花费。两类输出的共同点:金额还在,钥匙没了。
UTXO 账本:进门之前就被拦下
最关键的遭遇发生在 CCoinsViewCache::AddCoin:每笔交易的每个输出在被确认、写入未花费输出集合之前,先过 IsUnspendable,是则直接 return——这枚输出根本不进 UTXO 集合。也就是说,节点重建账本时,烧掉的聪不会占用任何一格:gettxoutsetinfo 统计的输出数、coinstatsindex 维护的计数器(源码里同样带 IsUnspendable 分支)都不把烧毁输出算作可花余额的一部分。这解释了铭文社区常见的账目困惑:链上历史里那些永远躺着的聪,为什么不体现在 UTXO 集合体积上——因为主流节点压根没给它们记账格。但注意边界:不进 UTXO 集合是 Bitcoin Core 的实现与索引行为,区块字节里它们仍然原样存在,仍需付费上链;UTXO 集是派生数据,共识规则是另一回事。
断链回滚与校验时的同一判据
src/validation.cpp 的断块路径(DisconnectBlock)在反向撤销交易时,对每个输出先问 IsUnspendable,才去核对并回滚对应的 UTXO——不可花费的输出从未进过账,回滚时自然也不该找到它们。verifychain 的深度校验复用同一判据,保持两条路径口径一致。另一处呼应是 rpc/blockchain.cpp 里遍历 utxo 快照的 RPC:遇到 IsUnspendable 的输出直接 continue,所以 dumptxoutset 一类导出里看不到烧掉的输出。这些点共同维持一个自洽世界观:账本里没有的,一切读账工具都不该发明出来。
钱包与费用层的连锁反应
钱包收下含 OP_RETURN 输出的交易时,IsMine 归类流程对不可花费且提不出地址的输出会走静默分支(wallet/receive.cpp 里 ExtractDestination 失败且 IsUnspendable 才不报错),这是”给钱包发一条刻数据不会被当成异常收款”的实现基础。费用侧则相反,钱包对烧掉的金额并不宽容:sendrawtransaction 与 submitpackage 都有 maxburnamount 参数,扫描交易输出时把”IsUnspendable 或含无效操作码、且金额超过许可值”的输出合计,超了直接拒——默认零。翻译成人话:不显式报出你能接受烧掉多少,钱包拒绝替你转发一笔”悄悄烧钱”的交易,防的是构造错误的交易把大笔资金误锁进黑洞。PSBT 解码路径也有小防御:尝试把见证推回签名格式时跳过不可花费脚本,避免把 OP_RETURN 数据误认成签名。
一张表带走
| 层 | 行为 |
|---|---|
| 共识验证 | OP_RETURN 输出解锁即失败;脚本超一万字节的输出无法通过执行 |
| UTXO 集合 | AddCoin 拒收,不进账、不进统计 |
| 索引与导出 | coinstatsindex、dumptxoutset 类接口跳过 |
| 断链/校验 | 回滚与 verifychain 同判据,保持口径 |
| 钱包 | 静默归类;maxburnamount 默认拒绝代烧超过零聪的交易 |
理解这条线,能避免两类误判:把”UTXO 统计没涨”当成铭文没上链;把”钱包拒发高输出金额交易”当成手续费故障——那是 maxburnamount 在保护你。
风险提示:本文为节点实现机制科普,行为以 Bitcoin Core 当前版本源码为准,属实现细节而非承诺的共识语义,可能随版本调整;不构成投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。