diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 43e9b464..de177241 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -515,26 +515,17 @@ public class SDKSynchronizer: Synchronizer { let shieldingSpend = try transactionManager.initSpend(zatoshi: tBalance.verified, toAddress: uAddr.stringEncoded, memo: try memo.asMemoBytes(), from: accountIndex) - transactionManager.encodeShieldingTransaction( - xprv: transparentAccountPrivateKey, - pendingTransaction: shieldingSpend - ) { [weak self] result in - guard let self = self else { return } + // TODO: Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487 + Task { + do { + let transaction = try await transactionManager.encodeShieldingTransaction( + xprv: transparentAccountPrivateKey, + pendingTransaction: shieldingSpend + ) - switch result { - case .success(let transaction): - self.transactionManager.submit(pendingTransaction: transaction) { submitResult in - switch submitResult { - case .success(let submittedTx): - resultBlock(.success(submittedTx)) - case .failure(let submissionError): - DispatchQueue.main.async { - resultBlock(.failure(submissionError)) - } - } - } - - case .failure(let error): + let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) + resultBlock(.success(submittedTx)) + } catch { resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) } } @@ -560,22 +551,15 @@ public class SDKSynchronizer: Synchronizer { from: accountIndex ) - transactionManager.encode(spendingKey: spendingKey, pendingTransaction: spend) { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let transaction): - self.transactionManager.submit(pendingTransaction: transaction) { submitResult in - switch submitResult { - case .success(let submittedTx): - resultBlock(.success(submittedTx)) - case .failure(let submissionError): - DispatchQueue.main.async { - resultBlock(.failure(submissionError)) - } - } - } - - case .failure(let error): + // TODO: Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487 + Task { + do { + let transaction = try await transactionManager.encode( + pendingTransaction: spend + ) + let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) + resultBlock(.success(submittedTx)) + } catch { resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) } } diff --git a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift index e3f328a9..021bb23a 100644 --- a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift @@ -67,142 +67,106 @@ class PersistentTransactionManager: OutboundTransactionManager { func encodeShieldingTransaction( xprv: TransparentAccountPrivKey, - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result) -> Void - ) { - queue.async { [weak self] in - guard let self = self else { return } - - do { - let encodedTransaction = try self.encoder.createShieldingTransaction( - tAccountPrivateKey: xprv, - memoBytes: try pendingTransaction.memo.intoMemoBytes(), - from: pendingTransaction.accountIndex - ) - let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) - - var pending = pendingTransaction - pending.encodeAttempts += 1 - pending.raw = encodedTransaction.raw - pending.rawTransactionId = encodedTransaction.transactionId - pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() - pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() - - try self.repository.update(pending) - - result(.success(pending)) - } catch StorageError.updateFailed { - DispatchQueue.main.async { - result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) - } - } catch MemoBytes.Errors.invalidUTF8 { - DispatchQueue.main.async { - result(.failure(TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo contains invalid UTF-8 bytes"))) - } - } catch MemoBytes.Errors.tooLong(let length) { - DispatchQueue.main.async { - result(.failure(TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo is too long. expected 512 bytes, received \(length)"))) - } - } catch { - DispatchQueue.main.async { - result(.failure(error)) - } - } + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity { + do { + let encodedTransaction = try self.encoder.createShieldingTransaction( + tAccountPrivateKey: xprv, + memoBytes: try pendingTransaction.memo.intoMemoBytes(), + from: pendingTransaction.accountIndex + ) + let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) + + var pending = pendingTransaction + pending.encodeAttempts += 1 + pending.raw = encodedTransaction.raw + pending.rawTransactionId = encodedTransaction.transactionId + pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() + pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() + + try self.repository.update(pending) + + return pending + } catch StorageError.updateFailed { + result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) + } catch MemoBytes.Errors.invalidUTF8 { + result(.failure(TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo contains invalid UTF-8 bytes"))) + } catch MemoBytes.Errors.tooLong(let length) { + result(.failure(TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo is too long. expected 512 bytes, received \(length)"))) + } catch { + result(.failure(error)) } } - + func encode( - spendingKey: SaplingExtendedSpendingKey, - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result) -> Void - ) { - queue.async { [weak self] in - guard let self = self else { return } + spendingKey: String, + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity { + do { + let encodedTransaction = try self.encoder.createTransaction( + spendingKey: spendingKey, + zatoshi: pendingTransaction.intValue, + to: pendingTransaction.toAddress, + memo: pendingTransaction.memo?.asZcashTransactionMemo(), + from: pendingTransaction.accountIndex + ) + let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) + var pending = pendingTransaction + pending.encodeAttempts += 1 + pending.raw = encodedTransaction.raw + pending.rawTransactionId = encodedTransaction.transactionId + pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() + pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() + + try self.repository.update(pending) + + return pending + } catch StorageError.updateFailed { + throw TransactionManagerError.updateFailed(pendingTransaction) + } catch { do { - - let encodedTransaction = try self.encoder.createTransaction( - spendingKey: spendingKey, - zatoshi: pendingTransaction.value, - to: pendingTransaction.toAddress, - memoBytes: try pendingTransaction.memo.intoMemoBytes(), - from: pendingTransaction.accountIndex - ) - - let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) - - var pending = pendingTransaction - pending.encodeAttempts += 1 - pending.raw = encodedTransaction.raw - pending.rawTransactionId = encodedTransaction.transactionId - pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() - pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() - - try self.repository.update(pending) - - result(.success(pending)) - } catch StorageError.updateFailed { - DispatchQueue.main.async { - result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) - } + try self.updateOnFailure(transaction: pendingTransaction, error: error) } catch { - do { - try self.updateOnFailure(transaction: pendingTransaction, error: error) - } catch { - DispatchQueue.main.async { - result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) - } - } - DispatchQueue.main.async { - result(.failure(error)) - } + throw TransactionManagerError.updateFailed(pendingTransaction) } + throw error } } func submit( - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result) -> Void - ) { + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity { guard let txId = pendingTransaction.id else { - result(.failure(TransactionManagerError.notPending(pendingTransaction)))// this transaction is not stored - return + throw TransactionManagerError.notPending(pendingTransaction) // this transaction is not stored } - queue.async { [weak self] in - guard let self = self else { return } - - do { - guard let storedTx = try self.repository.find(by: txId) else { - result(.failure(TransactionManagerError.notPending(pendingTransaction))) - return - } - - guard !storedTx.isCancelled else { - LoggerProxy.debug("ignoring cancelled transaction \(storedTx)") - result(.failure(TransactionManagerError.cancelled(storedTx))) - return - } - - guard let raw = storedTx.raw else { - LoggerProxy.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data") - result(.failure(TransactionManagerError.internalInconsistency(storedTx))) - return - } - - let response = try self.service.submit(spendTransaction: raw) - let transaction = try self.update(transaction: storedTx, on: response) - - guard response.errorCode >= 0 else { - result(.failure(TransactionManagerError.submitFailed(transaction, errorCode: Int(response.errorCode)))) - return - } - - result(.success(transaction)) - } catch { - try? self.updateOnFailure(transaction: pendingTransaction, error: error) - result(.failure(error)) + do { + guard let storedTx = try self.repository.find(by: txId) else { + throw TransactionManagerError.notPending(pendingTransaction) } + + guard !storedTx.isCancelled else { + LoggerProxy.debug("ignoring cancelled transaction \(storedTx)") + throw TransactionManagerError.cancelled(storedTx) + } + + guard let raw = storedTx.raw else { + LoggerProxy.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data") + throw TransactionManagerError.internalInconsistency(storedTx) + } + + let response = try self.service.submit(spendTransaction: raw) + let transaction = try self.update(transaction: storedTx, on: response) + + guard response.errorCode >= 0 else { + throw TransactionManagerError.submitFailed(transaction, errorCode: Int(response.errorCode)) + } + + return transaction + } catch { + try? self.updateOnFailure(transaction: pendingTransaction, error: error) + throw error } } diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift index 469a6707..5855efa3 100644 --- a/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift @@ -23,20 +23,17 @@ protocol OutboundTransactionManager { func encodeShieldingTransaction( xprv: TransparentAccountPrivKey, - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result) -> Void - ) + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity func encode( spendingKey: SaplingExtendedSpendingKey, - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result - ) -> Void) + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity func submit( - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result) -> Void - ) + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity func applyMinedHeight( pendingTransaction: PendingTransactionEntity,