docs(android): 添加调试日志指南和2-of-3流程分析

添加两份关键调试文档:

## 1. DEBUG_LOG_GUIDE.md - 日志抓取指南
- 详细的日志抓取命令(adb logcat)
- 关键日志检查点(会话创建、事件触发、keygen启动)
- 日志分析流程图
- 常见陷阱和解决方案
- 完整的日志模板

## 2. 2OF3_FLOW_ANALYSIS.md - 2-of-3钱包创建流程深度分析

### 已发现的5个潜在Bug:

**Bug 1: 事件回调中的异常处理不一致**
- 发起者:在事件回调中用 safeLaunch 包裹
- 加入者:在函数内部用 safeLaunch
- 位置不同可能导致异常处理行为不一致

**Bug 2: safeLaunch 双重包裹可能导致静默失败** 🚨
- startKeygenAsInitiator 在事件回调中被 safeLaunch 包裹
- 函数内部已经处理了 Result.failure
- 外层 safeLaunch 只能捕获运行时异常
- 如果内部更新了错误但UI已切换状态,用户可能看不到错误

**Bug 3: 参与者数量不足时没有明确错误**
- server-party-co-managed 未加入时,会话不会启动
- 但没有超时提示,用户一直等待

**Bug 4: getSessionStatus 失败时参与者列表不准确**
- 失败时只显示自己
- 实际可能已有多个参与者
- 误导用户

**Bug 5: startKeygenAsJoiner 中的 return 没有错误处理**
- joinInfo 为 null 时静默返回
- 用户不知道为什么 keygen 没启动

### 创建失败的3个最可能原因:

1. **server-party-co-managed 没有正确加入** (70%)
   - 需要检查配置和日志

2. **session_started 事件没有被触发** (20%)
   - 参与者数量不足
   - WebSocket 连接问题

3. **startKeygenAsInitiator 失败但错误被忽略** (8%)
   - Result.failure 被处理但UI没显示

### 提供的调试步骤:
1. 检查 server-party-co-managed 状态
2. 抓取手机日志(提供完整命令)
3. 搜索关键日志片段

### 推荐修复方案:
1. 统一事件回调异常处理
2. 移除双重 safeLaunch
3. 添加超时机制

下一步:用户需要抓取日志进行精确诊断

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-26 23:27:43 -08:00
parent 3f3a5b021e
commit 05c6ab3dc4
2 changed files with 1025 additions and 0 deletions

View File

