@@ -112,6 +112,16 @@ class PrivatePaykitRepo @Inject constructor(
112112 val firstError : Throwable ? ,
113113 )
114114
115+ private data class NormalizedPublicKeyBatch (
116+ val publicKeys : List <String >,
117+ val hadInvalidKey : Boolean ,
118+ )
119+
120+ private data class LinkedReceiverPathInspection (
121+ val receiverPaths : Set <String >,
122+ val error : Throwable ? ,
123+ )
124+
115125 private data class PrivateMessageDrainRetryKey (
116126 val publicKey : String ,
117127 val receiverPath : String ,
@@ -699,7 +709,7 @@ class PrivatePaykitRepo @Inject constructor(
699709 var firstError: Throwable ? = null
700710 val updates = mutableListOf<PrivatePaymentListReservationUpdateInput >()
701711 val linkRetryKeys = mutableListOf<PrivateMessageDrainRetryKey >()
702- val linkedReceiverPaths = linkedReceiverPathsByPublicKey( )
712+ val linkedReceiverPathsSnapshot = linkedReceiverPathsSnapshotOrNull(reason )
703713
704714 for (publicKey in publicKeys) {
705715 val receiverPaths = runSuspendCatching { receiverPathsForSavedContact(publicKey) }
@@ -718,22 +728,19 @@ class PrivatePaykitRepo @Inject constructor(
718728 firstError = firstError ? : it
719729 logPrivateReceiverPathSelectionFailure(publicKey, reason, it)
720730 }
731+ val linkedReceiverPathInspection = inspectLinkedReceiverPaths(
732+ publicKey,
733+ linkedReceiverPathsSnapshot,
734+ reason,
735+ )
736+ firstError = firstError ? : linkedReceiverPathInspection.error
721737 val cleanupReceiverPaths = receiverPathsForPrivateEndpointCleanup(
722738 publicKey = publicKey,
723739 excludedReceiverPaths = publicationReceiverPaths + receiverPathSelection.cleanupProtectedReceiverPaths,
724- linkedReceiverPaths = linkedReceiverPaths[publicKey].orEmpty() ,
740+ linkedReceiverPaths = linkedReceiverPathInspection.receiverPaths ,
725741 )
726742
727- (linkableReceiverPaths + cleanupReceiverPaths).distinct().forEach { receiverPath ->
728- linkRetryKeys + = PrivateMessageDrainRetryKey (publicKey, receiverPath)
729- runSuspendCatching { paykitSdkService.ensureLinkWithPeer(publicKey, receiverPath) }.onFailure {
730- Logger .warn(
731- " Failed to prepare private Paykit link for '${redacted(publicKey)} ' during '$reason '" ,
732- it,
733- context = TAG ,
734- )
735- }
736- }
743+ linkRetryKeys + = preparePrivateLinks(publicKey, linkableReceiverPaths + cleanupReceiverPaths, reason)
737744
738745 cleanupReceiverPaths.forEach { receiverPath ->
739746 updates + = PrivatePaymentListReservationUpdateInput (
@@ -785,6 +792,21 @@ class PrivatePaykitRepo @Inject constructor(
785792 drainAndSchedulePrivateLinkRetries(reason, retryKeys.distinct())
786793 }
787794
795+ private suspend fun preparePrivateLinks (
796+ publicKey : String ,
797+ receiverPaths : Collection <String >,
798+ reason : String ,
799+ ): List <PrivateMessageDrainRetryKey > = receiverPaths.distinct().map { receiverPath ->
800+ runSuspendCatching { paykitSdkService.ensureLinkWithPeer(publicKey, receiverPath) }.onFailure {
801+ Logger .warn(
802+ " Failed to prepare private Paykit link for '${redacted(publicKey)} ' during '$reason '" ,
803+ it,
804+ context = TAG ,
805+ )
806+ }
807+ PrivateMessageDrainRetryKey (publicKey, receiverPath)
808+ }
809+
788810 private suspend fun drainAndSchedulePrivateLinkRetries (
789811 reason : String ,
790812 retryKeys : Collection <PrivateMessageDrainRetryKey >,
@@ -1207,13 +1229,20 @@ class PrivatePaykitRepo @Inject constructor(
12071229 private suspend fun removePublishedEndpoints (publicKeys : Collection <String >): Result <Unit > =
12081230 withContext(serializedDispatcher) {
12091231 runSuspendCatching {
1210- val publicKeys = publicKeys.mapNotNull(::normalizedPublicKey).distinct()
1211- if (publicKeys.isEmpty()) return @runSuspendCatching Unit
1232+ val normalizedBatch = normalizedPublicKeyBatch(publicKeys)
1233+ val publicKeys = normalizedBatch.publicKeys
1234+ var firstError: Throwable ? = PrivatePaykitError .InvalidPublicKey .takeIf {
1235+ normalizedBatch.hadInvalidKey
1236+ }
1237+ if (publicKeys.isEmpty()) {
1238+ firstError?.let { throw it }
1239+ return @runSuspendCatching Unit
1240+ }
12121241
1213- val linkedReceiverPaths = linkedReceiverPathsByPublicKey( )
1214- val preparation = clearPrivatePaymentLists(publicKeys, linkedReceiverPaths )
1242+ val linkedReceiverPathsSnapshot = linkedReceiverPathsSnapshotOrNull( " private endpoint cleanup " )
1243+ val preparation = clearPrivatePaymentLists(publicKeys, linkedReceiverPathsSnapshot )
12151244 val failedPublicKeys = preparation.failedPublicKeys.toMutableSet()
1216- var firstError = preparation.firstError
1245+ firstError = firstError ? : preparation.firstError
12171246
12181247 if (preparation.clearedRetryKeys.isNotEmpty()) {
12191248 drainPendingPrivateMessages(
@@ -1237,16 +1266,28 @@ class PrivatePaykitRepo @Inject constructor(
12371266
12381267 private suspend fun clearPrivatePaymentLists (
12391268 publicKeys : Collection <String >,
1240- linkedReceiverPaths : Map <String , Set <String >>,
1269+ linkedReceiverPathsSnapshot : Map <String , Set <String >>? ,
12411270 ): PrivateEndpointCleanupPreparation {
12421271 val failedPublicKeys = mutableSetOf<String >()
12431272 val clearedRetryKeys = mutableListOf<PrivateMessageDrainRetryKey >()
12441273 var firstError: Throwable ? = null
12451274
12461275 publicKeys.forEach { publicKey ->
1276+ val contactLinkedReceiverPaths = linkedReceiverPaths(
1277+ publicKey = publicKey,
1278+ snapshot = linkedReceiverPathsSnapshot,
1279+ ).onFailure {
1280+ failedPublicKeys + = publicKey
1281+ firstError = firstError ? : it
1282+ Logger .warn(
1283+ " Failed to inspect private Paykit links for '${redacted(publicKey)} ' during cleanup" ,
1284+ it,
1285+ context = TAG ,
1286+ )
1287+ }.getOrDefault(emptySet())
12471288 receiverPathsForCleanup(
12481289 publicKey = publicKey,
1249- linkedReceiverPaths = linkedReceiverPaths[publicKey].orEmpty() ,
1290+ linkedReceiverPaths = contactLinkedReceiverPaths ,
12501291 ).forEach { receiverPath ->
12511292 runSuspendCatching {
12521293 val report = paykitSdkService.clearPrivatePaymentList(publicKey, receiverPath)
@@ -1325,6 +1366,53 @@ class PrivatePaykitRepo @Inject constructor(
13251366 return linkedPaths
13261367 }
13271368
1369+ private suspend fun linkedReceiverPathsSnapshotOrNull (reason : String ): Map <String , Set <String >>? =
1370+ runSuspendCatching { linkedReceiverPathsByPublicKey() }
1371+ .onFailure {
1372+ Logger .warn(
1373+ " Failed to inspect private Paykit links during '$reason '; retrying per contact" ,
1374+ it,
1375+ context = TAG ,
1376+ )
1377+ }.getOrNull()
1378+
1379+ private suspend fun linkedReceiverPaths (
1380+ publicKey : String ,
1381+ snapshot : Map <String , Set <String >>? ,
1382+ ): Result <Set <String >> = if (snapshot != null ) {
1383+ Result .success(snapshot[publicKey].orEmpty())
1384+ } else {
1385+ runSuspendCatching { linkedReceiverPathsByPublicKey()[publicKey].orEmpty() }
1386+ }
1387+
1388+ private suspend fun inspectLinkedReceiverPaths (
1389+ publicKey : String ,
1390+ snapshot : Map <String , Set <String >>? ,
1391+ reason : String ,
1392+ ): LinkedReceiverPathInspection {
1393+ val result = linkedReceiverPaths(publicKey, snapshot)
1394+ val error = result.exceptionOrNull()
1395+ if (error != null ) {
1396+ Logger .warn(
1397+ " Failed to inspect private Paykit links for '${redacted(publicKey)} ' during '$reason '" ,
1398+ error,
1399+ context = TAG ,
1400+ )
1401+ }
1402+ return LinkedReceiverPathInspection (result.getOrDefault(emptySet()), error)
1403+ }
1404+
1405+ private fun normalizedPublicKeyBatch (publicKeys : Collection <String >): NormalizedPublicKeyBatch {
1406+ var hadInvalidKey = false
1407+ val normalizedKeys = publicKeys.mapNotNull { publicKey ->
1408+ normalizedPublicKey(publicKey) ? : run {
1409+ hadInvalidKey = true
1410+ null
1411+ }
1412+ }.distinct()
1413+ return NormalizedPublicKeyBatch (normalizedKeys, hadInvalidKey)
1414+ }
1415+
13281416 private fun publishedPrivatePaymentReceiverPaths (publicKey : String ): List <String > {
13291417 val contactState = state?.contacts?.get(publicKey) ? : return emptyList()
13301418 return contactState.publishedPrivatePaymentReceiverPaths.toList()
0 commit comments