fix(android): 修复参与者计数竞态条件,使用服务端权威数据 [P1-1]
【架构安全修复 - 防止参与者计数竞态条件】
## 问题背景
原代码在处理 party_joined 事件时使用本地计数器递增来生成参与者名称。
## 存在的风险
1. 事件重放导致重复添加 - 网络重连后事件可能重新发送
2. 事件乱序导致编号错乱 - 网络延迟导致事件乱序到达
3. 状态不一致 - 本地状态与服务端真实状态不同步
4. 并发事件处理 - 多个事件快速连续到达时可能冲突
## 修复方案
使用服务端权威数据构建参与者列表:
- BEFORE: 本地计数器累加 (不可靠)
- AFTER: 使用 event.selectedParties.size 重建列表 (可靠)
核心改变:
val participantCount = event.selectedParties.size // 来自服务端
val participantList = List(participantCount) { index -> "参与方 ${index + 1}" }
_sessionParticipants.value = participantList // 直接设置,不累加
## 为什么这样修复有效
1. 单一真实来源: event.selectedParties 来自服务端
2. 幂等性: 无论事件重放多少次结果相同
3. 顺序无关: 无论事件以何种顺序到达最终状态正确
4. 自动去重: 直接设置 value 而非累加
## 影响范围
修改 MainViewModel.kt 的 party_joined 事件处理逻辑,影响 4 个参与者列表:
1. _sessionParticipants - 创建钱包页面
2. _joinKeygenParticipants - 加入密钥生成页面
3. _coSignParticipants - 联合签名加入页面
4. _signParticipants - 转账签名发起页面
行为变化:
- BEFORE: 每个事件递增计数器累加参与者
- AFTER: 每个事件重建整个参与者列表基于服务端数据
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6dda30c528
commit
bb6febb46b
|
|
@ -373,51 +373,55 @@ class MainViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
"party_joined", "participant_joined" -> {
|
||||
android.util.Log.d("MainViewModel", "Processing participant_joined event...")
|
||||
/**
|
||||
* 【架构安全修复 - 防止参与者计数竞态条件】
|
||||
*
|
||||
* 原问题:使用 current.size + 1 递增计数器存在多个风险
|
||||
* 1. 事件重放:重连后事件可能重复发送,导致参与者重复添加
|
||||
* 2. 事件乱序:网络延迟可能导致事件乱序到达,参与者编号错乱
|
||||
* 3. 状态不一致:本地计数与服务端真实参与者列表不同步
|
||||
*
|
||||
* 修复方案:使用事件的 selectedParties 字段构建权威的参与者列表
|
||||
* - selectedParties 来自服务端,是参与者的唯一真实来源
|
||||
* - 根据 selectedParties.size 构建参与者列表,确保与服务端一致
|
||||
* - 防止重复添加和计数错乱
|
||||
*/
|
||||
android.util.Log.d("MainViewModel", "Processing participant_joined event (selectedParties=${event.selectedParties.size})...")
|
||||
|
||||
// Build participant list from authoritative server data
|
||||
val participantCount = event.selectedParties.size
|
||||
val participantList = List(participantCount) { index -> "参与方 ${index + 1}" }
|
||||
android.util.Log.d("MainViewModel", " → Building participant list from server data: $participantList")
|
||||
|
||||
// Update participant count for initiator's CreateWallet screen
|
||||
val currentSessionId = _currentSessionId.value
|
||||
android.util.Log.d("MainViewModel", " Checking for initiator: currentSessionId=$currentSessionId, eventSessionId=${event.sessionId}")
|
||||
if (currentSessionId != null && event.sessionId == currentSessionId) {
|
||||
android.util.Log.d("MainViewModel", " → Matched initiator session! Updating _sessionParticipants")
|
||||
_sessionParticipants.update { current ->
|
||||
val newParticipant = "参与方 ${current.size + 1}"
|
||||
android.util.Log.d("MainViewModel", " → Adding participant: $newParticipant, total now: ${current.size + 1}")
|
||||
current + newParticipant
|
||||
}
|
||||
android.util.Log.d("MainViewModel", " → Matched initiator session! Updating _sessionParticipants to: $participantList")
|
||||
_sessionParticipants.value = participantList
|
||||
}
|
||||
|
||||
// Update participant count for keygen joiner's JoinKeygen screen
|
||||
val joinKeygenInfo = pendingJoinKeygenInfo
|
||||
android.util.Log.d("MainViewModel", " Checking for joiner: joinKeygenInfo?.sessionId=${joinKeygenInfo?.sessionId}")
|
||||
if (joinKeygenInfo != null && event.sessionId == joinKeygenInfo.sessionId) {
|
||||
android.util.Log.d("MainViewModel", " → Matched joiner session! Updating _joinKeygenParticipants")
|
||||
_joinKeygenParticipants.update { current ->
|
||||
val newParticipant = "参与方 ${current.size + 1}"
|
||||
current + newParticipant
|
||||
}
|
||||
android.util.Log.d("MainViewModel", " → Matched joiner session! Updating _joinKeygenParticipants to: $participantList")
|
||||
_joinKeygenParticipants.value = participantList
|
||||
}
|
||||
|
||||
// Update participant count for sign joiner's CoSign screen
|
||||
val joinSignInfo = pendingJoinSignInfo
|
||||
if (joinSignInfo != null && event.sessionId == joinSignInfo.sessionId) {
|
||||
android.util.Log.d("MainViewModel", " → Matched sign joiner session! Updating _coSignParticipants")
|
||||
_coSignParticipants.update { current ->
|
||||
val newParticipant = "参与方 ${current.size + 1}"
|
||||
current + newParticipant
|
||||
}
|
||||
android.util.Log.d("MainViewModel", " → Matched sign joiner session! Updating _coSignParticipants to: $participantList")
|
||||
_coSignParticipants.value = participantList
|
||||
}
|
||||
|
||||
// Update participant count for sign initiator's TransferScreen (SigningScreen)
|
||||
val signSessionId = _signSessionId.value
|
||||
android.util.Log.d("MainViewModel", " Checking for sign initiator: signSessionId=$signSessionId, eventSessionId=${event.sessionId}")
|
||||
if (signSessionId != null && event.sessionId == signSessionId) {
|
||||
android.util.Log.d("MainViewModel", " → Matched sign initiator session! Updating _signParticipants")
|
||||
_signParticipants.update { current ->
|
||||
val newParticipant = "参与方 ${current.size + 1}"
|
||||
android.util.Log.d("MainViewModel", " → Adding participant: $newParticipant, total now: ${current.size + 1}")
|
||||
current + newParticipant
|
||||
}
|
||||
android.util.Log.d("MainViewModel", " → Matched sign initiator session! Updating _signParticipants to: $participantList")
|
||||
_signParticipants.value = participantList
|
||||
}
|
||||
}
|
||||
"all_joined" -> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue