# 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 份），
所以挖矿分配总是把后续累加的正确值覆盖回最初同步的旧值。
