From 079bb08067fbc70008a182a64b41e56c7558d65a Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Oct 2021 16:53:42 -0300 Subject: [PATCH] Move MobileCoin SDK to SignalUI. --- Signal.xcodeproj/project.pbxproj | 8 +- .../InternalSettingsViewController.swift | 2 - .../MediaGallery/MediaGallery.swift | 4 - .../Payments/PaymentsHelperImpl.swift | 368 ++++++++++++++++++ SignalMessaging/environment/AppSetup.m | 7 +- .../src/Devices/OWSRecordTranscriptJob.m | 24 +- .../src/Messages/OWSMessageManager.m | 26 +- .../{Payments.swift => PaymentsHelper.swift} | 79 ++++ .../src/Payments/TSPaymentModel.m | 14 - .../src/Payments/TSPaymentModels.swift | 4 +- .../Storage/Database/SDSDatabaseStorage.swift | 1 - .../src/Storage/StorageCoordinator.h | 4 - .../src/Storage/StorageCoordinator.m | 25 -- .../src/TestUtils/MockSSKEnvironment.m | 2 +- SignalUI/Environment/SUIEnvironment.swift | 2 +- ...Payments+SignalUI.swift => Payments.swift} | 100 +---- SignalUI/Payments/PaymentsImpl.swift | 358 ++--------------- 17 files changed, 504 insertions(+), 524 deletions(-) rename SignalServiceKit/src/Payments/{Payments.swift => PaymentsHelper.swift} (76%) rename SignalUI/Payments/{Payments+SignalUI.swift => Payments.swift} (62%) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 86b0a56cff..fbf7de4e05 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -331,7 +331,7 @@ 3465F4D827287677001663AF /* PaymentsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3465F4D327287675001663AF /* PaymentsImpl.swift */; }; 3465F4D927287677001663AF /* MobileCoinAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3465F4D427287675001663AF /* MobileCoinAPI.swift */; }; 3465F4DB27287859001663AF /* PaymentsHelperImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3465F4DA27287858001663AF /* PaymentsHelperImpl.swift */; }; - 3465F4DD2728812B001663AF /* Payments+SignalUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3465F4DC2728812A001663AF /* Payments+SignalUI.swift */; }; + 3465F4DD2728812B001663AF /* Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3465F4DC2728812A001663AF /* Payments.swift */; }; 34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; }; 34684DAF25C2228D00C5A93C /* determinate_spinner_white.json in Resources */ = {isa = PBXBuildFile; fileRef = 34684DAB25C2228C00C5A93C /* determinate_spinner_white.json */; }; 34684DB025C2228D00C5A93C /* determinate_spinner_blue.json in Resources */ = {isa = PBXBuildFile; fileRef = 34684DAC25C2228D00C5A93C /* determinate_spinner_blue.json */; }; @@ -1325,7 +1325,7 @@ 3465F4D327287675001663AF /* PaymentsImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentsImpl.swift; sourceTree = ""; }; 3465F4D427287675001663AF /* MobileCoinAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MobileCoinAPI.swift; sourceTree = ""; }; 3465F4DA27287858001663AF /* PaymentsHelperImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentsHelperImpl.swift; sourceTree = ""; }; - 3465F4DC2728812A001663AF /* Payments+SignalUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Payments+SignalUI.swift"; sourceTree = ""; }; + 3465F4DC2728812A001663AF /* Payments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Payments.swift; sourceTree = ""; }; 34661FB720C1C0D60056EDD6 /* message_sent.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = message_sent.aiff; path = Signal/AudioFiles/message_sent.aiff; sourceTree = SOURCE_ROOT; }; 34684DAB25C2228C00C5A93C /* determinate_spinner_white.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = determinate_spinner_white.json; sourceTree = ""; }; 34684DAC25C2228D00C5A93C /* determinate_spinner_blue.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = determinate_spinner_blue.json; sourceTree = ""; }; @@ -3297,7 +3297,7 @@ children = ( 3465F4D427287675001663AF /* MobileCoinAPI.swift */, 3465F4D227287675001663AF /* MobileCoinAPI+Configuration.swift */, - 3465F4DC2728812A001663AF /* Payments+SignalUI.swift */, + 3465F4DC2728812A001663AF /* Payments.swift */, 34A955B8271B553D00B05242 /* PaymentsFormat.swift */, 3465F4D327287675001663AF /* PaymentsImpl.swift */, 3465F4D127287674001663AF /* PaymentsProcessor.swift */, @@ -5587,7 +5587,7 @@ 3402AA4B271D9DCD0084CBAE /* AttachmentApprovalToolbar.swift in Sources */, 3402AA9A271D9E180084CBAE /* ImageEditorBrushViewController.swift in Sources */, 34A954CB271B4BE600B05242 /* ShareViewDelegate.swift in Sources */, - 3465F4DD2728812B001663AF /* Payments+SignalUI.swift in Sources */, + 3465F4DD2728812B001663AF /* Payments.swift in Sources */, 3402AA7A271D9E180084CBAE /* CVUtils.swift in Sources */, 3465F4D527287677001663AF /* PaymentsReconciliation.swift in Sources */, 3402AAAB271D9E180084CBAE /* OWSActionSheets.swift in Sources */, diff --git a/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift index b03e58495b..65726c2685 100644 --- a/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift @@ -112,8 +112,6 @@ class InternalSettingsViewController: OWSTableViewController2 { infoSection.add(.label(withText: "Database WAL size: \(byteCountFormatter.string(for: databaseStorage.databaseWALFileSize) ?? "Unknown")")) infoSection.add(.label(withText: "Database SHM size: \(byteCountFormatter.string(for: databaseStorage.databaseSHMFileSize) ?? "Unknown")")) - infoSection.add(.label(withText: "dataStoreForUI: \(NSStringForDataStore(StorageCoordinator.dataStoreForUI))")) - infoSection.add(.label(withText: "hasGrdbFile: \(StorageCoordinator.hasGrdbFile)")) infoSection.add(.label(withText: "didEverUseYdb: \(SSKPreferences.didEverUseYdb())")) infoSection.add(.label(withText: "Core count: \(LocalDevice.allCoreCount) (active: \(LocalDevice.activeCoreCount))")) diff --git a/Signal/src/ViewControllers/MediaGallery/MediaGallery.swift b/Signal/src/ViewControllers/MediaGallery/MediaGallery.swift index 300b306d8a..d2655d3927 100644 --- a/Signal/src/ViewControllers/MediaGallery/MediaGallery.swift +++ b/Signal/src/ViewControllers/MediaGallery/MediaGallery.swift @@ -222,10 +222,6 @@ class MediaGallery: Dependencies { } func setupDatabaseObservation() { - guard StorageCoordinator.dataStoreForUI == .grdb else { - owsFailDebug("Invalid data store.") - return - } databaseStorage.appendDatabaseChangeDelegate(self) } diff --git a/SignalMessaging/Payments/PaymentsHelperImpl.swift b/SignalMessaging/Payments/PaymentsHelperImpl.swift index 2e78fea38e..f0d6d1e723 100644 --- a/SignalMessaging/Payments/PaymentsHelperImpl.swift +++ b/SignalMessaging/Payments/PaymentsHelperImpl.swift @@ -7,7 +7,375 @@ import MobileCoin @objc public class PaymentsHelperImpl: NSObject, PaymentsHelperSwift { + + // MARK: - PaymentsState + + private static let arePaymentsEnabledKey = "isPaymentEnabled" + private static let paymentsEntropyKey = "paymentsEntropy" + + private let paymentStateCache = AtomicOptional(nil) + + @objc + public static let arePaymentsEnabledDidChange = Notification.Name("arePaymentsEnabledDidChange") + + public func warmCaches() { + owsAssertDebug(GRDBSchemaMigrator.areMigrationsComplete) + + Self.databaseStorage.read { transaction in + self.paymentStateCache.set(Self.loadPaymentsState(transaction: transaction)) + } + } + + public var paymentsState: PaymentsState { + paymentStateCache.get() ?? .disabled + } + + public var arePaymentsEnabled: Bool { + paymentsState.isEnabled + } + + public var paymentsEntropy: Data? { + paymentsState.paymentsEntropy + } + + // MARK: - Incoming Messages + + func processIncomingPaymentRequest(thread: TSThread, + paymentRequest: TSPaymentRequest, + transaction: SDSAnyWriteTransaction) { + // TODO: Handle requests. + owsFailDebug("Not yet implemented.") + } + + func processIncomingPaymentNotification(thread: TSThread, + paymentNotification: TSPaymentNotification, + senderAddress: SignalServiceAddress, + transaction: SDSAnyWriteTransaction) { + guard paymentNotification.isValid else { + owsFailDebug("Invalid paymentNotification.") + return + } + guard senderAddress.isValid else { + owsFailDebug("Invalid senderAddress.") + return + } + upsertPaymentModelForIncomingPaymentNotification(paymentNotification, + thread: thread, + senderAddress: senderAddress, + transaction: transaction) + } + + func processIncomingPaymentCancellation(thread: TSThread, + paymentCancellation: TSPaymentCancellation, + transaction: SDSAnyWriteTransaction) { + guard paymentCancellation.isValid else { + owsFailDebug("Invalid paymentNotification.") + return + } + let requestUuidString = paymentCancellation.requestUuidString + guard let paymentRequestModel = Self.findPaymentRequestModel(forRequestUuidString: requestUuidString, + expectedIsIncomingRequest: nil, + transaction: transaction) else { + // This isn't necessarily an error; we might receive multiple + // cancellation messages for a given request. + owsFailDebug("Missing paymentRequestModel.") + return + } + paymentRequestModel.anyRemove(transaction: transaction) + } + + func processReceivedTranscriptPaymentRequest(thread: TSThread, + paymentRequest: TSPaymentRequest, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) { + do { + guard let contactThread = thread as? TSContactThread else { + throw OWSAssertionError("Invalid thread.") + } + guard let contactUuid = contactThread.contactAddress.uuid else { + throw OWSAssertionError("Missing contactUuid.") + } + let paymentRequestModel = TSPaymentRequestModel(requestUuidString: paymentRequest.requestUuidString, + addressUuidString: contactUuid.uuidString, + isIncomingRequest: false, + paymentAmount: paymentRequest.paymentAmount, + memoMessage: paymentRequest.memoMessage, + createdDate: NSDate.ows_date(withMillisecondsSince1970: messageTimestamp)) + guard paymentRequestModel.isValid else { + throw OWSAssertionError("Invalid paymentRequestModel.") + } + paymentRequestModel.anyInsert(transaction: transaction) + } catch { + owsFailDebug("Error: \(error)") + return + } + } + + func processReceivedTranscriptPaymentNotification(thread: TSThread, + paymentNotification: TSPaymentNotification, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) { + Logger.info("Ignoring payment notification from sync transcript.") + } + + func processReceivedTranscriptPaymentCancellation(thread: TSThread, + paymentCancellation: TSPaymentCancellation, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) { + let requestUuidString = paymentCancellation.requestUuidString + if let paymentRequestModel = Self.findPaymentRequestModel(forRequestUuidString: requestUuidString, + expectedIsIncomingRequest: nil, + transaction: transaction) { + paymentRequestModel.anyRemove(transaction: transaction) + } + } + + func processIncomingPaymentSyncMessage(_ paymentProto: SSKProtoSyncMessageOutgoingPayment, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) { + Logger.verbose("") + do { + guard let mobileCoinProto = paymentProto.mobileCoin else { + throw OWSAssertionError("Invalid payment sync message: Missing mobileCoinProto.") + } + var recipientUuid: UUID? + if let recipientUuidString = paymentProto.recipientUuid { + guard let uuid = UUID(uuidString: recipientUuidString) else { + throw OWSAssertionError("Invalid payment sync message: Missing recipientUuid.") + } + recipientUuid = uuid + } + let paymentAmount = TSPaymentAmount(currency: .mobileCoin, picoMob: mobileCoinProto.amountPicoMob) + guard paymentAmount.isValidAmount(canBeEmpty: true) else { + throw OWSAssertionError("Invalid payment sync message: invalid paymentAmount.") + } + let feeAmount = TSPaymentAmount(currency: .mobileCoin, picoMob: mobileCoinProto.feePicoMob) + guard feeAmount.isValidAmount(canBeEmpty: false) else { + throw OWSAssertionError("Invalid payment sync message: invalid feeAmount.") + } + let recipientPublicAddressData = mobileCoinProto.recipientAddress + let memoMessage = paymentProto.note?.nilIfEmpty + let spentKeyImages = Array(Set(mobileCoinProto.spentKeyImages)) + owsAssertDebug(spentKeyImages.count == mobileCoinProto.spentKeyImages.count) + guard !spentKeyImages.isEmpty else { + throw OWSAssertionError("Invalid payment sync message: Missing spentKeyImages.") + } + let outputPublicKeys = Array(Set(mobileCoinProto.outputPublicKeys)) + owsAssertDebug(outputPublicKeys.count == mobileCoinProto.outputPublicKeys.count) + guard !outputPublicKeys.isEmpty else { + throw OWSAssertionError("Invalid payment sync message: Missing outputPublicKeys.") + } + guard let mcReceiptData = mobileCoinProto.receipt, + !mcReceiptData.isEmpty, + nil != MobileCoin.Receipt(serializedData: mcReceiptData) else { + throw OWSAssertionError("Invalid payment sync message: Missing or invalid receipt.") + } + let ledgerBlockIndex = mobileCoinProto.ledgerBlockIndex + guard ledgerBlockIndex > 0 else { + throw OWSAssertionError("Invalid payment sync message: Invalid ledgerBlockIndex.") + } + let ledgerBlockTimestamp = mobileCoinProto.ledgerBlockTimestamp + // TODO: Support requests. + let requestUuidString: String? = nil + // We use .outgoingComplete. We can safely assume that the device which + // sent the payment has verified and notified. + let paymentState: TSPaymentState = .outgoingComplete + + let paymentType: TSPaymentType + if recipientPublicAddressData == nil { + // Possible defragmentation. + guard recipientUuid == nil else { + throw OWSAssertionError("Invalid payment sync message: unexpected recipientUuid.") + } + guard recipientPublicAddressData == nil else { + throw OWSAssertionError("Invalid payment sync message: unexpected recipientPublicAddressData.") + } + guard paymentAmount.isValidAmount(canBeEmpty: true), + paymentAmount.picoMob == 0 else { + throw OWSAssertionError("Invalid payment sync message: invalid paymentAmount.") + } + guard memoMessage == nil else { + throw OWSAssertionError("Invalid payment sync message: unexpected memoMessage.") + } + paymentType = .outgoingDefragmentationFromLinkedDevice + } else { + // Possible outgoing payment. + guard recipientUuid != nil else { + throw OWSAssertionError("Invalid payment sync message: missing recipientUuid.") + } + guard paymentAmount.isValidAmount(canBeEmpty: false) else { + throw OWSAssertionError("Invalid payment sync message: invalid paymentAmount.") + } + paymentType = .outgoingPaymentFromLinkedDevice + } + + let mobileCoin = MobileCoinPayment(recipientPublicAddressData: recipientPublicAddressData, + transactionData: nil, + receiptData: mcReceiptData, + incomingTransactionPublicKeys: nil, + spentKeyImages: spentKeyImages, + outputPublicKeys: outputPublicKeys, + ledgerBlockTimestamp: ledgerBlockTimestamp, + ledgerBlockIndex: ledgerBlockIndex, + feeAmount: feeAmount) + let paymentModel = TSPaymentModel(paymentType: paymentType, + paymentState: paymentState, + paymentAmount: paymentAmount, + createdDate: NSDate.ows_date(withMillisecondsSince1970: messageTimestamp), + addressUuidString: recipientUuid?.uuidString, + memoMessage: memoMessage, + requestUuidString: requestUuidString, + isUnread: false, + mobileCoin: mobileCoin) + + try tryToInsertPaymentModel(paymentModel, transaction: transaction) + } catch { + owsFailDebug("Error: \(error)") + return + } + } + + // This method enforces invariants around TSPaymentModel. + func tryToInsertPaymentModel(_ paymentModel: TSPaymentModel, + transaction: SDSAnyWriteTransaction) throws { + + Logger.info("Trying to insert: \(paymentModel.descriptionForLogs)") + + guard paymentModel.isValid else { + throw OWSAssertionError("Invalid paymentModel.") + } + + let isRedundant = try isProposedPaymentModelRedundant(paymentModel, + transaction: transaction) + guard !isRedundant else { + throw OWSAssertionError("Duplicate paymentModel.") + } + + paymentModel.anyInsert(transaction: transaction) + + if paymentModel.isOutgoing, + paymentModel.isIdentifiedPayment, + let requestUuidString = paymentModel.requestUuidString, + let paymentRequestModel = Self.findPaymentRequestModel(forRequestUuidString: requestUuidString, + expectedIsIncomingRequest: true, + transaction: transaction) { + paymentRequestModel.anyRemove(transaction: transaction) + } + } + + // This method enforces invariants around TSPaymentModel. + private func isProposedPaymentModelRedundant(_ paymentModel: TSPaymentModel, + transaction: SDSAnyWriteTransaction) throws -> Bool { + guard paymentModel.isValid else { + throw OWSAssertionError("Invalid paymentModel.") + } + + // Only one model in the database should have a given transaction. + if paymentModel.canHaveMCTransaction { + if let transactionData = paymentModel.mobileCoin?.transactionData { + let existingPaymentModels = PaymentFinder.paymentModels(forMcTransactionData: transactionData, + transaction: transaction) + if existingPaymentModels.count > 1 { + owsFailDebug("More than one conflict.") + } + if !existingPaymentModels.isEmpty { + owsFailDebug("Transaction conflict.") + return true + } + } else if paymentModel.shouldHaveMCTransaction { + throw OWSAssertionError("Missing transactionData.") + } + } + + // Only one model in the database should have a given receipt. + if paymentModel.shouldHaveMCReceipt { + if let receiptData = paymentModel.mobileCoin?.receiptData { + let existingPaymentModels = PaymentFinder.paymentModels(forMcReceiptData: receiptData, + transaction: transaction) + if existingPaymentModels.count > 1 { + owsFailDebug("More than one conflict.") + } + if !existingPaymentModels.isEmpty { + owsFailDebug("Receipt conflict.") + return true + } + } else { + throw OWSAssertionError("Missing receiptData.") + } + } + + // Only one _identified_ payment model in the database should correspond to any given + // spentKeyImage or outputPublicKey. + // + // We don't need to worry about conflicts with unidentified payment models; + // PaymentsReconciliation will avoid / clean those up. + let mcLedgerBlockIndex = paymentModel.mobileCoin?.ledgerBlockIndex ?? 0 + let spentKeyImages = Set(paymentModel.mobileCoin?.spentKeyImages ?? []) + let outputPublicKeys = Set(paymentModel.mobileCoin?.outputPublicKeys ?? []) + if !paymentModel.isUnidentified, + mcLedgerBlockIndex > 0 { + + let otherPaymentModels = PaymentFinder.paymentModels(forMcLedgerBlockIndex: mcLedgerBlockIndex, + transaction: transaction) + for otherPaymentModel in otherPaymentModels { + guard !otherPaymentModel.isUnidentified else { + continue + } + guard paymentModel.uniqueId != otherPaymentModel.uniqueId else { + owsFailDebug("Duplicate paymentModel.") + return true + } + let otherSpentKeyImages = Set(otherPaymentModel.mobileCoin?.spentKeyImages ?? []) + let otherOutputPublicKeys = Set(otherPaymentModel.mobileCoin?.outputPublicKeys ?? []) + if !spentKeyImages.intersection(otherSpentKeyImages).isEmpty { + for value in spentKeyImages { + Logger.verbose("spentKeyImage: \(value.hexadecimalString)") + } + for value in otherSpentKeyImages { + Logger.verbose("otherSpentKeyImage: \(value.hexadecimalString)") + } + owsFailDebug("spentKeyImage conflict.") + return true + } + if !outputPublicKeys.intersection(otherOutputPublicKeys).isEmpty { + for value in outputPublicKeys { + Logger.verbose("outputPublicKey: \(value.hexadecimalString)") + } + for value in otherOutputPublicKeys { + Logger.verbose("otherOutputPublicKey: \(value.hexadecimalString)") + } + owsFailDebug("outputPublicKey conflict.") + return true + } + } + } + + return false + } + // var arePaymentsEnabled: Bool { get } // func setArePaymentsEnabled(for address: SignalServiceAddress, hasPaymentsEnabled: Bool, transaction: SDSAnyWriteTransaction) // func arePaymentsEnabled(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool } + +// MARK: - + +public struct PaymentsPassphrase: Equatable, Dependencies { + + public let words: [String] + + public init(words: [String]) throws { + guard words.count == PaymentsConstants.passphraseWordCount else { + owsFailDebug("words.count \(words.count) != \(PaymentsConstants.passphraseWordCount)") + throw PaymentsError.invalidPassphrase + } + + self.words = words + } + + public var wordCount: Int { words.count } + + public var asPassphrase: String { words.joined(separator: " ") } + + public var debugDescription: String { asPassphrase } +} diff --git a/SignalMessaging/environment/AppSetup.m b/SignalMessaging/environment/AppSetup.m index ab15b9f9b4..43f69acd66 100644 --- a/SignalMessaging/environment/AppSetup.m +++ b/SignalMessaging/environment/AppSetup.m @@ -219,9 +219,7 @@ NS_ASSUME_NONNULL_BEGIN [DeviceSleepManager.shared removeBlockWithBlockObject:sleepBlockObject]; - if (StorageCoordinator.dataStoreForUI == DataStoreGrdb) { - [SSKEnvironment.shared warmCaches]; - } + [SSKEnvironment.shared warmCaches]; migrationCompletion(nil); OWSAssertDebug(backgroundTask); @@ -243,9 +241,6 @@ NS_ASSUME_NONNULL_BEGIN + (BOOL)shouldTruncateGrdbWal { - if (StorageCoordinator.dataStoreForUI != DataStoreGrdb) { - return NO; - } if (!CurrentAppContext().isMainApp) { return NO; } diff --git a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m index 942091160e..b8b97eaa15 100644 --- a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m +++ b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m @@ -85,24 +85,24 @@ NS_ASSUME_NONNULL_BEGIN if (transcript.paymentRequest != nil) { OWSLogInfo(@"Processing payment request from sync transcript."); - [self.payments processReceivedTranscriptPaymentRequestWithThread:transcript.thread - paymentRequest:transcript.paymentRequest - messageTimestamp:messageTimestamp - transaction:transaction]; + [self.paymentsHelper processReceivedTranscriptPaymentRequestWithThread:transcript.thread + paymentRequest:transcript.paymentRequest + messageTimestamp:messageTimestamp + transaction:transaction]; return; } else if (transcript.paymentNotification != nil) { OWSLogInfo(@"Processing payment notification from sync transcript."); - [self.payments processReceivedTranscriptPaymentNotificationWithThread:transcript.thread - paymentNotification:transcript.paymentNotification - messageTimestamp:messageTimestamp - transaction:transaction]; + [self.paymentsHelper processReceivedTranscriptPaymentNotificationWithThread:transcript.thread + paymentNotification:transcript.paymentNotification + messageTimestamp:messageTimestamp + transaction:transaction]; return; } else if (transcript.paymentCancellation != nil) { OWSLogInfo(@"Processing payment cancellation from sync transcript."); - [self.payments processReceivedTranscriptPaymentCancellationWithThread:transcript.thread - paymentCancellation:transcript.paymentCancellation - messageTimestamp:messageTimestamp - transaction:transaction]; + [self.paymentsHelper processReceivedTranscriptPaymentCancellationWithThread:transcript.thread + paymentCancellation:transcript.paymentCancellation + messageTimestamp:messageTimestamp + transaction:transaction]; return; } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index b1350e11ef..a0f4aa69ac 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1756,9 +1756,9 @@ NS_ASSUME_NONNULL_BEGIN transaction:transaction]; } else if (syncMessage.outgoingPayment) { // An "incoming" sync message notifies us of an "outgoing" payment. - [self.payments processIncomingPaymentSyncMessage:syncMessage.outgoingPayment - messageTimestamp:serverDeliveryTimestamp - transaction:transaction]; + [self.paymentsHelper processIncomingPaymentSyncMessage:syncMessage.outgoingPayment + messageTimestamp:serverDeliveryTimestamp + transaction:transaction]; } else { OWSLogWarn(@"Ignoring unsupported sync message."); } @@ -2025,22 +2025,22 @@ NS_ASSUME_NONNULL_BEGIN thread:thread]; if (paymentModels.request != nil) { OWSLogInfo(@"Processing payment request."); - [self.payments processIncomingPaymentRequestWithThread:thread - paymentRequest:paymentModels.request - transaction:transaction]; + [self.paymentsHelper processIncomingPaymentRequestWithThread:thread + paymentRequest:paymentModels.request + transaction:transaction]; return nil; } else if (paymentModels.notification != nil) { OWSLogInfo(@"Processing payment notification."); - [self.payments processIncomingPaymentNotificationWithThread:thread - paymentNotification:paymentModels.notification - senderAddress:envelope.sourceAddress - transaction:transaction]; + [self.paymentsHelper processIncomingPaymentNotificationWithThread:thread + paymentNotification:paymentModels.notification + senderAddress:envelope.sourceAddress + transaction:transaction]; return nil; } else if (paymentModels.cancellation != nil) { OWSLogInfo(@"Processing payment cancellation."); - [self.payments processIncomingPaymentCancellationWithThread:thread - paymentCancellation:paymentModels.cancellation - transaction:transaction]; + [self.paymentsHelper processIncomingPaymentCancellationWithThread:thread + paymentCancellation:paymentModels.cancellation + transaction:transaction]; return nil; } else if (paymentModels != nil) { OWSFailDebug(@"Unexpected payment model."); diff --git a/SignalServiceKit/src/Payments/Payments.swift b/SignalServiceKit/src/Payments/PaymentsHelper.swift similarity index 76% rename from SignalServiceKit/src/Payments/Payments.swift rename to SignalServiceKit/src/Payments/PaymentsHelper.swift index 73beb94c55..1953113803 100644 --- a/SignalServiceKit/src/Payments/Payments.swift +++ b/SignalServiceKit/src/Payments/PaymentsHelper.swift @@ -47,6 +47,39 @@ public protocol PaymentsHelper: AnyObject { var arePaymentsEnabled: Bool { get } func setArePaymentsEnabled(for address: SignalServiceAddress, hasPaymentsEnabled: Bool, transaction: SDSAnyWriteTransaction) func arePaymentsEnabled(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool + + @objc(processIncomingPaymentSyncMessage:messageTimestamp:transaction:) + func processIncomingPaymentSyncMessage(_ paymentProto: SSKProtoSyncMessageOutgoingPayment, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) + + func processIncomingPaymentRequest(thread: TSThread, + paymentRequest: TSPaymentRequest, + transaction: SDSAnyWriteTransaction) + + func processIncomingPaymentNotification(thread: TSThread, + paymentNotification: TSPaymentNotification, + senderAddress: SignalServiceAddress, + transaction: SDSAnyWriteTransaction) + + func processIncomingPaymentCancellation(thread: TSThread, + paymentCancellation: TSPaymentCancellation, + transaction: SDSAnyWriteTransaction) + + func processReceivedTranscriptPaymentRequest(thread: TSThread, + paymentRequest: TSPaymentRequest, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) + + func processReceivedTranscriptPaymentNotification(thread: TSThread, + paymentNotification: TSPaymentNotification, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) + + func processReceivedTranscriptPaymentCancellation(thread: TSThread, + paymentCancellation: TSPaymentCancellation, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) } // MARK: - @@ -156,6 +189,52 @@ extension MockPaymentsHelper: PaymentsHelperSwift { public func arePaymentsEnabled(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool { owsFail("Not implemented.") } + + public func processIncomingPaymentRequest(thread: TSThread, + paymentRequest: TSPaymentRequest, + transaction: SDSAnyWriteTransaction) { + owsFail("Not implemented.") + } + + public func processIncomingPaymentNotification(thread: TSThread, + paymentNotification: TSPaymentNotification, + senderAddress: SignalServiceAddress, + transaction: SDSAnyWriteTransaction) { + owsFail("Not implemented.") + } + + public func processIncomingPaymentCancellation(thread: TSThread, + paymentCancellation: TSPaymentCancellation, + transaction: SDSAnyWriteTransaction) { + owsFail("Not implemented.") + } + + public func processReceivedTranscriptPaymentRequest(thread: TSThread, + paymentRequest: TSPaymentRequest, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) { + owsFail("Not implemented.") + } + + public func processReceivedTranscriptPaymentNotification(thread: TSThread, + paymentNotification: TSPaymentNotification, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) { + owsFail("Not implemented.") + } + + public func processReceivedTranscriptPaymentCancellation(thread: TSThread, + paymentCancellation: TSPaymentCancellation, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) { + owsFail("Not implemented.") + } + + public func processIncomingPaymentSyncMessage(_ paymentProto: SSKProtoSyncMessageOutgoingPayment, + messageTimestamp: UInt64, + transaction: SDSAnyWriteTransaction) { + owsFail("Not implemented.") + } } // MARK: - diff --git a/SignalServiceKit/src/Payments/TSPaymentModel.m b/SignalServiceKit/src/Payments/TSPaymentModel.m index faecfa3889..abd10cce34 100644 --- a/SignalServiceKit/src/Payments/TSPaymentModel.m +++ b/SignalServiceKit/src/Payments/TSPaymentModel.m @@ -52,20 +52,6 @@ NS_ASSUME_NONNULL_BEGIN @implementation TSPaymentModel -#pragma mark - Dependencies - -+ (id)payments -{ - return SSKEnvironment.shared.payments; -} - -- (id)payments -{ - return SSKEnvironment.shared.payments; -} - -#pragma mark - - - (instancetype)initWithPaymentType:(TSPaymentType)paymentType paymentState:(TSPaymentState)paymentState paymentAmount:(nullable TSPaymentAmount *)paymentAmount diff --git a/SignalServiceKit/src/Payments/TSPaymentModels.swift b/SignalServiceKit/src/Payments/TSPaymentModels.swift index fac551bac2..af475ac17d 100644 --- a/SignalServiceKit/src/Payments/TSPaymentModels.swift +++ b/SignalServiceKit/src/Payments/TSPaymentModels.swift @@ -84,7 +84,7 @@ extension TSPaymentAddress: TSPaymentBaseModel { owsFailDebug("Unexpected currency.") return false } - return SSKEnvironment.shared.payments.isValidMobileCoinPublicAddress(mobileCoinPublicAddressData) + return true } public func buildProto() throws -> SSKProtoPaymentAddress { @@ -331,7 +331,7 @@ public class TSPaymentModels: NSObject { guard !CurrentAppContext().isRunningTests else { return nil } - guard payments.arePaymentsEnabled else { + guard paymentsHelper.arePaymentsEnabled else { return nil } guard let paymentProto = dataMessage.payment else { diff --git a/SignalServiceKit/src/Storage/Database/SDSDatabaseStorage.swift b/SignalServiceKit/src/Storage/Database/SDSDatabaseStorage.swift index 86c858a898..d708528c78 100644 --- a/SignalServiceKit/src/Storage/Database/SDSDatabaseStorage.swift +++ b/SignalServiceKit/src/Storage/Database/SDSDatabaseStorage.swift @@ -50,7 +50,6 @@ public class SDSDatabaseStorage: SDSTransactable { guard !CurrentAppContext().isRunningTests else { return } - owsAssertDebug(StorageCoordinator.dataStoreForUI == .grdb) // Cross process writes crossProcess.callback = { [weak self] in DispatchQueue.main.async { diff --git a/SignalServiceKit/src/Storage/StorageCoordinator.h b/SignalServiceKit/src/Storage/StorageCoordinator.h index 0d16d2e22f..9e78dc746f 100644 --- a/SignalServiceKit/src/Storage/StorageCoordinator.h +++ b/SignalServiceKit/src/Storage/StorageCoordinator.h @@ -33,10 +33,6 @@ NSString *NSStringFromStorageCoordinatorState(StorageCoordinatorState value); @property (class, nonatomic, readonly) BOOL hasInvalidDatabaseVersion; -// The data store that will be used once the app is ready. -// This data store may not be available before then. -@property (class, nonatomic, readonly) DataStore dataStoreForUI; - - (BOOL)isDatabasePasswordAccessible; - (void)markStorageSetupAsComplete; diff --git a/SignalServiceKit/src/Storage/StorageCoordinator.m b/SignalServiceKit/src/Storage/StorageCoordinator.m index c2a51a27cf..4cac17f9f0 100644 --- a/SignalServiceKit/src/Storage/StorageCoordinator.m +++ b/SignalServiceKit/src/Storage/StorageCoordinator.m @@ -37,8 +37,6 @@ NSString *NSStringForDataStore(DataStore value) @property (atomic) BOOL isStorageSetupComplete; -@property (nonatomic, readonly) DataStore dataStoreForUI; - @end #pragma mark - @@ -54,10 +52,6 @@ NSString *NSStringForDataStore(DataStore value) OWSSingletonAssert(); - _dataStoreForUI = [StorageCoordinator computeDataStoreForUI]; - - OWSLogInfo(@"dataStoreForUI: %@", NSStringForDataStore(self.dataStoreForUI)); - _databaseStorage = [[SDSDatabaseStorage alloc] initWithDelegate:self]; [self configure]; @@ -97,25 +91,6 @@ NSString *NSStringForDataStore(DataStore value) return self.state; } -+ (DataStore)dataStoreForUI -{ - // Computing dataStoreForUI is slightly expensive. - // Once SSKEnvironment is configured, we use the cached value - // that hangs on StorageCoordinator. Until then, we compute every - // time. - if (!SSKEnvironment.hasShared) { - return self.computeDataStoreForUI; - } - - return SSKEnvironment.shared.storageCoordinator.dataStoreForUI; -} - -+ (DataStore)computeDataStoreForUI -{ - // TODO: - return DataStoreGrdb; -} - - (void)configure { OWSLogInfo(@"storageMode: %@", SSKFeatureFlags.storageModeDescription); diff --git a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m index 51e99ac558..2a5f6257e2 100644 --- a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m +++ b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m @@ -85,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN OWSMessagePipelineSupervisor *messagePipelineSupervisor = [OWSMessagePipelineSupervisor createStandardSupervisor]; AppExpiry *appExpiry = [AppExpiry new]; MessageProcessor *messageProcessor = [MessageProcessor new]; - id payments = [MockPaymentsHelper new]; + id paymentsHelper = [MockPaymentsHelper new]; id paymentsCurrencies = [MockPaymentsCurrencies new]; SpamChallengeResolver *spamChallengeResolver = [SpamChallengeResolver new]; SenderKeyStore *senderKeyStore = [SenderKeyStore new]; diff --git a/SignalUI/Environment/SUIEnvironment.swift b/SignalUI/Environment/SUIEnvironment.swift index 2ad98f621f..be6b47b8d3 100644 --- a/SignalUI/Environment/SUIEnvironment.swift +++ b/SignalUI/Environment/SUIEnvironment.swift @@ -37,7 +37,7 @@ public class SUIEnvironment: NSObject { @objc public var paymentsRef: Payments = PaymentsImpl() - paymentsSwift + private override init() { super.init() diff --git a/SignalUI/Payments/Payments+SignalUI.swift b/SignalUI/Payments/Payments.swift similarity index 62% rename from SignalUI/Payments/Payments+SignalUI.swift rename to SignalUI/Payments/Payments.swift index f00aeae100..5dba0ac5eb 100644 --- a/SignalUI/Payments/Payments+SignalUI.swift +++ b/SignalUI/Payments/Payments.swift @@ -4,8 +4,6 @@ import Foundation -// TODO: Rename this source file. - @objc public protocol Payments: AnyObject { @@ -21,42 +19,9 @@ public protocol Payments: AnyObject { func isValidMobileCoinPublicAddress(_ publicAddressData: Data) -> Bool - func processIncomingPaymentRequest(thread: TSThread, - paymentRequest: TSPaymentRequest, - transaction: SDSAnyWriteTransaction) - - func processIncomingPaymentNotification(thread: TSThread, - paymentNotification: TSPaymentNotification, - senderAddress: SignalServiceAddress, - transaction: SDSAnyWriteTransaction) - - func processIncomingPaymentCancellation(thread: TSThread, - paymentCancellation: TSPaymentCancellation, - transaction: SDSAnyWriteTransaction) - - func processReceivedTranscriptPaymentRequest(thread: TSThread, - paymentRequest: TSPaymentRequest, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) - - func processReceivedTranscriptPaymentNotification(thread: TSThread, - paymentNotification: TSPaymentNotification, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) - - func processReceivedTranscriptPaymentCancellation(thread: TSThread, - paymentCancellation: TSPaymentCancellation, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) - func willInsertPayment(_ paymentModel: TSPaymentModel, transaction: SDSAnyWriteTransaction) func willUpdatePayment(_ paymentModel: TSPaymentModel, transaction: SDSAnyWriteTransaction) - @objc(processIncomingPaymentSyncMessage:messageTimestamp:transaction:) - func processIncomingPaymentSyncMessage(_ paymentProto: SSKProtoSyncMessageOutgoingPayment, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) - func scheduleReconciliationNow(transaction: SDSAnyWriteTransaction) func replaceAsUnidentified(paymentModel oldPaymentModel: TSPaymentModel, @@ -120,24 +85,7 @@ public protocol PaymentsSwift: Payments { // MARK: - -public struct PaymentsPassphrase: Equatable, Dependencies { - - public let words: [String] - - public init(words: [String]) throws { - guard words.count == PaymentsConstants.passphraseWordCount else { - owsFailDebug("words.count \(words.count) != \(PaymentsConstants.passphraseWordCount)") - throw PaymentsError.invalidPassphrase - } - - self.words = words - } - - public var wordCount: Int { words.count } - - public var asPassphrase: String { words.joined(separator: " ") } - - public var debugDescription: String { asPassphrase } +extension PaymentsPassphrase { public static func parse(passphrase: String, validateWords: Bool) throws -> PaymentsPassphrase { @@ -249,52 +197,6 @@ extension MockPayments: PaymentsSwift { owsFail("Not implemented.") } - public func processIncomingPaymentRequest(thread: TSThread, - paymentRequest: TSPaymentRequest, - transaction: SDSAnyWriteTransaction) { - owsFail("Not implemented.") - } - - public func processIncomingPaymentNotification(thread: TSThread, - paymentNotification: TSPaymentNotification, - senderAddress: SignalServiceAddress, - transaction: SDSAnyWriteTransaction) { - owsFail("Not implemented.") - } - - public func processIncomingPaymentCancellation(thread: TSThread, - paymentCancellation: TSPaymentCancellation, - transaction: SDSAnyWriteTransaction) { - owsFail("Not implemented.") - } - - public func processReceivedTranscriptPaymentRequest(thread: TSThread, - paymentRequest: TSPaymentRequest, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) { - owsFail("Not implemented.") - } - - public func processReceivedTranscriptPaymentNotification(thread: TSThread, - paymentNotification: TSPaymentNotification, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) { - owsFail("Not implemented.") - } - - public func processReceivedTranscriptPaymentCancellation(thread: TSThread, - paymentCancellation: TSPaymentCancellation, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) { - owsFail("Not implemented.") - } - - public func processIncomingPaymentSyncMessage(_ paymentProto: SSKProtoSyncMessageOutgoingPayment, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) { - owsFail("Not implemented.") - } - public func willInsertPayment(_ paymentModel: TSPaymentModel, transaction: SDSAnyWriteTransaction) { owsFail("Not implemented.") } diff --git a/SignalUI/Payments/PaymentsImpl.swift b/SignalUI/Payments/PaymentsImpl.swift index 89d860315a..51652b31d2 100644 --- a/SignalUI/Payments/PaymentsImpl.swift +++ b/SignalUI/Payments/PaymentsImpl.swift @@ -4,6 +4,7 @@ import Foundation import MobileCoin +import SignalServiceKit @objc public class PaymentsImpl: NSObject, PaymentsSwift { @@ -160,32 +161,32 @@ public class PaymentsImpl: NSObject, PaymentsSwift { // MARK: - PaymentsState - private static let arePaymentsEnabledKey = "isPaymentEnabled" - private static let paymentsEntropyKey = "paymentsEntropy" - - private let paymentStateCache = AtomicOptional(nil) - - @objc - public static let arePaymentsEnabledDidChange = Notification.Name("arePaymentsEnabledDidChange") - - public func warmCaches() { - owsAssertDebug(GRDBSchemaMigrator.areMigrationsComplete) - - Self.databaseStorage.read { transaction in - self.paymentStateCache.set(Self.loadPaymentsState(transaction: transaction)) - } - } - - public var paymentsState: PaymentsState { - paymentStateCache.get() ?? .disabled - } +// private static let arePaymentsEnabledKey = "isPaymentEnabled" +// private static let paymentsEntropyKey = "paymentsEntropy" +// +// private let paymentStateCache = AtomicOptional(nil) +// +// @objc +// public static let arePaymentsEnabledDidChange = Notification.Name("arePaymentsEnabledDidChange") +// +// public func warmCaches() { +// owsAssertDebug(GRDBSchemaMigrator.areMigrationsComplete) +// +// Self.databaseStorage.read { transaction in +// self.paymentStateCache.set(Self.loadPaymentsState(transaction: transaction)) +// } +// } +// +// public var paymentsState: PaymentsState { +// paymentStateCache.get() ?? .disabled +// } public var arePaymentsEnabled: Bool { - paymentsState.isEnabled + paymentsHelper.arePaymentsEnabled } public var paymentsEntropy: Data? { - paymentsState.paymentsEntropy + paymentsHelper.paymentsEntropy } public var passphrase: PaymentsPassphrase? { @@ -1329,321 +1330,6 @@ public extension PaymentsImpl { Self.messageSenderJobQueue.add(message: message.asPreparer, transaction: transaction) return message } - - // MARK: - Incoming Messages - - func processIncomingPaymentRequest(thread: TSThread, - paymentRequest: TSPaymentRequest, - transaction: SDSAnyWriteTransaction) { - // TODO: Handle requests. - owsFailDebug("Not yet implemented.") - } - - func processIncomingPaymentNotification(thread: TSThread, - paymentNotification: TSPaymentNotification, - senderAddress: SignalServiceAddress, - transaction: SDSAnyWriteTransaction) { - guard paymentNotification.isValid else { - owsFailDebug("Invalid paymentNotification.") - return - } - guard senderAddress.isValid else { - owsFailDebug("Invalid senderAddress.") - return - } - upsertPaymentModelForIncomingPaymentNotification(paymentNotification, - thread: thread, - senderAddress: senderAddress, - transaction: transaction) - } - - func processIncomingPaymentCancellation(thread: TSThread, - paymentCancellation: TSPaymentCancellation, - transaction: SDSAnyWriteTransaction) { - guard paymentCancellation.isValid else { - owsFailDebug("Invalid paymentNotification.") - return - } - let requestUuidString = paymentCancellation.requestUuidString - guard let paymentRequestModel = Self.findPaymentRequestModel(forRequestUuidString: requestUuidString, - expectedIsIncomingRequest: nil, - transaction: transaction) else { - // This isn't necessarily an error; we might receive multiple - // cancellation messages for a given request. - owsFailDebug("Missing paymentRequestModel.") - return - } - paymentRequestModel.anyRemove(transaction: transaction) - } - - func processReceivedTranscriptPaymentRequest(thread: TSThread, - paymentRequest: TSPaymentRequest, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) { - do { - guard let contactThread = thread as? TSContactThread else { - throw OWSAssertionError("Invalid thread.") - } - guard let contactUuid = contactThread.contactAddress.uuid else { - throw OWSAssertionError("Missing contactUuid.") - } - let paymentRequestModel = TSPaymentRequestModel(requestUuidString: paymentRequest.requestUuidString, - addressUuidString: contactUuid.uuidString, - isIncomingRequest: false, - paymentAmount: paymentRequest.paymentAmount, - memoMessage: paymentRequest.memoMessage, - createdDate: NSDate.ows_date(withMillisecondsSince1970: messageTimestamp)) - guard paymentRequestModel.isValid else { - throw OWSAssertionError("Invalid paymentRequestModel.") - } - paymentRequestModel.anyInsert(transaction: transaction) - } catch { - owsFailDebug("Error: \(error)") - return - } - } - - func processReceivedTranscriptPaymentNotification(thread: TSThread, - paymentNotification: TSPaymentNotification, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) { - Logger.info("Ignoring payment notification from sync transcript.") - } - - func processIncomingPaymentSyncMessage(_ paymentProto: SSKProtoSyncMessageOutgoingPayment, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) { - Logger.verbose("") - do { - guard let mobileCoinProto = paymentProto.mobileCoin else { - throw OWSAssertionError("Invalid payment sync message: Missing mobileCoinProto.") - } - var recipientUuid: UUID? - if let recipientUuidString = paymentProto.recipientUuid { - guard let uuid = UUID(uuidString: recipientUuidString) else { - throw OWSAssertionError("Invalid payment sync message: Missing recipientUuid.") - } - recipientUuid = uuid - } - let paymentAmount = TSPaymentAmount(currency: .mobileCoin, picoMob: mobileCoinProto.amountPicoMob) - guard paymentAmount.isValidAmount(canBeEmpty: true) else { - throw OWSAssertionError("Invalid payment sync message: invalid paymentAmount.") - } - let feeAmount = TSPaymentAmount(currency: .mobileCoin, picoMob: mobileCoinProto.feePicoMob) - guard feeAmount.isValidAmount(canBeEmpty: false) else { - throw OWSAssertionError("Invalid payment sync message: invalid feeAmount.") - } - let recipientPublicAddressData = mobileCoinProto.recipientAddress - let memoMessage = paymentProto.note?.nilIfEmpty - let spentKeyImages = Array(Set(mobileCoinProto.spentKeyImages)) - owsAssertDebug(spentKeyImages.count == mobileCoinProto.spentKeyImages.count) - guard !spentKeyImages.isEmpty else { - throw OWSAssertionError("Invalid payment sync message: Missing spentKeyImages.") - } - let outputPublicKeys = Array(Set(mobileCoinProto.outputPublicKeys)) - owsAssertDebug(outputPublicKeys.count == mobileCoinProto.outputPublicKeys.count) - guard !outputPublicKeys.isEmpty else { - throw OWSAssertionError("Invalid payment sync message: Missing outputPublicKeys.") - } - guard let mcReceiptData = mobileCoinProto.receipt, - !mcReceiptData.isEmpty, - nil != MobileCoin.Receipt(serializedData: mcReceiptData) else { - throw OWSAssertionError("Invalid payment sync message: Missing or invalid receipt.") - } - let ledgerBlockIndex = mobileCoinProto.ledgerBlockIndex - guard ledgerBlockIndex > 0 else { - throw OWSAssertionError("Invalid payment sync message: Invalid ledgerBlockIndex.") - } - let ledgerBlockTimestamp = mobileCoinProto.ledgerBlockTimestamp - // TODO: Support requests. - let requestUuidString: String? = nil - // We use .outgoingComplete. We can safely assume that the device which - // sent the payment has verified and notified. - let paymentState: TSPaymentState = .outgoingComplete - - let paymentType: TSPaymentType - if recipientPublicAddressData == nil { - // Possible defragmentation. - guard recipientUuid == nil else { - throw OWSAssertionError("Invalid payment sync message: unexpected recipientUuid.") - } - guard recipientPublicAddressData == nil else { - throw OWSAssertionError("Invalid payment sync message: unexpected recipientPublicAddressData.") - } - guard paymentAmount.isValidAmount(canBeEmpty: true), - paymentAmount.picoMob == 0 else { - throw OWSAssertionError("Invalid payment sync message: invalid paymentAmount.") - } - guard memoMessage == nil else { - throw OWSAssertionError("Invalid payment sync message: unexpected memoMessage.") - } - paymentType = .outgoingDefragmentationFromLinkedDevice - } else { - // Possible outgoing payment. - guard recipientUuid != nil else { - throw OWSAssertionError("Invalid payment sync message: missing recipientUuid.") - } - guard paymentAmount.isValidAmount(canBeEmpty: false) else { - throw OWSAssertionError("Invalid payment sync message: invalid paymentAmount.") - } - paymentType = .outgoingPaymentFromLinkedDevice - } - - let mobileCoin = MobileCoinPayment(recipientPublicAddressData: recipientPublicAddressData, - transactionData: nil, - receiptData: mcReceiptData, - incomingTransactionPublicKeys: nil, - spentKeyImages: spentKeyImages, - outputPublicKeys: outputPublicKeys, - ledgerBlockTimestamp: ledgerBlockTimestamp, - ledgerBlockIndex: ledgerBlockIndex, - feeAmount: feeAmount) - let paymentModel = TSPaymentModel(paymentType: paymentType, - paymentState: paymentState, - paymentAmount: paymentAmount, - createdDate: NSDate.ows_date(withMillisecondsSince1970: messageTimestamp), - addressUuidString: recipientUuid?.uuidString, - memoMessage: memoMessage, - requestUuidString: requestUuidString, - isUnread: false, - mobileCoin: mobileCoin) - - try tryToInsertPaymentModel(paymentModel, transaction: transaction) - } catch { - owsFailDebug("Error: \(error)") - return - } - } - - // This method enforces invariants around TSPaymentModel. - func tryToInsertPaymentModel(_ paymentModel: TSPaymentModel, - transaction: SDSAnyWriteTransaction) throws { - - Logger.info("Trying to insert: \(paymentModel.descriptionForLogs)") - - guard paymentModel.isValid else { - throw OWSAssertionError("Invalid paymentModel.") - } - - let isRedundant = try isProposedPaymentModelRedundant(paymentModel, - transaction: transaction) - guard !isRedundant else { - throw OWSAssertionError("Duplicate paymentModel.") - } - - paymentModel.anyInsert(transaction: transaction) - - if paymentModel.isOutgoing, - paymentModel.isIdentifiedPayment, - let requestUuidString = paymentModel.requestUuidString, - let paymentRequestModel = Self.findPaymentRequestModel(forRequestUuidString: requestUuidString, - expectedIsIncomingRequest: true, - transaction: transaction) { - paymentRequestModel.anyRemove(transaction: transaction) - } - } - - // This method enforces invariants around TSPaymentModel. - private func isProposedPaymentModelRedundant(_ paymentModel: TSPaymentModel, - transaction: SDSAnyWriteTransaction) throws -> Bool { - guard paymentModel.isValid else { - throw OWSAssertionError("Invalid paymentModel.") - } - - // Only one model in the database should have a given transaction. - if paymentModel.canHaveMCTransaction { - if let transactionData = paymentModel.mobileCoin?.transactionData { - let existingPaymentModels = PaymentFinder.paymentModels(forMcTransactionData: transactionData, - transaction: transaction) - if existingPaymentModels.count > 1 { - owsFailDebug("More than one conflict.") - } - if !existingPaymentModels.isEmpty { - owsFailDebug("Transaction conflict.") - return true - } - } else if paymentModel.shouldHaveMCTransaction { - throw OWSAssertionError("Missing transactionData.") - } - } - - // Only one model in the database should have a given receipt. - if paymentModel.shouldHaveMCReceipt { - if let receiptData = paymentModel.mobileCoin?.receiptData { - let existingPaymentModels = PaymentFinder.paymentModels(forMcReceiptData: receiptData, - transaction: transaction) - if existingPaymentModels.count > 1 { - owsFailDebug("More than one conflict.") - } - if !existingPaymentModels.isEmpty { - owsFailDebug("Receipt conflict.") - return true - } - } else { - throw OWSAssertionError("Missing receiptData.") - } - } - - // Only one _identified_ payment model in the database should correspond to any given - // spentKeyImage or outputPublicKey. - // - // We don't need to worry about conflicts with unidentified payment models; - // PaymentsReconciliation will avoid / clean those up. - let mcLedgerBlockIndex = paymentModel.mobileCoin?.ledgerBlockIndex ?? 0 - let spentKeyImages = Set(paymentModel.mobileCoin?.spentKeyImages ?? []) - let outputPublicKeys = Set(paymentModel.mobileCoin?.outputPublicKeys ?? []) - if !paymentModel.isUnidentified, - mcLedgerBlockIndex > 0 { - - let otherPaymentModels = PaymentFinder.paymentModels(forMcLedgerBlockIndex: mcLedgerBlockIndex, - transaction: transaction) - for otherPaymentModel in otherPaymentModels { - guard !otherPaymentModel.isUnidentified else { - continue - } - guard paymentModel.uniqueId != otherPaymentModel.uniqueId else { - owsFailDebug("Duplicate paymentModel.") - return true - } - let otherSpentKeyImages = Set(otherPaymentModel.mobileCoin?.spentKeyImages ?? []) - let otherOutputPublicKeys = Set(otherPaymentModel.mobileCoin?.outputPublicKeys ?? []) - if !spentKeyImages.intersection(otherSpentKeyImages).isEmpty { - for value in spentKeyImages { - Logger.verbose("spentKeyImage: \(value.hexadecimalString)") - } - for value in otherSpentKeyImages { - Logger.verbose("otherSpentKeyImage: \(value.hexadecimalString)") - } - owsFailDebug("spentKeyImage conflict.") - return true - } - if !outputPublicKeys.intersection(otherOutputPublicKeys).isEmpty { - for value in outputPublicKeys { - Logger.verbose("outputPublicKey: \(value.hexadecimalString)") - } - for value in otherOutputPublicKeys { - Logger.verbose("otherOutputPublicKey: \(value.hexadecimalString)") - } - owsFailDebug("outputPublicKey conflict.") - return true - } - } - } - - return false - } - - func processReceivedTranscriptPaymentCancellation(thread: TSThread, - paymentCancellation: TSPaymentCancellation, - messageTimestamp: UInt64, - transaction: SDSAnyWriteTransaction) { - let requestUuidString = paymentCancellation.requestUuidString - if let paymentRequestModel = Self.findPaymentRequestModel(forRequestUuidString: requestUuidString, - expectedIsIncomingRequest: nil, - transaction: transaction) { - paymentRequestModel.anyRemove(transaction: transaction) - } - } } // MARK: - Formatting