Signal-iOS/Signal/src/ViewControllers/DebugUI/DebugUIPayments.swift
2021-04-06 13:57:08 -03:00

336 lines
16 KiB
Swift

//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalServiceKit
import SignalMessaging
import PromiseKit
#if DEBUG
class DebugUIPayments: DebugUIPage {
// MARK: Dependencies
private static var databaseStorage: SDSDatabaseStorage {
return SDSDatabaseStorage.shared
}
private var databaseStorage: SDSDatabaseStorage {
return SDSDatabaseStorage.shared
}
private var tsAccountManager: TSAccountManager {
return .shared()
}
private var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
private static var payments: PaymentsSwift {
SSKEnvironment.shared.payments as! PaymentsSwift
}
// MARK: Overrides
override func name() -> String {
return "Payments"
}
override func section(thread: TSThread?) -> OWSTableSection? {
var sectionItems = [OWSTableItem]()
if let contactThread = thread as? TSContactThread {
sectionItems.append(OWSTableItem(title: "Send payment request") { [weak self] in
self?.sendPaymentRequestMessage(contactThread: contactThread)
})
sectionItems.append(OWSTableItem(title: "Send payment notification") { [weak self] in
self?.sendPaymentNotificationMessage(contactThread: contactThread)
})
sectionItems.append(OWSTableItem(title: "Send payment cancellation") { [weak self] in
self?.sendPaymentCancellationMessage(contactThread: contactThread)
})
sectionItems.append(OWSTableItem(title: "Send payment request + cancellation") { [weak self] in
self?.sendPaymentRequestAndCancellation(contactThread: contactThread)
})
sectionItems.append(OWSTableItem(title: "Create all possible payment models") { [weak self] in
self?.insertAllPaymentModelVariations(contactThread: contactThread)
})
sectionItems.append(OWSTableItem(title: "Send tiny payments: 10") {
Self.sendTinyPayments(contactThread: contactThread, count: 10)
})
// For testing defragmentation.
sectionItems.append(OWSTableItem(title: "Send tiny payments: 17") {
Self.sendTinyPayments(contactThread: contactThread, count: 17)
})
// For testing defragmentation.
sectionItems.append(OWSTableItem(title: "Send tiny payments: 40") {
Self.sendTinyPayments(contactThread: contactThread, count: 40)
})
// For testing defragmentation.
sectionItems.append(OWSTableItem(title: "Send tiny payments: 100") {
Self.sendTinyPayments(contactThread: contactThread, count: 100)
})
sectionItems.append(OWSTableItem(title: "Send tiny payments: 1000") {
Self.sendTinyPayments(contactThread: contactThread, count: 1000)
})
}
sectionItems.append(OWSTableItem(title: "Delete all payment models") { [weak self] in
self?.deleteAllPaymentModels()
})
sectionItems.append(OWSTableItem(title: "Reconcile now") {
Self.databaseStorage.write { transaction in
Self.payments.scheduleReconciliationNow(transaction: transaction)
}
})
return OWSTableSection(title: "Payments", items: sectionItems)
}
private func sendPaymentRequestMessage(contactThread: TSContactThread) {
let address = contactThread.contactAddress
let picoMob: UInt64 = 1 + UInt64(arc4random_uniform(255))
let paymentAmount = TSPaymentAmount(currency: .mobileCoin,
picoMob: picoMob)
let memoMessage = "Please pay me because: \(UUID().uuidString)."
firstly {
PaymentsImpl.sendPaymentRequestMessagePromise(address: address,
paymentAmount: paymentAmount,
memoMessage: memoMessage)
}.done { (_) -> Void in
Logger.info("Success.")
}.catch { error in
owsFailDebug("Error: \(error)")
}
}
private func sendPaymentNotificationMessage(contactThread: TSContactThread) {
let address = contactThread.contactAddress
// PAYMENTS TODO: Can we make this the right length?
let mcReceiptData = Randomness.generateRandomBytes(1)
let memoMessage = "I'm sending payment for: \(UUID().uuidString)."
firstly {
PaymentsImpl.sendPaymentNotificationMessagePromise(address: address,
memoMessage: memoMessage,
mcReceiptData: mcReceiptData,
requestUuidString: nil)
}.done { (_) -> Void in
Logger.info("Success.")
}.catch { error in
owsFailDebug("Error: \(error)")
}
}
private func sendPaymentCancellationMessage(contactThread: TSContactThread) {
let address = contactThread.contactAddress
firstly { () -> Promise<OWSOutgoingPaymentMessage> in
let requestUuidString = UUID().uuidString
return PaymentsImpl.sendPaymentCancellationMessagePromise(address: address,
requestUuidString: requestUuidString)
}.done { (_) -> Void in
Logger.info("Success.")
}.catch { error in
owsFailDebug("Error: \(error)")
}
}
private func sendPaymentRequestAndCancellation(contactThread: TSContactThread) {
let address = contactThread.contactAddress
let picoMob: UInt64 = 1 + UInt64(arc4random_uniform(255))
let paymentAmount = TSPaymentAmount(currency: .mobileCoin,
picoMob: picoMob)
let memoMessage = "Please pay me because: \(UUID().uuidString)."
firstly { () -> Promise<OWSOutgoingPaymentMessage> in
PaymentsImpl.sendPaymentRequestMessagePromise(address: address,
paymentAmount: paymentAmount,
memoMessage: memoMessage)
}.then { (requestMessage: OWSOutgoingPaymentMessage) -> Promise<OWSOutgoingPaymentMessage> in
guard let requestUuidString = requestMessage.paymentRequest?.requestUuidString else {
throw OWSAssertionError("Missing requestUuidString.")
}
return PaymentsImpl.sendPaymentCancellationMessagePromise(address: address,
requestUuidString: requestUuidString)
}.done { (_) -> Void in
Logger.info("Success.")
}.catch { error in
owsFailDebug("Error: \(error)")
}
}
private func insertAllPaymentModelVariations(contactThread: TSContactThread) {
let address = contactThread.contactAddress
let uuid = address.uuid!
databaseStorage.write { transaction in
let paymentAmounts = [
TSPaymentAmount(currency: .mobileCoin, picoMob: 1),
TSPaymentAmount(currency: .mobileCoin, picoMob: 1000),
TSPaymentAmount(currency: .mobileCoin, picoMob: 1000 * 1000),
TSPaymentAmount(currency: .mobileCoin, picoMob: 1000 * 1000 * 1000),
TSPaymentAmount(currency: .mobileCoin, picoMob: 1000 * 1000 * 1000 * 1000),
TSPaymentAmount(currency: .mobileCoin, picoMob: 1000 * 1000 * 1000 * 1000 * 1000)
]
func insertPaymentModel(paymentType: TSPaymentType,
paymentState: TSPaymentState) -> TSPaymentModel {
let mcReceiptData = Randomness.generateRandomBytes(32)
var mcTransactionData: Data?
if paymentState.isIncoming {
} else {
mcTransactionData = Randomness.generateRandomBytes(32)
}
var memoMessage: String?
if arc4random_uniform(2) == 0 {
memoMessage = "Pizza Party 🍕"
}
var addressUuidString: String?
if !paymentType.isUnidentified {
addressUuidString = uuid.uuidString
}
// TODO: requestUuidString
// TODO: isUnread
// TODO: mcRecipientPublicAddressData
// TODO: mobileCoin
// TODO: feeAmount
let mobileCoin = MobileCoinPayment(recipientPublicAddressData: nil,
transactionData: mcTransactionData,
receiptData: mcReceiptData,
incomingTransactionPublicKeys: nil,
spentKeyImages: nil,
outputPublicKeys: nil,
ledgerBlockTimestamp: 0,
ledgerBlockIndex: 0,
feeAmount: nil)
let paymentModel = TSPaymentModel(paymentType: paymentType,
paymentState: paymentState,
paymentAmount: paymentAmounts.randomElement()!,
createdDate: Date(),
addressUuidString: addressUuidString,
memoMessage: memoMessage,
requestUuidString: nil,
isUnread: false,
mobileCoin: mobileCoin)
do {
try Self.payments.tryToInsertPaymentModel(paymentModel, transaction: transaction)
} catch {
owsFailDebug("Error: \(error)")
}
return paymentModel
}
var paymentModel: TSPaymentModel
// MARK: - Incoming
paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingUnverified)
paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingVerified)
paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingMissingLedgerTimestamp)
paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingComplete)
// MARK: - Outgoing
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingUnsubmitted)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingUnverified)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingVerified)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingSending)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingSent)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingMissingLedgerTimestamp)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingComplete)
// MARK: - Failures
// TODO: We probably don't want to create .none and .unknown
// paymentModel = insertPaymentModel(paymentState: .outgoingFailed)
// paymentModel.update(withPaymentFailure: .none,
// paymentState: .outgoingFailed,
// transaction: transaction)
//
// paymentModel = insertPaymentModel(paymentState: .incomingFailed)
// paymentModel.update(withPaymentFailure: .none,
// paymentState: .incomingFailed,
// transaction: transaction)
//
// paymentModel = insertPaymentModel(paymentState: .outgoingFailed)
// paymentModel.update(withPaymentFailure: .unknown,
// paymentState: .outgoingFailed,
// transaction: transaction)
//
// paymentModel = insertPaymentModel(paymentState: .incomingFailed)
// paymentModel.update(withPaymentFailure: .unknown,
// paymentState: .incomingFailed,
// transaction: transaction)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingFailed)
paymentModel.update(withPaymentFailure: .insufficientFunds,
paymentState: .outgoingFailed,
transaction: transaction)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingFailed)
paymentModel.update(withPaymentFailure: .validationFailed,
paymentState: .outgoingFailed,
transaction: transaction)
paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingFailed)
paymentModel.update(withPaymentFailure: .validationFailed,
paymentState: .incomingFailed,
transaction: transaction)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingFailed)
paymentModel.update(withPaymentFailure: .notificationSendFailed,
paymentState: .outgoingFailed,
transaction: transaction)
paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingFailed)
paymentModel.update(withPaymentFailure: .invalid,
paymentState: .incomingFailed,
transaction: transaction)
paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingFailed)
paymentModel.update(withPaymentFailure: .invalid,
paymentState: .outgoingFailed,
transaction: transaction)
// MARK: - Unidentified
paymentModel = insertPaymentModel(paymentType: .incomingUnidentified, paymentState: .incomingComplete)
paymentModel = insertPaymentModel(paymentType: .outgoingUnidentified, paymentState: .outgoingComplete)
}
}
private static func sendTinyPayments(contactThread: TSContactThread, count: UInt) {
let picoMob = PaymentsConstants.picoMobPerMob + UInt64(arc4random_uniform(1000))
let paymentAmount = TSPaymentAmount(currency: .mobileCoin, picoMob: picoMob)
firstly(on: .global()) { () -> Promise<TSPaymentModel> in
let recipient = SendPaymentRecipientImpl.address(address: contactThread .contactAddress)
return Self.payments.createNewOutgoingPayment(recipient: recipient,
paymentAmount: paymentAmount,
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)
} else {
Logger.info("Complete.")
}
}.catch(on: .global()) { error in
owsFailDebug("Error: \(error)")
}
}
private func deleteAllPaymentModels() {
databaseStorage.write { transaction in
TSPaymentModel.anyRemoveAllWithInstantation(transaction: transaction)
}
}
}
#endif