区块链(06):Bitcoin 原始交易如何花费、找零和付费
05 篇用合成 outpoint 完成了 100 = 70 + 29 + 1。在 Bitcoin 中,发送方并非从一个余额格子扣 70;钱包挑选若干尚未消费的输出,以 (txid, vout) 引用它们,再给卖家和自己的找零锁定新输出。原始交易携带版本、输入、输出和锁定时间;输入的解锁材料如何放置还取决于花费的输出类型。这些字节必须由协议序列化规则决定,不能用 Python str(dict) 代替。
先修区块链(05):同一笔支付在 UTXO 与账户账本里的两种状态迁移的守恒和区块链(03):签名证明授权了哪条消息的签名边界。信任模型是用户控制签名密钥,全节点独立检查交易和待花输出;节点看到一笔有效、已广播的交易,不承诺它会进入候选块。
flowchart LR
W[钱包: 选币、找零、构造与签名] --> R[RPC: 提交原始字节]
R --> N[全节点: 输入是否存在、脚本与守恒]
N --> M[交易池: 本地接收策略]
M --> B[候选块: 再按共识验证]
B --> U[chainstate: 删除旧输出、写入新输出]
费用是输入总额减输出总额,不是输出里一条写着“miner fee”的普通转账。找零遗漏时,余额不会自动返回;以 100 面额输入只生成卖家 70 输出,其余 30 在合适的有效交易中全算手续费。找零地址也需要备份或可恢复的描述符,否则支付已完成而剩余资金失去控制。输入重复、金额为负、输出合计超过输入、签名不满足被花费的锁定条件,都应当在对应规则处拒绝;广播失败和业务订单失败不是同一回事。
从交易字节回到状态转换
输入引用的 (txid, vout) 不是付款人的余额地址。验证节点要在当前可花费输出集中取出该 outpoint 的金额与锁定条件;新交易不能靠自报输入金额通过守恒检查。取合成输入 100,输出分别锁定卖家 70 与找零 29,费用为 1。签名满足旧输出的花费条件,但不会创造 100 的面额。钱包可以在广播后暂时将此输入标成占用,这是本地防重复选币策略;交易进入有效历史后,旧输出才从当前链的 UTXO 集移除,新输出进入该集合。未确认找零也可能在本地交易池按依赖关系被子交易引用,但它尚不是当前链已确认的输出。
如果交易只生成卖家 70,协议不会猜测“应返还 29”,差额 30 成了手续费;若找零 31,则输出和 101 超过输入。再造一笔签名合法且引用同一输入的交易,两笔可能各自独立验签,冲突由当前状态和排序决定。examples/blockchain/test_models.py 测到的是人为提供 UTXO 集时的守恒与重复引用拒绝,没有 Core 签名预映像、手续费率、灰尘限额或替换策略;不能把模型的 1 单位当作真实 sat/vbyte。
真实验收须保存原始字节、解码后的每个 outpoint 与输出脚本、广播回应、纳入块哈希/高度、付款前后的钱包可识别 UTXO。若节点拒绝,还须记录该版本的原始错误,不能依赖本文猜测错误字符串。
可检查范围:examples/blockchain/models/ledgers.py 已实测合成找零、超额、重复输入与双花拒绝,但它根本没有真实交易序列化和脚本。bitcoin-cli 不在当前环境,createrawtransaction → fundrawtransaction → signrawtransactionwithwallet → decoderawtransaction → sendrawtransaction 这条 regtest 链路 NOT_RUN;命令名只是待按冻结 Core RPC 帮助核对的实验入口,没有虚构 txid、区块高度或手续费实测值。
练习一:若输入是 100,卖家输出 70,找零 29,费用是多少?若想支付 70、找零 31 呢?答案:前者 1;后者输出 101 大于输入,拒绝。练习二:重复广播一笔已确认交易的字节,能否因此再造一个找零输出?答案:旧 outpoint 已被花费,不能再更新 UTXO;是否作为重复已知交易返回特殊 RPC 错误由冻结版节点决定,不能仅靠本模型断言具体错误字符串。
可迁移原则是先核算所有被消费输入和产生输出,再谈 API 返回。参考入口:Bitcoin Core 交易开发指南(待联网核验)、Bitcoin Core RPC 帮助(待冻结 release)。完整 Core SHA NOT_FROZEN。导航:区块链(05):同一笔支付在 UTXO 与账户账本里的两种状态迁移 · 06 · 区块链(07):Script、时间锁和见证不是一件事。






