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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions