fix(service-party-app): 修复 gRPC 响应字段名 snake_case 问题

问题:
- proto-loader 使用 keepCase: true,导致 gRPC 响应字段为 snake_case
- TypeScript 接口使用 camelCase,导致字段不匹配
- joinSession 响应的 session_info.threshold_t 和 threshold_n 无法读取
- 导致 activeKeygenSession.threshold 为 {t: 0, n: 0}
- TSS 进程收到错误的 threshold 参数导致 exit code 1

修复:
- grpc-client.ts 接口改为 snake_case 以匹配 proto 定义
- main.ts 更新为使用 snake_case 字段名
- SessionEvent 处理转换为 camelCase 再传递给 handleSessionStart

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-29 12:14:30 -08:00
parent 1b48c05aa7
commit 989364969d
2 changed files with 80 additions and 57 deletions

View File

@ -608,36 +608,44 @@ async function connectAndRegisterToMessageRouter() {
});
// 监听会话事件并处理
// Note: gRPC response uses snake_case field names due to keepCase: true in proto-loader
grpcClient.on('sessionEvent', async (event: {
eventId: string;
eventType: string;
sessionId: string;
thresholdN: number;
thresholdT: number;
selectedParties: string[];
joinTokens: Record<string, string>;
messageHash?: Buffer;
event_id: string;
event_type: string;
session_id: string;
threshold_n: number;
threshold_t: number;
selected_parties: string[];
join_tokens: Record<string, string>;
message_hash?: Buffer;
}) => {
debugLog.info('grpc', `Received session event: ${event.eventType} for session ${event.sessionId}`);
debugLog.info('grpc', `Received session event: ${event.event_type} for session ${event.session_id}`);
const eventData: SessionEventData = {
type: event.eventType,
sessionId: event.sessionId,
thresholdN: event.thresholdN,
thresholdT: event.thresholdT,
selectedParties: event.selectedParties,
type: event.event_type,
sessionId: event.session_id,
thresholdN: event.threshold_n,
thresholdT: event.threshold_t,
selectedParties: event.selected_parties,
};
// 缓存事件(解决前端可能还未订阅的时序问题)
cacheSessionEvent(event.sessionId, eventData);
cacheSessionEvent(event.session_id, eventData);
// 转发事件到前端
mainWindow?.webContents.send(`session:events:${event.sessionId}`, eventData);
mainWindow?.webContents.send(`session:events:${event.session_id}`, eventData);
// 根据事件类型处理
if (event.eventType === 'session_started' || event.eventType === 'all_joined') {
if (event.event_type === 'session_started' || event.event_type === 'all_joined') {
// 当会话开始或所有参与方都加入时,检查是否需要触发 keygen
await handleSessionStart(event);
// Convert snake_case to camelCase for handleSessionStart
await handleSessionStart({
eventType: event.event_type,
sessionId: event.session_id,
thresholdN: event.threshold_n,
thresholdT: event.threshold_t,
selectedParties: event.selected_parties,
});
}
});
} catch (error) {
@ -680,16 +688,17 @@ function setupIpcHandlers() {
const result = await grpcClient?.joinSession(sessionId, partyId, joinToken);
if (result?.success) {
// 设置活跃的 keygen 会话信息
const participants: Array<{ partyId: string; partyIndex: number; name: string }> = result.otherParties?.map((p: { partyId: string; partyIndex: number }, idx: number) => ({
partyId: p.partyId,
partyIndex: p.partyIndex,
// Note: gRPC response uses snake_case field names due to keepCase: true in proto-loader
const participants: Array<{ partyId: string; partyIndex: number; name: string }> = result.other_parties?.map((p: { party_id: string; party_index: number }, idx: number) => ({
partyId: p.party_id,
partyIndex: p.party_index,
name: `参与方 ${idx + 1}`, // 暂时使用默认名称
})) || [];
// 添加自己到参与者列表
participants.push({
partyId: partyId,
partyIndex: result.partyIndex,
partyIndex: result.party_index,
name: '我',
});
@ -698,13 +707,13 @@ function setupIpcHandlers() {
activeKeygenSession = {
sessionId: sessionId,
partyIndex: result.partyIndex,
partyIndex: result.party_index,
participants: participants,
threshold: {
t: result.sessionInfo?.thresholdT || 0,
n: result.sessionInfo?.thresholdN || 0,
t: result.session_info?.threshold_t || 0,
n: result.session_info?.threshold_n || 0,
},
walletName: walletName || result.sessionInfo?.sessionId || 'Shared Wallet',
walletName: walletName || result.session_info?.session_id || 'Shared Wallet',
encryptionPassword: '', // 不使用加密密码
};
@ -768,18 +777,19 @@ function setupIpcHandlers() {
if (joinResult?.success) {
// 设置活跃的 keygen 会话信息
// Note: gRPC response uses snake_case field names due to keepCase: true in proto-loader
const participants: Array<{ partyId: string; partyIndex: number; name: string }> = [];
// 添加发起方(自己)
participants.push({
partyId: partyId,
partyIndex: joinResult.partyIndex,
partyIndex: joinResult.party_index,
name: params.initiatorName || '发起者',
});
activeKeygenSession = {
sessionId: result.session_id,
partyIndex: joinResult.partyIndex,
partyIndex: joinResult.party_index,
participants: participants,
threshold: {
t: params.thresholdT,

View File

@ -44,48 +44,65 @@ function loadProtoDefinition(): protoLoader.PackageDefinition {
return packageDefinition;
}
// Note: field names must match proto definitions with keepCase: true
// Proto uses snake_case: session_id, session_type, threshold_n, threshold_t
interface SessionInfo {
sessionId: string;
sessionType: string;
thresholdN: number;
thresholdT: number;
messageHash?: Buffer;
keygenSessionId?: string;
session_id: string;
session_type: string;
threshold_n: number;
threshold_t: number;
message_hash?: Buffer;
keygen_session_id?: string;
status?: string;
}
interface PartyInfo {
partyId: string;
partyIndex: number;
party_id: string;
party_index: number;
}
interface JoinSessionResponse {
success: boolean;
sessionInfo?: SessionInfo;
partyIndex: number;
otherParties: PartyInfo[];
session_info?: SessionInfo;
party_index: number;
other_parties: PartyInfo[];
}
interface MPCMessage {
messageId: string;
sessionId: string;
fromParty: string;
isBroadcast: boolean;
roundNumber: number;
message_id: string;
session_id: string;
from_party: string;
is_broadcast: boolean;
round_number: number;
payload: Buffer;
createdAt: string;
created_at: string;
}
interface SessionEvent {
eventId: string;
eventType: string;
sessionId: string;
thresholdN: number;
thresholdT: number;
selectedParties: string[];
joinTokens: Record<string, string>;
messageHash?: Buffer;
event_id: string;
event_type: string;
session_id: string;
threshold_n: number;
threshold_t: number;
selected_parties: string[];
join_tokens: Record<string, string>;
message_hash?: Buffer;
}
// Raw proto response (snake_case)
interface RegisteredPartyProto {
party_id: string;
role: string;
online: boolean;
registered_at: string;
last_seen_at: string;
}
interface GetRegisteredPartiesResponse {
parties: RegisteredPartyProto[];
}
// Converted response (camelCase) - used by callers
interface RegisteredParty {
partyId: string;
role: string;
@ -94,10 +111,6 @@ interface RegisteredParty {
lastSeenAt: string;
}
interface GetRegisteredPartiesResponse {
parties: RegisteredParty[];
}
// 重连配置
interface ReconnectConfig {
maxRetries: number;