fix(service-party-app): checkAndTriggerKeygen 改为轮询等待

问题:
- 原来 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 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-29 10:44:29 -08:00
parent dfead071ab
commit bd6537a2cb
1 changed files with 83 additions and 64 deletions

View File

@ -123,7 +123,8 @@ function cleanExpiredEventCache() {
}
}
// 检查并触发 keygen100% 可靠方案:不依赖事件,直接检查状态)
// 检查并触发 keygen100% 可靠方案:轮询等待所有参与者加入)
// 与 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}`);
}
// 创建主窗口