From bd6537a2cbc072b1cba67080a6685c57528453ba Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 29 Dec 2025 10:44:29 -0800 Subject: [PATCH] =?UTF-8?q?fix(service-party-app):=20checkAndTriggerKeygen?= =?UTF-8?q?=20=E6=94=B9=E4=B8=BA=E8=BD=AE=E8=AF=A2=E7=AD=89=E5=BE=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - 原来 checkAndTriggerKeygen 只检查一次 - 如果首次检查时会话状态还不是 in_progress,就直接返回 - 导致 external party 永远不触发 keygen 修复: - 改为与 server-party 的 waitForAllParticipants 一致的轮询逻辑 - 2 秒轮询间隔,最多等待 5 分钟 - 持续检查直到所有参与者加入且状态正确 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../service-party-app/electron/main.ts | 147 ++++++++++-------- 1 file changed, 83 insertions(+), 64 deletions(-) diff --git a/backend/mpc-system/services/service-party-app/electron/main.ts b/backend/mpc-system/services/service-party-app/electron/main.ts index d2892a49..7b981e61 100644 --- a/backend/mpc-system/services/service-party-app/electron/main.ts +++ b/backend/mpc-system/services/service-party-app/electron/main.ts @@ -123,7 +123,8 @@ function cleanExpiredEventCache() { } } -// 检查并触发 keygen(100% 可靠方案:不依赖事件,直接检查状态) +// 检查并触发 keygen(100% 可靠方案:轮询等待所有参与者加入) +// 与 server-party 的 waitForAllParticipants 逻辑保持一致 async function checkAndTriggerKeygen(sessionId: string) { if (!activeKeygenSession || activeKeygenSession.sessionId !== sessionId) { console.log('No matching active keygen session for', sessionId); @@ -136,80 +137,98 @@ async function checkAndTriggerKeygen(sessionId: string) { return; } - // 从 Account 服务获取最新会话状态 - try { - const status = await accountClient?.getSessionStatus(sessionId); - if (!status) { - debugLog.warn('main', 'Failed to get session status'); + const pollIntervalMs = 2000; // 2秒轮询间隔,与 server-party 保持一致 + const maxWaitMs = 5 * 60 * 1000; // 5分钟超时 + const startTime = Date.now(); + + debugLog.info('main', `Starting to poll session status for ${sessionId}`); + + while (Date.now() - startTime < maxWaitMs) { + // 检查会话是否仍然有效 + if (!activeKeygenSession || activeKeygenSession.sessionId !== sessionId) { + debugLog.warn('main', 'Active keygen session changed, stopping poll'); return; } - debugLog.info('main', `Session ${sessionId} status: ${status.status} (${status.completed_parties}/${status.total_parties} parties)`); - - // 对于 co_managed_keygen,必须等待所有 N 个参与者加入后才能开始 - // 这与 server-party 的 waitForAllParticipants 逻辑一致 - const expectedN = status.total_parties || activeKeygenSession.threshold.n; - const currentParticipants = status.participants?.length || 0; - - if (currentParticipants < expectedN) { - debugLog.debug('main', `Waiting for all participants: ${currentParticipants}/${expectedN}`); + // 如果 TSS 已经在运行(可能被其他事件触发),停止轮询 + if (tssHandler?.getIsRunning()) { + debugLog.info('main', 'TSS started running, stopping poll'); return; } - // 如果会话已经是 in_progress 或 all_joined,或者参与方已满,触发 keygen - // 使用 status 字段判断是否可以开始 - if (status.status === 'in_progress' || - status.status === 'all_joined' || - status.status === 'waiting_for_keygen') { - - debugLog.info('main', `All ${expectedN} participants joined, triggering keygen...`); - - // 使用后端返回的 participants 信息(包含正确的 party_index) - // 这解决了 co_managed_keygen 中 server-party 和 service-party-app 混合的问题 - if (status.participants && status.participants.length > 0) { - const myPartyId = grpcClient?.getPartyId(); - const updatedParticipants: Array<{ partyId: string; partyIndex: number; name: string }> = []; - - for (const p of status.participants) { - // 查找已有的参与者名称 - const existing = activeKeygenSession.participants.find(ep => ep.partyId === p.party_id); - updatedParticipants.push({ - partyId: p.party_id, - partyIndex: p.party_index, - name: existing?.name || (p.party_id === myPartyId ? '我' : `参与方 ${p.party_index + 1}`), - }); - } - - // 更新参与者列表 - activeKeygenSession.participants = updatedParticipants; - - // 更新自己的 partyIndex - const myInfo = updatedParticipants.find(p => p.partyId === myPartyId); - if (myInfo) { - activeKeygenSession.partyIndex = myInfo.partyIndex; - } - - debugLog.info('main', `Updated participants from server: ${JSON.stringify(updatedParticipants.map(p => ({ - partyId: p.partyId.substring(0, 8) + '...', - partyIndex: p.partyIndex, - })))}`); + try { + const status = await accountClient?.getSessionStatus(sessionId); + if (!status) { + debugLog.warn('main', 'Failed to get session status, will retry'); + await new Promise(resolve => setTimeout(resolve, pollIntervalMs)); + continue; } - const selectedParties = activeKeygenSession.participants.map(p => p.partyId); + const expectedN = status.total_parties || activeKeygenSession.threshold.n; + const currentParticipants = status.participants?.length || 0; - await handleSessionStart({ - eventType: 'session_started', - sessionId: sessionId, - thresholdN: activeKeygenSession.threshold.n, - thresholdT: activeKeygenSession.threshold.t, - selectedParties: selectedParties, - }); - } else { - debugLog.debug('main', `Session not ready yet, status: ${status.status}`); + debugLog.debug('main', `Session ${sessionId} status: ${status.status}, participants: ${currentParticipants}/${expectedN}`); + + // 检查是否满足启动条件 + const hasAllParticipants = currentParticipants >= expectedN; + const statusReady = status.status === 'in_progress' || + status.status === 'all_joined' || + status.status === 'waiting_for_keygen'; + + if (hasAllParticipants && statusReady) { + debugLog.info('main', `All ${expectedN} participants joined (status: ${status.status}), triggering keygen...`); + + // 使用后端返回的 participants 信息(包含正确的 party_index) + if (status.participants && status.participants.length > 0) { + const myPartyId = grpcClient?.getPartyId(); + const updatedParticipants: Array<{ partyId: string; partyIndex: number; name: string }> = []; + + for (const p of status.participants) { + const existing = activeKeygenSession.participants.find(ep => ep.partyId === p.party_id); + updatedParticipants.push({ + partyId: p.party_id, + partyIndex: p.party_index, + name: existing?.name || (p.party_id === myPartyId ? '我' : `参与方 ${p.party_index + 1}`), + }); + } + + activeKeygenSession.participants = updatedParticipants; + + const myInfo = updatedParticipants.find(p => p.partyId === myPartyId); + if (myInfo) { + activeKeygenSession.partyIndex = myInfo.partyIndex; + } + + debugLog.info('main', `Updated participants from server: ${JSON.stringify(updatedParticipants.map(p => ({ + partyId: p.partyId.substring(0, 8) + '...', + partyIndex: p.partyIndex, + })))}`); + } + + const selectedParties = activeKeygenSession.participants.map(p => p.partyId); + + await handleSessionStart({ + eventType: 'session_started', + sessionId: sessionId, + thresholdN: activeKeygenSession.threshold.n, + thresholdT: activeKeygenSession.threshold.t, + selectedParties: selectedParties, + }); + + return; // 成功触发,退出循环 + } + + // 等待下次轮询 + await new Promise(resolve => setTimeout(resolve, pollIntervalMs)); + + } catch (error) { + debugLog.error('main', `Failed to check session status: ${error}, will retry`); + await new Promise(resolve => setTimeout(resolve, pollIntervalMs)); } - } catch (error) { - debugLog.error('main', `Failed to check session status: ${error}`); } + + // 超时 + debugLog.error('main', `Timeout waiting for all participants to join session ${sessionId}`); } // 创建主窗口