From 1f434f32fb7b6d70894f88651980ab17ee490740 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 2 Feb 2026 03:24:14 -0800 Subject: [PATCH] =?UTF-8?q?fix(service-party-android):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=AF=BC=E5=85=A5=E9=92=B1=E5=8C=85=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E6=97=B6=20'party=20not=20registered'=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 导入的钱包份额携带原始 keygen partyId,与设备自身 partyId 不同。 签名时用原始 partyId 订阅 session events,但该 ID 未在 message-router 注册,导致服务端返回 FAILED_PRECONDITION。 修复:签名前将导入份额的 partyId 也注册到 message-router(带 3 次重试), 注册失败则中断签名流程并提示用户;断线重连时自动恢复双 partyId 注册; 签名结束后清理额外注册。 Co-Authored-By: Claude Opus 4.5 --- .../durian/tssparty/data/remote/GrpcClient.kt | 39 +++++++++++++++++++ .../tssparty/data/repository/TssRepository.kt | 30 +++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/remote/GrpcClient.kt b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/remote/GrpcClient.kt index 52b374ba..7286252d 100644 --- a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/remote/GrpcClient.kt +++ b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/remote/GrpcClient.kt @@ -97,6 +97,11 @@ class GrpcClient @Inject constructor() { private var registeredPartyId: String? = null private var registeredPartyRole: String? = null + // Additional signing party registration (for imported/restored shares) + // When signing with a restored wallet, the signing partyId differs from the device partyId + // and must also be registered with the message-router + private var registeredSigningPartyId: String? = null + // Heartbeat state private var heartbeatJob: Job? = null private val heartbeatFailCount = AtomicInteger(0) @@ -460,6 +465,17 @@ class GrpcClient @Inject constructor() { Log.e(TAG, "Re-registration failed: ${e.message}") } } + + // Also re-register the signing partyId if active (for imported/restored shares) + val signingId = registeredSigningPartyId + if (signingId != null && signingId != partyId) { + Log.d(TAG, "Re-registering signing party: $signingId") + try { + registerPartyInternal(signingId, "temporary", "1.0.0") + } catch (e: Exception) { + Log.e(TAG, "Re-registration of signing party failed: ${e.message}") + } + } } /** @@ -651,6 +667,29 @@ class GrpcClient @Inject constructor() { } } + /** + * Register an additional signing partyId with the message-router. + * Used when signing with imported/restored shares where the signing partyId + * differs from the device's own partyId. Does not overwrite the device registration. + */ + suspend fun registerSigningParty(signingPartyId: String): Result = withContext(Dispatchers.IO) { + if (signingPartyId == registeredPartyId) { + // Same as device partyId, already registered + return@withContext Result.success(true) + } + Log.d(TAG, "Registering signing partyId: $signingPartyId (device partyId: $registeredPartyId)") + registeredSigningPartyId = signingPartyId + registerPartyInternal(signingPartyId, "temporary", "1.0.0") + } + + /** + * Clear the additional signing party registration. + * Called when signing completes or fails. + */ + fun clearSigningPartyRegistration() { + registeredSigningPartyId = null + } + /** * Join a session */ diff --git a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt index 062880d7..6535194e 100644 --- a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt +++ b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt @@ -388,12 +388,34 @@ class TssRepository @Inject constructor( * CRITICAL: For signing with restored wallets, this should be the original * partyId from keygen (shareEntity.partyId). */ - private fun ensureSessionEventSubscriptionActive(signingPartyId: String? = null) { + private suspend fun ensureSessionEventSubscriptionActive(signingPartyId: String? = null) { // Check if the session event job is still active val isActive = sessionEventJob?.isActive == true val effectivePartyId = signingPartyId ?: currentSessionEventPartyId ?: partyId android.util.Log.d("TssRepository", "Checking session event subscription: isActive=$isActive, effectivePartyId=$effectivePartyId") + // If subscribing with a different partyId (imported/restored share), + // register it with the message-router first to avoid "party not registered" error. + // Retry up to 3 times to handle transient network issues. + if (effectivePartyId != partyId) { + var registered = false + for (attempt in 1..3) { + android.util.Log.d("TssRepository", "Registering signing partyId before subscribing: $effectivePartyId (attempt $attempt/3)") + val regResult = grpcClient.registerSigningParty(effectivePartyId) + if (regResult.isSuccess) { + registered = true + break + } + android.util.Log.e("TssRepository", "Failed to register signing partyId (attempt $attempt/3): ${regResult.exceptionOrNull()?.message}") + if (attempt < 3) { + kotlinx.coroutines.delay(1000L * attempt) + } + } + if (!registered) { + throw Exception("无法注册签名方 $effectivePartyId,请检查网络连接后重试") + } + } + if (!isActive) { android.util.Log.w("TssRepository", "Session event subscription is not active, restarting...") startSessionEventSubscription(signingPartyId) @@ -1427,6 +1449,7 @@ class TssRepository @Inject constructor( pendingSessionId = null // Clear pending session ID on completion messageCollectionJob?.cancel() currentSigningPartyId = null // Clear after signing completes + grpcClient.clearSigningPartyRegistration() android.util.Log.d("TssRepository", "Sign as joiner completed: signature=${result.signature.take(20)}...") @@ -1438,6 +1461,7 @@ class TssRepository @Inject constructor( _sessionStatus.value = SessionStatus.FAILED pendingSessionId = null // Clear pending session ID on failure currentSigningPartyId = null // Clear on failure too + grpcClient.clearSigningPartyRegistration() Result.failure(e) } } @@ -1734,6 +1758,7 @@ class TssRepository @Inject constructor( _sessionStatus.value = SessionStatus.COMPLETED messageCollectionJob?.cancel() currentSigningPartyId = null // Clear after signing completes + grpcClient.clearSigningPartyRegistration() Result.success(result) @@ -1741,6 +1766,7 @@ class TssRepository @Inject constructor( android.util.Log.e("TssRepository", "Join sign session failed", e) _sessionStatus.value = SessionStatus.FAILED currentSigningPartyId = null // Clear on failure too + grpcClient.clearSigningPartyRegistration() Result.failure(e) } } @@ -2764,10 +2790,12 @@ class TssRepository @Inject constructor( _sessionStatus.value = SessionStatus.COMPLETED messageCollectionJob?.cancel() currentSigningPartyId = null // Clear after signing completes + grpcClient.clearSigningPartyRegistration() Result.success(result) } catch (e: Exception) { _sessionStatus.value = SessionStatus.FAILED + grpcClient.clearSigningPartyRegistration() Result.failure(e) } }