Web3 链上事件补偿任务
受众:开发者 摘要:Web3 事件补偿专题:失败检测 / 重试策略 / 补偿任务 / 幂等保证。
Web3 链上事件补偿任务用于把“链上事件扫描”和“业务状态修复”拆开处理。它不直接签名、不广播、不托管私钥,也不默认修改支付订单;它负责稳定地发现链上事件、去重、重试、记录日志,并把需要业务处理的事件交给对应 handler。
这类任务适合:
- ERC-20 / ERC-721 / ERC-1155 Transfer 事件归档。
- Web3 支付到账候选发现。
- 链上交易确认提醒。
- 资产流水补扫。
- 权益发放或撤销补偿。
- 多实例环境下的链上任务重试。
设计边界
| 层级 | 职责 |
|---|---|
| Web3 starter | RPC、事件 ABI、topic 编码、事件查询、扫描窗口计算、事件扫描编排 |
| Money Web3 | 检查点持久化、扫描日志、后台运维、Scheduler 自动扫描、失败通知 |
| 业务模块 | 注册扫描器、处理事件、业务幂等、订单或资产状态更新 |
| Scheduler | 周期触发、多实例任务协调、失败重试 |
| Queue | 可选异步处理耗时业务逻辑 |
| Notification | 可选失败通知和人工复核提醒 |
Web3 starter 不落库、不加分布式锁、不启动任务、不更新支付或资产状态。业务状态更新必须放在业务 Service 中。
已有能力
当前已具备:
Web3BlockScans:计算稳定扫描窗口。Web3.scanEvents(...):按检查点执行事件查询和 handler 回调。Web3EventScanner:业务扫描器 SPI。Web3EventScannerRegistry:按scannerName查找扫描器。Web3EventIdempotencyKeys:生成事件幂等 key。money_web3_scan_checkpoint:检查点持久化。money_web3_scan_log:扫描执行日志。- Money Web3 自动扫描 Scheduler 任务。
- Money Web3 手动全量触发和单检查点重跑入口。
- 扫描失败 Notification 通知。
- 业务模块内注册的 Web3 事件处理器。
标准流程
推荐补偿任务按以下流程运行:
- 后台配置或创建
money_web3_scan_checkpoint。 - 检查点保存
chainName、scannerName、nextBlock、确认数和单次扫描跨度。 - Scheduler 周期读取
active检查点。 - 根据
scannerName从Web3EventScannerRegistry找到业务扫描器。 - 通过
Web3.scanEvents(...)读取链头、计算稳定窗口并查询事件日志。 - 业务 handler 对每条事件生成幂等 key。
- 业务 handler 在事务内写入业务表或发起后续 Queue job。
- handler 全部成功后推进检查点。
- 失败时不推进检查点,记录
money_web3_scan_log并标记检查点异常。 - 必要时发送 Notification 失败提醒,运维修复后单检查点重跑。
检查点
检查点是补偿任务的运行游标。核心字段:
| 字段 | 说明 |
|---|---|
chainName | 链配置名称,例如 bsc、ethereum |
scannerName | 扫描器名称,例如 sample-erc20-transfer |
nextBlock | 下一次待扫描起始区块 |
lastScannedBlock | 上次成功扫描结束区块 |
requiredConfirmations | 扫描确认数 |
blockRangeSize | 单次最大扫描跨度 |
status | active、paused、error |
重要规则:
nextBlock只能在本轮业务处理全部成功后推进。error状态不会被自动扫描。paused状态适合临时停用某个业务扫描器。- 大范围历史补扫要降低
blockRangeSize,避免 RPC 超时或服务商限流。
扫描器
业务模块通过 Web3EventScanner 注册自己的扫描器:
java
@Component
public class SampleErc20TransferScanner implements Web3EventScanner {
@Override
public String scannerName() {
return "sample-erc20-transfer";
}
@Override
public Web3EventLogQuery query(Web3BlockScanCheckpoint checkpoint) {
return Web3EventLogQuery.builder()
.chainName(checkpoint.getChainName())
.event(Web3TokenEvents.ERC20_TRANSFER)
.address("0x...")
.build();
}
@Override
public void handle(Web3EventScanContext context, Web3EventLog log) {
// 在业务 Service 中完成幂等落库。
}
}命名建议:
- 使用小写短横线。
- 名称表达业务含义,不要只写
transfer。 - 示例:
web3-payment-confirmation、asset-erc20-transfer、member-nft-rights。
幂等规则
链上事件补偿必须以事件唯一 key 做幂等。
推荐 key:
| 场景 | key |
|---|---|
| 同一链交易日志唯一 | Web3EventIdempotencyKeys.transactionLog(...) |
| 多合约多事件归档 | Web3EventIdempotencyKeys.contractEventLog(...) |
业务表必须建立唯一约束。handler 收到重复事件时,应返回已处理结果,而不是抛出不可恢复异常。
示例:
java
String key = Web3EventIdempotencyKeys.contractEventLog(
log.chainName(),
log.transactionHash(),
log.logIndex(),
log.address(),
Web3TokenEvents.ERC20_TRANSFER.signature());失败处理
失败时的处理原则:
- 不推进检查点。
- 写入扫描日志。
- 将检查点标记为
error。 - 记录错误消息,便于后台排查。
- 可发送 Notification 失败提醒。
- 修复原因后,用单检查点触发入口重跑。
常见失败:
| 失败类型 | 处理建议 |
|---|---|
| RPC 超时 | 降低 blockRangeSize,检查节点限流 |
| 扫描器不存在 | 确认业务模块已加载并注册 scanner |
| 事件解析失败 | 检查 ABI、topic 和合约地址 |
| 业务唯一键冲突 | handler 应兼容重复事件 |
| 业务表写入失败 | 修复业务数据或迁移后重跑 |
后台入口
检查点管理:
http
GET /api/v1/admin/web3/scan-checkpoints
GET /api/v1/admin/web3/scan-checkpoints/{id}
GET /api/v1/admin/web3/scan-checkpoints/{id}/next-window
POST /api/v1/admin/web3/scan-checkpoints
PUT /api/v1/admin/web3/scan-checkpoints/{id}
POST /api/v1/admin/web3/scan-checkpoints/{id}/advance
POST /api/v1/admin/web3/scan-checkpoints/{id}/pause
POST /api/v1/admin/web3/scan-checkpoints/{id}/resume
POST /api/v1/admin/web3/scan-checkpoints/{id}/mark-error
DELETE /api/v1/admin/web3/scan-checkpoints/{id}
DELETE /api/v1/admin/web3/scan-checkpoints扫描触发:
http
POST /api/v1/admin/web3/scans/trigger
POST /api/v1/admin/web3/scans/checkpoints/{id}/trigger扫描日志:
http
GET /api/v1/admin/web3/scan-logs
GET /api/v1/admin/web3/scan-logs/{id}
DELETE /api/v1/admin/web3/scan-logs/{id}
DELETE /api/v1/admin/web3/scan-logs配置
yaml
spring:
open:
money:
web3:
enabled: true
scanner:
enabled: true
auto-startup: true
task-name: money.web3.scan
instance-id: default
fixed-delay: 1m
limit: 50
failure-notification:
enabled: true
channels:
- log
subject: Web3 scan failed
recipient-type: ""
recipient-value: ""配置说明:
| 配置 | 说明 |
|---|---|
enabled | 是否启用 Money Web3 运维能力 |
scanner.enabled | 是否启用扫描任务能力 |
scanner.auto-startup | 是否随应用启动注册 Scheduler 任务 |
scanner.fixed-delay | 自动扫描间隔 |
scanner.limit | 单轮最多扫描检查点数量 |
failure-notification.enabled | 是否启用失败通知 |
failure-notification.channels | 通知通道,例如 log、database、websocket、mail |
与支付结算的关系
链上事件补偿任务可以发现 Web3 支付候选事件,但不等于自动结算。
支付结算仍需要:
- 本地订单状态校验。
- 金额、收款地址、Token 合约和链 ID 校验。
- 事件唯一 key 幂等。
reviewFingerprint或同等复核机制。- 支付流水和订单状态事务写入。
- 审计和通知。
自动结算设计见 Web3 支付自动结算设计。
上线检查
上线前至少确认:
- 每个扫描器都有明确的
scannerName。 - 每个业务表都有事件幂等唯一约束。
- 每条链的
requiredConfirmations合理。 - 历史补扫设置了安全的
blockRangeSize。 - 业务 handler 失败不会吞异常。
- 扫描失败通知已经接入可见通道。
- 运维知道如何暂停、恢复、标记异常和单检查点重跑。
- 生产 RPC 支持所需历史区块范围。
补偿任务的第一原则是:宁可不推进游标,也不要在业务处理失败后假装扫描成功。