Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,8 @@ class PaykitPaymentRequestRepo @Inject constructor(
completedRefreshGeneration = -1L
RefreshCheckpoint(actionVersion, proofVersion)
} ?: return@refresh
val priority = if (forceFresh) Priority.Ordered else Priority.Background
val snapshot = runSuspendCatching {
val priority = if (forceFresh) Priority.Ordered else Priority.Background
fetchRequestSnapshot(
expectedIdentity,
mode,
Expand All @@ -605,7 +605,12 @@ class PaykitPaymentRequestRepo @Inject constructor(
runSuspendCatching {
val records = snapshot.getOrThrow()
if (checkpoint != RefreshCheckpoint(actionVersion, proofVersion)) {
synchronizeLocked(generation, expectedIdentity, PaykitPaymentRequestRefreshMode.STORED)
synchronizeLocked(
generation,
expectedIdentity,
PaykitPaymentRequestRefreshMode.STORED,
priority,
)
} else {
applyRequestSnapshotLocked(records, generation, expectedIdentity, proofVersion)
}
Expand Down Expand Up @@ -668,6 +673,7 @@ class PaykitPaymentRequestRepo @Inject constructor(
}
}
}.onFailure {
if (it == PaykitPaymentRequestError.OperationInProgress) return@onFailure
Logger.warn("Failed to refresh Paykit payment request recipients", it, context = TAG)
val savedKeys = savedPublicKeys.mapNotNull(PubkyPublicKeyFormat::normalized).toSet()
operationMutex.withLock {
Expand Down Expand Up @@ -1221,9 +1227,10 @@ class PaykitPaymentRequestRepo @Inject constructor(
generation: Long,
expectedIdentity: String?,
mode: PaykitPaymentRequestRefreshMode = PaykitPaymentRequestRefreshMode.FULL,
priority: Priority = Priority.Ordered,
) {
val proofVersion = paymentProofVersion()
val snapshot = fetchRequestSnapshot(expectedIdentity, mode)
val snapshot = fetchRequestSnapshot(expectedIdentity, mode, priority)
applyRequestSnapshotLocked(snapshot, generation, expectedIdentity, proofVersion)
}

Expand Down
76 changes: 58 additions & 18 deletions app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,17 @@ class PrivatePaykitRepo @Inject constructor(

private suspend fun contactPreparationPriority(priority: Priority): Priority {
awaitContactPreparationActive(priority)
val retry = currentCoroutineContext()[PrivateMessageDrainRetry] ?: return priority
return if (retry.interactiveUntil?.let { it > clock.now() } == true) {
Priority.Interactive
} else {
Priority.Background
val retry = currentCoroutineContext()[PrivateMessageDrainRetry]
val admittedPriority = when (retry) {
null -> priority
else -> if (retry.interactiveUntil?.let { it > clock.now() } == true) {
Priority.Interactive
} else {
Priority.Background
}
}
paykitSdkService.checkRefreshAdmission(admittedPriority)
return admittedPriority
}

private fun scheduleContactPreparation(
Expand All @@ -322,8 +327,9 @@ class PrivatePaykitRepo @Inject constructor(
activePreparationKeys = keys
val generation = preparationGeneration
runSuspendCatching {
paykitSdkService.checkRefreshAdmission(Priority.Background)
if (isContactSharingCleanupPending()) return@runSuspendCatching
if (canPublishPrivateEndpoints()) {
if (canPublishPrivateEndpoints(priority = Priority.Background)) {
addressReservationRepo.reconcileReservedIndexesWithLdk().getOrThrow()
if (generation != preparationGeneration) return@runSuspendCatching
publishLocalEndpoints(
Expand All @@ -340,7 +346,15 @@ class PrivatePaykitRepo @Inject constructor(
)
}
}.onFailure {
Logger.warn("Failed to prepare private Paykit contacts", it, context = TAG)
if (it == PaykitPaymentRequestError.OperationInProgress) {
if (generation == preparationGeneration && !isDeletingProfile) {
pendingPreparationKeys.addAll(keys.intersect(knownSavedContactKeys))
activePreparationKeys = emptySet()
delay(privateMessageDrainRetryDelays.first())
}
} else {
Logger.warn("Failed to prepare private Paykit contacts", it, context = TAG)
}
}
activePreparationKeys = emptySet()
}
Expand Down Expand Up @@ -1013,13 +1027,14 @@ class PrivatePaykitRepo @Inject constructor(
)

runSuspendCatching {
awaitContactPreparationActive(priority)
val publicationPriority = contactPreparationPriority(priority)
publicationMutex.withLock {
val status = paykitSdkService.identityStatus()
paykitSdkService.checkRefreshAdmission(publicationPriority)
Comment thread
ben-kaufman marked this conversation as resolved.
val status = paykitSdkService.identityStatus(publicationPriority)
if (!isCurrentPublication(generation, identity, requireImmediatePublication, status)) {
return@withLock
}
if (!canPublishPrivateEndpoints(status)) {
if (!canPublishPrivateEndpoints(status, publicationPriority)) {
if (requireImmediatePublication) throw PrivatePaykitError.PrivateUnavailable
return@withLock
}
Expand All @@ -1042,6 +1057,7 @@ class PrivatePaykitRepo @Inject constructor(
val report = paykitSdkService.syncPrivatePaymentListsWithReservations(
updates = publication.updates,
clearUnlistedLinkedPeers = false,
deferDuringPayment = publicationPriority == Priority.Background,
)
val deliveryError = applyPrivatePaymentListDeliveryReport(report, reason, publicationWindow)
val firstError = publication.firstError ?: deliveryError
Expand All @@ -1052,7 +1068,9 @@ class PrivatePaykitRepo @Inject constructor(
handlePrivatePublicationError(firstError, reason, requireImmediatePublication)
}
}.onFailure {
schedulePreparedLinkRetries(links.linkRetryKeys, reason, generation, identity)
if (it != PaykitPaymentRequestError.OperationInProgress) {
schedulePreparedLinkRetries(links.linkRetryKeys, reason, generation, identity)
}
}.getOrThrow()
}
}
Expand Down Expand Up @@ -1169,6 +1187,7 @@ class PrivatePaykitRepo @Inject constructor(
if (it != LinkedPeerState.LINKED) linkRetryKeys += publicKey
preparedKeys += publicKey
}.onFailure {
if (it == PaykitPaymentRequestError.OperationInProgress) throw it
Logger.warn("Failed to prepare private Paykit link during '$reason'", it, context = TAG)
awaitContactPreparationActive(priority)
if (generation != preparationGeneration) return@onFailure
Expand Down Expand Up @@ -1197,7 +1216,10 @@ class PrivatePaykitRepo @Inject constructor(
val state = paykitSdkService.linkedPeers(contactPreparationPriority(priority))
.firstOrNull { it.counterparty == publicKey }?.state
state == null || state == LinkedPeerState.NOT_LINKED
}.getOrDefault(false)
}.getOrElse {
if (it == PaykitPaymentRequestError.OperationInProgress) throw it
false
}
else -> false
}

Expand Down Expand Up @@ -1242,7 +1264,7 @@ class PrivatePaykitRepo @Inject constructor(
priority: Priority = Priority.Ordered,
) {
if (isDeletingProfile || generation != preparationGeneration) return
if (isContactSharingCleanupPending() || !hasPrivatePaymentAccessForCurrentProfile()) return
if (isContactSharingCleanupPending() || !hasPrivatePaymentAccessForCurrentProfile(priority)) return
if (isDeletingProfile || generation != preparationGeneration) return
drainAndSchedulePrivateLinkRetries(reason, publicKeys.distinct(), generation, priority)
}
Expand Down Expand Up @@ -1381,6 +1403,7 @@ class PrivatePaykitRepo @Inject constructor(
runSuspendCatching {
paykitSdkService.processOutboundPrivateMessages(publicKey, contactPreparationPriority(priority))
}.onFailure {
if (it == PaykitPaymentRequestError.OperationInProgress) throw it
Logger.warn("Failed to send private Paykit messages during '$reason'", it, context = TAG)
}
}
Expand All @@ -1400,11 +1423,13 @@ class PrivatePaykitRepo @Inject constructor(
}.onSuccess {
receivedKeys += publicKey
}.onFailure {
if (it == PaykitPaymentRequestError.OperationInProgress) throw it
Logger.warn("Failed to receive private Paykit messages during '$reason'", it, context = TAG)
}
}
receivedKeys
}.onFailure {
if (it == PaykitPaymentRequestError.OperationInProgress) throw it
Logger.warn("Failed to process pending private Paykit messages during '$reason'", it, context = TAG)
}.getOrDefault(emptySet())
}
Expand All @@ -1430,6 +1455,7 @@ class PrivatePaykitRepo @Inject constructor(
runSuspendCatching {
advanceLinkIfIdle(retryKey, priority)
}.onFailure {
if (it == PaykitPaymentRequestError.OperationInProgress) throw it
currentCoroutineContext()[PrivateMessageDrainRetry]?.stopIfUnavailable(retryKey, it)
Logger.warn(
"Failed to advance private Paykit link for '${redacted(retryKey)}' during '$reason'",
Expand Down Expand Up @@ -1499,7 +1525,15 @@ class PrivatePaykitRepo @Inject constructor(
retry.refreshReadiness = false
Priority.Background
}
val keepRetrying = runSuspendCatching { drainPrivateMessageRetry(retry, reason, priority) }
val result = runSuspendCatching {
paykitSdkService.checkRefreshAdmission(priority)
drainPrivateMessageRetry(retry, reason, priority)
}
if (result.exceptionOrNull() == PaykitPaymentRequestError.OperationInProgress) {
retryIndex = 0
continue
}
val keepRetrying = result
.onFailure { Logger.warn("Failed to retry private Paykit messages", it, context = TAG) }
.getOrDefault(true)
if (!keepRetrying && !retry.prepareEndpoints) return
Expand Down Expand Up @@ -1647,6 +1681,7 @@ class PrivatePaykitRepo @Inject constructor(
val generation = preparationGeneration
val snapshot = runSuspendCatching { paykitSdkService.privateMessageSchedulingSnapshot(priority()) }
.getOrElse {
if (it == PaykitPaymentRequestError.OperationInProgress) throw it
Logger.warn("Failed to inspect private Paykit scheduling state", it, context = TAG)
return null
}
Expand Down Expand Up @@ -2128,7 +2163,10 @@ class PrivatePaykitRepo @Inject constructor(
return paymentHash in paymentHashes
}

private suspend fun canPublishPrivateEndpoints(status: IdentityStatus? = null): Boolean {
private suspend fun canPublishPrivateEndpoints(
status: IdentityStatus? = null,
priority: Priority = Priority.Ordered,
): Boolean {
if (isDeletingProfile) return false
val settings = settingsStore.data.first()
val locallyEligible = settings.sharesPrivatePaykitEndpoints &&
Expand All @@ -2140,13 +2178,15 @@ class PrivatePaykitRepo @Inject constructor(
val hasAccess = if (status?.publicKey != null) {
status.capability == PubkyIdentityCapability.PRIVATE_LINK_CAPABLE
} else {
hasPrivatePaymentAccessForCurrentProfile()
hasPrivatePaymentAccessForCurrentProfile(priority)
}
return hasAccess && !isDeletingProfile
}

private suspend fun hasPrivatePaymentAccessForCurrentProfile(): Boolean = runSuspendCatching {
paykitSdkService.hasPrivatePaymentAccess()
private suspend fun hasPrivatePaymentAccessForCurrentProfile(priority: Priority): Boolean = runSuspendCatching {
paykitSdkService.hasPrivatePaymentAccess(priority)
}.onFailure {
if (it == PaykitPaymentRequestError.OperationInProgress) throw it
}.getOrDefault(false)

private suspend fun isContactSharingCleanupPending(): Boolean =
Expand Down
51 changes: 44 additions & 7 deletions app/src/main/java/to/bitkit/services/PaykitSdkService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -442,10 +442,14 @@ class PaykitSdkService @Inject constructor(
}
}

suspend fun hasPrivatePaymentAccess(): Boolean {
suspend fun hasPrivatePaymentAccess(): Boolean = hasPrivatePaymentAccess(Priority.Ordered)

internal suspend fun hasPrivatePaymentAccess(priority: Priority): Boolean {
isSetup.await()
return operationLock.withLock {
return operationLock.withLock(priority) {
checkRefreshAdmission(priority)
withPaykitKey {
checkRefreshAdmission(priority)
completeSdkCall { it.identityStatus() }?.capability == PubkyIdentityCapability.PRIVATE_LINK_CAPABLE
}
}
Expand Down Expand Up @@ -857,6 +861,16 @@ class PaykitSdkService @Inject constructor(
suspend fun syncPrivatePaymentListsWithReservations(
updates: List<PrivatePaymentListReservationUpdateInput>,
clearUnlistedLinkedPeers: Boolean,
): PrivatePaymentListDeliveryReport = syncPrivatePaymentListsWithReservations(
updates,
clearUnlistedLinkedPeers,
deferDuringPayment = false,
)

internal suspend fun syncPrivatePaymentListsWithReservations(
updates: List<PrivatePaymentListReservationUpdateInput>,
clearUnlistedLinkedPeers: Boolean,
deferDuringPayment: Boolean,
): PrivatePaymentListDeliveryReport {
isSetup.await()
val priority = if (clearUnlistedLinkedPeers || updates.any { it.reservations.isEmpty() }) {
Expand All @@ -865,7 +879,9 @@ class PaykitSdkService @Inject constructor(
PaykitSdkOperationLock.Priority.Background
}
return operationLock.withLock(priority) {
withStateRevisionTracking { handle ->
val admissionPriority = if (deferDuringPayment) priority else Priority.Ordered
checkRefreshAdmission(admissionPriority)
withStateRevisionTracking(beforeOperation = { checkRefreshAdmission(admissionPriority) }) { handle ->
completeSdkCall {
handle.syncPrivatePaymentListsWithReservationsAndProcessOutbound(
updates = updates,
Expand Down Expand Up @@ -1062,13 +1078,16 @@ class PaykitSdkService @Inject constructor(
): List<PaymentRequestRecord> {
isSetup.await()
return measuredOperation("allPaymentRequests", priority) {
checkRefreshAdmission(priority)
withPaykitKey { handle ->
checkRefreshAdmission(priority)
if (expectedIdentity != null) {
val identity = completeSdkCall { handle.identityStatus() }?.publicKey
check(PubkyPublicKeyFormat.matches(identity, expectedIdentity)) {
"Payment Request identity changed"
}
}
checkRefreshAdmission(priority)
completeSdkCall {
handle.listPaymentRequests(
PaymentRequestFilter(
Expand All @@ -1089,7 +1108,13 @@ class PaykitSdkService @Inject constructor(
internal suspend fun identityStatus(priority: Priority): IdentityStatus? {
isSetup.await()
return measuredOperation("identityStatus", priority) {
withPaykitKey { handle -> retryContendedRead { handle.identityStatus() } }
checkRefreshAdmission(priority)
withPaykitKey { handle ->
retryContendedRead {
checkRefreshAdmission(priority)
handle.identityStatus()
}
}
}
}

Expand Down Expand Up @@ -1230,7 +1255,13 @@ class PaykitSdkService @Inject constructor(
internal suspend fun linkedPeers(priority: Priority): List<LinkedPeerRecord> {
isSetup.await()
return operationLock.withLock(priority) {
withPaykitKey { handle -> retryContendedRead { handle.linkedPeers() } }
checkRefreshAdmission(priority)
withPaykitKey { handle ->
retryContendedRead {
checkRefreshAdmission(priority)
handle.linkedPeers()
}
}
}
}

Expand All @@ -1239,7 +1270,13 @@ class PaykitSdkService @Inject constructor(
): PrivateMessageSchedulingSnapshot {
isSetup.await()
return operationLock.withLock(priority) {
withPaykitKey { handle -> retryContendedRead { handle.privateMessageSchedulingSnapshot() } }
checkRefreshAdmission(priority)
withPaykitKey { handle ->
retryContendedRead {
checkRefreshAdmission(priority)
handle.privateMessageSchedulingSnapshot()
}
}
}
}

Expand Down Expand Up @@ -1553,7 +1590,7 @@ class PaykitSdkService @Inject constructor(
cachedBackupState = null
}

private fun checkRefreshAdmission(priority: Priority) {
internal fun checkRefreshAdmission(priority: Priority) {
val reviewing = synchronized(paymentReviews) { paymentReviews.isNotEmpty() }
if (priority == Priority.Background && (isPaymentSubmissionActive.value || reviewing)) {
throw PaykitPaymentRequestError.OperationInProgress
Expand Down
Loading
Loading