一份订单支付曾在 A 分支入块,后台将它标为“可交付”;恢复连接后节点改选另一条有效分支,支付离开当前规范链。旧区块头与旧交易的历史存在没有消失,但按当前链计算的 UTXO 集合已改变。区块链(10):交易池与 P2P 为什么不能当共识账本提到的传播差异只是起点:真正危险的是应用不撤回旧链衍生出的业务决策。

设共同祖先高度 h,A 后继块消费买家输入并支付卖家,B 后继块把同一输入付给攻击者。节点不能简单把两条分支的 UTXO 结果取并集;必须断开被替换区块,撤销旧输出产生与旧输入消费,再按新分支顺序接入块。若旧交易与新分支冲突,它无法自动重新纳入;若仍有效,可能重新进入池等待未来纳入。具体再接收与否还依赖节点政策。

flowchart LR
  H[共同祖先 h] --> A[分支 A: 卖家收款] --> A2[订单暂记已付]
  H --> B[分支 B: 冲突支出] --> B2[累计工作胜出]
  B2 --> U[撤销 A 的 UTXO 效果并应用 B]
  U --> X[索引与业务账本撤销/补偿]
  A2 -.不能只留旧状态.-> X

业务端应当按交易、区块哈希、高度和确认策略保存暂时观察,不仅保存 txid。进入同高度但不同哈希的区块是一个新事实;即使交易再次出现,也须重算是否已执行、收款是否相同、业务是否已经不可逆地发货。单纯等待固定确认数只能在特定攻击与网络假设下降低风险,不能给出概率为零的绝对终局。

具体撤销一枚 outpoint

祖先状态中有 X:100。在旧分支 A,交易 T 消耗 X,创建卖家输出 T:0=70、找零 T:1=29,费用 1。切换至分支 B 时,必须先撤销 T:从当前视图移除 T 新建的输出,恢复其曾消费的 X,再按 B 的区块顺序处理交易 U。若 U 消耗 X 并给攻击者 99(手续费 1),最终状态不可能同时含卖家的 T:0 与攻击者的 U:0。这不是清空全库重建的唯一实现方式,但任何优化都须给出等价的逆向状态效果。实际 Core 的撤销数据、索引及持久化顺序需要冻结源码核验,当前只作账本推导。

链下索引的 confirmed 记录也应标记来源块,而不是把数据库行号当不可撤销事实。若卖家在 A 上已发货,撤销订单的“链上已付款”标签并不撤销实体交付:应用要另存交付凭证、风险承担者与补偿单。在 28 篇的 SQLite 有限模型里,输入共同祖先与替换块可演示旧事件删除、订单金额从 70 降为 0;模型本身不知道 B 是否真正有更强 Bitcoin 工作量,所以不能替代双节点 regtest 重组验收。

练习一:h+1 块中给卖家的交易被撤,B 分支在 h+1 用相同输入付给他人,旧交易应如何处理?答案:先撤销原 UTXO 改变,再应用 B;旧交易与当前状态冲突,不能照旧视为已确认。练习二:把索引库仅按 txid 唯一存储,不存区块哈希,重组回到相同高度时缺哪条证据?答案:无法判别观察来自哪个块及撤销哪个分支的派生状态。

真实双节点 regtest 的分区、两分支高度、块哈希及恢复后状态 NOT_RUN;此篇时间线为明确标注的反例,不是实测输出。可迁移原则:可撤销观察必须配套撤销、重放与补偿。参考入口:Core regtest 测试入口、Core 区块验证实现入口(源码 SHA 待核);导航:区块链(10):交易池与 P2P 为什么不能当共识账本 · 11 · 区块链(12):全节点、裁剪节点与轻客户端查的不是同一件事。