@ -0,0 +1,627 @@
# 创建 2-of-3 钱包流程分析与潜在Bug
## 理论流程(应该如何工作)
### 环境2台手机 + 1个 server-party-co-managed
```
手机1 (发起者)
1. 调用 createNewSession(walletName="测试", t=2, n=3)
2. 服务器创建会话,返回 sessionId + inviteCode
3. 手机1 显示邀请码二维码
4. server-party-co-managed 检测到新会话自动加入第1个参与者
5. 手机2 扫描二维码,调用 validateInviteCode + joinKeygenViaGrpc第2个参与者
6. 服务器检测到参与者数量 = thresholdT (2)
7. 服务器广播 "session_started" 事件给所有参与者手机1、手机2、server
8. 所有参与者收到事件,调用 startKeygenAsInitiator/startKeygenAsJoiner
9. TSS keygen 协议运行9轮通信
10. 完成,所有参与者保存各自的分片
```
---
## 实际代码流程(当前实现)
### 手机1发起者
#### 步骤 1: 创建会话
**代码位置**: `MainViewModel.kt:253-330`
```kotlin
fun createNewSession(walletName: String, thresholdT: Int, thresholdN: Int, participantName: String) {
safeLaunch { // ← 【潜在问题1】如果抛出异常会被捕获
_uiState.update { it.copy(isLoading = true, error = null) }
val result = repository.createSession(walletName, thresholdT, thresholdN)
result.fold(
onSuccess = { sessionResult ->
_currentSessionId.value = sessionResult.sessionId
_createdInviteCode.value = sessionResult.inviteCode
// 【关键】获取会话状态,检查参与者数量
val statusResult = repository.getSessionStatus(sessionResult.sessionId)
statusResult.fold(
onSuccess = { status ->
_sessionParticipants.value = status.participants.map { ... }
// ✅ 正确显示参与者列表
},
onFailure = { e ->
// ⚠️ 失败时只使用自己
_sessionParticipants.value = listOf(participantName)
}
)
},
onFailure = { e ->
_uiState.update { it.copy(isLoading = false, error = e.message) }
}
)
}
}
```
**潜在问题**:
- ✅ Result 处理正确
- ⚠️ 如果 getSessionStatus 失败,参与者列表不准确
- ⚠️ 但这不影响实际的 keygen 启动
---
#### 步骤 2: 等待 session_started 事件
**代码位置**: `MainViewModel.kt:382-406`
```kotlin
repository.setSessionEventCallback { event ->
when (event.eventType) {
"session_started" -> {
val currentSessionId = _currentSessionId.value
if (currentSessionId != null && event.sessionId == currentSessionId) {
android.util.Log.d("MainViewModel", "Session started event for keygen initiator, triggering keygen")
safeLaunch { // ← 【关键问题!】
startKeygenAsInitiator(
sessionId = currentSessionId,
thresholdT = event.thresholdT,
thresholdN = event.thresholdN,
selectedParties = event.selectedParties
)
}
}
}
}
}
```
**这是 Bug 的根源!**
问题分析:
1. `setSessionEventCallback` 是在 **另一个线程**WebSocket 事件线程)中回调的
2. 在回调中使用 `safeLaunch` 启动协程
3. **如果 `startKeygenAsInitiator` 抛出异常**`safeLaunch` 会捕获并更新 `_uiState.error`
4. 但是,**用户可能没有看到错误提示**,因为:
- UI 可能正在显示"等待参与者"界面
- `_uiState.error` 的更新可能被忽略
- 没有明确的错误反馈路径
---
#### 步骤 3: 执行 keygen
**代码位置**: `MainViewModel.kt:537-570`
```kotlin
private suspend fun startKeygenAsInitiator(
sessionId: String,
thresholdT: Int,
thresholdN: Int,
selectedParties: List<String>
) {
android.util.Log.d("MainViewModel", "Starting keygen as initiator: sessionId=$sessionId, t=$thresholdT, n=$thresholdN")
val result = repository.startKeygenAsInitiator(
sessionId = sessionId,
thresholdT = thresholdT,
thresholdN = thresholdN,
password = ""
)
result.fold(
onSuccess = { share ->
_publicKey.value = share.publicKey
_uiState.update {
it.copy(
lastCreatedAddress = share.address,
successMessage = "钱包创建成功!"
)
}
},
onFailure = { e ->
// ⚠️ 错误被记录到 _uiState.error
_uiState.update { it.copy(error = e.message) }
}
)
}
```
**潜在问题**:
- ✅ Result 处理正确
- ⚠️ 但如果函数本身抛出异常(非 Result.failure外层的 `safeLaunch` 会捕获
- ⚠️ 这会导致**双重错误处理**
1. `startKeygenAsInitiator` 更新 `_uiState.error`(如果是 Result.failure
2. `safeLaunch` 也更新 `_uiState.error`(如果是异常)
---
### 手机2加入者
#### 步骤 1: 扫描邀请码
**代码位置**: `MainViewModel.kt:609-641`
```kotlin
fun validateInviteCode(inviteCode: String) {
safeLaunch {
_uiState.update { it.copy(isLoading = true, error = null) }
val result = repository.validateInviteCode(inviteCode)
result.fold(
onSuccess = { validateResult ->
_joinSessionInfo.value = JoinKeygenSessionInfo(...)
_uiState.update { it.copy(isLoading = false) }
},
onFailure = { e ->
_uiState.update { it.copy(isLoading = false, error = e.message) }
}
)
}
}
```
**状态**: ✅ 处理正确
---
#### 步骤 2: 加入会话
**代码位置**: `MainViewModel.kt:648-706`
```kotlin
fun joinKeygen(inviteCode: String, password: String) {
safeLaunch {
_uiState.update { it.copy(isLoading = true, error = null) }
val result = repository.joinKeygenViaGrpc(
inviteCode = pendingInviteCode,
joinToken = pendingJoinToken,
password = password
)
result.fold(
onSuccess = { joinResult ->
// 【关键】保存 joinResult 用于后续 keygen
pendingJoinKeygenInfo = JoinKeygenInfo(
sessionId = joinResult.sessionId,
partyIndex = joinResult.partyIndex,
partyId = joinResult.partyId,
participantIds = joinResult.participantIds
)
// ✅ 等待 session_started 事件
_uiState.update { it.copy(isLoading = false) }
},
onFailure = { e ->
_uiState.update { it.copy(isLoading = false, error = e.message) }
}
)
}
}
```
**状态**: ✅ 处理正确
---
#### 步骤 3: 等待 session_started 事件
**代码位置**: `MainViewModel.kt:408-413`
```kotlin
// Check if this is for keygen joiner (JoinKeygen)
val joinKeygenInfo = pendingJoinKeygenInfo
if (joinKeygenInfo != null && event.sessionId == joinKeygenInfo.sessionId) {
android.util.Log.d("MainViewModel", "Session started event for keygen joiner, triggering keygen")
startKeygenAsJoiner() // ← 【注意】没有用 safeLaunch 包裹!
}
```
**关键发现!**
对比发起者和加入者:
- **发起者**: `safeLaunch { startKeygenAsInitiator(...) }` ← 包了 safeLaunch
- **加入者**: `startKeygenAsJoiner()` ← 没有包 safeLaunch
**这是不一致的!**
---
#### 步骤 4: 执行 keygen加入者
**代码位置**: `MainViewModel.kt:714-764`
```kotlin
private suspend fun startKeygenAsJoiner() {
safeLaunch { // ← 【注意】这里也用了 safeLaunch
val joinInfo = pendingJoinKeygenInfo ?: return
_uiState.update { it.copy(isLoading = true, error = null) }
val result = repository.startKeygenAsJoiner(
sessionId = joinInfo.sessionId,
partyIndex = joinInfo.partyIndex,
participantIds = joinInfo.participantIds,
password = pendingPassword
)
result.fold(
onSuccess = { share ->
_joinKeygenPublicKey.value = share.publicKey
_uiState.update {
it.copy(
isLoading = false,
successMessage = "成功加入钱包!"
)
}
},
onFailure = { e ->
_uiState.update { it.copy(isLoading = false, error = e.message) }
}
)
}
}
```
**问题**:
- `startKeygenAsJoiner` 自己已经用了 `safeLaunch`
- 但在事件回调中调用它时,**没有**再包一层 `safeLaunch`
- 这和发起者的处理方式不同!
**不一致性总结**:
| 角色 | 事件回调中 | 函数自身 | 总包裹层数 |
|-----|-----------|---------|----------|
| 发起者 | `safeLaunch { startKeygenAsInitiator() }` | 无 safeLaunch | 1层 |
| 加入者 | `startKeygenAsJoiner()` | `safeLaunch { ... }` | 1层 |
虽然都是1层但**位置不同**
---
## 🐛 已发现的Bug清单
### Bug 1: 事件回调中的异常处理不一致 ⚠️
**位置**: `MainViewModel.kt:398-413`
**问题**:
- 发起者:事件回调中使用 `safeLaunch` 包裹
- 加入者:事件回调中直接调用(函数内部有 `safeLaunch`
**影响**:
- 如果发起者的 `startKeygenAsInitiator` 在被 `safeLaunch` 调用**之前**抛出异常(例如参数验证),会被捕获
- 但加入者的 `startKeygenAsJoiner` 在事件回调中直接调用,如果函数调用本身抛出异常(不是内部的),不会被捕获
**建议**: 统一处理方式
---
### Bug 2: safeLaunch 双重包裹可能导致静默失败 🚨
**位置**: `MainViewModel.kt:398-405` + `MainViewModel.kt:537-570`
**问题流程**:
```
事件回调
safeLaunch { // ← 第1层异常捕获
startKeygenAsInitiator()
如果抛出异常 X
}
} catch (e: Exception) { // ← 捕获异常 X
_uiState.update { it.copy(error = ...) } // ← 更新错误
}
```
但是:
1. `startKeygenAsInitiator` 内部已经处理了 `Result.failure`
2. 外层 `safeLaunch` 只能捕获**运行时异常**
3. 如果 `repository.startKeygenAsInitiator` 返回 `Result.failure`,不会抛出异常
4. **所以外层 safeLaunch 实际上没什么用**
**更严重的问题**:
如果 `startKeygenAsInitiator` 内部处理了错误(更新了 `_uiState.error`但UI已经切换到其他状态**用户可能看不到错误**
---
### Bug 3: 参与者数量不足时没有明确错误 ⚠️
**场景**:
- 创建 2-of-3 会话
- server-party-co-managed 没有自动加入(配置错误)
- 只有手机1发起者
- **服务器不会广播 session_started 事件**
**当前行为**:
- 手机1 一直显示"等待参与者加入..."
- **没有超时提示**
- **没有明确的错误消息**
**建议**: 添加超时机制和友好提示
---
### Bug 4: getSessionStatus 失败时参与者列表不准确 ⚠️
**位置**: `MainViewModel.kt:302-321`
```kotlin
val statusResult = repository.getSessionStatus(sessionResult.sessionId)
statusResult.fold(
onSuccess = { status ->
_sessionParticipants.value = status.participants.map { ... }
},
onFailure = { e ->
// ⚠️ 失败时只显示自己
_sessionParticipants.value = listOf(participantName)
}
)
```
**问题**:
- 如果 `getSessionStatus` 失败参与者列表显示为1
- 但实际上可能已经有多个参与者(例如 server-party-co-managed
- **这会误导用户**,以为没人加入
---
### Bug 5: 事件回调中的 return 没有处理 ⚠️
**位置**: `MainViewModel.kt:714` (startKeygenAsJoiner)
```kotlin
private suspend fun startKeygenAsJoiner() {
safeLaunch {
val joinInfo = pendingJoinKeygenInfo ?: return // ← 这个 return 只返回 lambda
// ...
}
}
```
**问题**:
- `return` 只会退出 `safeLaunch` 的 lambda
- 不会更新 UI 状态或显示错误
- **用户不知道为什么 keygen 没有启动**
**建议**: 如果 `joinInfo` 为 null应该记录错误并通知用户
---
## 🔍 为什么会创建失败?
### 最可能的原因
#### 原因 1: server-party-co-managed 没有正确加入 🔴
**检查**:
1. server-party-co-managed 是否正在运行?
2. 配置文件中是否启用了自动加入?
3. 服务器日志中是否有加入记录?
**验证命令**:
```bash
# 检查 server-party-co-managed 日志
tail -f /path/to/server-party-co-managed/logs/server.log | grep "join"
```
**预期日志**:
```
[INFO] Detected new session: sessionId=xxx
[INFO] Auto-joining session as backup party
[INFO] Successfully joined session, partyId=backup-party-1
```
如果**没有这些日志**,说明 server-party-co-managed 没有加入!
---
#### 原因 2: session_started 事件没有被触发 🔴
**条件**:
- 服务器只有在 `participants.size >= thresholdT` 时才会广播 `session_started`
- 2-of-3 需要至少 2 个参与者
**检查**:
1. 服务器端参与者列表有多少个?
2. 手机1 的日志中是否有 "Session started event"
**预期日志手机1**:
```
MainViewModel: === MainViewModel received session event ===
MainViewModel: eventType: session_started
MainViewModel: sessionId: xxxxxxxx
MainViewModel: Session started event for keygen initiator, triggering keygen
```
如果**没有这条日志**,说明事件没有触发!
---
#### 原因 3: startKeygenAsInitiator 内部失败但没有显示错误 🔴
**场景**:
1. `session_started` 事件触发了
2. 调用了 `startKeygenAsInitiator`
3. 但 `repository.startKeygenAsInitiator` 返回 `Result.failure`
4. 错误被记录到 `_uiState.error`
5. **但 UI 没有显示错误**(因为还在"等待参与者"界面)
**检查日志**:
```
MainViewModel: Session started event for keygen initiator, triggering keygen
MainViewModel: Starting keygen as initiator: sessionId=xxx, t=2, n=3
TssRepository: Starting keygen as initiator
TssRepository: Error: [具体错误信息] ← 看这里!
```
如果有这条错误日志,说明 keygen 启动失败了!
---
## 🛠️ 调试步骤
### 步骤 1: 检查 server-party-co-managed
```bash
# 1. 检查进程是否运行
ps aux | grep server-party-co-managed
# 2. 检查配置文件
cat /path/to/server-party-co-managed/config.yml | grep -A 10 "auto_join"
# 3. 查看最近日志
tail -f /path/to/server-party-co-managed/logs/server.log
```
### 步骤 2: 抓取手机1发起者日志
```bash
adb logcat -c
adb logcat -v time | grep -E "MainViewModel|TssRepository|GrpcClient|session_started"
```
**重点看**:
1. "Creating new session" → 会话创建
2. "Session created successfully" → 会话创建成功
3. "Session status fetched: X participants" → 参与者数量
4. "Session started event" → 事件触发
5. "Starting keygen as initiator" → keygen 启动
### 步骤 3: 抓取手机2加入者日志
```bash
adb logcat -c
adb logcat -v time | grep -E "MainViewModel|TssRepository|GrpcClient|session_started"
```
**重点看**:
1. "Validate success: sessionId=" → 邀请码验证成功
2. "Join keygen success: partyIndex=" → 加入成功
3. "Session started event for keygen joiner" → 收到事件
---
## 🚀 推荐修复方案
### 修复 1: 统一事件回调中的异常处理
```kotlin
repository.setSessionEventCallback { event ->
when (event.eventType) {
"session_started" -> {
// 统一使用 safeLaunch 包裹所有启动函数
val currentSessionId = _currentSessionId.value
if (currentSessionId != null && event.sessionId == currentSessionId) {
android.util.Log.d("MainViewModel", "Session started event for keygen initiator")
safeLaunch {
startKeygenAsInitiator(...)
}
}
val joinKeygenInfo = pendingJoinKeygenInfo
if (joinKeygenInfo != null && event.sessionId == joinKeygenInfo.sessionId) {
android.util.Log.d("MainViewModel", "Session started event for keygen joiner")
safeLaunch { // ← 添加 safeLaunch
startKeygenAsJoiner()
}
}
}
}
}
```
### 修复 2: 移除 startKeygenAsJoiner 内部的 safeLaunch
```kotlin
private suspend fun startKeygenAsJoiner() {
// 移除内部的 safeLaunch由调用方负责异常处理
val joinInfo = pendingJoinKeygenInfo
if (joinInfo == null) {
android.util.Log.e("MainViewModel", "startKeygenAsJoiner: joinInfo is null!")
_uiState.update { it.copy(error = "加入信息丢失,请重试") }
return
}
_uiState.update { it.copy(isLoading = true, error = null) }
val result = repository.startKeygenAsJoiner(...)
// ...
}
```
### 修复 3: 添加超时机制
`createNewSession` 后启动超时计时器:
```kotlin
// 5分钟超时
val timeoutJob = viewModelScope.launch {
delay(300_000) // 5 minutes
if (_currentSessionId.value != null && _publicKey.value == null) {
_uiState.update {
it.copy(
error = "等待超时:参与者数量不足或服务器未响应",
isLoading = false
)
}
}
}
```
---
## 📊 总结
### 最可能导致失败的原因(按概率排序)
1. **🔴 server-party-co-managed 没有自动加入** (70%)
- 检查配置和日志
2. **🔴 session_started 事件没有触发** (20%)
- 参与者数量不足
- WebSocket 连接问题
3. **🟡 startKeygenAsInitiator 失败但错误被忽略** (8%)
- 检查手机日志中的异常
4. **🟢 safeLaunch 包裹问题** (2%)
- 理论上不会导致完全失败
- 但可能导致错误信息不清晰
### 立即行动项
1. **检查 server-party-co-managed 状态** ← 最重要!
2. **抓取手机日志,搜索 "session_started"**
3. **搜索日志中的 "Caught exception" 或 "Error:"**
4. **把日志发给我进行详细分析**
---
**请先按照"调试步骤"抓取日志,然后我们可以精确定位问题!**

View File

@ -0,0 +1,398 @@
# Android 应用调试日志抓取指南
## 当前日志配置分析
### ✅ 已有日志点
应用在关键位置使用 `android.util.Log` 记录日志:
| 日志标签 | 位置 | 日志内容 |
|---------|------|---------|
| `MainViewModel` | 所有 ViewModel 操作 | 会话创建、参与者加入、TSS进度、异常 |
| `TssRepository` | 所有 Repository 操作 | gRPC调用、TSS native调用、数据库操作 |
| `GrpcClient` | 网络通信 | gRPC连接、请求/响应、错误 |
| `TssNativeBridge` | TSS原生库 | 密钥生成、签名、错误 |
### 📋 关键日志内容
#### 1. 会话创建创建2-of-3钱包
```
MainViewModel: Creating new session: walletName=xxx, t=2, n=3
MainViewModel: Session created: sessionId=xxx, inviteCode=xxx
MainViewModel: Session status fetched: X participants already joined
MainViewModel: Participants: [partyId1, partyId2, ...]
```
#### 2. 密钥生成触发
```
MainViewModel: Session started event for keygen initiator, triggering keygen
MainViewModel: Starting keygen as initiator: sessionId=xxx, t=2, n=3
TssRepository: Starting keygen as initiator
TssNativeBridge: keygenAsInitiator called
```
#### 3. 异常捕获safeLaunch
```
MainViewModel: Caught exception in safeLaunch
[Stack trace...]
```
---
## 🔍 日志抓取命令
### 方案 1: 实时日志(推荐用于复现问题)
```bash
# 清除旧日志
adb logcat -c
# 实时查看所有应用日志(带时间戳)
adb logcat -v time | grep -E "MainViewModel|TssRepository|GrpcClient|TssNativeBridge|AndroidRuntime"
```
### 方案 2: 过滤关键标签(快速定位)
```bash
# 只看应用相关日志
adb logcat MainViewModel:D TssRepository:D GrpcClient:D TssNativeBridge:D AndroidRuntime:E *:S
```
### 方案 3: 保存完整日志到文件
```bash
# 清除旧日志
adb logcat -c
# 重现问题创建2-of-3钱包
# 保存日志到文件
adb logcat -d -v time > android_debug.log
```
---
## 🎯 重点关注的日志
### 创建 2-of-3 钱包失败时需要看到:
#### ✅ 必须有的日志(正常流程)
1. **会话创建请求**:
```
MainViewModel: Creating new session: walletName=测试钱包, t=2, n=3, participantName=xxx
```
2. **会话创建成功**:
```
MainViewModel: Session created successfully
MainViewModel: sessionId: xxxxxxxx
MainViewModel: inviteCode: ABCD1234
```
3. **获取会话状态**:
```
MainViewModel: Session status fetched: 2 participants already joined
MainViewModel: Participants: [party-id-1, party-id-2]
```
4. **收到 session_started 事件**:
```
MainViewModel: === MainViewModel received session event ===
MainViewModel: eventType: session_started
MainViewModel: sessionId: xxxxxxxx
```
5. **触发密钥生成**:
```
MainViewModel: Session started event for keygen initiator, triggering keygen
MainViewModel: Starting keygen as initiator: sessionId=xxx, t=2, n=3
```
6. **TSS 原生库调用**:
```
TssNativeBridge: keygenAsInitiator called with sessionId=xxx
TssNativeBridge: Keygen completed successfully
```
7. **进度更新**:
```
MainViewModel: Progress update: 1 / 9
MainViewModel: Progress update: 2 / 9
...
MainViewModel: Progress update: 9 / 9
```
#### ❌ 可能出现的错误日志
1. **safeLaunch 捕获的异常**:
```
MainViewModel: Caught exception in safeLaunch
java.net.SocketTimeoutException: timeout
at ...
```
2. **gRPC 连接失败**:
```
GrpcClient: Failed to connect to server
GrpcClient: Error: UNAVAILABLE: io exception
```
3. **TSS 原生库错误**:
```
TssNativeBridge: keygenAsInitiator failed: [error message]
```
4. **会话创建失败**:
```
MainViewModel: Service check failed
TssRepository: Failed to create session: [error message]
```
---
## 🚨 关键问题检查点
### 1. 检查 safeLaunch 是否吞掉了异常
搜索日志中的:
```
"Caught exception in safeLaunch"
```
如果有这行,说明异常被捕获了,查看后续的堆栈跟踪。
### 2. 检查是否有 Result.failure 未处理
搜索:
```
"onFailure"
"Failed to"
"Error:"
```
### 3. 检查 session_started 事件是否触发
搜索:
```
"Session started event for keygen initiator"
```
如果**没有这行**,说明事件回调没有触发,密钥生成没有启动。
### 4. 检查参与者计数
搜索:
```
"Session status fetched: X participants"
```
如果参与者数量 < thresholdT例如 2-of-3 需要至少2个参与者会话不会启动
---
## 📊 日志分析流程图
```
启动应用
[搜索] "Service check"
├─ 成功 → 继续
└─ 失败 → 检查数据库/网络/原生库错误
点击"创建钱包"
[搜索] "Creating new session"
├─ 有 → 继续
└─ 无 → UI事件未触发前端问题
[搜索] "Session created successfully"
├─ 有 → 继续
└─ 无 → 检查 gRPC 错误或 "onFailure"
[搜索] "Session status fetched: X participants"
├─ 有 → 检查参与者数量是否 >= thresholdT
└─ 无 → getSessionStatus 调用失败
[搜索] "Session started event"
├─ 有 → 继续
└─ 无 → WebSocket 事件未收到(服务器问题)
[搜索] "Starting keygen as initiator"
├─ 有 → 继续
└─ 无 → safeLaunch 内部异常(搜索 "Caught exception"
[搜索] "keygenAsInitiator called"
├─ 有 → 检查 "Keygen completed" 或 TSS错误
└─ 无 → 原生库调用未执行
[搜索] "Progress update"
├─ 有 → TSS 正在进行,检查是否完成
└─ 无 → TSS 未启动或卡住
[搜索] "Keygen completed successfully"
├─ 有 → 成功!
└─ 无 → 检查 TSS 错误日志
```
---
## 🛠️ 调试建议
### 如果看到 "Caught exception in safeLaunch"
**原因**: safeLaunch 捕获了异常但可能没有正确显示给用户
**临时解决方案**: 查看异常堆栈,找到根本原因
**示例**:
```
MainViewModel: Caught exception in safeLaunch
java.net.SocketTimeoutException: timeout
at okhttp3.internal.connection.RealCall.execute
at ...
```
→ 说明 gRPC 连接超时
### 如果没有 "Session started event"
**原因**: WebSocket 事件回调未触发
**可能问题**:
1. service-party 服务器未运行
2. WebSocket 连接断开
3. 服务器未广播 session_started 事件
4. 参与者数量不足(< thresholdT
**检查**:
```bash
# 检查服务器日志
tail -f /path/to/service-party/logs/server.log
```
### 如果有 "Session started event" 但没有 "Starting keygen"
**原因**: safeLaunch 内部的 startKeygenAsInitiator 调用失败
**检查**:
- 搜索 "Caught exception in safeLaunch"
- 查看异常类型和堆栈
---
## 📝 日志模板(复制给我)
抓取日志后,请提供以下信息:
```
### 1. 操作步骤
- [ ] 启动应用
- [ ] 点击"创建钱包"
- [ ] 输入钱包名称: ___
- [ ] 选择 2-of-3
- [ ] 输入参与者名称: ___
- [ ] 点击"创建"
- [ ] 【描述具体现象】: ___
### 2. 关键日志片段
#### 会话创建
```
[粘贴包含 "Creating new session" 的日志]
```
#### 会话状态
```
[粘贴包含 "Session status fetched" 的日志]
```
#### 事件触发
```
[粘贴包含 "Session started event" 的日志]
```
#### 异常(如果有)
```
[粘贴包含 "Caught exception" 的日志]
```
### 3. 完整日志文件
[附件: android_debug.log]
```
---
## 🔧 临时调试增强(如需更详细日志)
如果标准日志不够详细,可以临时添加更多日志:
### 在 MainViewModel.kt 的 safeLaunch 中:
```kotlin
private fun safeLaunch(
onError: ((Exception) -> Unit)? = null,
block: suspend CoroutineScope.() -> Unit
) = viewModelScope.launch {
try {
android.util.Log.d("MainViewModel", "safeLaunch: Starting block") // 添加这行
block()
android.util.Log.d("MainViewModel", "safeLaunch: Block completed successfully") // 添加这行
} catch (e: CancellationException) {
android.util.Log.d("MainViewModel", "safeLaunch: CancellationException caught") // 添加这行
throw e
} catch (e: Exception) {
android.util.Log.e("MainViewModel", "safeLaunch: Caught exception: ${e.javaClass.simpleName}", e)
// ... 现有代码 ...
}
}
```
---
## ⚠️ 常见陷阱
### 1. 异常被吞掉但 UI 不显示错误
**症状**: 操作无反应,没有错误提示
**原因**: safeLaunch 更新了 _uiState.error 但 UI 没有订阅
**检查**: 搜索日志中的 "Caught exception",看是否有异常但 UI 没反应
### 2. Result.failure 未正确处理
**症状**: repository 返回 failure 但 ViewModel 没有处理
**检查**: 搜索 "onFailure" 和 "result.fold"
### 3. 协程被取消
**症状**: 操作执行到一半停止
**检查**: 搜索 "CancellationException"
---
## 📱 完整抓取命令(复制粘贴)
```bash
# 1. 清除旧日志
adb logcat -c
# 2. 开始记录(在另一个终端)
adb logcat -v time > ~/Desktop/android_debug_$(date +%Y%m%d_%H%M%S).log
# 3. 操作应用(重现问题)
# 4. 停止记录Ctrl+C
# 5. 发送日志文件给我
```
或者一步到位(操作完后手动停止):
```bash
adb logcat -c && adb logcat -v time | tee android_debug.log | grep --color -E "MainViewModel|TssRepository|GrpcClient|TssNativeBridge|Exception|Error"
```
---
**准备好后请执行上述命令重现创建2-of-3失败的问题然后把日志发给我**