diff --git a/Sources/Account/Account+BalanceUpdater.swift b/Sources/Account/Account+BalanceUpdater.swift index fba30fe6..cfe924b9 100644 --- a/Sources/Account/Account+BalanceUpdater.swift +++ b/Sources/Account/Account+BalanceUpdater.swift @@ -43,34 +43,39 @@ extension Account { logger.info("Updating balance...", logFunction: false) checkForNewTxOuts { guard $0.successOr(completion: completion) != nil else { - logger.warning("Failed to update balance.", logFunction: false) + logger.warning( + "Failed to update balance: checkForNewTxOuts error: \($0)", + logFunction: false) return } - self.viewKeyScanUnscannedMissedBlocks { + self.checkForSpentTxOuts { guard $0.successOr(completion: completion) != nil else { - logger.warning("Failed to update balance.", logFunction: false) + logger.warning( + "Failed to update balance: checkForSpentTxOuts error: \($0)", + logFunction: false) return } - self.checkForSpentTxOuts { - guard $0.successOr(completion: completion) != nil else { - logger.warning("Failed to update balance.", logFunction: false) - return - } - - let balance = self.account.readSync { $0.cachedBalance } + let balance = self.account.readSync { $0.cachedBalance } - logger.info( - "Update balance successful. balance: \(redacting: balance)", - logFunction: false) - completion(.success(balance)) - } + logger.info( + "Balance update successful. balance: \(redacting: balance)", + logFunction: false) + completion(.success(balance)) } } } func checkForNewTxOuts(completion: @escaping (Result<(), ConnectionError>) -> Void) { + checkForNewFogViewTxOuts { + guard $0.successOr(completion: completion) != nil else { return } + + self.viewKeyScanUnscannedMissedBlocks(completion: completion) + } + } + + func checkForNewFogViewTxOuts(completion: @escaping (Result<(), ConnectionError>) -> Void) { txOutFetcher.fetchTxOuts(partialResultsWithWriteLock: { newTxOuts in logger.info( "Found \(redacting: newTxOuts.count) new TxOuts using Fog View", diff --git a/Sources/Account/Account+TransactionEstimator.swift b/Sources/Account/Account+TransactionEstimator.swift index 07eef6d8..de6e9675 100644 --- a/Sources/Account/Account+TransactionEstimator.swift +++ b/Sources/Account/Account+TransactionEstimator.swift @@ -2,6 +2,8 @@ // Copyright (c) 2020-2021 MobileCoin. All rights reserved. // +// swiftlint:disable closure_body_length + import Foundation extension Account { @@ -34,8 +36,8 @@ extension Account { .flatMap { feeStrategy in let txOuts = self.account.readSync { $0.unspentTxOuts } logger.info( - "Calculating amountTransferable. unspentTxOutValues: " + - "\(redacting: txOuts.map { $0.value })", + "Calculating amountTransferable. feeLevel: \(feeLevel), " + + "unspentTxOutValues: \(redacting: txOuts.map { $0.value })", logFunction: false) return self.txOutSelector .amountTransferable(feeStrategy: feeStrategy, txOuts: txOuts) @@ -62,10 +64,9 @@ extension Account { feeLevel: FeeLevel, completion: @escaping (Result) -> Void ) { - logger.info("toSendAmount: \(redacting: amount), feeLevel: \(feeLevel)") guard amount > 0 else { - let errorMessage = "Cannot spend 0 MOB" - logger.error(errorMessage) + let errorMessage = "estimateTotalFee failure: Cannot spend 0 MOB" + logger.error(errorMessage, logFunction: false) serialQueue.async { completion(.failure(.invalidInput(errorMessage))) } @@ -76,7 +77,12 @@ extension Account { completion($0.mapError { .connectionError($0) } .flatMap { feeStrategy in let txOuts = self.account.readSync { $0.unspentTxOuts } - let totalFee = self.txOutSelector + logger.info( + "Estimating total fee: amount: \(redacting: amount), feeLevel: " + + "\(feeLevel), unspentTxOutValues: " + + "\(redacting: txOuts.map { $0.value })", + logFunction: false) + return self.txOutSelector .estimateTotalFee( toSendAmount: amount, feeStrategy: feeStrategy, @@ -84,9 +90,13 @@ extension Account { .mapError { _ in TransactionEstimationFetcherError.insufficientBalance() } - .map { $0.totalFee } - logger.info("totalFee result: \(redacting: totalFee)") - return totalFee + .map { + logger.info( + "estimateTotalFee: \(redacting: $0.totalFee), " + + "requiresDefrag: \($0.requiresDefrag)", + logFunction: false) + return $0.totalFee + } }) } } @@ -96,10 +106,9 @@ extension Account { feeLevel: FeeLevel, completion: @escaping (Result) -> Void ) { - logger.info("toSendAmount: \(redacting: amount), feeLevel: \(feeLevel)") guard amount > 0 else { - let errorMessage = "Cannot spend 0 MOB" - logger.error(errorMessage) + let errorMessage = "requiresDefragmentation failure: Cannot spend 0 MOB" + logger.error(errorMessage, logFunction: false) serialQueue.async { completion(.failure(.invalidInput(errorMessage))) } @@ -110,7 +119,12 @@ extension Account { completion($0.mapError { .connectionError($0) } .flatMap { feeStrategy in let txOuts = self.account.readSync { $0.unspentTxOuts } - let requiresDefragmentation = self.txOutSelector + logger.info( + "Calculation defragmentation required: amount: \(redacting: amount), " + + "feeLevel: \(feeLevel), unspentTxOutValues: " + + "\(redacting: txOuts.map { $0.value })", + logFunction: false) + return self.txOutSelector .estimateTotalFee( toSendAmount: amount, feeStrategy: feeStrategy, @@ -118,10 +132,13 @@ extension Account { .mapError { _ in TransactionEstimationFetcherError.insufficientBalance() } - .map { $0.requiresDefrag } - logger.info( - "requiresDefragmentation result: \(redacting: requiresDefragmentation)") - return requiresDefragmentation + .map { + logger.info( + "requiresDefragmentation: \($0.requiresDefrag), totalFee: " + + "\(redacting: $0.totalFee)", + logFunction: false) + return $0.requiresDefrag + } }) } } @@ -133,12 +150,12 @@ extension Account.TransactionEstimator { func amountTransferable(feeLevel: FeeLevel) -> Result { + let feeStrategy = feeLevel.defaultFeeStrategy let txOuts = account.readSync { $0.unspentTxOuts } logger.info( - "Calculating amountTransferable. unspentTxOutValues: " + + "Calculating amountTransferable. feeLevel: \(feeLevel), unspentTxOutValues: " + "\(redacting: txOuts.map { $0.value })", logFunction: false) - let feeStrategy = feeLevel.defaultFeeStrategy return txOutSelector.amountTransferable(feeStrategy: feeStrategy, txOuts: txOuts) .mapError { switch $0 { @@ -159,21 +176,28 @@ extension Account.TransactionEstimator { func estimateTotalFee(toSendAmount amount: UInt64, feeLevel: FeeLevel) -> Result { - logger.info("toSendAmount: \(redacting: amount), feeLevel: \(feeLevel)") guard amount > 0 else { - let errorMessage = "Cannot spend 0 MOB" - logger.error(errorMessage) + let errorMessage = "estimateTotalFee failure: Cannot spend 0 MOB" + logger.error(errorMessage, logFunction: false) return .failure(.invalidInput(errorMessage)) } - let txOuts = account.readSync { $0.unspentTxOuts } let feeStrategy = feeLevel.defaultFeeStrategy - let totalFee = txOutSelector + let txOuts = account.readSync { $0.unspentTxOuts } + logger.info( + "Estimating total fee: amount: \(redacting: amount), feeLevel: \(feeLevel), " + + "unspentTxOutValues: \(redacting: txOuts.map { $0.value })", + logFunction: false) + return txOutSelector .estimateTotalFee(toSendAmount: amount, feeStrategy: feeStrategy, txOuts: txOuts) .mapError { _ -> TransactionEstimationError in .insufficientBalance() } - .map { $0.totalFee } - logger.info("totalFee result: \(redacting: totalFee)") - return totalFee + .map { + logger.info( + "estimateTotalFee: \(redacting: $0.totalFee), requiresDefrag: " + + "\($0.requiresDefrag)", + logFunction: false) + return $0.totalFee + } } @available(*, deprecated, message: @@ -181,20 +205,27 @@ extension Account.TransactionEstimator { func requiresDefragmentation(toSendAmount amount: UInt64, feeLevel: FeeLevel) -> Result { - logger.info("toSendAmount: \(redacting: amount), feeLevel: \(feeLevel)") guard amount > 0 else { - let errorMessage = "Cannot spend 0 MOB" - logger.error(errorMessage) + let errorMessage = "requiresDefragmentation failure: Cannot spend 0 MOB" + logger.error(errorMessage, logFunction: false) return .failure(.invalidInput(errorMessage)) } - let txOuts = account.readSync { $0.unspentTxOuts } let feeStrategy = feeLevel.defaultFeeStrategy - let requiresDefragmentation = txOutSelector + let txOuts = account.readSync { $0.unspentTxOuts } + logger.info( + "Calculation defragmentation required: amount: \(redacting: amount), feeLevel: " + + "\(feeLevel), unspentTxOutValues: \(redacting: txOuts.map { $0.value })", + logFunction: false) + return txOutSelector .estimateTotalFee(toSendAmount: amount, feeStrategy: feeStrategy, txOuts: txOuts) .mapError { _ -> TransactionEstimationError in .insufficientBalance() } - .map { $0.requiresDefrag } - logger.info("requiresDefragmentation result: \(redacting: requiresDefragmentation)") - return requiresDefragmentation + .map { + logger.info( + "requiresDefragmentation: \($0.requiresDefrag), totalFee: " + + "\(redacting: $0.totalFee)", + logFunction: false) + return $0.requiresDefrag + } } } diff --git a/Sources/Account/Account+TransactionOperations.swift b/Sources/Account/Account+TransactionOperations.swift index 2f33d6b7..3bd58583 100644 --- a/Sources/Account/Account+TransactionOperations.swift +++ b/Sources/Account/Account+TransactionOperations.swift @@ -45,13 +45,8 @@ extension Account { Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError> ) -> Void ) { - logger.info("Preparing transaction with provided fee...", logFunction: false) - logger.info( - "recipient: \(redacting: recipient), amount: \(redacting: amount), fee: " + - "\(redacting: fee)", - logFunction: false) guard amount > 0 else { - let errorMessage = "Cannot spend 0 MOB" + let errorMessage = "prepareTransactionWithFee failure: Cannot spend 0 MOB" logger.error(errorMessage, logFunction: false) serialQueue.async { completion(.failure(.invalidInput(errorMessage))) @@ -60,9 +55,12 @@ extension Account { } let (unspentTxOuts, ledgerBlockCount) = - account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) } - let tombstoneBlockIndex = ledgerBlockCount + 50 - + account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) } + logger.info( + "Preparing transaction with provided fee... recipient: \(redacting: recipient), " + + "amount: \(redacting: amount), fee: \(redacting: fee), unspentTxOutValues: " + + "\(redacting: unspentTxOuts.map { $0.value })", + logFunction: false) switch txOutSelector .selectTransactionInputs(amount: amount, fee: fee, fromTxOuts: unspentTxOuts) .mapError({ error -> TransactionPreparationError in @@ -76,7 +74,10 @@ extension Account { { case .success(let txOutsToSpend): logger.info( - "success - txOutsToSpend: \(redacting: txOutsToSpend.map { $0.publicKey })") + "Transaction prepared with fee. txOutsToSpend: " + + "0x\(redacting: txOutsToSpend.map { $0.publicKey.hexEncodedString() })", + logFunction: false) + let tombstoneBlockIndex = ledgerBlockCount + 50 transactionPreparer.prepareTransaction( inputs: txOutsToSpend, recipient: recipient, @@ -85,7 +86,7 @@ extension Account { tombstoneBlockIndex: tombstoneBlockIndex, completion: completion) case .failure(let error): - logger.info("failure - error: \(error)") + logger.info("prepareTransactionWithFee failure: \(error)", logFunction: false) serialQueue.async { completion(.failure(error)) } @@ -100,13 +101,8 @@ extension Account { Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError> ) -> Void ) { - logger.info("Preparing transaction with fee level...", logFunction: false) - logger.info( - "recipient: \(redacting: recipient), amount: \(redacting: amount), feeLevel: " + - "\(feeLevel)", - logFunction: false) guard amount > 0 else { - let errorMessage = "Cannot spend 0 MOB" + let errorMessage = "prepareTransactionWithFeeLevel failure: Cannot spend 0 MOB" logger.error(errorMessage, logFunction: false) serialQueue.async { completion(.failure(.invalidInput(errorMessage))) @@ -114,13 +110,17 @@ extension Account { return } - let (unspentTxOuts, ledgerBlockCount) = - account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) } - let tombstoneBlockIndex = ledgerBlockCount + 50 - feeFetcher.feeStrategy(for: feeLevel) { switch $0 { case .success(let feeStrategy): + let (unspentTxOuts, ledgerBlockCount) = + self.account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) } + logger.info( + "Preparing transaction with fee level... recipient: " + + "\(redacting: recipient), amount: \(redacting: amount), feeLevel: " + + "\(feeLevel), unspentTxOutValues: " + + "\(redacting: unspentTxOuts.map { $0.value })", + logFunction: false) switch self.txOutSelector .selectTransactionInputs( amount: amount, @@ -136,7 +136,10 @@ extension Account { }) { case .success(let (inputs: inputs, fee: fee)): - logger.info("success - fee: \(redacting: fee)") + logger.info( + "Transaction prepared with fee level. fee: \(redacting: fee)", + logFunction: false) + let tombstoneBlockIndex = ledgerBlockCount + 50 self.transactionPreparer.prepareTransaction( inputs: inputs, recipient: recipient, @@ -145,7 +148,9 @@ extension Account { tombstoneBlockIndex: tombstoneBlockIndex, completion: completion) case .failure(let error): - logger.info("failure - error: \(error)") + logger.info( + "prepareTransactionWithFeeLevel failure: \(error)", + logFunction: false) completion(.failure(error)) } case .failure(let connectionError): @@ -156,14 +161,13 @@ extension Account { } func prepareDefragmentationStepTransactions( - toSendAmount amount: UInt64, + toSendAmount amountToSend: UInt64, feeLevel: FeeLevel, completion: @escaping (Result<[Transaction], DefragTransactionPreparationError>) -> Void ) { - logger.info("Preparing defragmentation step transactions...", logFunction: false) - logger.info("toSendAmount: \(redacting: amount), feeLevel: \(feeLevel)") - guard amount > 0 else { - let errorMessage = "Cannot spend 0 MOB" + guard amountToSend > 0 else { + let errorMessage = + "prepareDefragmentationStepTransactions failure: Cannot spend 0 MOB" logger.error(errorMessage, logFunction: false) serialQueue.async { completion(.failure(.invalidInput(errorMessage))) @@ -171,15 +175,18 @@ extension Account { return } - let (unspentTxOuts, ledgerBlockCount) = - account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) } - let tombstoneBlockIndex = ledgerBlockCount + 50 - feeFetcher.feeStrategy(for: feeLevel) { switch $0 { case .success(let feeStrategy): + let (unspentTxOuts, ledgerBlockCount) = + self.account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) } + logger.info( + "Preparing defragmentation step transactions... amountToSend: " + + "\(redacting: amountToSend), feeLevel: \(feeLevel), " + + "unspentTxOutValues: \(redacting: unspentTxOuts.map { $0.value })", + logFunction: false) switch self.txOutSelector.selectInputsForDefragTransactions( - toSendAmount: amount, + toSendAmount: amountToSend, feeStrategy: feeStrategy, fromTxOuts: unspentTxOuts) { @@ -189,6 +196,7 @@ extension Account { "Preparing \(defragTxInputs.count) defrag transactions", logFunction: false) } + let tombstoneBlockIndex = ledgerBlockCount + 50 defragTxInputs.mapAsync({ defragInputs, callback in self.transactionPreparer.prepareSelfAddressedTransaction( inputs: defragInputs.inputs, @@ -196,8 +204,10 @@ extension Account { tombstoneBlockIndex: tombstoneBlockIndex, completion: callback) }, serialQueue: self.serialQueue, completion: completion) - case .failure: - logger.warning("failure") + case .failure(let error): + logger.info( + "prepareDefragmentationStepTransactions failure: \(error)", + logFunction: false) self.serialQueue.async { completion(.failure(.insufficientBalance())) } diff --git a/Sources/Account/Account.swift b/Sources/Account/Account.swift index cf678abf..19f2c273 100644 --- a/Sources/Account/Account.swift +++ b/Sources/Account/Account.swift @@ -92,25 +92,14 @@ final class Account { func cachedReceivedStatus(of receipt: Receipt) -> Result { - logger.info("receipt.txOutPublicKey: \(redacting: receipt.txOutPublicKey)") - return ownedTxOut(for: receipt).map { + ownedTxOut(for: receipt).map { if let ownedTxOut = $0 { - logger.info("received - txOut.publicKey: \(redacting: ownedTxOut.publicKey)") return .received(block: ownedTxOut.block) } else { let knownToBeNotReceivedBlockCount = allTxOutsFoundBlockCount guard receipt.txTombstoneBlockIndex > knownToBeNotReceivedBlockCount else { - logger.info(""" - tombstone exceeded - receipt.txTombstoneBlockIndex: \ - \(receipt.txTombstoneBlockIndex) > \ - knownToBeNotReceivedBlockCount: \(knownToBeNotReceivedBlockCount) - """) return .tombstoneExceeded } - logger.info(""" - not received - \ - knownToBeNotReceivedBlockCount: \(knownToBeNotReceivedBlockCount) - """) return .notReceived(knownToBeNotReceivedBlockCount: knownToBeNotReceivedBlockCount) } } @@ -118,19 +107,14 @@ final class Account { /// Retrieves the `KnownTxOut`'s corresponding to `receipt` and verifies `receipt` is valid. private func ownedTxOut(for receipt: Receipt) -> Result { - logger.info(""" - receipt.txOutPublicKey: \(redacting: receipt.txOutPublicKey), \ - account: \(redacting: accountKey.publicAddress) - """) - if let lastTxOut = ownedTxOuts.last { - logger.info("Last received TxOut: Tx pubkey: \(redacting: lastTxOut.publicKey)") - } + logger.debug( + "Last received TxOut: TxOut pubkey: " + + "\(redacting: ownedTxOuts.last?.publicKey.hexEncodedString() ?? "None")", + logFunction: false) // First check if we've received the TxOut (either from Fog View or from view key scanning). // This has the benefit of providing a guarantee that the TxOut is owned by this account. guard let ownedTxOut = ownedTxOut(for: receipt.txOutPublicKeyTyped) else { - logger.info( - "Receipt status check failed. Account has not received Receipt TxOut.") return .success(nil) } @@ -144,7 +128,7 @@ final class Account { "Receipt data doesn't match the corresponding TxOut found in the ledger. " + "Receipt: \(redacting: receipt.serializedData.base64EncodedString()) - " + "Account TxOut: \(redacting: ownedTxOut)" - logger.error(errorMessage) + logger.error(errorMessage, sensitive: true, logFunction: false) return .failure(InvalidInputError(errorMessage)) } @@ -153,12 +137,10 @@ final class Account { guard receipt.validateConfirmationNumber(accountKey: accountKey) else { let errorMessage = "Receipt confirmation number is invalid for this account. " + "Receipt: \(redacting: receipt.serializedData.base64EncodedString())" - logger.error(errorMessage) + logger.error(errorMessage, sensitive: true, logFunction: false) return .failure(InvalidInputError(errorMessage)) } - logger.info("Receipt status check succeeded. TxOut was received in block index " + - "\(ownedTxOut.block.index)") return .success(ownedTxOut) } @@ -172,7 +154,7 @@ extension Account { static func make(accountKey: AccountKey) -> Result { guard let accountKey = AccountKeyWithFog(accountKey: accountKey) else { let errorMessage = "Accounts without fog URLs are not currently supported." - logger.error(errorMessage) + logger.error(errorMessage, logFunction: false) return .failure(InvalidInputError(errorMessage)) } return .success(Account(accountKey: accountKey)) @@ -221,12 +203,7 @@ final class TxOutTracker { extension OwnedTxOut { fileprivate init?(_ txOutTracker: TxOutTracker, atBlockCount blockCount: UInt64) { - logger.info(""" - txOut.publicKey: \(redacting: txOutTracker.knownTxOut.publicKey), \ - atBlockCount: \(blockCount) - """) guard txOutTracker.knownTxOut.block.index < blockCount else { - logger.info("knownTxout block index < blockCount") return nil } let receivedBlock = txOutTracker.knownTxOut.block diff --git a/Sources/Account/AccountActivity.swift b/Sources/Account/AccountActivity.swift index 987cf84b..7cb510a2 100644 --- a/Sources/Account/AccountActivity.swift +++ b/Sources/Account/AccountActivity.swift @@ -12,7 +12,6 @@ public struct AccountActivity { public let blockCount: UInt64 init(txOuts: [OwnedTxOut], blockCount: UInt64) { - logger.info("txOuts: \(redacting: txOuts.map { $0.publicKey }), blockCount: \(blockCount)") self.txOuts = Set(txOuts) self.blockCount = blockCount } diff --git a/Sources/Account/AccountKey.swift b/Sources/Account/AccountKey.swift index 9c010df4..b571e2b2 100644 --- a/Sources/Account/AccountKey.swift +++ b/Sources/Account/AccountKey.swift @@ -56,7 +56,7 @@ public struct AccountKey { /// - Returns: `nil` when the input is not deserializable. public init?(serializedData: Data) { guard let proto = try? External_AccountKey(serializedData: serializedData) else { - logger.warning("External_AccountKey deserialization failed.") + logger.error("External_AccountKey deserialization failed.", logFunction: false) return nil } self.init(proto) @@ -64,12 +64,7 @@ public struct AccountKey { public var serializedData: Data { let proto = External_AccountKey(self) - do { - return try proto.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } + return proto.serializedDataInfallible } var fogReportUrlString: String? { fogInfo?.reportUrlString } diff --git a/Sources/Account/Balance.swift b/Sources/Account/Balance.swift index f3808663..cf78e82a 100644 --- a/Sources/Account/Balance.swift +++ b/Sources/Account/Balance.swift @@ -10,7 +10,6 @@ public struct Balance { let blockCount: UInt64 init(values: [UInt64], blockCount: UInt64) { - logger.info("values: \(redacting: values), blockCount: \(blockCount)") var amountLow: UInt64 = 0 var amountHigh: UInt8 = 0 for value in values { diff --git a/Sources/Account/PublicAddress.swift b/Sources/Account/PublicAddress.swift index 9d5addc0..3d96c907 100644 --- a/Sources/Account/PublicAddress.swift +++ b/Sources/Account/PublicAddress.swift @@ -47,12 +47,7 @@ public struct PublicAddress { public var serializedData: Data { let proto = External_PublicAddress(self) - do { - return try proto.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } + return proto.serializedDataInfallible } /// Subaddress view public key, `C`, in bytes. diff --git a/Sources/Common/Errors.swift b/Sources/Common/Errors.swift index 211b50e4..105571de 100644 --- a/Sources/Common/Errors.swift +++ b/Sources/Common/Errors.swift @@ -10,7 +10,6 @@ public struct InvalidInputError: Error { let reason: String init(_ reason: String) { - logger.info("reason: \(reason)") self.reason = reason } } diff --git a/Sources/Common/MobileCoinLogging.swift b/Sources/Common/MobileCoinLogging.swift index ba191733..c2b73ab5 100644 --- a/Sources/Common/MobileCoinLogging.swift +++ b/Sources/Common/MobileCoinLogging.swift @@ -142,11 +142,14 @@ extension Logger { _ message: @autoclosure () -> Logger.Message, metadata: @autoclosure () -> Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { + guard !sensitive || logSensitiveDataInternal.get() else { return } + let metadata = logFunction ? { Self.addingLogFunctionKey(metadata()) } : metadata trace( message(), @@ -161,11 +164,14 @@ extension Logger { _ message: @autoclosure () -> Logger.Message, metadata: @autoclosure () -> Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { + guard !sensitive || logSensitiveDataInternal.get() else { return } + let metadata = logFunction ? { Self.addingLogFunctionKey(metadata()) } : metadata debug( message(), @@ -180,11 +186,14 @@ extension Logger { _ message: @autoclosure () -> Logger.Message, metadata: @autoclosure () -> Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { + guard !sensitive || logSensitiveDataInternal.get() else { return } + let metadata = logFunction ? { Self.addingLogFunctionKey(metadata()) } : metadata info( message(), @@ -199,11 +208,14 @@ extension Logger { _ message: @autoclosure () -> Logger.Message, metadata: @autoclosure () -> Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { + guard !sensitive || logSensitiveDataInternal.get() else { return } + let metadata = logFunction ? { Self.addingLogFunctionKey(metadata()) } : metadata notice( message(), @@ -218,11 +230,14 @@ extension Logger { _ message: @autoclosure () -> Logger.Message, metadata: @autoclosure () -> Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { + guard !sensitive || logSensitiveDataInternal.get() else { return } + let metadata = logFunction ? { Self.addingLogFunctionKey(metadata()) } : metadata warning( message(), @@ -237,11 +252,14 @@ extension Logger { _ message: @autoclosure () -> Logger.Message, metadata: @autoclosure () -> Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { + guard !sensitive || logSensitiveDataInternal.get() else { return } + let metadata = logFunction ? { Self.addingLogFunctionKey(metadata()) } : metadata error( message(), @@ -256,11 +274,14 @@ extension Logger { _ message: @autoclosure () -> Logger.Message, metadata: @autoclosure () -> Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { + guard !sensitive || logSensitiveDataInternal.get() else { return } + let metadata = logFunction ? { Self.addingLogFunctionKey(metadata()) } : metadata critical( message(), @@ -283,6 +304,7 @@ extension Logger { _ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, @@ -292,6 +314,7 @@ extension Logger { Message(stringLiteral: message()), metadata: metadata(), source: source(), + sensitive: sensitive, logFunction: logFunction, file: file, function: function, @@ -302,6 +325,7 @@ extension Logger { _ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, @@ -311,6 +335,7 @@ extension Logger { Message(stringLiteral: message()), metadata: metadata(), source: source(), + sensitive: sensitive, logFunction: logFunction, file: file, function: function, @@ -321,6 +346,7 @@ extension Logger { _ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, @@ -330,6 +356,7 @@ extension Logger { Message(stringLiteral: message()), metadata: metadata(), source: source(), + sensitive: sensitive, logFunction: logFunction, file: file, function: function, @@ -340,6 +367,7 @@ extension Logger { _ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, @@ -349,6 +377,7 @@ extension Logger { Message(stringLiteral: message()), metadata: metadata(), source: source(), + sensitive: sensitive, logFunction: logFunction, file: file, function: function, @@ -359,6 +388,7 @@ extension Logger { _ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, @@ -368,6 +398,7 @@ extension Logger { Message(stringLiteral: message()), metadata: metadata(), source: source(), + sensitive: sensitive, logFunction: logFunction, file: file, function: function, @@ -378,6 +409,7 @@ extension Logger { _ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, @@ -387,6 +419,7 @@ extension Logger { Message(stringLiteral: message()), metadata: metadata(), source: source(), + sensitive: sensitive, logFunction: logFunction, file: file, function: function, @@ -397,6 +430,7 @@ extension Logger { _ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, + sensitive: Bool = false, logFunction: Bool = true, file: String = #file, function: String = #function, @@ -406,6 +440,7 @@ extension Logger { Message(stringLiteral: message()), metadata: metadata(), source: source(), + sensitive: sensitive, logFunction: logFunction, file: file, function: function, diff --git a/Sources/Encodings/MobUri.swift b/Sources/Encodings/MobUri.swift index 960c9873..2a3a1acf 100644 --- a/Sources/Encodings/MobUri.swift +++ b/Sources/Encodings/MobUri.swift @@ -12,7 +12,6 @@ public enum MobUri { } public static func decode(uri uriString: String) -> Result { - logger.info("") guard let uri = URL(string: uriString) else { logger.info("Could not parse MobURI as URL: \(redacting: uriString)") return .failure( @@ -33,23 +32,19 @@ public enum MobUri { } public static func encode(_ publicAddress: PublicAddress) -> String { - logger.info("") - return encode(.publicAddress(publicAddress)) + encode(.publicAddress(publicAddress)) } public static func encode(_ paymentRequest: PaymentRequest) -> String { - logger.info("") - return encode(.paymentRequest(paymentRequest)) + encode(.paymentRequest(paymentRequest)) } public static func encode(_ transferPayload: TransferPayload) -> String { - logger.info("") - return encode(.transferPayload(transferPayload)) + encode(.transferPayload(transferPayload)) } static func encode(_ payload: Payload) -> String { - logger.info("") - return "\(McConstants.MOB_URI_SCHEME)://\(payload.uriPath)" + "\(McConstants.MOB_URI_SCHEME)://\(payload.uriPath)" } } diff --git a/Sources/Encodings/PrintableWrapper+Base58.swift b/Sources/Encodings/PrintableWrapper+Base58.swift index 76b90b27..a45ffbb0 100644 --- a/Sources/Encodings/PrintableWrapper+Base58.swift +++ b/Sources/Encodings/PrintableWrapper+Base58.swift @@ -27,14 +27,7 @@ extension Printable_PrintableWrapper { } func base58EncodedString() -> String { - let serialized: Data - do { - serialized = try serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } - + let serialized = serializedDataInfallible return serialized.asMcBuffer { bufferPtr in String(mcString: withMcInfallibleReturningOptional { mc_printable_wrapper_b58_encode(bufferPtr) diff --git a/Sources/Fog/FogKeyImageChecker.swift b/Sources/Fog/FogKeyImageChecker.swift index 510793bf..5958256d 100644 --- a/Sources/Fog/FogKeyImageChecker.swift +++ b/Sources/Fog/FogKeyImageChecker.swift @@ -50,7 +50,6 @@ struct FogKeyImageChecker { maxKeyImagesPerQuery: Int, completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void ) { - logger.info("") let queryArrays = keyImageQueries.chunked(maxLength: maxKeyImagesPerQuery).map { Array($0) } queryArrays.mapAsync({ chunk, callback in checkKeyImages(keyImageQueries: chunk, completion: callback) @@ -72,7 +71,6 @@ struct FogKeyImageChecker { keyImageQueries: [(KeyImage, nextKeyImageQueryBlockIndex: UInt64)], completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void ) { - logger.info("") var request = FogLedger_CheckKeyImagesRequest() request.queries = keyImageQueries.map { var query = FogLedger_KeyImageQuery() diff --git a/Sources/Fog/FogMerkleProofFetcher.swift b/Sources/Fog/FogMerkleProofFetcher.swift index e1dc4170..e98b390f 100644 --- a/Sources/Fog/FogMerkleProofFetcher.swift +++ b/Sources/Fog/FogMerkleProofFetcher.swift @@ -52,9 +52,9 @@ struct FogMerkleProofFetcher { globalIndicesArray.map { globalIndices in guard let results = allResults[globalIndices] else { return .failure(.connectionError(.invalidServerResponse( - "global txout indices not found in " + - "GetOutputs reponse. globalTxOutIndices: \(globalIndices), returned " + - "outputs: \(allResults)"))) + "Global txout indices not found in GetOutputs reponse. " + + "globalTxOutIndices: \(globalIndices), returned outputs: " + + "\(allResults)"))) } return .success(results) }.collectResult() @@ -70,7 +70,6 @@ struct FogMerkleProofFetcher { Result<[UInt64: (TxOut, TxOutMembershipProof)], FogMerkleProofFetcherError> ) -> Void ) { - logger.info("") let globalIndicesArrays = globalIndices.chunked(maxLength: maxNumIndicesPerQuery).map { Array($0) } globalIndicesArrays.mapAsync({ chunk, callback in @@ -91,7 +90,6 @@ struct FogMerkleProofFetcher { Result<[UInt64: (TxOut, TxOutMembershipProof)], FogMerkleProofFetcherError> ) -> Void ) { - logger.info("") var request = FogLedger_GetOutputsRequest() request.indices = globalIndices request.merkleRootBlock = merkleRootBlock @@ -108,29 +106,37 @@ struct FogMerkleProofFetcher { response.results.map { outputResult in switch outputResult.resultCodeEnum { case .exists: - guard let txOut = TxOut(outputResult.output), - let membershipProof = TxOutMembershipProof(outputResult.proof) - else { - logger.info("FAILURE: returned invalid result") - return .failure(.connectionError(.invalidServerResponse( - "FogMerkleProofService.getOutputs returned " + - "invalid result."))) - } - logger.info("SUCCESS: txOut: \(redacting: txOut.publicKey.data)") - return .success((outputResult.index, (txOut, membershipProof))) + break case .doesNotExist: - logger.info("FAILURE: outOfBounds with " + - "blockCount: \(response.numBlocks), " + - "ledgerTxOutCount: \(response.globalTxoCount)") return .failure(.outOfBounds( blockCount: response.numBlocks, ledgerTxOutCount: response.globalTxoCount)) case .outputDatabaseError, .intentionallyUnused, .UNRECOGNIZED: - logger.info("FAILURE: invalidServerResponse") return .failure(.connectionError(.invalidServerResponse( - "Fog MerkleProof result error: \(outputResult.resultCodeEnum), response: " + - "\(response)"))) + "FogMerkleProofService.getOutputs result code error: " + + "\(outputResult.resultCodeEnum), response: \(redacting: response)"))) + } + + let txOut: TxOut + switch TxOut.make(outputResult.output) { + case .success(let result): + txOut = result + case .failure(let error): + return .failure(.connectionError(.invalidServerResponse( + "FogMerkleProofService.getOutputs returned invalid TxOut. error: \(error)"))) } + + let membershipProof: TxOutMembershipProof + switch TxOutMembershipProof.make(outputResult.proof) { + case .success(let result): + membershipProof = result + case .failure(let error): + return .failure(.connectionError(.invalidServerResponse( + "FogMerkleProofService.getOutputs returned invalid membership proof. error: " + + "\(error)"))) + } + + return .success((outputResult.index, (txOut, membershipProof))) }.collectResult().map { Dictionary($0, uniquingKeysWith: { key1, _ in key1 }) } diff --git a/Sources/Fog/FogUntrustedTxOutFetcher.swift b/Sources/Fog/FogUntrustedTxOutFetcher.swift index a9b7a6ee..712d34a9a 100644 --- a/Sources/Fog/FogUntrustedTxOutFetcher.swift +++ b/Sources/Fog/FogUntrustedTxOutFetcher.swift @@ -41,7 +41,8 @@ struct FogUntrustedTxOutFetcher { Result<(results: [FogLedger_TxOutResult], blockCount: UInt64), ConnectionError> ) -> Void ) { - logger.info("outputPublicKeys: \(redacting: outputPublicKeys)") + logger.info( + "outputPublicKeys: \(redacting: outputPublicKeys.map { $0.hexEncodedString() })") var request = FogLedger_TxOutRequest() request.txOutPubkeys = outputPublicKeys.map { External_CompressedRistretto($0) } fogUntrustedTxOutService.getTxOuts(request: request) { diff --git a/Sources/Fog/Report/FogReportServer.swift b/Sources/Fog/Report/FogReportServer.swift index d697dc27..f106f22a 100644 --- a/Sources/Fog/Report/FogReportServer.swift +++ b/Sources/Fog/Report/FogReportServer.swift @@ -48,7 +48,6 @@ final class FogReportServer { reportService: FogReportService, completion: @escaping (Result) -> Void ) { - logger.info("") serialConnectionQueue.append({ callback in self.doFetchReports(reportService: reportService, completion: callback) }, completion: completion) @@ -80,7 +79,6 @@ final class FogReportServer { reportService: FogReportService, completion: @escaping (Result) -> Void ) { - logger.info("") reportService.getReports(request: Report_ReportRequest()) { guard let reportResponse = $0.successOr(completion: completion) else { return } @@ -97,7 +95,6 @@ final class FogReportServer { _ reportResponse: Report_ReportResponse, completion: @escaping () -> Void ) { - logger.info("") inner.accessAsync { $0.cacheReportResponse(reportResponse) @@ -114,9 +111,10 @@ extension FogReportServer { satisfyingReportParams reportParams: [(reportId: String, minPubkeyExpiry: UInt64)] ) -> Report_ReportResponse? { logger.info("reportParams: \(reportParams)") - guard let reportResponse = cachedReportResponse, - reportResponse.isValid(reportParams: reportParams) - else { + guard let reportResponse = cachedReportResponse else { + return nil + } + guard reportResponse.isValid(reportParams: reportParams) else { logger.info("report response invalid - reportParams: \(reportParams)") return nil } diff --git a/Sources/Fog/Report/FogResolver.swift b/Sources/Fog/Report/FogResolver.swift index 6d1ced34..39b3a7fc 100644 --- a/Sources/Fog/Report/FogResolver.swift +++ b/Sources/Fog/Report/FogResolver.swift @@ -43,14 +43,7 @@ final class FogResolver { private func addReportResponse(reportUrl: FogUrl, reportResponse: Report_ReportResponse) { logger.info("") - let serializedReportResponse: Data - do { - serializedReportResponse = try reportResponse.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } - + let serializedReportResponse = reportResponse.serializedDataInfallible serializedReportResponse.asMcBuffer { reportResponsePtr in switch withMcError({ errorPtr in mc_fog_resolver_add_report_response( diff --git a/Sources/Fog/View/FogRngSet.swift b/Sources/Fog/View/FogRngSet.swift index 42453e28..7910cebb 100644 --- a/Sources/Fog/View/FogRngSet.swift +++ b/Sources/Fog/View/FogRngSet.swift @@ -98,7 +98,7 @@ final class FogRngSet { { logger.info( "New RngRecord: ingestInvocationID: \(rngRecord.ingestInvocationID), pubkey: " + - "\(rngRecord.pubkey.pubkey.base64EncodedString()), version: " + + "\(rngRecord.pubkey.pubkey.hexEncodedString()), version: " + "\(rngRecord.pubkey.version), startBlockIndex: \(rngRecord.startBlock)", logFunction: false) @@ -124,7 +124,7 @@ final class FogRngSet { // more up-to-date information already). if highestProcessedBlockCount > rngRecordsKnownBlockCount { logger.info( - "FogRngSet updated rngRecordsKnownBlockCount from \(rngRecordsKnownBlockCount) " + + "FogRngSet updating rngRecordsKnownBlockCount from \(rngRecordsKnownBlockCount) " + "to \(highestProcessedBlockCount)", logFunction: false) rngRecordsKnownBlockCount = highestProcessedBlockCount @@ -194,7 +194,7 @@ final class FogRngSet { guard let txOutResult = searchKeyToTxOutResult[searchKeyBytes] else { logger.error( "Searched key not in search results. searched key: " + - "\(searchKeyBytes.base64EncodedString())", + "\(searchKeyBytes.hexEncodedString())", logFunction: false) return nil } @@ -273,7 +273,7 @@ private final class RngTracker { // else we can do with this rng. logger.debug( "Next rng output not found in searched keys. rng output: " + - "\(redacting: output.base64EncodedString())", + "0x\(redacting: output.hexEncodedString())", logFunction: false) break } diff --git a/Sources/Fog/View/FogView.swift b/Sources/Fog/View/FogView.swift index 1c8d9c16..8fb2e855 100644 --- a/Sources/Fog/View/FogView.swift +++ b/Sources/Fog/View/FogView.swift @@ -161,7 +161,7 @@ final class FogView { guard let knownTxOut = txOut.decrypt(accountKey: accountKey) else { logger.warning( "TxOut received from Fog View is not owned by this account. txOut: " + - "\(redacting: txOut.targetKey.data.base64EncodedString())", + "\(redacting: txOut.targetKey.data.hexEncodedString())", logFunction: false) return nil } diff --git a/Sources/Ledger/TxOut.swift b/Sources/Ledger/TxOut.swift index 06e11320..3ed64ded 100644 --- a/Sources/Ledger/TxOut.swift +++ b/Sources/Ledger/TxOut.swift @@ -15,20 +15,27 @@ struct TxOut: TxOutProtocol { /// - Returns: `nil` when the input is not deserializable. init?(serializedData: Data) { guard let proto = try? External_TxOut(serializedData: serializedData) else { - logger.warning("External_TxOut deserialization failed. serializedData: " + - "\(redacting: serializedData.base64EncodedString())") + logger.warning( + "External_TxOut deserialization failed. serializedData: " + + "\(redacting: serializedData.base64EncodedString())", + logFunction: false) + return nil + } + + switch TxOut.make(proto) { + case .success(let txOut): + self = txOut + case .failure(let error): + logger.warning( + "External_TxOut deserialization failed. serializedData: " + + "\(redacting: serializedData.base64EncodedString()), error: \(error)", + logFunction: false) return nil } - self.init(proto) } var serializedData: Data { - do { - return try proto.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } + proto.serializedDataInfallible } var maskedValue: UInt64 { proto.amount.maskedValue } @@ -39,13 +46,29 @@ extension TxOut: Equatable {} extension TxOut: Hashable {} extension TxOut { - init?(_ proto: External_TxOut) { - guard let commitment = Data32(proto.amount.commitment.data), - let targetKey = RistrettoPublic(proto.targetKey.data), - let publicKey = RistrettoPublic(proto.publicKey.data) - else { - return nil + static func make(_ proto: External_TxOut) -> Result { + guard let commitment = Data32(proto.amount.commitment.data) else { + return .failure( + InvalidInputError("Failed parsing External_TxOut: invalid commitment format")) } + guard let targetKey = RistrettoPublic(proto.targetKey.data) else { + return .failure( + InvalidInputError("Failed parsing External_TxOut: invalid target key format")) + } + guard let publicKey = RistrettoPublic(proto.publicKey.data) else { + return .failure( + InvalidInputError("Failed parsing External_TxOut: invalid public key format")) + } + return .success( + TxOut(proto: proto, commitment: commitment, targetKey: targetKey, publicKey: publicKey)) + } + + private init( + proto: External_TxOut, + commitment: Data32, + targetKey: RistrettoPublic, + publicKey: RistrettoPublic + ) { self.proto = proto self.commitment = commitment self.targetKey = targetKey diff --git a/Sources/Ledger/TxOutMembershipProof.swift b/Sources/Ledger/TxOutMembershipProof.swift index dcbd8a20..1877882f 100644 --- a/Sources/Ledger/TxOutMembershipProof.swift +++ b/Sources/Ledger/TxOutMembershipProof.swift @@ -8,8 +8,11 @@ import LibMobileCoin struct TxOutMembershipProof { let serializedData: Data - /// - Returns: `nil` when the input is not deserializable. - init?(serializedData: Data) { + static func make(serializedData: Data) -> Result { + .success(TxOutMembershipProof(serializedData: serializedData)) + } + + private init(serializedData: Data) { self.serializedData = serializedData } } @@ -18,14 +21,10 @@ extension TxOutMembershipProof: Equatable {} extension TxOutMembershipProof: Hashable {} extension TxOutMembershipProof { - init?(_ txOutMembershipProof: External_TxOutMembershipProof) { - let serializedData: Data - do { - serializedData = try txOutMembershipProof.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } - self.init(serializedData: serializedData) + static func make(_ txOutMembershipProof: External_TxOutMembershipProof) + -> Result + { + let serializedData = txOutMembershipProof.serializedDataInfallible + return TxOutMembershipProof.make(serializedData: serializedData) } } diff --git a/Sources/Ledger/TxOutUtils.swift b/Sources/Ledger/TxOutUtils.swift index c01c0b81..216bbacc 100644 --- a/Sources/Ledger/TxOutUtils.swift +++ b/Sources/Ledger/TxOutUtils.swift @@ -89,12 +89,11 @@ enum TxOutUtils { // Safety: This condition indicates a programming error and can only // happen if arguments to mc_tx_out_get_subaddress_spend_public_key are // supplied incorrectly. - logger.fatalError("error: \(error)") + logger.fatalError("error: \(redacting: error)") default: // Safety: mc_fog_resolver_add_report_response should not throw // non-documented errors. - logger.fatalError("Unhandled " + - "LibMobileCoin error: \(error)") + logger.fatalError("Unhandled LibMobileCoin error: \(redacting: error)") } } } @@ -136,11 +135,10 @@ enum TxOutUtils { case .invalidInput: // Safety: This condition indicates a programming error and can only // happen if arguments to mc_tx_out_get_value are supplied incorrectly. - logger.fatalError("error: \(error)") + logger.fatalError("error: \(redacting: error)") default: // Safety: mc_tx_out_get_value should not throw non-documented errors. - logger.fatalError("Unhandled " + - "LibMobileCoin error: \(error)") + logger.fatalError("Unhandled LibMobileCoin error: \(redacting: error)") } } } @@ -185,12 +183,12 @@ enum TxOutUtils { // Safety: This condition indicates a programming error and can only // happen if arguments to mc_tx_out_get_key_image are supplied // incorrectly. - logger.fatalError("error: \(error)") + logger.fatalError("error: \(redacting: error)") default: // Safety: mc_tx_out_get_key_image should not throw non-documented // errors. - logger.fatalError("Unhandled " + - "LibMobileCoin error: \(error)") + logger.fatalError( + "Unhandled LibMobileCoin error: \(redacting: error)") } } } diff --git a/Sources/LibMobileCoin/Data+withMcMutableBuffer.swift b/Sources/LibMobileCoin/Data+withMcMutableBuffer.swift index f92cfa47..93ae53f9 100644 --- a/Sources/LibMobileCoin/Data+withMcMutableBuffer.swift +++ b/Sources/LibMobileCoin/Data+withMcMutableBuffer.swift @@ -25,8 +25,8 @@ extension Data { }.map { numBytesWritten in guard numBytesWritten <= numBytes else { // This condition indicates a programming error. - logger.fatalError("Error: \(#function): numBytesWritten (\(numBytesWritten)) " + - "must be <= numBytes (\(numBytes))") + logger.fatalError( + "numBytesWritten (\(numBytesWritten)) must be <= numBytes (\(numBytes))") } return bytes.prefix(numBytesWritten) @@ -58,8 +58,9 @@ extension Data { }.map { numBytesReturned in guard numBytesReturned <= numBytes else { // This condition indicates a programming error. - logger.fatalError("Error: \(#function): Number of bytes returned from " + - "LibMobileCoin (\(numBytesReturned)) is greater than estimated (\(numBytes))") + logger.fatalError( + "Number of bytes returned from LibMobileCoin (\(numBytesReturned)) is " + + "greater than estimated (\(numBytes))") } return bytes.prefix(numBytesReturned) @@ -76,7 +77,7 @@ extension Data { } guard success else { // This condition indicates a programming error. - logger.fatalError("Error: \(#function): Infallible LibMobileCoin function failed.") + logger.fatalError("Infallible LibMobileCoin function failed.") } } @@ -85,7 +86,7 @@ extension Data { let numBytes = body(nil) guard numBytes >= 0 else { // This condition indicates a programming error. - logger.fatalError("Error: \(#function): Infallible LibMobileCoin function failed.") + logger.fatalError("Infallible LibMobileCoin function failed.") } var bytes = Data(repeating: 0, count: numBytes) @@ -94,13 +95,13 @@ extension Data { } guard numBytesWritten >= 0 else { // This condition indicates a programming error. - logger.fatalError("Error: \(#function): Infallible LibMobileCoin function failed.") + logger.fatalError("Infallible LibMobileCoin function failed.") } guard numBytesWritten <= numBytes else { // This condition indicates a programming error. - logger.fatalError("\(#function): numBytesWritten (\(numBytesWritten)) must be <= " + - "numBytes (\(numBytes))") + logger.fatalError( + "numBytesWritten (\(numBytesWritten)) must be <= numBytes (\(numBytes))") } self = bytes.prefix(numBytesWritten) @@ -114,8 +115,9 @@ extension Data { let numBytesReturned = bytes.asMcMutableBuffer(body) guard numBytesReturned <= numBytes else { // This condition indicates a programming error. - logger.fatalError("Error: \(#function): Number of bytes returned from LibMobileCoin " + - "\(numBytesReturned) is greater than estimated \(numBytes)") + logger.fatalError( + "Number of bytes returned from LibMobileCoin \(numBytesReturned) is greater than " + + "estimated \(numBytes)") } self = bytes.prefix(numBytesReturned) diff --git a/Sources/LibMobileCoin/Data32+withMcMutableBuffer.swift b/Sources/LibMobileCoin/Data32+withMcMutableBuffer.swift index 2984d231..b19f6370 100644 --- a/Sources/LibMobileCoin/Data32+withMcMutableBuffer.swift +++ b/Sources/LibMobileCoin/Data32+withMcMutableBuffer.swift @@ -32,8 +32,9 @@ extension Data32 { }.map { numBytesReturned in guard numBytesReturned == 32 else { // This condition indicates a programming error. - logger.fatalError("Error: LibMobileCoin function " + - "returned unexpected byte count (\(numBytesReturned)). Expected 32.") + logger.fatalError( + "LibMobileCoin function returned unexpected byte count " + + "(\(numBytesReturned)). Expected 32.") } return bytes } @@ -44,8 +45,7 @@ extension Data32 { asMcMutableBuffer { bufferPtr in guard body(bufferPtr) else { // This condition indicates a programming error. - logger.fatalError("Error: Infallible LibMobileCoin " + - "function failed.") + logger.fatalError("Infallible LibMobileCoin function failed.") } } } @@ -57,13 +57,13 @@ extension Data32 { } guard numBytesReturned > 0 else { // This condition indicates a programming error. - logger.fatalError("Error: Infallible LibMobileCoin " + - "function failed.") + logger.fatalError("Infallible LibMobileCoin function failed.") } guard numBytesReturned == 32 else { // This condition indicates a programming error. - logger.fatalError("Error: LibMobileCoin function returned " + - "unexpected byte count (\(numBytesReturned)). Expected 32.") + logger.fatalError( + "LibMobileCoin function returned unexpected byte count (\(numBytesReturned)). " + + "Expected 32.") } } } diff --git a/Sources/LibMobileCoin/ProtoExtensions.swift b/Sources/LibMobileCoin/ProtoExtensions.swift index f61bdc9c..b640ee50 100644 --- a/Sources/LibMobileCoin/ProtoExtensions.swift +++ b/Sources/LibMobileCoin/ProtoExtensions.swift @@ -84,15 +84,6 @@ extension FogView_TxOutSearchResult { } extension FogView_TxOutRecord { - var serializedDataInfallible: Data { - do { - return try serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } - } - var timestampDate: Date? { get { timestamp != UInt64.max ? Date(timeIntervalSince1970: TimeInterval(timestamp)) : nil } set { diff --git a/Sources/LibMobileCoin/ProtoTypes+InfallibleDataSerializable.swift b/Sources/LibMobileCoin/ProtoTypes+InfallibleDataSerializable.swift new file mode 100644 index 00000000..64762638 --- /dev/null +++ b/Sources/LibMobileCoin/ProtoTypes+InfallibleDataSerializable.swift @@ -0,0 +1,50 @@ +// swiftlint:disable:this file_name + +// +// Copyright (c) 2020-2021 MobileCoin. All rights reserved. +// + +import Foundation +import LibMobileCoin + +// MARK: - External + +extension External_AccountKey: InfallibleDataSerializable {} + +extension External_PublicAddress: InfallibleDataSerializable {} + +extension External_TxOutMembershipProof: InfallibleDataSerializable {} + +extension External_TxOut: InfallibleDataSerializable {} + +extension External_Tx: InfallibleDataSerializable {} + +extension External_Receipt: InfallibleDataSerializable {} + +// MARK: - Printable + +extension Printable_PrintableWrapper: InfallibleDataSerializable {} + +// MARK: - Attest + +extension Attest_Message: InfallibleDataSerializable {} + +// MARK: - Fog Report + +extension Report_ReportResponse: InfallibleDataSerializable {} + +// MARK: - Fog View + +extension FogView_QueryRequestAAD: InfallibleDataSerializable {} +extension FogView_QueryRequest: InfallibleDataSerializable {} +extension FogView_QueryResponse: InfallibleDataSerializable {} + +extension FogView_TxOutRecord: InfallibleDataSerializable {} + +// MARK: - Fog Ledger + +extension FogLedger_GetOutputsRequest: InfallibleDataSerializable {} +extension FogLedger_GetOutputsResponse: InfallibleDataSerializable {} + +extension FogLedger_CheckKeyImagesRequest: InfallibleDataSerializable {} +extension FogLedger_CheckKeyImagesResponse: InfallibleDataSerializable {} diff --git a/Sources/Mnemonic/AccountKey+Mnemonic.swift b/Sources/Mnemonic/AccountKey+Mnemonic.swift index 44632ac7..7db43614 100644 --- a/Sources/Mnemonic/AccountKey+Mnemonic.swift +++ b/Sources/Mnemonic/AccountKey+Mnemonic.swift @@ -12,8 +12,7 @@ extension AccountKey { fogAuthoritySpki: Data, accountIndex: UInt32 = 0 ) -> Result { - logger.info("") - return Bip39Utils.mnemonic(fromEntropy: entropy).flatMap { mnemonic in + Bip39Utils.mnemonic(fromEntropy: entropy).flatMap { mnemonic in make( mnemonic: mnemonic.phrase, fogReportUrl: fogReportUrl, @@ -30,8 +29,7 @@ extension AccountKey { fogAuthoritySpki: Data, accountIndex: UInt32 = 0 ) -> Result { - logger.info("") - return Slip10Utils.accountPrivateKeys(fromMnemonic: mnemonic, accountIndex: accountIndex) + Slip10Utils.accountPrivateKeys(fromMnemonic: mnemonic, accountIndex: accountIndex) .flatMap { AccountKey.make( viewPrivateKey: $0.viewPrivateKey, diff --git a/Sources/Mnemonic/Slip10Utils.swift b/Sources/Mnemonic/Slip10Utils.swift index 8da10b03..729b4e0f 100644 --- a/Sources/Mnemonic/Slip10Utils.swift +++ b/Sources/Mnemonic/Slip10Utils.swift @@ -12,7 +12,6 @@ enum Slip10Utils { static func accountPrivateKeys(fromMnemonic mnemonic: Mnemonic, accountIndex: UInt32) -> (viewPrivateKey: RistrettoPrivate, spendPrivateKey: RistrettoPrivate) { - logger.info("") var viewPrivateKeyOut = Data32() var spendPrivateKeyOut = Data32() viewPrivateKeyOut.asMcMutableBuffer { viewPrivateKeyOutPtr in @@ -53,7 +52,6 @@ enum Slip10Utils { -> Result<(viewPrivateKey: RistrettoPrivate, spendPrivateKey: RistrettoPrivate), InvalidInputError> { - logger.info("") var viewPrivateKeyOut = Data32() var spendPrivateKeyOut = Data32() return viewPrivateKeyOut.asMcMutableBuffer { viewPrivateKeyOutPtr in diff --git a/Sources/MobileCoinClient.swift b/Sources/MobileCoinClient.swift index 20f68c22..660bd4af 100644 --- a/Sources/MobileCoinClient.swift +++ b/Sources/MobileCoinClient.swift @@ -14,7 +14,7 @@ public final class MobileCoinClient { { guard let accountKey = AccountKeyWithFog(accountKey: accountKey) else { let errorMessage = "Accounts without fog URLs are not currently supported." - logger.error(errorMessage) + logger.error(errorMessage, logFunction: false) return .failure(InvalidInputError(errorMessage)) } @@ -387,7 +387,7 @@ extension MobileCoinClient { } catch { let errorMessage = "Error parsing trust root certificate: " + "\(trustRootBytes.base64EncodedString()) - Error: \(error)" - logger.error(errorMessage) + logger.error(errorMessage, logFunction: false) return .failure(InvalidInputError(errorMessage)) } } diff --git a/Sources/Network/Attestation/AttestAke.swift b/Sources/Network/Attestation/AttestAke.swift index e0259c7c..d103b35b 100644 --- a/Sources/Network/Attestation/AttestAke.swift +++ b/Sources/Network/Attestation/AttestAke.swift @@ -85,12 +85,7 @@ final class AttestAke { -> Result { guard case .authPending(let attestAke) = state else { - logger.info(""" - failure - Error: authEnd can only be called \ - when there is an auth pending. - """) - return .failure(.invalidInput("Error: authEnd can only be " + - "called when there is an auth pending.")) + return .failure(.invalidInput("AttestAke.authEnd called without a pending auth.")) } return attestAke.authEnd( diff --git a/Sources/Network/Attestation/Attestation.swift b/Sources/Network/Attestation/Attestation.swift index 2bfa1d0c..d5028607 100644 --- a/Sources/Network/Attestation/Attestation.swift +++ b/Sources/Network/Attestation/Attestation.swift @@ -86,12 +86,10 @@ extension Attestation { allowedHardeningAdvisories: [String] = [] ) -> Result { guard let mrEnclave32 = Data32(mrEnclave) else { - logger.info(""" - failure - mrEnclave must be 32 bytes in length. \ - \(mrEnclave.count) != 32 - """) - return .failure(InvalidInputError("mrEnclave must be " + - "32 bytes in length. \(mrEnclave.count) != 32")) + let errorMessage = "mrEnclave must be 32 bytes in length. mrEnclave: " + + "\(mrEnclave.hexEncodedString())" + logger.error(errorMessage, logFunction: false) + return .failure(InvalidInputError(errorMessage)) } return .success(MrEnclave( mrEnclave: mrEnclave32, @@ -141,12 +139,10 @@ extension Attestation { allowedHardeningAdvisories: [String] = [] ) -> Result { guard let mrSigner32 = Data32(mrSigner) else { - logger.info(""" - failure - mrSigner must be 32 bytes in length. \ - \(mrSigner.count) != 32 - """) - return .failure(InvalidInputError("mrSigner must be " + - "32 bytes in length. \(mrSigner.count) != 32")) + let errorMessage = + "mrSigner must be 32 bytes in length. mrSigner: \(mrSigner.hexEncodedString())" + logger.error(errorMessage, logFunction: false) + return .failure(InvalidInputError(errorMessage)) } return .success(MrSigner( diff --git a/Sources/Network/Connection/ArbitraryConnection.swift b/Sources/Network/Connection/ArbitraryConnection.swift index 40d7ebdc..f04dceb8 100644 --- a/Sources/Network/Connection/ArbitraryConnection.swift +++ b/Sources/Network/Connection/ArbitraryConnection.swift @@ -18,23 +18,42 @@ class ArbitraryConnection { request: Call.Request, completion: @escaping (Result) -> Void ) { - inner.accessAsync { - let callOptions = $0.requestCallOptions() - - call.call(request: request, callOptions: callOptions) { callResult in - self.inner.accessAsync { - completion($0.processResponse(callResult: callResult)) + func performCallCallback(callResult: UnaryCallResult) { + inner.accessAsync { + let result = $0.processResponse(callResult: callResult) + switch result { + case .success: + logger.info("Call complete. url: \($0.url)", logFunction: false) + case .failure(let connectionError): + let errorMessage = + "Connection failure. url: \($0.url), error: \(connectionError)" + switch connectionError { + case .connectionFailure, .serverRateLimited: + logger.warning(errorMessage, logFunction: false) + case .authorizationFailure, .invalidServerResponse, + .attestationVerificationFailed, .outdatedClient: + logger.error(errorMessage, logFunction: false) + } } + completion(result) } } + inner.accessAsync { + logger.info("Performing call... url: \($0.url)", logFunction: false) + + let callOptions = $0.requestCallOptions() + call.call(request: request, callOptions: callOptions, completion: performCallCallback) + } } } extension ArbitraryConnection { private struct Inner { + let url: MobileCoinUrlProtocol private let session: ConnectionSession init(url: MobileCoinUrlProtocol) { + self.url = url self.session = ConnectionSession(url: url) } diff --git a/Sources/Network/Connection/AttestedConnection.swift b/Sources/Network/Connection/AttestedConnection.swift index 73bc716c..d3b7a9a6 100644 --- a/Sources/Network/Connection/AttestedConnection.swift +++ b/Sources/Network/Connection/AttestedConnection.swift @@ -2,8 +2,8 @@ // Copyright (c) 2020-2021 MobileCoin. All rights reserved. // -// swiftlint:disable function_body_length function_parameter_count multiline_function_chains -// swiftlint:disable operator_usage_whitespace +// swiftlint:disable closure_body_length cyclomatic_complexity function_body_length +// swiftlint:disable multiline_function_chains operator_usage_whitespace import Foundation import GRPC @@ -107,6 +107,7 @@ extension AttestedConnection { // This means that calls to `AttestedConnection.Inner` can assume thread-safety until the call // invokes the completion handler. private struct Inner { + private let url: MobileCoinUrlProtocol private let session: ConnectionSession private let client: AttestableGrpcClient private let attestAke: AttestAke @@ -122,6 +123,7 @@ extension AttestedConnection { rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG, rngContext: Any? = nil ) { + self.url = config.url self.session = ConnectionSession(config: config) self.client = client self.attestAke = AttestAke() @@ -148,35 +150,23 @@ extension AttestedConnection { call, requestAad: requestAad, request: request, - attestAkeCipher: attestAke.cipher, - freshCipher: false - ) { - switch $0 { - case .success: - logger.info( - "Attested call successful. responderId: \(self.responderId)", - logFunction: false) - case .failure(let error): - logger.error("Failure performing attested call: \(error)", logFunction: false) - } - completion($0) - } + attestAkeCipher: attestAke.cipher.map { ($0, freshCipher: false) }, + completion: completion) } private func doPerformAttestedCallWithAuth( _ call: Call, requestAad: Call.InnerRequestAad, request: Call.InnerRequest, - attestAkeCipher: AttestAke.Cipher?, - freshCipher: Bool, + attestAkeCipher: (AttestAke.Cipher, freshCipher: Bool)?, completion: @escaping ( Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse), ConnectionError> ) -> Void ) { - if let attestAkeCipher = attestAkeCipher { + if let (attestAkeCipher, freshCipher) = attestAkeCipher { logger.info( - "Performing attested call... responderId: \(responderId)", + "Performing attested call... url: \(self.url)", logFunction: false) doPerformAttestedCall( @@ -187,47 +177,78 @@ extension AttestedConnection { ) { switch $0 { case .success(let response): + logger.info( + "Attested call successful. url: \(self.url)", + logFunction: false) + completion(.success(response)) case .failure(.connectionError(let connectionError)): + let errorMessage = "Connection failure while performing attested call. " + + "url: \(self.url), error: \(connectionError)" + switch connectionError { + case .connectionFailure, .serverRateLimited: + logger.warning(errorMessage, logFunction: false) + case .authorizationFailure, .invalidServerResponse, + .attestationVerificationFailed, .outdatedClient: + logger.error(errorMessage, logFunction: false) + } + completion(.failure(connectionError)) case .failure(.attestationFailure): self.attestAke.deattest() if freshCipher { - completion(.failure( - .invalidServerResponse("Attestation failure with fresh auth."))) + let errorMessage = + "Attestation failure with fresh auth. url: \(self.url)" + logger.warning(errorMessage, logFunction: false) + + completion(.failure(.invalidServerResponse(errorMessage))) } else { - logger.info("Attestation failure. Reattesting...", logFunction: false) + logger.info( + "Attestation failure using cached auth, reattesting... url: " + + "\(self.url)", + logFunction: false) self.doPerformAttestedCallWithAuth( call, requestAad: requestAad, request: request, attestAkeCipher: nil, - freshCipher: false, completion: completion) } } } } else { logger.info( - "Peforming attestation... responderId: \(responderId)", + "Peforming attestation... url: \(url)", logFunction: false) doPerformAuthCall { - guard let attestAkeCipher = $0.successOr(completion: completion) else { return } - - logger.info( - "Attestation successful. responderId: \(self.responderId)", - logFunction: false) + switch $0 { + case .success(let attestAkeCipher): + logger.info( + "Attestation successful. url: \(self.url)", + logFunction: false) + + self.doPerformAttestedCallWithAuth( + call, + requestAad: requestAad, + request: request, + attestAkeCipher: (attestAkeCipher, freshCipher: true), + completion: completion) + case .failure(let connectionError): + let errorMessage = "Connection failure while performing attestation. " + + "url: \(self.url), error: \(connectionError)" + switch connectionError { + case .connectionFailure, .serverRateLimited: + logger.warning(errorMessage, logFunction: false) + case .authorizationFailure, .invalidServerResponse, + .attestationVerificationFailed, .outdatedClient: + logger.error(errorMessage, logFunction: false) + } - self.doPerformAttestedCallWithAuth( - call, - requestAad: requestAad, - request: request, - attestAkeCipher: attestAkeCipher, - freshCipher: true, - completion: completion) + completion(.failure(connectionError)) + } } } } @@ -251,7 +272,9 @@ extension AttestedConnection { return connectionError case .attestationFailure: self.attestAke.deattest() - return .invalidServerResponse("Attestation failure during auth.") + + return .invalidServerResponse( + "Attestation failure during auth. url: \(self.url)") } }.flatMap { response in self.attestAke.authEnd( @@ -318,8 +341,7 @@ extension AttestedConnection { { // Basic credential authorization failure guard callResult.status.code != .unauthenticated else { - return .failure( - .connectionError(.authorizationFailure(String(describing: callResult.status)))) + return .failure(.connectionError(.authorizationFailure("url: \(url)"))) } // Attestation failure, reattest @@ -328,8 +350,8 @@ extension AttestedConnection { } guard callResult.status.isOk, let response = callResult.response else { - return .failure( - .connectionError(.connectionFailure(String(describing: callResult.status)))) + return .failure(.connectionError( + .connectionFailure("url: \(url), status: \(callResult.status)"))) } if let initialMetadata = callResult.initialMetadata { diff --git a/Sources/Network/Connection/Connection.swift b/Sources/Network/Connection/Connection.swift index 9acc50f3..39a1d067 100644 --- a/Sources/Network/Connection/Connection.swift +++ b/Sources/Network/Connection/Connection.swift @@ -24,15 +24,33 @@ class Connection { request: Call.Request, completion: @escaping (Result) -> Void ) { - inner.accessAsync { - let callOptions = $0.requestCallOptions() - - call.call(request: request, callOptions: callOptions) { callResult in - self.inner.accessAsync { - completion($0.processResponse(callResult: callResult)) + func performCallCallback(callResult: UnaryCallResult) { + inner.accessAsync { + let result = $0.processResponse(callResult: callResult) + switch result { + case .success: + logger.info("Call complete. url: \($0.url)", logFunction: false) + case .failure(let connectionError): + let errorMessage = + "Connection failure. url: \($0.url), error: \(connectionError)" + switch connectionError { + case .connectionFailure, .serverRateLimited: + logger.warning(errorMessage, logFunction: false) + case .authorizationFailure, .invalidServerResponse, + .attestationVerificationFailed, .outdatedClient: + logger.error(errorMessage, logFunction: false) + } } + completion(result) } } + + inner.accessAsync { + logger.info("Performing call... url: \($0.url)", logFunction: false) + + let callOptions = $0.requestCallOptions() + call.call(request: request, callOptions: callOptions, completion: performCallCallback) + } } func performCall( @@ -45,9 +63,11 @@ class Connection { extension Connection { private struct Inner { + let url: MobileCoinUrlProtocol private let session: ConnectionSession init(config: ConnectionConfigProtocol) { + self.url = config.url self.session = ConnectionSession(config: config) } @@ -65,11 +85,11 @@ extension Connection { -> Result { guard callResult.status.code != .unauthenticated else { - return .failure(.authorizationFailure(String(describing: callResult.status))) + return .failure(.authorizationFailure("url: \(url)")) } guard callResult.status.isOk, let response = callResult.response else { - return .failure(.connectionFailure(String(describing: callResult.status))) + return .failure(.connectionFailure("url: \(url), status: \(callResult.status)")) } if let initialMetadata = callResult.initialMetadata { diff --git a/Sources/Network/Connection/GrpcCallable/AttestedGrpcCallable.swift b/Sources/Network/Connection/GrpcCallable/AttestedGrpcCallable.swift index de90eec6..ba564ce8 100644 --- a/Sources/Network/Connection/GrpcCallable/AttestedGrpcCallable.swift +++ b/Sources/Network/Connection/GrpcCallable/AttestedGrpcCallable.swift @@ -63,7 +63,7 @@ extension AttestedGrpcCallable where InnerResponseAad == (), InnerResponse == Re extension AttestedGrpcCallable where InnerRequestAad == (), Request == Attest_Message, - InnerRequest: Message + InnerRequest: InfallibleDataSerializable { func processRequest( requestAad: InnerRequestAad, @@ -71,13 +71,7 @@ extension AttestedGrpcCallable attestAkeCipher: AttestAke.Cipher ) -> Result { let aad = Data() - let plaintext: Data - do { - plaintext = try request.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } + let plaintext = request.serializedDataInfallible return attestAkeCipher.encryptMessage(aad: aad, plaintext: plaintext) } @@ -93,46 +87,39 @@ extension AttestedGrpcCallable attestAkeCipher: AttestAke.Cipher ) -> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError> { guard response.aad == Data() else { - let errorMessage = "\(Self.self) received unexpected aad: " + - "\(redacting: response.aad.base64EncodedString()), message: \(redacting: response)" - logger.error(errorMessage) - return .failure(.connectionError(.invalidServerResponse(errorMessage))) + return .failure(.connectionError(.invalidServerResponse( + "\(Self.self) received unexpected aad: " + + "\(redacting: response.aad.base64EncodedString()), message: " + + "\(redacting: response.serializedDataInfallible.base64EncodedString())"))) } return attestAkeCipher.decryptMessage(response) .mapError { _ in .attestationFailure() } .flatMap { plaintext in guard let response = try? InnerResponse(serializedData: plaintext) else { - let errorMessage = "Failed to deserialized attested message plaintext into " + - "\(InnerResponse.self). serializedData: " + - "\(redacting: plaintext.base64EncodedString())" - logger.error(errorMessage) - return .failure(.connectionError(.invalidServerResponse(errorMessage))) + return .failure(.connectionError(.invalidServerResponse( + "Failed to deserialized attested message plaintext into " + + "\(InnerResponse.self). plaintext: " + + "\(redacting: plaintext.base64EncodedString())"))) } + return .success((responseAad: (), response: response)) } } } extension AttestedGrpcCallable - where InnerRequestAad: Message, + where InnerRequestAad: InfallibleDataSerializable, Request == Attest_Message, - InnerRequest: Message + InnerRequest: InfallibleDataSerializable { func processRequest( requestAad: InnerRequestAad, request: InnerRequest, attestAkeCipher: AttestAke.Cipher ) -> Result { - let aad: Data - let plaintext: Data - do { - aad = try requestAad.serializedData() - plaintext = try request.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } + let aad = requestAad.serializedDataInfallible + let plaintext = request.serializedDataInfallible return attestAkeCipher.encryptMessage(aad: aad, plaintext: plaintext) } @@ -147,18 +134,22 @@ extension AttestedGrpcCallable response: Attest_Message, attestAkeCipher: AttestAke.Cipher ) -> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError> { - attestAkeCipher.decryptMessage(response) + guard let responseAad = try? InnerResponseAad(serializedData: response.aad) else { + return .failure(.connectionError(.invalidServerResponse( + "Failed to deserialized attested message aad into \(InnerResponseAad.self). aad: " + + "\(redacting: response.aad.base64EncodedString())"))) + } + + return attestAkeCipher.decryptMessage(response) .mapError { _ in .attestationFailure() } .flatMap { plaintext in - guard let plaintextResponse = try? InnerResponse(serializedData: plaintext), - let responseAad = try? InnerResponseAad(serializedData: response.aad) - else { - let errorMessage = "Failed to deserialized attested message plaintext using " + - "\(InnerResponse.self) and \(InnerResponseAad.self). serializedData: " + - "\(redacting: plaintext.base64EncodedString())" - logger.error(errorMessage) - return .failure(.connectionError(.invalidServerResponse(errorMessage))) + guard let plaintextResponse = try? InnerResponse(serializedData: plaintext) else { + return .failure(.connectionError(.invalidServerResponse( + "Failed to deserialized attested message plaintext into " + + "\(InnerResponse.self). plaintext: " + + "\(redacting: plaintext.base64EncodedString())"))) } + return .success((responseAad: responseAad, response: plaintextResponse)) } } diff --git a/Sources/Network/Url/MobileCoinUrl.swift b/Sources/Network/Url/MobileCoinUrl.swift index 1d83fddc..34258bba 100644 --- a/Sources/Network/Url/MobileCoinUrl.swift +++ b/Sources/Network/Url/MobileCoinUrl.swift @@ -12,7 +12,7 @@ protocol Scheme { static var defaultInsecurePort: Int { get } } -protocol MobileCoinUrlProtocol { +protocol MobileCoinUrlProtocol: CustomStringConvertible { var url: URL { get } var host: String { get } var port: Int { get } @@ -96,3 +96,9 @@ struct MobileCoinUrl: MobileCoinUrlProtocol { extension MobileCoinUrl: Equatable {} extension MobileCoinUrl: Hashable {} + +extension MobileCoinUrl: CustomStringConvertible { + var description: String { + url.description + } +} diff --git a/Sources/Transaction/Inputs/DefaultMixinSelectionStrategy.swift b/Sources/Transaction/Inputs/DefaultMixinSelectionStrategy.swift index 5489d922..92125d0b 100644 --- a/Sources/Transaction/Inputs/DefaultMixinSelectionStrategy.swift +++ b/Sources/Transaction/Inputs/DefaultMixinSelectionStrategy.swift @@ -19,7 +19,6 @@ final class DefaultMixinSelectionStrategy: MixinSelectionStrategy { excludedTxOutIndices: [UInt64], ringSize: Int ) -> [Set] { - logger.info("") // Ensure selectionRange width is at least as large as the intended selection window width, // otherwise disable selectionRange. let selectionRange = diff --git a/Sources/Transaction/Inputs/DefaultTxOutSelectionStrategy.swift b/Sources/Transaction/Inputs/DefaultTxOutSelectionStrategy.swift index 432c8d7c..89ce98de 100644 --- a/Sources/Transaction/Inputs/DefaultTxOutSelectionStrategy.swift +++ b/Sources/Transaction/Inputs/DefaultTxOutSelectionStrategy.swift @@ -20,9 +20,7 @@ struct DefaultTxOutSelectionStrategy: TxOutSelectionStrategy { ) -> Result { let txOutValues = txOuts.map { $0.value } if txOutValues.allSatisfy({ $0 == 0 }) { - logger.warning( - "Tried to calculate amountTransferable with 0 balance", - logFunction: false) + logger.info("Calculating amountTransferable with 0 balance", logFunction: false) return .success(0) } @@ -32,8 +30,8 @@ struct DefaultTxOutSelectionStrategy: TxOutSelectionStrategy { maxInputsPerTransaction: maxInputsPerTransaction) guard UInt64.safeCompare(sumOfValues: txOutValues, isGreaterThanValue: totalFee) else { - logger.error( - "Fee is equal to or greater than balance. txOut values: " + + logger.warning( + "amountTransferable: Fee is equal to or greater than balance. txOut values: " + "\(redacting: txOutValues), totalFee: \(redacting: totalFee)", logFunction: false) return .failure(.feeExceedsBalance()) @@ -43,8 +41,8 @@ struct DefaultTxOutSelectionStrategy: TxOutSelectionStrategy { UInt64.safeSubtract(sumOfValues: txOutValues, minusValue: totalFee) else { logger.error( - "Balance minus fee exceeds UInt64.max. txOut values: \(redacting: txOutValues), " + - "totalFee: \(redacting: totalFee)", + "amountTransferable failure: Balance minus fee exceeds UInt64.max. txOut values: " + + "\(redacting: txOutValues), totalFee: \(redacting: totalFee)", logFunction: false) return .failure(.balanceOverflow()) } @@ -110,8 +108,8 @@ struct DefaultTxOutSelectionStrategy: TxOutSelectionStrategy { } }.flatMap { guard !$0.requiresDefrag else { - logger.warning( - "Select Transaction Inputs failed: defragmentation required. amount: " + + logger.error( + "Transaction input selection failed: defragmentation required. amount: " + "\(redacting: amount), txOut values: \(redacting: txOuts.map { $0.value })", logFunction: false) return .failure(.defragmentationRequired()) @@ -202,16 +200,12 @@ struct DefaultTxOutSelectionStrategy: TxOutSelectionStrategy { { // Success! Sum value of selectedTxOuts is enough to cover sendAmount + totalFee. let requiresDefrag = selectedTxOuts.count > maxInputsPerTransaction - logger.info( - "Estimated total fee successful. totalFee: \(redacting: totalFee), " + - "requiresDefrag: \(requiresDefrag)", - logFunction: false) return .success((totalFee, requiresDefrag)) } } // Insufficient balance to cover sendAmount + the cost of any required defragmentation. - logger.warning( + logger.error( "Estimate total fee failed: insufficient TxOuts. amountToSend: \(redacting: amount), " + "txOut values: \(redacting: txOuts.map { $0.value })", logFunction: false) @@ -255,7 +249,7 @@ struct DefaultTxOutSelectionStrategy: TxOutSelectionStrategy { while true { guard !availableTxOuts.isEmpty else { // Insufficient balance to cover amount + fee. - logger.warning( + logger.error( "Select TxOuts failed: insufficient TxOuts. amountToSend: " + "\(redacting: amount), txOut values: \(redacting: txOuts.map { $0.value })", logFunction: false) @@ -297,7 +291,7 @@ struct DefaultTxOutSelectionStrategy: TxOutSelectionStrategy { else { // We've selected the highest value TxOuts already which means the TxOuts don't // have enough total value to cover amount + fees. - logger.warning( + logger.error( "Select TxOuts failed: insufficient TxOuts. amountToSend: " + "\(redacting: amount), txOut values: " + "\(redacting: txOuts.map { $0.value })", @@ -510,6 +504,14 @@ struct DefaultTxOutSelectionStrategy: TxOutSelectionStrategy { numInputsToAdd -= 1 } + + guard !txOutsToAdd.isEmpty else { + return + } + + logger.info( + "Dust cleanup: adding \(txOutsToAdd.count) inputs to transaction", + logFunction: false) selectedTxOuts.append(contentsOf: txOutsToAdd) availableTxOuts.removeLast(txOutsToAdd.count) } diff --git a/Sources/Transaction/Inputs/PreparedTxInput.swift b/Sources/Transaction/Inputs/PreparedTxInput.swift index 5e38d135..77315981 100644 --- a/Sources/Transaction/Inputs/PreparedTxInput.swift +++ b/Sources/Transaction/Inputs/PreparedTxInput.swift @@ -14,11 +14,11 @@ struct PreparedTxInput { guard let realInputIndex = ring.firstIndex(where: { $0.0.publicKey == knownTxOut.publicKey }) else { - logger.info("failure - txOut not found in ring") - return .failure(InvalidInputError("TxOut not found in ring")) + let errorMessage = "PreparedTxInput.make: TxOut not found in ring" + logger.error(errorMessage, logFunction: false) + return .failure(InvalidInputError(errorMessage)) } - logger.info("success") return .success( PreparedTxInput(knownTxOut: knownTxOut, ring: ring, realInputIndex: realInputIndex)) } diff --git a/Sources/Transaction/Receipt.swift b/Sources/Transaction/Receipt.swift index 2af8eb94..a628ff52 100644 --- a/Sources/Transaction/Receipt.swift +++ b/Sources/Transaction/Receipt.swift @@ -44,8 +44,10 @@ public struct Receipt { /// - Returns: `nil` when the input is not deserializable. public init?(serializedData: Data) { guard let proto = try? External_Receipt(serializedData: serializedData) else { - logger.warning("External_Receipt deserialization failed. serializedData: " + - "\(redacting: serializedData.base64EncodedString())") + logger.warning( + "External_Receipt deserialization failed. serializedData: " + + "\(redacting: serializedData.base64EncodedString())", + logFunction: false) return nil } self.init(proto) @@ -53,12 +55,7 @@ public struct Receipt { public var serializedData: Data { let proto = External_Receipt(self) - do { - return try proto.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } + return proto.serializedDataInfallible } /// Public key of the `TxOut` that this `Receipt` represents, in bytes. @@ -67,22 +64,19 @@ public struct Receipt { } func matchesTxOut(_ txOut: TxOutProtocol) -> Bool { - logger.info("") - return txOutPublicKeyTyped == txOut.publicKey + txOutPublicKeyTyped == txOut.publicKey && commitment == txOut.commitment && maskedValue == txOut.maskedValue } func validateConfirmationNumber(accountKey: AccountKey) -> Bool { - logger.info("") - return TxOutUtils.validateConfirmationNumber( + TxOutUtils.validateConfirmationNumber( publicKey: txOutPublicKeyTyped, confirmationNumber: confirmationNumber, viewPrivateKey: accountKey.viewPrivateKey) } func unmaskValue(accountKey: AccountKey) -> Result { - logger.info("") guard let value = TxOutUtils.value( commitment: commitment, maskedValue: maskedValue, @@ -104,7 +98,6 @@ public struct Receipt { /// subaddress of the `accountKey`, but not which one. @discardableResult public func validateAndUnmaskValue(accountKey: AccountKey) -> UInt64? { - logger.info("") guard validateConfirmationNumber(accountKey: accountKey) else { return nil } @@ -146,7 +139,10 @@ extension Receipt { let commitment = Data32(proto.amount.commitment.data), let confirmationNumber = TxOutConfirmationNumber(proto.confirmation) else { - logger.warning("Failed to initialize Receipt with External_Receipt") + logger.warning( + "Failed to initialize Receipt with External_Receipt. serialized proto: " + + "\(redacting: proto.serializedDataInfallible.base64EncodedString())", + logFunction: false) return nil } diff --git a/Sources/Transaction/ReceiptStatusChecker.swift b/Sources/Transaction/ReceiptStatusChecker.swift index 0d712fa8..4b64936d 100644 --- a/Sources/Transaction/ReceiptStatusChecker.swift +++ b/Sources/Transaction/ReceiptStatusChecker.swift @@ -12,6 +12,14 @@ struct ReceiptStatusChecker { } func status(_ receipt: Receipt) -> Result { - account.readSync { $0.cachedReceivedStatus(of: receipt) }.map { ReceiptStatus($0) } + let result = account.readSync { $0.cachedReceivedStatus(of: receipt) } + .map { ReceiptStatus($0) } + logger.info( + "Receipt status check complete. receipt.txOutPublicKey: " + + "\(redacting: receipt.txOutPublicKey.hexEncodedString()), " + + "receipt.txTombstoneBlockIndex: \(redacting: receipt.txTombstoneBlockIndex), " + + "result: \(redacting: result)", + logFunction: false) + return result } } diff --git a/Sources/Transaction/Transaction.swift b/Sources/Transaction/Transaction.swift index 0c779d12..7103505a 100644 --- a/Sources/Transaction/Transaction.swift +++ b/Sources/Transaction/Transaction.swift @@ -26,12 +26,7 @@ public struct Transaction { } public var serializedData: Data { - do { - return try proto.serializedData() - } catch { - // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. - logger.fatalError("Protobuf serialization failed: \(redacting: error)") - } + proto.serializedDataInfallible } var anyInputKeyImage: KeyImage { @@ -115,11 +110,13 @@ extension Transaction { var outputs: [TxOut] = [] for output in proto.prefix.outputs { - guard let txOut = TxOut(output) else { - logger.warning("External_Tx contains an invalid output") + switch TxOut.make(output) { + case .success(let txOut): + outputs.append(txOut) + case .failure(let error): + logger.warning("External_Tx contains an invalid output. error: \(error)") return nil } - outputs.append(txOut) } self.outputs = Set(outputs) } diff --git a/Sources/Transaction/TransactionBuilder.swift b/Sources/Transaction/TransactionBuilder.swift index 31f92f2d..e983d267 100644 --- a/Sources/Transaction/TransactionBuilder.swift +++ b/Sources/Transaction/TransactionBuilder.swift @@ -64,8 +64,7 @@ final class TransactionBuilder { rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG, rngContext: Any? = nil ) -> Result<(transaction: Transaction, receipt: Receipt), TransactionBuilderError> { - logger.info("") - return positiveRemainingAmount(inputValues: inputs.map { $0.knownTxOut.value }, fee: fee) + positiveRemainingAmount(inputValues: inputs.map { $0.knownTxOut.value }, fee: fee) .flatMap { outputAmount in build( inputs: inputs, @@ -121,7 +120,6 @@ final class TransactionBuilder { rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG, rngContext: Any? = nil ) -> Result<(transaction: Transaction, receipts: [Receipt]), TransactionBuilderError> { - logger.info("") guard UInt64.safeCompare( sumOfValues: inputs.map { $0.knownTxOut.value }, isEqualToSumOfValues: outputs.map { $0.amount.value } + [fee]) @@ -181,7 +179,6 @@ final class TransactionBuilder { rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?, rngContext: Any? ) -> Result<(txOut: TxOut, receipt: Receipt), TransactionBuilderError> { - logger.info("") let transactionBuilder = TransactionBuilder( fee: 0, tombstoneBlockIndex: tombstoneBlockIndex, @@ -322,7 +319,6 @@ final class TransactionBuilder { rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?, rngContext: Any? ) -> Result<(txOut: TxOut, receipt: Receipt), TransactionBuilderError> { - logger.info("") var confirmationNumberData = Data32() return publicAddress.withUnsafeCStructPointer { publicAddressPtr in withMcRngCallback(rng: rng, rngContext: rngContext) { rngCallbackPtr in @@ -353,7 +349,8 @@ final class TransactionBuilder { guard let txOut = TxOut(serializedData: txOutData) else { // Safety: mc_transaction_builder_add_output should always return valid data on // success. - logger.fatalError("mc_transaction_builder_add_output returned invalid data.") + logger.fatalError("mc_transaction_builder_add_output returned invalid data: " + + "\(redacting: txOutData.base64EncodedString())") } let confirmationNumber = TxOutConfirmationNumber(confirmationNumberData) @@ -369,8 +366,7 @@ final class TransactionBuilder { rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?, rngContext: Any? ) -> Result { - logger.info("") - return withMcRngCallback(rng: rng, rngContext: rngContext) { rngCallbackPtr in + withMcRngCallback(rng: rng, rngContext: rngContext) { rngCallbackPtr in Data.make(withMcDataBytes: { errorPtr in mc_transaction_builder_build(ptr, rngCallbackPtr, &errorPtr) }).mapError { @@ -385,7 +381,8 @@ final class TransactionBuilder { }.map { txBytes in guard let transaction = Transaction(serializedData: txBytes) else { // Safety: mc_transaction_builder_build should always return valid data on success. - logger.fatalError("mc_transaction_builder_build returned invalid data.") + logger.fatalError("mc_transaction_builder_build returned invalid data: " + + "\(redacting: txBytes.base64EncodedString())") } return transaction } diff --git a/Sources/Transaction/TransactionPreparer.swift b/Sources/Transaction/TransactionPreparer.swift index 96b1c28c..55c1ec82 100644 --- a/Sources/Transaction/TransactionPreparer.swift +++ b/Sources/Transaction/TransactionPreparer.swift @@ -89,7 +89,7 @@ struct TransactionPreparer { ) -> Void ) { guard amount > 0, let amount = PositiveUInt64(amount) else { - let errorMessage = "Cannot spend 0 MOB" + let errorMessage = "PrepareTransactionWithFee error: Cannot spend 0 MOB" logger.error(errorMessage, logFunction: false) serialQueue.async { completion(.failure(.invalidInput(errorMessage))) @@ -181,13 +181,15 @@ struct TransactionPreparer { case .failure(let error): switch error { case .connectionError(let connectionError): - logger.warning("failure - connection error: \(connectionError)") + logger.error("FetchMerkleProofs error: \(connectionError)", logFunction: false) completion(.failure(connectionError)) case let .outOfBounds(blockCount: blockCount, ledgerTxOutCount: responseTxOutCount): if let ledgerTxOutCount = ledgerTxOutCount { let errorMessage = "Fog GetMerkleProof returned doesNotExist, even though " + "txo indices were limited by globalTxoCount returned by previous call to " + - "GetMerkleProof. Previously returned globalTxoCount: \(ledgerTxOutCount)" + "GetMerkleProof. Previously returned globalTxoCount: " + + "\(ledgerTxOutCount), response globalTxoCount: " + + "\(responseTxOutCount), response blockCount: \(blockCount)" logger.error(errorMessage, logFunction: false) completion(.failure(.invalidServerResponse(errorMessage))) } else { diff --git a/Sources/Transaction/TransactionStatusChecker.swift b/Sources/Transaction/TransactionStatusChecker.swift index a59acb0c..e0ec193f 100644 --- a/Sources/Transaction/TransactionStatusChecker.swift +++ b/Sources/Transaction/TransactionStatusChecker.swift @@ -27,8 +27,10 @@ struct TransactionStatusChecker { _ transaction: Transaction, completion: @escaping (Result) -> Void ) { - logger.info("Checking transaction status...") - logger.info("transaction: \(redacting: transaction.serializedData)") + logger.info( + "Checking transaction status... transaction: " + + "\(redacting: transaction.serializedData.base64EncodedString())", + logFunction: false) checkAcceptedStatus(transaction) { completion($0.map { let status = TransactionStatus($0) @@ -51,7 +53,6 @@ struct TransactionStatusChecker { _ transaction: Transaction, completion: @escaping (Result) -> Void ) { - logger.info("") performAsync(body1: { callback in fogUntrustedTxOutFetcher.getTxOut( outputPublicKey: transaction.anyOutput.publicKey, diff --git a/Sources/Transaction/TransactionSubmitter.swift b/Sources/Transaction/TransactionSubmitter.swift index a2526e5c..1bd72c71 100644 --- a/Sources/Transaction/TransactionSubmitter.swift +++ b/Sources/Transaction/TransactionSubmitter.swift @@ -16,8 +16,10 @@ struct TransactionSubmitter { _ transaction: Transaction, completion: @escaping (Result<(), TransactionSubmissionError>) -> Void ) { - logger.info("Submitting transaction...") - logger.info("transaction: \(redacting: transaction.serializedData)") + logger.info( + "Submitting transaction... transaction: " + + "\(redacting: transaction.serializedData.base64EncodedString())", + logFunction: false) consensusService.proposeTx(External_Tx(transaction)) { completion($0.mapError { .connectionError($0) }.flatMap { self.processResponse($0) }) } diff --git a/Sources/Utils/Async/AsyncUtils.swift b/Sources/Utils/Async/AsyncUtils.swift index 1db6cc17..0c7d051e 100644 --- a/Sources/Utils/Async/AsyncUtils.swift +++ b/Sources/Utils/Async/AsyncUtils.swift @@ -27,7 +27,7 @@ func performAsync( if OSAtomicIncrement32(&completedTaskCount) == 2 { guard let result1 = results.0, let result2 = results.1 else { // This condition should never be reached and indicates a programming error. - logger.fatalError("Results not ready: \(results)") + logger.fatalError("Results not ready: \(redacting: results)") } completion(.success((result1, result2))) } diff --git a/Sources/Utils/Async/Collection+Async.swift b/Sources/Utils/Async/Collection+Async.swift index cacc34a3..f3ea3157 100644 --- a/Sources/Utils/Async/Collection+Async.swift +++ b/Sources/Utils/Async/Collection+Async.swift @@ -40,9 +40,9 @@ extension Collection { let returnedResults = results.compactMap { $0 } guard returnedResults.count == taskCount else { if OSAtomicIncrement32(&callbackFailureInvoked) == 1 { - logger.fatalError("Error: " + + logger.fatalError( "returnedResults.count (\(returnedResults.count)) != " + - "taskCount (\(taskCount)), results: \(results)") + "taskCount (\(taskCount)), results: \(redacting: results)") } return } diff --git a/Sources/Utils/Serialization/InfallibleDataSerializable.swift b/Sources/Utils/Serialization/InfallibleDataSerializable.swift new file mode 100644 index 00000000..d5ec41e1 --- /dev/null +++ b/Sources/Utils/Serialization/InfallibleDataSerializable.swift @@ -0,0 +1,21 @@ +// +// Copyright (c) 2020-2021 MobileCoin. All rights reserved. +// + +import Foundation +import SwiftProtobuf + +protocol InfallibleDataSerializable { + var serializedDataInfallible: Data { get } +} + +extension InfallibleDataSerializable where Self: Message { + var serializedDataInfallible: Data { + do { + return try serializedData() + } catch { + // Safety: Protobuf binary serialization is no fail when not using proto2 or `Any`. + logger.fatalError("Protobuf serialization failed: \(redacting: error)") + } + } +} diff --git a/Tests/Common/Fixtures/Transaction/Transaction+Fixtures.swift b/Tests/Common/Fixtures/Transaction/Transaction+Fixtures.swift index 26c8844f..971e91a9 100644 --- a/Tests/Common/Fixtures/Transaction/Transaction+Fixtures.swift +++ b/Tests/Common/Fixtures/Transaction/Transaction+Fixtures.swift @@ -84,15 +84,15 @@ extension Transaction.Fixtures.BuildTx { lCjsDfrBV4Tu3HRm4= """)!)))) - let ring: [(TxOut, TxOutMembershipProof)] = [ + let ring: [(TxOut, TxOutMembershipProof)] = try [ ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgoguImiYd/FgPnNUbRkBu5+F61QNO4DXF8NNCPIzKy/2UARV6YtFOobDygSIgogVECBlIdhtmT\ FaXtlWphlqELpDL04EKMbbPWu3CoJ2UEaIgogOHyDzGA0vvts1Rkgsb2sAYfgCTBQqnOQ4cz5iI7JSh\ 4iVgpUFDnmnE50RaxomaHM2Pr6R2NTrMdK4wEILd6fCqLjf8p2PgDbnphk2sEBUpKqf4broDg2qx9MN\ 31M5GBQVcVK+BUdpY4T4wkT4SU/LFNJqZVWfwEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CKuNBhCumQkaLgoICKuNBhCrjQYSIgogGKFP8jdRPcBssJ3qH60iJ8gXnqwn3uziW/KyknkifGsaLgo\ ICKqNBhCqjQYSIgogjK36vJ1F+3wX1tq9ps8k9Z/cb5IS0PT20J489/mVOHsaLgoICKiNBhCpjQYSIg\ ogRiPZVtRxM4VBd2WFb8WY3E62qxn4IN6W1kKKE/PBBM4aLgoICKyNBhCvjQYSIgogze9YatBYcx5/G\ @@ -109,16 +109,16 @@ extension Transaction.Fixtures.BuildTx { ZSNRbWRcaLgoICICABBD//wUSIgogHHj77MOGpM4oFjdG2Mke1MkKp4t/YVkTvYLzyvpEaJ0aKgoEEP\ //AxIiCiDHA1UmtYroaXdipx+2xfiOad6aEFbtzhfEu2uTUXPM6xouCggIgIAIEP//DxIiCiChCfLXM\ 66iL1JTrn5mPeS2lNGO9oJ5dBzqDCvBKIi+dg== - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgogZLMpTUm3codYcrMIiokeWe7kNYy5lWDA5ykOwYV9AgURY2zBa0WXvVISIgogunrz4fo0z/k\ 04C/7q6z6ffHTcLMHgXku3Lx7IK6xnEIaIgogOqF+vLrpwQFhJv657RJmG258vv7lhxhvfZ99f98TCH\ 0iVgpUZSSUOAosJNxrMGlwOkwFbclxoPMjz/Mg7vjzmNjA+RcEhWxMRXxryIJt8bnYrWlbbRxhskeP4\ uCn6zzsslAxWXY5FnE87nfJ628u7Ti3AozxdgEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAkQrpkJGioKBAgJEAkSIgognKcDx/hu4iAlKyWkdCwgIYagjCWGZzVfCM/naGPvAZ4aKgoECAgQCBI\ iCiBAr62n9cfbTlmKxQkNEfWZNmLxi+hblOFG6/G/P/d18hoqCgQIChALEiIKIIn5OYPIj2hUREaxG8\ AvwpPWjBeUwZ2gV4wInAB88BnRGioKBAgMEA8SIgogTxjaTwHcjvTJsYuG6v4/ccOxJ5pP/R6s8uXOd\ @@ -134,16 +134,16 @@ extension Transaction.Fixtures.BuildTx { PrtDnSPrngR2C0aF4J10Gi4KCAiAgAIQ//8DEiIKIE7l402lC0ruffB1VkIQKe+2f6Sh/hE5gUDmY5Z\ xKIK7Gi4KCAiAgAQQ//8HEiIKIOTq52imyqFiZjil2jHXNtBx2BKZxSmw944x7i68ljgFGi4KCAiAgA\ gQ//8PEiIKIKEJ8tczrqIvUlOufmY95LaU0Y72gnl0HOoMK8EoiL52 - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgogRJwm18iyHr2hCjCq/QvGlbFST27ImGBUct9rcgn1XUgR1nrVJg0ckf4SIgogatL2qq9Cb3l\ fO5c4/V9suoxwijtYAyWiMoCZdA7RhQYaIgogOsOx3pQ5RaFVZNCatfQtygmFZxJjefWX9mB1DlrCrz\ oiVgpUKGCpebKIWBiTC4O1ykKPTyscIG+ZuQpHkJ7FXCh7//gF9mqsw7NnaFvhAKUSo9r6FGmyXis7x\ b2NisTiU8eEDnp3u+9Ytg8Fh4UqUQTQdWJPAAEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAMQrpkJGioKBAgDEAMSIgogGAK3qiA9ptHNKzPRElCq9+8mPrwUjQSpSvamEgtlcOkaKgoECAIQAhI\ iCiBWq8jIqCOu7SgBZv5DeOqnDAmEtlcbgIpkul7BgOnXIRooCgIQARIiCiDQPZiR2FSBtq2dlZmagn\ b6LbeW4BSCgg62mFTbgYjakBoqCgQIBBAHEiIKIJ0prgNhwF3q1oSSwUUsrzg9J1uVxmWHHlMW4Y77S\ @@ -159,16 +159,16 @@ extension Transaction.Fixtures.BuildTx { PrtDnSPrngR2C0aF4J10Gi4KCAiAgAIQ//8DEiIKIE7l402lC0ruffB1VkIQKe+2f6Sh/hE5gUDmY5Z\ xKIK7Gi4KCAiAgAQQ//8HEiIKIOTq52imyqFiZjil2jHXNtBx2BKZxSmw944x7i68ljgFGi4KCAiAgA\ gQ//8PEiIKIKEJ8tczrqIvUlOufmY95LaU0Y72gnl0HOoMK8EoiL52 - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgog/ktfF7xz1dXNmdmgLtBSUQUlRaSc+7CpXsDLmxQC81YRIO/xU2mLJFkSIgog3NZ7my81+bq\ ZzH9mXKNzyrPhnIpo5kGcuHPLkpJcHAYaIgogTA5KRyuFu+RZuYZKP9i1l533CiZILz0NbNFGgMnc4V\ MiVgpULeJwjU4b5KANeMCR/aHm897cb5VuRVwLgYAQ/T+CbqkV3uoF8dE4q2TYgD5BpK2yLbaALjjze\ mMGgKDWWOyOBnBdYdD7WVDrCoWBC4XsClX8MwEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAUQrpkJGioKBAgFEAUSIgog5OBhinRIZNLnVkVoINdeEMttYbdsan9yQFrJ9C5upboaKgoECAQQBBI\ iCiDL5KsDoVcmtHNkKp1Ul6+5Ki3v/hFZ6bDvu4bW193pGBoqCgQIBhAHEiIKIBzdtDBABzwQ79Cvwo\ 2CkimC3N9lhLMIQbSojyNxMf/bGigKAhADEiIKIHzANt8/nHW7NYTk1eWF6LTv1bHBHIgXWDvGOYwFD\ @@ -184,16 +184,16 @@ extension Transaction.Fixtures.BuildTx { PrtDnSPrngR2C0aF4J10Gi4KCAiAgAIQ//8DEiIKIE7l402lC0ruffB1VkIQKe+2f6Sh/hE5gUDmY5Z\ xKIK7Gi4KCAiAgAQQ//8HEiIKIOTq52imyqFiZjil2jHXNtBx2BKZxSmw944x7i68ljgFGi4KCAiAgA\ gQ//8PEiIKIKEJ8tczrqIvUlOufmY95LaU0Y72gnl0HOoMK8EoiL52 - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgogDNohyGL2/DCezioQv/Ue1x2LpcZ9s2y13CgNFxcaVVgR1U5EWVS1yysSIgogOttVHolhls9\ EpQxuHCBg3zT2u7swbIKKbVr01w/VxXcaIgogWGzYZTA45PoK1UeBPL7egAn3v9ZvT8VIm/8Zgy0HR1\ AiVgpUv9dqZZmbYvab6w++sl64TDN62Vpd7lqf+UMTUTYyl/e/9QYazT/nFbvA5M6i86EqcJWtwkbUM\ 7wqfqOYQ+iJwFFYULx3vRDVUri5nMHIFFmeZQEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAcQrpkJGioKBAgHEAcSIgogd7+1GO045m8/MwW8BzJjdAS37OF/f7EHno4rQF1znDsaKgoECAYQBhI\ iCiAI6gBZk5XeJfSfJgMIe6natxaQzBXkDybhkupEVasD4RoqCgQIBBAFEiIKIK0BPLZ1ashlqRI0fI\ ofhQShArbArY01cy4/kmSsbnhVGigKAhADEiIKIHzANt8/nHW7NYTk1eWF6LTv1bHBHIgXWDvGOYwFD\ @@ -209,16 +209,16 @@ extension Transaction.Fixtures.BuildTx { PrtDnSPrngR2C0aF4J10Gi4KCAiAgAIQ//8DEiIKIE7l402lC0ruffB1VkIQKe+2f6Sh/hE5gUDmY5Z\ xKIK7Gi4KCAiAgAQQ//8HEiIKIOTq52imyqFiZjil2jHXNtBx2BKZxSmw944x7i68ljgFGi4KCAiAgA\ gQ//8PEiIKIKEJ8tczrqIvUlOufmY95LaU0Y72gnl0HOoMK8EoiL52 - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgogMhh2S6d1d1GgBwBTiZH5FWpKzOEBRRkoQ7cei0wRcCgRMwLJvTnC0coSIgogGgdI8wsR4No\ BdekqxRwdKvkQbr12Oo+KlEn3crEIrzIaIgoghNKt6Nt6AVseYpqNFNVt3v/EEuaZDH5ceI+IyK4Wqk\ 8iVgpUxPI7yAPeAwmoD2wEQzQbB/o6CA0gMjN/PLk/kKrbKj/VhKRinppI/ze3jfPggE0fpIySqI0Zx\ F+yyjAtUMt8dAV262JISu+yiqIUnZhWaWZlswEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAIQrpkJGioKBAgCEAISIgogVqvIyKgjru0oAWb+Q3jqpwwJhLZXG4CKZLpewYDp1yEaKgoECAMQAxI\ iCiAYAreqID2m0c0rM9ESUKr37yY+vBSNBKlK9qYSC2Vw6RooCgIQARIiCiDQPZiR2FSBtq2dlZmagn\ b6LbeW4BSCgg62mFTbgYjakBoqCgQIBBAHEiIKIJ0prgNhwF3q1oSSwUUsrzg9J1uVxmWHHlMW4Y77S\ @@ -234,16 +234,16 @@ extension Transaction.Fixtures.BuildTx { PrtDnSPrngR2C0aF4J10Gi4KCAiAgAIQ//8DEiIKIE7l402lC0ruffB1VkIQKe+2f6Sh/hE5gUDmY5Z\ xKIK7Gi4KCAiAgAQQ//8HEiIKIOTq52imyqFiZjil2jHXNtBx2BKZxSmw944x7i68ljgFGi4KCAiAgA\ gQ//8PEiIKIKEJ8tczrqIvUlOufmY95LaU0Y72gnl0HOoMK8EoiL52 - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgogILXm9RU1TrCsCipooMnzY4R9vKFe6MDDkB0QuhtM2mkRsuQP0bOMA0ASIgogxpwvORW4PeS\ roRWZaaiOMv/g2AC+zp4T3mlxLOTMh0AaIgogmP9LhFw3IVtMASLLVRmvuFCTDVCQfuPKI7ObX7oo8S\ IiVgpUNzrW1dfY4HfZxQYOMakL3ewLjVlxJQzLcSvX0iRqfMpsxzCdxSYNIWDvzsew2R9lgaq1iX17K\ r0IdpROjXUPNUY9gUiTXm2zwECshrR6LVzY4gEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAEQrpkJGioKBAgBEAESIgogen5UJWgJCWh9t42T4vsiG80oY0EdPOOvuV/bdD/ECiUaJgoAEiIKIBc\ yF1mQ38h+9QTe0j61UMpoDBh+ibhrEeUcfxZR1gX6GioKBAgCEAMSIgogx5HG9np4mN6FRS9spOpee/\ DPmUNkucr1UFlDear3EVEaKgoECAQQBxIiCiCdKa4DYcBd6taEksFFLK84PSdblcZlhx5TFuGO+0jrY\ @@ -259,16 +259,16 @@ extension Transaction.Fixtures.BuildTx { Q50j654EdgtGheCddBouCggIgIACEP//AxIiCiBO5eNNpQtK7n3wdVZCECnvtn+kof4ROYFA5mOWcSi\ CuxouCggIgIAEEP//BxIiCiDk6udopsqhYmY4pdox1zbQcdgSmcUpsPeOMe4uvJY4BRouCggIgIAIEP\ //DxIiCiChCfLXM66iL1JTrn5mPeS2lNGO9oJ5dBzqDCvBKIi+dg== - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgogrr60Ucb4VG31yEnwQ5QPDp2aeHFLwAjEKpTxxY/cQWcRWh6K91+S5KsSIgogapTxe7NUEQj\ IlQiMdU7zQAElEpr/rIUTWY7qkzcciBQaIgogpIvHBLQ0G5qJggSUisbPe1zhKjYaaRIjkTdcmUofLz\ ciVgpUsOcFxWqHLQJvRtSEvtBZdW7NFFCL1eaDGT15nZNUmixoMJphUkxnIowkOo9WyEaU0a/1BegSZ\ bfhfQ/ouZgzOmtLFXyKng0gFiIV6y6umhm8VQEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAYQrpkJGioKBAgGEAYSIgogCOoAWZOV3iX0nyYDCHup2rcWkMwV5A8m4ZLqRFWrA+EaKgoECAcQBxI\ iCiB3v7UY7Tjmbz8zBbwHMmN0BLfs4X9/sQeejitAXXOcOxoqCgQIBBAFEiIKIK0BPLZ1ashlqRI0fI\ ofhQShArbArY01cy4/kmSsbnhVGigKAhADEiIKIHzANt8/nHW7NYTk1eWF6LTv1bHBHIgXWDvGOYwFD\ @@ -284,16 +284,16 @@ extension Transaction.Fixtures.BuildTx { PrtDnSPrngR2C0aF4J10Gi4KCAiAgAIQ//8DEiIKIE7l402lC0ruffB1VkIQKe+2f6Sh/hE5gUDmY5Z\ xKIK7Gi4KCAiAgAQQ//8HEiIKIOTq52imyqFiZjil2jHXNtBx2BKZxSmw944x7i68ljgFGi4KCAiAgA\ gQ//8PEiIKIKEJ8tczrqIvUlOufmY95LaU0Y72gnl0HOoMK8EoiL52 - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgogqvIgtiuxQAqHxzv/VcLiLQlf+BK2GoP3nI+LUY4fh3wRyGwR1Q1+hGcSIgogbuh/V1ScuAV\ 2mUulOdUsvUMEFRucCSQrV5J2PncHo2oaIgoguA6uOB6g8Mf/9ZqmC9Phk3+ShSfuTT1QaK1eDVBu1A\ 4iVgpUEpXKMP+ltj+dGbetGv3IzVlVyzwZ+hCL4BgocsWxf0FE0E4+DLwxufdcy0WbqC8ZTumKAGFK0\ O2Ey8yOMBELartXClbRSescdXAIT80DrH0mCwEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ EK6ZCRomCgASIgogFzIXWZDfyH71BN7SPrVQymgMGH6JuGsR5Rx/FlHWBfoaKgoECAEQARIiCiB6flQ\ laAkJaH23jZPi+yIbzShjQR0846+5X9t0P8QKJRoqCgQIAhADEiIKIMeRxvZ6eJjehUUvbKTqXnvwz5\ lDZLnK9VBZQ3mq9xFRGioKBAgEEAcSIgognSmuA2HAXerWhJLBRSyvOD0nW5XGZYceUxbhjvtI62EaK\ @@ -309,16 +309,16 @@ extension Transaction.Fixtures.BuildTx { I+ueBHYLRoXgnXQaLgoICICAAhD//wMSIgogTuXjTaULSu598HVWQhAp77Z/pKH+ETmBQOZjlnEogrs\ aLgoICICABBD//wcSIgog5OrnaKbKoWJmOKXaMdc20HHYEpnFKbD3jjHuLryWOAUaLgoICICACBD//w\ 8SIgogoQny1zOuoi9SU65+Zj3ktpTRjvaCeXQc6gwrwSiIvnY= - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgog4AjVI2JEDOua3ZWQppBFMifhOiUIVwaRGbcihmTcDzcRu5AHLxzto3ISIgogitebMb1dXe6\ erA6jj1G0wuYSE9ZvqvB4KHBk34I6DzoaIgog3s29+r7yHHrDYY671ORDKq7SCwewHM/ceqgjgP8GrE\ giVgpUKd+nw3qBKRkTnh3RsxSgS2AvAFgdb+sFtUYRWwnm24gVUO65MAC2DlBDIhCtluEITRR+M/o25\ ELeCJ4RZ0HnTaBIEQ9i08LVYTdgwuMr7ByLkAEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAQQrpkJGioKBAgEEAQSIgogy+SrA6FXJrRzZCqdVJevuSot7/4RWemw77uG1tfd6RgaKgoECAUQBRI\ iCiDk4GGKdEhk0udWRWgg114Qy21ht2xqf3JAWsn0Lm6luhoqCgQIBhAHEiIKIBzdtDBABzwQ79Cvwo\ 2CkimC3N9lhLMIQbSojyNxMf/bGigKAhADEiIKIHzANt8/nHW7NYTk1eWF6LTv1bHBHIgXWDvGOYwFD\ @@ -334,16 +334,16 @@ extension Transaction.Fixtures.BuildTx { PrtDnSPrngR2C0aF4J10Gi4KCAiAgAIQ//8DEiIKIE7l402lC0ruffB1VkIQKe+2f6Sh/hE5gUDmY5Z\ xKIK7Gi4KCAiAgAQQ//8HEiIKIOTq52imyqFiZjil2jHXNtBx2BKZxSmw944x7i68ljgFGi4KCAiAgA\ gQ//8PEiIKIKEJ8tczrqIvUlOufmY95LaU0Y72gnl0HOoMK8EoiL52 - """)!)) + """ ), ( - try XCTUnwrap(TxOut(serializedData: Data(base64Encoded: """ + """ Ci0KIgogvA4PvbpHrTensVBf0897C/ochZ0572kVLUXSKBeZJFMRwjpOuYk6KWkSIgog6nEnUo3DVNo\ 5oMuWQbuCC+3mjSLiqSs54Z5i+QpFIkMaIgog+neKXgDk+w0aA02sNras9kjtfR8PfmIWaTe0+uMilX\ UiVgpUEYiDkL8tAB+Qow6z49Ve0rkLNj98fP6t9MUycACJn5paF37y6W5QuAGtGEJoF6rexHAaRnSqm\ 2J7QYXjK7lwmSkXGde1ztKuJShDN/DJ1CMfhQEA - """)!)), - try XCTUnwrap(TxOutMembershipProof(serializedData: Data(base64Encoded: """ + """, + """ CAgQrpkJGioKBAgIEAgSIgogQK+tp/XH205ZisUJDRH1mTZi8YvoW5ThRuvxvz/3dfIaKgoECAkQCRI\ iCiCcpwPH+G7iICUrJaR0LCAhhqCMJYZnNV8Iz+doY+8BnhoqCgQIChALEiIKIIn5OYPIj2hUREaxG8\ AvwpPWjBeUwZ2gV4wInAB88BnRGioKBAgMEA8SIgogTxjaTwHcjvTJsYuG6v4/ccOxJ5pP/R6s8uXOd\ @@ -359,9 +359,15 @@ extension Transaction.Fixtures.BuildTx { PrtDnSPrngR2C0aF4J10Gi4KCAiAgAIQ//8DEiIKIE7l402lC0ruffB1VkIQKe+2f6Sh/hE5gUDmY5Z\ xKIK7Gi4KCAiAgAQQ//8HEiIKIOTq52imyqFiZjil2jHXNtBx2BKZxSmw944x7i68ljgFGi4KCAiAgA\ gQ//8PEiIKIKEJ8tczrqIvUlOufmY95LaU0Y72gnl0HOoMK8EoiL52 - """)!)) + """ ), - ] + ].map { + ( + try XCTUnwrap(TxOut(serializedData: XCTUnwrap(Data(base64Encoded: $0.0)))), + try XCTUnwrapSuccess( + TxOutMembershipProof.make(serializedData: XCTUnwrap(Data(base64Encoded: $0.1)))) + ) + } return [try PreparedTxInput.make(knownTxOut: knownTxOut, ring: ring).get()] } diff --git a/Tests/Integration/MobileCoinClientPublicApiIntTests.swift b/Tests/Integration/MobileCoinClientPublicApiIntTests.swift index a1f774dc..53ca2892 100644 --- a/Tests/Integration/MobileCoinClientPublicApiIntTests.swift +++ b/Tests/Integration/MobileCoinClientPublicApiIntTests.swift @@ -209,13 +209,11 @@ class MobileCoinClientPublicApiIntTests: XCTestCase { do { let balancePicoMob = try XCTUnwrap(balance.amountPicoMob()) let initialBalancePicoMob = try XCTUnwrap(initialBalance.amountPicoMob()) - let expectedBalancePicoMob = - initialBalancePicoMob - IntegrationTestFixtures.fee - guard balancePicoMob == expectedBalancePicoMob else { + guard balancePicoMob != initialBalancePicoMob else { guard numChecksRemaining > 0 else { - XCTFail("Failed to receive a changed balance. balance: " + - "\(balancePicoMob), expected balance: " + - "\(expectedBalancePicoMob) picoMOB") + XCTFail("Failed to receive a changed balance. initial balance: " + + "\(initialBalancePicoMob), current balance: " + + "\(balancePicoMob) picoMOB") expect.fulfill() return } diff --git a/Tests/Integration/Network/Connection/ConsensusConnectionIntTests.swift b/Tests/Integration/Network/Connection/ConsensusConnectionIntTests.swift index 78b152d1..5834a5d0 100644 --- a/Tests/Integration/Network/Connection/ConsensusConnectionIntTests.swift +++ b/Tests/Integration/Network/Connection/ConsensusConnectionIntTests.swift @@ -17,9 +17,7 @@ class ConsensusConnectionIntTests: XCTestCase { let expect = expectation(description: "Consensus connection") try createConsensusConnection().proposeTx(fixture.tx, completion: { guard let response = $0.successOrFulfill(expectation: expect) else { return } - - print("result: \(response.result)") - print("blockCount: \(response.blockCount)") + print("response: \(response)") XCTAssertNotEqual(response.result, .ok) XCTAssertGreaterThan(response.blockCount, 0)