rwadurian/docs/issues/mining-contribution-sync-lo...

111 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# BUG: 挖矿分配并发覆盖贡献值同步 (Lost Update)
**状态**: 待解决
**严重程度**: 高 - 影响用户挖矿收益
**发现日期**: 2026-03-03
**影响范围**: 所有预种用户 (D2602260xxxx)12个账户数据异常
---
## 1. 问题描述
用户购买多份预种后contribution-service 正确记录了累计贡献值,但 mining-service 中只显示第一份的贡献值,导致挖矿收益严重偏低。
**示例:**
- D26022600018 购买 4 份预种 → contribution 记录 13,117.860 → mining 只显示 3,279.465
- D26022600001 购买 5 份合成 1 棵树 → contribution 记录 16,397.325 → mining 只显示 3,279.465
---
## 2. 根因分析
### 主因MiningDistributionService 的 save() 覆盖 totalContribution
mining-service 存在两个并发写入路径共享同一个 `save()` 方法:
**路径 A - 贡献值同步**Kafka 事件触发,不定时):
```
ContributionEventHandler → ContributionSyncService.handleContributionCalculated()
→ account.updateContribution(newValue) // 更新 totalContribution
→ miningAccountRepository.save(account) // upsert 所有字段
```
**路径 B - 挖矿分配**(每秒执行):
```
MiningDistributionService.executeSecondDistribution()
→ findAllWithContribution() // 加载账户到内存(含 totalContribution
→ account.mine(reward) // 只改 totalMined/availableBalance
→ miningAccountRepository.save(account) // upsert 所有字段(含 stale totalContribution
```
**竞争时序:**
```
T0 挖矿分配加载 D26022600018, totalContribution=3279.465
T1 贡献值同步更新 D26022600018 → totalContribution=13117.860 ✓ (DB 已更新)
T2 挖矿分配保存 D26022600018, 写回 totalContribution=3279.465 ← 覆盖!
```
**日志证据:**
```
03/01 02:50:30 - Updated contribution for D26022600001: 16397.325 ← 同步成功
03/03 当前 DB - D26022600001 totalContribution = 3279.465 ← 被覆盖回旧值
```
挖矿分配每秒运行,贡献值同步后几乎必然在下一秒被覆盖。
**代码位置:**
- `mining-account.repository.ts:57-62` — save() 的 upsert 无条件写入所有字段
- `mining-distribution.service.ts:122-135` — 每秒加载→mine()→save()
- `mining-account.aggregate.ts:126-141` — mine() 只改余额但 save() 会写回所有字段
### 次因DailySnapshot 全量同步完全失效
本应作为安全网的每日全量同步,自上线以来一直 synced 0 accounts
```
03/01 01:00 - DailySnapshotCreated for 2026-02-27 → synced 0 accounts
03/02 01:00 - DailySnapshotCreated for 2026-02-28 → synced 0 accounts
03/03 01:00 - DailySnapshotCreated for 2026-03-01 → synced 0 accounts
```
**原因:**
1. contribution-service 发布的事件 payload 缺少 `snapshotId` 字段 → mining-service 读到 undefined
2. 字段名不匹配contribution 发 `networkTotalContribution`mining 读 `totalContribution`
3. `fetchContributionRatios()` 返回数据解析失败(`result.data` 路径不对)
**代码位置:**
- `contribution-service/snapshot.service.ts:99-110` — payload 缺少 snapshotId
- `mining-service/contribution-sync.service.ts:63-68` — 期望 snapshotId
- `mining-service/contribution-sync.service.ts:126-141` — API 调用和解析
---
## 3. 受影响用户
| 用户 | contribution 正确值 | mining 当前值 | 差额 |
|------|-------------------|-------------|------|
| D26022600001 | 16,397.325 | 3,279.465 | -13,117.860 |
| D26022600014 | 13,253.562 | 3,279.465 | -9,974.097 |
| D26022600015 | 6,649.398 | 3,279.465 | -3,369.933 |
| D26022600016 | 13,117.860 | 3,279.465 | -9,838.395 |
| D26022600018 | 13,117.860 | 3,279.465 | -9,838.395 |
| D26022600033 | 3,754.422 | 3,279.465 | -474.957 |
| D26022600034 | 6,988.653 | 3,279.465 | -3,709.188 |
| D26022600035 | 3,799.656 | 3,392.550 | -407.106 |
| D26022600036 | 17,211.537 | 3,279.465 | -13,932.072 |
| D26022600037 | 16,397.325 | 3,279.465 | -13,117.860 |
| D26022600038 | 19,676.790 | 3,279.465 | -16,397.325 |
| D26022600043 | 6,558.930 | 3,279.465 | -3,279.465 |
D26022600000 是唯一正常的预种用户。旧用户 (D2512xxxx) 不受影响。
---
## 4. 为什么旧用户不受影响
旧用户在系统稳定后完成了一次性同步,之后没有新的贡献值变更事件。
挖矿分配虽然每秒覆写 totalContribution但写回的值恰好等于正确值没有新同步来改变它
预种用户是分批多次产生贡献值的(先 1 份 → 2 份 → ... → N 份),
所以挖矿分配总是把后续累加的正确值覆盖回最初同步的旧值。