diff --git a/backend/mpc-system/services/service-party-android/2OF3_FLOW_ANALYSIS.md b/backend/mpc-system/services/service-party-android/2OF3_FLOW_ANALYSIS.md new file mode 100644 index 00000000..f75f55f2 --- /dev/null +++ b/backend/mpc-system/services/service-party-android/2OF3_FLOW_ANALYSIS.md @@ -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 +) { + 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. **把日志发给我进行详细分析** + +--- + +**请先按照"调试步骤"抓取日志,然后我们可以精确定位问题!** diff --git a/backend/mpc-system/services/service-party-android/DEBUG_LOG_GUIDE.md b/backend/mpc-system/services/service-party-android/DEBUG_LOG_GUIDE.md new file mode 100644 index 00000000..908f8abc --- /dev/null +++ b/backend/mpc-system/services/service-party-android/DEBUG_LOG_GUIDE.md @@ -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失败的问题,然后把日志发给我!**