Defragmentation.

This commit is contained in:
Matthew Chen 2021-03-12 14:30:22 -03:00
parent bac06335d4
commit d1f6b56658
7 changed files with 70 additions and 137 deletions

View File

@ -312,6 +312,8 @@ class DebugUIPayments: DebugUIPage {
memoMessage: "Tiny: \(count)",
paymentRequestModel: nil,
isOutgoingTransfer: false)
}.then(on: .global()) { (paymentModel: TSPaymentModel) in
Self.payments.blockOnOutgoingVerification(paymentModel: paymentModel)
}.done(on: .global()) { _ in
if count > 1 {
Self.sendTinyPayments(contactThread: contactThread, count: count - 1)

View File

@ -509,7 +509,7 @@ public class SendPaymentCompletionActionSheet: ActionSheetController {
// TODO: Finalize this timeout duration with design.
let blockInterval: TimeInterval = kSecondInterval * 30
return firstly(on: .global()) { () -> Promise<Void> in
Self.paymentsSwift.blockOnOutgoingVerification(paymentModel: paymentModel)
Self.paymentsSwift.blockOnOutgoingVerification(paymentModel: paymentModel).asVoid()
}.timeout(seconds: blockInterval, description: "Payments Verify Submission") {
PaymentsError.timeout
}.recover(on: .global()) { (error: Error) -> Guarantee<()> in

View File

@ -596,78 +596,6 @@ class MobileCoinAPI {
}
}
// private func defragmentIfNecessary(forPaymentAmount paymentAmount: TSPaymentAmount) -> Promise<Void> {
// Logger.verbose("")
//
// let client = self.client
//
// return firstly(on: .global()) { () throws -> Promise<Void> in
// let result = client.requiresDefragmentation(toSendAmount: paymentAmount.picoMob, feeLevel: Self.feeLevel)
// switch result {
// case .success(let shouldDefragment):
// guard shouldDefragment else {
// return Promise.value(())
// }
// return self.defragment(forPaymentAmount: paymentAmount)
// case .failure(let error):
// let error = Self.convertMCError(error: error)
// throw error
// }
// }
// }
//
// private func defragment(forPaymentAmount paymentAmount: TSPaymentAmount) -> Promise<Void> {
// Logger.verbose("")
//
// let client = self.client
//
// return firstly(on: .global()) { () throws -> Promise<[MobileCoin.Transaction]> in
// let (promise, resolver) = Promise<[MobileCoin.Transaction]>.pending()
// if DebugFlags.paymentsNoRequestsComplete.get() {
// // Never resolve.
// return promise
// }
// client.prepareDefragmentationStepTransactions(toSendAmount: paymentAmount.picoMob,
// feeLevel: Self.feeLevel) { (result: Swift.Result<[MobileCoin.Transaction],
// MobileCoin.TransactionPreparationError>) in
// switch result {
// case .success(let transactions):
// resolver.fulfill(transactions)
// break
// case .failure(let error):
// let error = Self.convertMCError(error: error)
// resolver.reject(error)
// break
// }
// }
// return promise
// }.then(on: .global()) { (transactions: [MobileCoin.Transaction]) -> Promise<Void> in
// // 1. Prepare defragmentation transactions.
// // 2. Record defragmentation transactions in database.
// // 3. Notify linked devices of possible defragmentation transactions.
// // 4. Submit defragmentation transactions.
// // 5. Verify defragmentation transactions.
// return firstly(on: .global()) { () throws -> Promise<Void> in
// let submissionPromises = transactions.map { transaction in
// self.submitTransaction(transaction: transaction)
// }
// return when(fulfilled: submissionPromises)
// }.then(on: .global()) { () -> Promise<Void> in
// let submissionPromises = transactions.map { transaction in
// self.getOutgoingTransactionStatus(transaction: transaction)
// }
// return when(fulfilled: submissionPromises)
// }.then(on: .global()) { (transactionStatuses: [MCOutgoingTransactionStatus]) -> Promise<Void> in
// }
//// func getOutgoingTransactionStatus(transaction: MobileCoin.Transaction) -> Promise<MCOutgoingTransactionStatus> {
// }
// }
// public func prepareDefragmentationStepTransactions(
// toSendAmount amount: UInt64,
// feeLevel: FeeLevel = .minimum,
// completion: @escaping (Result<[Transaction], TransactionPreparationError>) -> Void
// ) {
func submitTransaction(transaction: MobileCoin.Transaction) -> Promise<Void> {
Logger.verbose("")
@ -982,7 +910,8 @@ public extension PaymentsError {
.missingModel,
.defragmentationRequired,
.invalidTransaction,
.inputsAlreadySpent:
.inputsAlreadySpent,
.defragmentationFailed:
return false
case .connectionFailure,
.timeout:

View File

@ -478,27 +478,27 @@ public class PaymentsImpl: NSObject, PaymentsSwift {
return [
// iPhone 11 Pro Max Simulator
try! DevDevice(phoneNumber: "+441752395464",
fakeRootEntropy: MobileCoinAPI.rootEntropy1),
fakeRootEntropy: MobileCoinAPI.rootEntropy1)
// iPhone Xs Simulator
try! DevDevice(phoneNumber: "+14503002620",
fakeRootEntropy: MobileCoinAPI.rootEntropy2),
// iPhone Xr Simulator
try! DevDevice(phoneNumber: "+13602090656",
fakeRootEntropy: MobileCoinAPI.rootEntropy3),
// iPhone XS Max Device
try! DevDevice(phoneNumber: "+12262864592",
fakeRootEntropy: MobileCoinAPI.rootEntropy4),
// iPhone 12 mini Device
try! DevDevice(phoneNumber: "+12534877762",
fakeRootEntropy: MobileCoinAPI.rootEntropy5),
// iPhone 11 Pro Simulator
try! DevDevice(phoneNumber: "+15092608677",
fakeRootEntropy: MobileCoinAPI.rootEntropy6)
// // iPhone Xs Simulator
// try! DevDevice(phoneNumber: "+14503002620",
// fakeRootEntropy: MobileCoinAPI.rootEntropy2),
//
// // iPhone Xr Simulator
// try! DevDevice(phoneNumber: "+13602090656",
// fakeRootEntropy: MobileCoinAPI.rootEntropy3),
//
// // iPhone XS Max Device
// try! DevDevice(phoneNumber: "+12262864592",
// fakeRootEntropy: MobileCoinAPI.rootEntropy4),
//
// // iPhone 12 mini Device
// try! DevDevice(phoneNumber: "+12534877762",
// fakeRootEntropy: MobileCoinAPI.rootEntropy5),
//
// // iPhone 11 Pro Simulator
// try! DevDevice(phoneNumber: "+15092608677",
// fakeRootEntropy: MobileCoinAPI.rootEntropy6)
]
}
@ -935,27 +935,6 @@ public extension PaymentsImpl {
// 6. Block defragmentation transactions.
return firstly(on: .global()) { () throws -> Promise<[MobileCoin.Transaction]> in
mobileCoinAPI.prepareDefragmentationStepTransactions(forPaymentAmount: paymentAmount)
// {
//
// let (promise, resolver) = Promise<[MobileCoin.Transaction]>.pending()
// if DebugFlags.paymentsNoRequestsComplete.get() {
// // Never resolve.
// return promise
// }
// client.prepareDefragmentationStepTransactions(toSendAmount: paymentAmount.picoMob,
// feeLevel: Self.feeLevel) { (result: Swift.Result<[MobileCoin.Transaction],
// MobileCoin.TransactionPreparationError>) in
// switch result {
// case .success(let transactions):
// resolver.fulfill(transactions)
// break
// case .failure(let error):
// let error = Self.convertMCError(error: error)
// resolver.reject(error)
// break
// }
// }
// return promise
}.map(on: .global()) { (mcTransactions: [MobileCoin.Transaction]) -> [TSPaymentModel] in
// To initiate the defragmentation transactions, all we need to do
// is save TSPaymentModels to the database. The PaymentsProcessor
@ -1009,7 +988,13 @@ public extension PaymentsImpl {
return firstly(on: .global()) { () -> Promise<Void> in
let promises = paymentModels.map { paymentModel in
self.blockOnOutgoingVerification(paymentModel: paymentModel)
firstly(on: .global()) { () -> Promise<Bool> in
self.blockOnOutgoingVerification(paymentModel: paymentModel)
}.map(on: .global()) { (didSucceed: Bool) -> Void in
guard didSucceed else {
throw PaymentsError.defragmentationFailed
}
}
}
return when(fulfilled: promises)
}.timeout(seconds: maxBlockInterval, description: "blockOnVerificationOfDefragmentation") { () -> Error in
@ -1017,8 +1002,8 @@ public extension PaymentsImpl {
}
}
func blockOnOutgoingVerification(paymentModel: TSPaymentModel) -> Promise<Void> {
firstly(on: .global()) { () -> Promise<Void> in
func blockOnOutgoingVerification(paymentModel: TSPaymentModel) -> Promise<Bool> {
firstly(on: .global()) { () -> Promise<Bool> in
let paymentModelLatest = Self.databaseStorage.read { transaction in
TSPaymentModel.anyFetch(uniqueId: paymentModel.uniqueId,
transaction: transaction)
@ -1032,7 +1017,7 @@ public extension PaymentsImpl {
// Not yet verified, wait then try again.
return firstly(on: .global()) {
after(seconds: 0.05)
}.then(on: .global()) { () -> Promise<Void> in
}.then(on: .global()) { () -> Promise<Bool> in
// Recurse.
self.blockOnOutgoingVerification(paymentModel: paymentModel)
}
@ -1040,10 +1025,12 @@ public extension PaymentsImpl {
.outgoingSending,
.outgoingSent,
.outgoingMissingLedgerTimestamp,
.outgoingComplete,
.outgoingFailed:
// Success: Verified or failed.
return Promise.value(())
.outgoingComplete:
// Success: Verified.
return Promise.value(true)
case .outgoingFailed:
// Success: Failed.
return Promise.value(false)
case .incomingUnverified,
.incomingVerified,
.incomingMissingLedgerTimestamp,

View File

@ -542,7 +542,8 @@ private class PaymentProcessingOperation: OWSOperation {
.connectionFailure,
.timeout,
.invalidTransaction,
.inputsAlreadySpent:
.inputsAlreadySpent,
.defragmentationFailed:
owsFailDebugUnlessMCNetworkFailure(error)
case .authorizationFailure:
owsFailDebugUnlessMCNetworkFailure(error)
@ -595,7 +596,8 @@ private class PaymentProcessingOperation: OWSOperation {
.serializationError,
.missingModel,
.invalidTransaction,
.inputsAlreadySpent:
.inputsAlreadySpent,
.defragmentationFailed:
// Do not retry these errors.
delegate?.endProcessing(paymentId: self.paymentId)
case .serverRateLimited:
@ -828,9 +830,10 @@ private class PaymentProcessingOperation: OWSOperation {
// If we've verified a payment, our balance may have changed.
Self.payments.updateCurrentPaymentBalance()
case .failed:
try paymentModel.updatePaymentModelState(fromState: .outgoingUnverified,
toState: .outgoingFailed,
transaction: transaction)
Self.markAsFailed(paymentModel: paymentModel,
paymentFailure: .validationFailed,
paymentState: .outgoingFailed,
transaction: transaction)
}
}
}

View File

@ -33,6 +33,7 @@ public enum PaymentsError: Error {
case defragmentationRequired
case invalidTransaction
case inputsAlreadySpent
case defragmentationFailed
}
// MARK: -
@ -150,7 +151,7 @@ public protocol PaymentsSwift: Payments {
var allPossiblePassphraseWords: [String] { get }
func blockOnOutgoingVerification(paymentModel: TSPaymentModel) -> Promise<Void>
func blockOnOutgoingVerification(paymentModel: TSPaymentModel) -> Promise<Bool>
}
// MARK: -
@ -476,7 +477,7 @@ extension MockPayments: PaymentsSwift {
owsFail("Not implemented.")
}
public func blockOnOutgoingVerification(paymentModel: TSPaymentModel) -> Promise<Void> {
public func blockOnOutgoingVerification(paymentModel: TSPaymentModel) -> Promise<Bool> {
owsFail("Not implemented.")
}
}

View File

@ -437,12 +437,12 @@ extension TSPaymentModel: TSPaymentBaseModel {
isValid = false
}
let shouldHaveMCRecipientPublicAddressData = isOutgoing && (isIdentifiedPayment || isOutgoingTransfer)
let shouldHaveMCRecipientPublicAddressData = isOutgoing && (isIdentifiedPayment || isOutgoingTransfer) && !isFailed
if shouldHaveMCRecipientPublicAddressData, mcRecipientPublicAddressData == nil {
owsFailDebug("Missing mcRecipientPublicAddressData: \(formattedState).")
}
let shouldHaveMCTransaction = isOutgoing && !isUnidentified
let shouldHaveMCTransaction = isOutgoing && !isUnidentified && !isFailed
if shouldHaveMCTransaction, mcTransactionData == nil {
owsFailDebug("Missing mcTransactionData: \(formattedState).")
}
@ -471,7 +471,7 @@ extension TSPaymentModel: TSPaymentBaseModel {
isValid = false
}
let shouldHaveMCSpentKeyImages = isOutgoing
let shouldHaveMCSpentKeyImages = isOutgoing && !isFailed
if shouldHaveMCSpentKeyImages,
mcSpentKeyImages == nil {
owsFailDebug("Missing mcSpentKeyImages: \(formattedState).")
@ -482,8 +482,8 @@ extension TSPaymentModel: TSPaymentBaseModel {
isValid = false
}
let canHaveMCOutputPublicKeys = isOutgoing
let shouldHaveMCOutputPublicKeys = isOutgoing && !isUnidentified
let canHaveMCOutputPublicKeys = isOutgoing && !isFailed
let shouldHaveMCOutputPublicKeys = isOutgoing && !isUnidentified && !isFailed
if shouldHaveMCOutputPublicKeys,
mcOutputPublicKeys == nil {
owsFailDebug("Missing mcOutputPublicKeys: \(formattedState).")
@ -494,20 +494,31 @@ extension TSPaymentModel: TSPaymentBaseModel {
isValid = false
}
let shouldHaveMCLedgerBlockTimestamp = isComplete && !isUnidentified
let shouldHaveMCLedgerBlockTimestamp = isComplete && !isUnidentified && !isFailed
if shouldHaveMCLedgerBlockTimestamp,
!hasMCLedgerBlockTimestamp {
// For some payments, we'll never be able to fill in the block timestamp.
Logger.warn("Missing mcLedgerBlockTimestamp: \(formattedState).")
}
let shouldHaveMCLedgerBlockIndex = isVerified || isUnidentified
let shouldHaveMCLedgerBlockIndex = isVerified || isUnidentified && !isFailed
if shouldHaveMCLedgerBlockIndex,
!hasMCLedgerBlockIndex {
owsFailDebug("Missing mcLedgerBlockIndex: \(formattedState).")
isValid = false
}
let shouldHaveMobileCoin = !isFailed
if shouldHaveMobileCoin,
mobileCoin == nil {
owsFailDebug("Missing mobileCoin: \(formattedState).")
isValid = false
} else if !shouldHaveMobileCoin,
mobileCoin != nil {
owsFailDebug("Unexpected mobileCoin: \(formattedState).")
isValid = false
}
return isValid
}