写比特币集成测试的人都有过一段”陪节点等闹钟”的经历:测试想验证费率估算的每小时落盘、想确认地址库的定期导出真的发生了,就得对着真实时间干等——调度器设定的那些周期,最短也是几十秒起步,长则一刻钟。比特币核心给这类场景留了一个阀门:mockscheduler,一条把进程内调度器整体拨快、但绝不允许在生产链上碰的regtest专用命令。
它拨的是哪种钟
节点内部有一台独立的调度器(scheduler):所有”周期性杂务”都挂在它身上——对端地址库的定期落盘、失效对端的巡检、未广播交易的重新尝试、费用估算数据的定期刷盘,都是它排程的任务。这台钟用的是进程的单调时钟,跟链上区块时间戳无关,也跟 setmocktime 摆弄的那台”区块时间机器”无关。mockscheduler 接受一个参数 delta_time,含义是把这台调度器的执行时钟向前推这么多秒,让排在未来的周期任务立即到期。
三条硬边界来自源码本身。第一,它只活在 regtest:实现开头就检查链类型,任何主网、测试网、signet 进程调用都直接抛错,没有绕过参数。第二,delta_time 必须在 1 到 3600 秒之间——非正数与超过一小时的值都被拒绝,单次最多拨一小时,需要更多就用循环。第三,它没有返回值,成功即静默。

拨完之后发生了什么
命令的实现做完三件事。先调用调度器的 MockForward 把执行时间前移,触发所有已到期任务;接着同步验证接口队列,确保由这些任务引发的派生事件也处理完毕;最后把同样的前移转发给钱包端(chain client)各自的调度器,让节点侧与钱包侧的定时器一起跨过这段时间。也就是说,一条命令之后,两端调度器上原本要等十几分钟的例行任务都会集中兑现一轮:地址库文件被重写、巡检逻辑跑了一遍、该刷的估算数据落了盘。
这正是测试想要的效果。回归测试验证”peers.dat 会周期性更新”这类行为时,真实等待意味着每个用例多花好几分钟;换成 mockscheduler 拨一次,整个周期语义在同一次调用里演完,测试从分钟级缩到秒级。它验证的是行为逻辑本身,而非流逝的墙钟时间——测试断言的对象本来也不该是”现实过了一刻钟”。
两台时间机器不要混
比特币核心里容易和它混淆的是 setmocktime:那台机器改的是节点眼中的”当前时间”用于区块时间戳与过期判断,影响交易与区块的验证语义;mockscheduler 改的是”任务什么时候执行”,不改变任何共识判断。一个常见的测试组合是两台并用:先用 mocktime 推进区块时间,再用 mockscheduler 让定时器跟上节奏。各自只在自己的层生效,越界理解会导致”明明拨了钟为什么交易还没过期”这类错误预期。
还要泼一盆冷水:这条命令对生产环境毫无意义,也绝无可能误用——链类型检查站在门口。任何教程如果让你在同步主网的节点上执行它,那不是教程写错了版本号,就是它根本没打算让你测出真结果。同样,在 regtest 里它也帮不了需要真实异步并发的测试:调度器时间可以伪造,磁盘延迟和网络往返不能,别指望它加速那些卡在 I/O 上的等待。
顺带一提,这条命令与钱包重扫(rescanblockchain)毫无关系:后者改变钱包的扫描状态、要消耗可观的 I/O,前者只推一把调度器,开销近似于无。也不要在同一轮测试里把它与区块时间机器循环叠加使用——两个时间体系各自推进后,交叉产生的时序组合可能连测试框架自己都解释不了,这是集成测试里最难查的一类假故障。稳妥的用法始终是单步推进:拨一次钟,断言一轮,再决定下一步。
从测试工程的角度看,它是”确定性优先”哲学的又一个样本:与其让测试在随机调度里赌时序,不如把时钟交还给测试本身,让每次运行都面对同一个节拍。看懂它,也就看懂了核心测试框架的一条底层纪律——时间不该是不可控变量。
风险提示:本文面向测试与开发场景,不构成功能建议;regtest 行为不代表主网表现,任何结论请以目标链实测为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。