去中心化共识机制——无服务端下的可信时间

05/1144 浏览开发心得 疑似 AI 合成内容
可以用来作为玩家自动自治的反作弊手段,利用诚实玩家自动算出当天真实时间,作为无服务端时的可信时间。

流程

玩家登录
  │
  ├─ 1. 校准本地时钟 ── 记录系统时间 + 单调时钟基准
  │
  ├─ 2. 读取私有锚点(云端) ── 上次确认的共识天数
  │
  ├─ 3. 拉取排行榜 ── 所有人的投票数据
  │       │
  │       ├─ 解析各天票数
  │       │
  │       ├─ 投票资格检查 ─── 只能投 锚点天 或 锚点天+1
  │       │    │                       (新玩家: 共识天 或 +1)
  │       │    │                       (回归玩家: 共识强时放行)
  │       │    │
  │       │    └─ 通过 → 写入投票 ── gameDay × 10000 + 序号
  │       │
  │       └─ 共识判定(递进式) ─── 下一天票数 ≥ 阈值 → 推进+1
  │                                    否则保持当前共识天
  │
  ├─ 4. 更新锚点 ─── 共识天 → 私有云变量
  │
  └─ 5. 时间验证 & 使用
          │
          ├─ 合法性: |本地天 - 共识天| 在容忍窗口内?
          │
          └─ 钳制时间 = min(本地时间, 共识天上限, 存档天上限)
                │
                └─ 用于: 离线收益 / 每日奖励 / 体力回复 ...
联网游戏有服务器,时间以服务器为准。
单机游戏没有这个条件,所有逻辑跑在玩家本地,时间只能从设备取。
而设备时间是玩家可以随意修改的——往前拨几个小时,倒计时瞬间结束;往后调一天,离线收益翻倍。
存档数据全都"合法",只是时间被人为篡改了。
问题的本质是:在没有中心服务器的前提下,怎么拿到一个玩家改不了的时间。

所有人投票,所有人校验

大多数单机游戏都有排行榜接口,底层是一个云端键值存储。
这个接口虽然不是时间服务,但它能存数据、能被所有人读到,这就够了。
思路很简单:每个玩家登录时把自己的本地日期写进排行榜,然后拉取所有人的数据,看哪一天票数最多,那一天就是"共识时间"。
没有管理员,没有特权账号,所有人的投票权重相同。
单个玩家的时间不可信,但当大量玩家报告同一个日期时,这个日期就是可信的——作弊者可以伪造自己的时钟,但没法让几十个真实玩家都报告同一个假日期。
这就是"去中心化"的含义:可信时间不来自某个权威源,而来自群体共识。

递进式推进:共识只能往前走一天

如果单纯取票数最多的天作为共识,会有一个问题:一批作弊者同时把时钟拨到未来某一天集中投票,就可能直接把共识拉到那一天。
解决办法是递进式推进。
共识天数每次只允许向前推进一天,而且推进到下一天需要达到一个最低票数阈值,比如三票。
也就是说,今天的共识是第 10 天,即使有人在第 50 天投了一百票,共识也只会推进到第 11 天——前提是第 11 天也攒够了三票。
首次启动时,取排行榜中票数最多的天作为初始共识,平票时取更小的天。
这是一个保守策略:宁可慢一点,也不被作弊者拉高。
此后每次拉取排行榜都尝试递进,满足条件就推进一步,不满足就保持原地。共识永不回退。
这个设计让攻击变得极其低效。作弊者想把共识从第 10 天拉到第 50 天,需要在第 11、12、13……一直到第 50 天的每一天都攒够三票,而且必须等真实玩家逐天推进到那里才行。
时间跨度越大,攻击成本越高,最终不值得。

锚点机制:限制谁能投票

