Skip to content

EVM WebSocket 监听在断线 / 限流 / 节点切换后没有区块补扫,存在漏单风险 #83

Description

@Yufeifeio

提交前检查

  • 我已阅读文档并检查常见问题
  • 我已搜索现有 issue,确认不是重复问题
  • 我已尽量提供可复现信息(代码、配置、日志)

1. epusdt 版本

v1.0.3

2. 问题概述

目前 BSC / Polygon 的 EVM 自动监听逻辑主要依赖实时 WebSocket eth_subscribe(logs) 推送。

我检查 v1.0.3 源码后发现:

  • 监听断开后会重连
  • 节点失败达到阈值后会切换到其他 WS 节点
  • 但是没有看到任何“区块断点续扫 / 历史补扫”逻辑

也就是说,如果在以下时间窗口内发生充值:

  • subscribe: Too many requests
  • websocket abnormal closure
  • subscription error
  • log channel closed
  • WS 节点故障后切换节点的间隔

系统看起来只会“重新订阅”,不会从断线前最后处理区块继续补扫到最新区块,因此存在漏单风险。

3. 相关代码

1)BSC / Polygon 只建立实时 WS 订阅

BSC:

  • src/task/listen_bsc.go
query := ethereum.FilterQuery{
    Addresses: contracts,
    Topics:    [][]common.Hash{},
}

runEvmWsLogListener(ctx, "[BSC-WS]", wsNode, query, func(client *ethclient.Client, vLog types.Log) {
    ...
    service.TryProcessEvmERC20Transfer(...)
})

Polygon:

  • src/task/listen_polygon.go
query := ethereum.FilterQuery{
    Addresses: contracts,
    Topics:    [][]common.Hash{},
}

runEvmWsLogListener(ctx, "[POLYGON-WS]", wsNode, query, func(client *ethclient.Client, vLog types.Log) {
    ...
    service.TryProcessEvmERC20Transfer(...)
})

这两处都没有看到 fromBlock / toBlock / FilterLogs / getLogs 一类补扫逻辑。

2)公共 EVM WS 监听器只做“重连订阅”,没有历史回放

  • src/task/listen_evm_ws.go
client, err := ethclient.Dial(wsURL)
...
sub, err := client.SubscribeFilterLogs(ctx, query, logsCh)
...
case err := <-sub.Err():
    if err != nil {
        log.Sugar.Warnf("%s subscription error: %v, reconnecting", logPrefix, err)
        return fmt.Errorf("subscription error")
    } else {
        log.Sugar.Warnf("%s subscription closed, reconnecting", logPrefix)
        return fmt.Errorf("subscription closed")
    }
case vLog, ok := <-logsCh:
    if !ok {
        log.Sugar.Warnf("%s log channel closed, reconnecting", logPrefix)
        return fmt.Errorf("log channel closed")
    }
    handleLog(client, vLog)

这里可以看到:

  • 断线后会返回外层重连
  • 但没有记录 lastProcessedBlock
  • 没有在重连后执行 FilterLogs / eth_getLogs
  • 没有从“上次处理到的区块 + 1”补扫到当前最新区块

3)对比:Tron 的 block scanner 是有块号推进逻辑的

  • src/task/listen_trc20_block.go
latestNum := latest.BlockHeader.RawData.Number
if latestNum <= s.lastBlock {
    ...
    return
}

for num := s.lastBlock + 1; num <= latestNum; num++ {
    ...
    s.lastBlock = num
}

Tron 这里至少明确有 lastBlock 和逐块推进逻辑,但 EVM WS 监听侧没有看到对应实现。

4. 风险点

如果充值恰好发生在以下窗口,可能不会被自动识别:

  1. WS 订阅断开到重新建立之间
  2. 节点限流报错到下一次重试之间
  3. 当前节点失败到切换到新节点之间
  4. 进程重启后,如果启动前一段时间有 EVM 充值,也没有看到自动补扫逻辑

从代码实现看,这部分当前更像是:

  • 实时 WS 自动监听
  • 失败后重连 / 切节点
  • 漏掉的单需要依赖手动补单

而不是:

  • 实时监听 + 断线后自动补扫

5. 建议

建议为 EVM 链增加区块补扫机制,例如:

  1. 记录每条链 / 每个监听器最近成功处理到的区块号
  2. WS 重连成功后,先获取当前最新区块
  3. FilterLogs / eth_getLogslastProcessedBlock + 1 补扫到最新区块
  4. 补扫完成后再继续实时 SubscribeFilterLogs
  5. 节点切换时也执行同样的补扫逻辑

这样可以显著降低 subscribe: Too many requestsabnormal closure、节点故障切换期间的漏单风险。

6. 补充说明

我这里说的是“自动监听流程”本身缺少补扫,不是“完全没有补单手段”。

项目目前确实有手动提交交易哈希 / 手动补单能力,但这属于人工兜底,不能替代自动区块补扫。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions