节点怎么知道你电脑时钟不准:只取出站邻居的样本,中位数过了十分钟才报警 图 1
节点怎么知道你电脑时钟不准:只取出站邻居的样本,中位数过了十分钟才报警 · 图 1

给比特币节点配过机器的人大多见过这条警告:你的计算机日期和时间似乎与网络不同步超过十分钟。它不是随口一提,而是一条有采样、有中位数、有阈值、有清除条件的完整判定链。本文按 Bitcoin Core 31.0 源码把每个环节对上号。

样本从哪来

节点与每个对端握手时会交换时间戳:version 消息里带着对方自报的本地时间。收到之后,节点算一笔减法——对方的时间减去我的时间——得到这个对端相对我的偏移,存进 peer.m_time_offset,这也是 getpeerinfo 输出里 timeoffset 字段的来源。正数表示对方的钟比你的快。

关键在于采样范围:只有出站连接(你自己拨出去的那些对端)的样本会被送入时钟体检模块。net_processing.cpp 里的注释解释了原因——不用入站样本,是为了不让别人轻易伪造出「你的时钟不准」的假警告。攻击者若能控制入站对端的自报时间,就能随意往你的样本池里灌偏差;出站对端由你自己挑选,样本池的可信度完全不同。

节点怎么知道你电脑时钟不准:只取出站邻居的样本,中位数过了十分钟才报警 图 2
节点怎么知道你电脑时钟不准:只取出站邻居的样本,中位数过了十分钟才报警 · 图 2

五十条样本、五条起算的中位数

样本进入一个上限五十条的环形容器,超出就把最旧的挤出去。判定用的是中位数:Median 函数先把样本复制一份排序,取正中间那个值。两个防误报的细节藏在代码里:样本不足五条时直接返回零,不报警;中位数取的是排序后的中间位近似值,源码注释坦承「粗略中位数已经够用,保持简单」。中位数而非平均值,意味着个别对端的离谱时钟无法左右判定结果。

十分钟阈值与警告的一生

WARN_THRESHOLD 定义为十分钟。中位数偏差的绝对值一旦超过它,WarnIfOutOfSync 就把 CLOCK_OUT_OF_SYNC 挂进节点警告集合并写一条日志;回到阈值以内则 Unset 撤下。警告原文值得逐句读:它会提示这可能导致共识失败;告诉你确认过电脑时钟后重启节点就会消失;若不重启,在连上足够多新的出站对端之后,旧样本被逐渐挤出容器,警告也会自动消退;还顺手指路——去查 getpeerinfo 与 getnetworkinfo 里的 timeoffset 字段。getnetworkinfo 的 warnings 数组就是这条警告对外暴露的位置。

为什么值得认真对待

时钟偏差在协议里不是小事。区块时间戳的校验窗口、time-too-new 拒绝等规则都依赖节点自己的时间认知,偏差过大的节点可能拒收合法区块或接受不该接受的块(区块时间规则的细节见 区块时间只能领先两小时)。反方向的误判同样存在:如果你看到警告但 NTP 显示本机时钟正常,多半是出站对端样本恰好偏移,继续观察新连接建立后警告是否自行消退,比急着重启更符合这条链路的设计意图。

一个值得记住的运维事实:这条警告自带消退机制。它考察的是你与网络的中位共识,而不是任何单点对端的说法——五十条样本、五条起算、阈值十分钟,层层都替「不冤枉好机器」留了余地。反过来,它也替攻击者留了余地:只要你的出站样本池里混入少数时钟离谱但对端正常的节点(比如某些时区配置错误的主机),中位数也可能被短暂拖偏,这时正确动作是等样本轮换而非立刻改系统时间。把 getnetworkinfo 的 warnings、getpeerinfo 的逐对端 timeoffset 与系统 NTP 状态三样东西放在一起看,几分钟内就能区分「本机时钟真飘了」与「样本池恰好偏了」,这比任何第三方监控都直接(把节点纳入监控的常见组合可参考 给节点装心跳)。顺带一提,超时线在协议里处处有同类思路:区块时间窗口用的是「与中位数比较」而非绝对时刻,共识对时间的容忍度总是交给群体统计,本文的十分钟阈值不过是同一哲学在你笔记本时钟上的投影。