递进式推进解决了共识被拉高的问题,但没解决投票本身被污染的问题。
如果不限制投票资格,作弊者可以把时钟拨到共识天的下一天反复投票,加速推进。
应对方式是给每个玩家设一个私有锚点。
锚点存在云端的私有变量里,记录的是这个玩家上一次确认的共识天数。
投票时,玩家只能投自己的锚点天或锚点天加一。
如果本地时钟被拨到了远超锚点的日期,投票会被拒绝。
锚点随共识更新:每次共识推进后,把新的共识天写回私有变量,作为下一次的锚点。
这意味着玩家每次登录最多只能把共识往前推一天,和递进式推进的逻辑完全吻合。
对于新玩家(没有锚点),如果排行榜已经有了强共识(票数达到阈值),只允许投共识天或共识天加一。
如果排行榜完全是空的,只允许投第 0 天或第 1 天。
这样即使有人注册大量新账号,也无法直接投出一个虚假的未来日期。
对于长期不上线的回归玩家,锚点可能远远落后于当前共识。
这时如果共识足够强,允许玩家直接跳到共识天附近投票,避免回归玩家被锁死。

编码技巧:一个整数装两个信息

排行榜通常只能存一个整数值。
但投票需要同时记录日期和投票序号。
解决办法是把两个信息编码到一个整数里:高位存日期,低位存序号。
比如乘以一万再加序号,第 10 天的第 3 票就是 100003。
拉取数据时用整除和取余拆开。
投票序号的作用是近似统计每天有多少人投票。
玩家投票前先看当天已有的最大序号,加一作为自己的序号。
不需要精确——只要能区分"一个人投的"和"很多人投的"就够了。

两层钳制:共识天 + 存档天

共识时间是天级别的,不能精确到秒。游戏内的时间敏感功能——离线收益、每日奖励、体力回复——需要精确的秒级时间。
直接用本地时间又不安全。
做法是设一个容忍窗口
共识天是第 10 天,容忍窗口是 7 天,那么本地时间最多只被认可到第 17 天结束。
超出这个窗口的部分直接截断。这是第一层上限,由共识天决定。
第二层上限来自存档
记录玩家上次领取奖励时的游戏天数,加上同样的容忍窗口作为另一个上限。
这一层不依赖排行榜——即使共识还没拉到(网络异常、首次启动),存档天仍然有效。
实际使用的时间取本地时间和两层上限的最小值。
这样无论本地时钟怎么拨,能领到的收益都被框定在一个合理范围内。

单调时钟:校准后不再依赖系统时间

还有一个细节:玩家可能在游戏运行过程中修改系统时间。
如果每次取时间都直接调系统接口,中途改时钟就能立即生效。
解决办法是校准机制
游戏启动时读一次系统时间,同时记下引擎的单调时钟值(从启动开始累计的秒数,不受系统时间影响)。
之后每次需要时间时,用校准时刻的系统时间加上单调时钟的增量来推算。
这样即使玩家在游戏中修改了系统时间,推算出的时间也不会变。
只有重启游戏才会重新校准,而重启后又会触发排行榜投票和共识判定,回到整个检测流程的起点。

异常处理

判定异常的方式是看玩家的本地天数和共识天的差值。在容忍窗口以内视为合法,超出视为异常。如果共识本身票数不足,不做判定——宁可放行也不误杀。
检测到异常后不应该封号或清档。时钟不准可能是设备问题、时区切换、系统更新。合理做法是冻结受时间影响的收益——离线奖励不发放,倒计时不推进,体力不回复——等时间回到合理范围后自动恢复。玩家会发现拨时钟没有好处,但不会因为误判损失已有进度。

整体结构

整个方案不需要服务器,不需要管理员账号,不需要常连接。
只需要一个排行榜接口和一个私有云变量接口——这两样东西大多数平台都提供。
每个玩家每次登录做三件事:读取锚点、拉排行榜投票、更新锚点。
三个极低成本的操作组合在一起,构成了一个对单机游戏来说足够用的去中心化可信时间体系。
递进式推进保证共识不被跳跃拉高,锚点机制保证投票不被伪造,两层钳制保证即使共识延迟也有硬上限,单调时钟保证运行时不被篡改。
每一层都有明确的防御目标,任意一层失效时其余层仍然有效。
8
1