listsinceblock 增量对账:游标推进、重组退单与幂等写入 图 1
listsinceblock 增量对账:游标推进、重组退单与幂等写入 · 图 1

自托管钱包和小型记账服务都有一个共同需求:把钱包相关的交易增量拉进自己的账本,既不漏账也不重复。全表扫描 listtransactions 对小钱包无所谓,对交易密集的地址越来越慢;listsinceblock 就是为”记住上次读到哪”设计的游标式接口。它的参数不多,但有一组反直觉的行为,用错方向会以非常隐蔽的方式账不平。

先看签名:listsinceblock "blockhash" target_confirmations include_watchonly include_removed。第一个参数是游标——上次读到的区块哈希,省略则从头列起。第二个参数最容易被望文生义:官方文档明确写着 target_confirmations 不作为过滤器使用,只影响返回值里 lastblock 取哪个块——填 6 不会把”不足 6 确认”的交易藏起来,只是让返回的 lastblock 指向离链尖 6 个块深的位置。把它当确认数过滤器的实现,第一遍看似正常,链一重组就出事。

游标的推进协议是这套接口的精髓。返回值里有个 lastblock 字段,按文档口径,它是从当前最佳区块向后数第 target_confirmations-1 个块的哈希——填 6 时对应”第 6 深的块”,下次调用把它原样传回,配合 target_confirmations=6,交易会在达到 6 确认前被反复报告若干次,然后稳定。官方文档对此有一句话点题:this is typically used to feed back into listsinceblock the next time you call it。也就是说,深确认游标的正确姿势不是”每次都从链尖读”,而是”反复读同一个深游标,直到新块把水位线推过去”。重复报告不是 bug,账本写入要做幂等(以交易哈希去重),这是第一个纪律。

重组行为是第二个纪律。文档写明:如果传入的 blockhash 已经不在主链上,返回结果从分叉点起算;若 include_removed 为 true(默认值),被这次重组挤出主链的钱包交易还会出现在 removed 数组里。对账程序因此天然拿到了重组的”退单凭证”:removed 里出现的交易要回滚状态,同一批结果里重新出现在 transactions 的(重组后又被打包的)则保持有效。文档还专门提示了一个细节:被重新加回活动链的交易会原样出现在 removed 数组里,确认数甚至可能是正数——只看”在不在 removed 里”会误回滚,必须再对照 transactions 数组裁决。另一个边界:removed 的完整性在修剪节点上没有保证,做对账服务的别拿修剪节点当后端。

include_watchonly 的默认值随钱包类型而变——观察钱包默认 true,普通钱包默认 false。同一个脚本换一台节点跑,覆盖范围可能不同,对账程序应当显式传参而不是依赖默认。返回的交易条目里还能看到 abandoned 标志(true 表示该交易已被钱包放弃、输入可重新花费)与 label 标签,把 abandoned 处理成”状态取消”是常见集成需求。

把以上规则拼成一个稳的增量循环:启动时不传游标全量扫描一遍建立基线;此后每轮调用带上持久化的游标和 target_confirmations=6;收到结果后按交易哈希幂等写入,处理 removed 时二次核对 transactions;成功处理完才把 lastblock 落盘作为新游标;崩溃重启从旧游标重放即可,代价只是若干次重复报告。整个循环里唯一需要持久化的状态就一个字符串哈希,这就是游标式接口的美德。

对照另一条路线可以加深理解:walletconflicts 是事后的冲突清单,适合盘账时人工排查;listsinceblock 是事前的增量流,适合程序持续消费。两者数据源一致,工程角色不同。如果场景要求”交易一进块就通知”,那已经越出轮询接口的能力边界,应该去看 ZMQ 事件推送——listsinceblock 的定位始终是对账循环,不是消息总线。

风险提示:接口参数默认值与行为随比特币核心版本变化,请以所用版本官方 RPC 文档复核;对账逻辑错误可能导致账务错乱,上线前请在测试网完整演练重组场景。本文不构成投资建议。

listsinceblock 增量对账:游标推进、重组退单与幂等写入 图 2
listsinceblock 增量对账:游标推进、重组退单与幂等写入 · 图 2