fix(service-party-android): 修复导入钱包签名时 'party not registered' 错误

导入的钱包份额携带原始 keygen partyId,与设备自身 partyId 不同。
签名时用原始 partyId 订阅 session events,但该 ID 未在 message-router
注册,导致服务端返回 FAILED_PRECONDITION。

修复:签名前将导入份额的 partyId 也注册到 message-router(带 3 次重试),
注册失败则中断签名流程并提示用户;断线重连时自动恢复双 partyId 注册;
签名结束后清理额外注册。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-02 03:24:14 -08:00
parent fe6c1b3fce
commit 1f434f32fb
2 changed files with 68 additions and 1 deletions

View File

@ -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<Boolean> = 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
*/

View File

@ -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)
}
}