From 3f130ebebfea6704c796cb2cce0a70b1d489dc21 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Thu, 4 Jun 2020 18:36:25 -0300 Subject: [PATCH] Fixes issue 136 - expiry height -1 on pending transactions (#139) --- .../PersistentTransactionManager.swift | 5 +- .../Transaction/TransactionEncoder.swift | 8 ++ .../WalletTransactionEncoder.swift | 18 +++- ZcashLightClientKitTests/BalanceTests.swift | 97 ++++++++++--------- 4 files changed, 77 insertions(+), 51 deletions(-) diff --git a/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift b/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift index 62857e37..95737d40 100644 --- a/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift +++ b/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift @@ -45,11 +45,14 @@ class PersistentTransactionManager: OutboundTransactionManager { guard let self = self else { return } do { let encodedTransaction = try self.encoder.createTransaction(spendingKey: spendingKey, zatoshi: pendingTransaction.value, to: pendingTransaction.toAddress, memo: pendingTransaction.memo?.asZcashTransactionMemo(), from: pendingTransaction.accountIndex) + let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) + var pending = pendingTransaction pending.encodeAttempts = 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 { diff --git a/ZcashLightClientKit/Transaction/TransactionEncoder.swift b/ZcashLightClientKit/Transaction/TransactionEncoder.swift index b9b45faf..276e0ea5 100644 --- a/ZcashLightClientKit/Transaction/TransactionEncoder.swift +++ b/ZcashLightClientKit/Transaction/TransactionEncoder.swift @@ -14,6 +14,7 @@ public enum TransactionEncoderError: Error { case NotEncoded(transactionId: Int) case missingParams case spendingKeyWrongNetwork + case couldNotExpand(txId: Data) } protocol TransactionEncoder { @@ -51,4 +52,11 @@ protocol TransactionEncoder { */ func createTransaction(spendingKey: String, zatoshi: Int, to: String, memo: String?, from accountIndex: Int, result: @escaping TransactionEncoderResultBlock) + /** + Fetch the Transaction Entity from the encoded representation + - Parameter encodedTransaction: The encoded transaction to expand + - Returns: a TransactionEntity based on the given Encoded Transaction + - Throws: a TransactionEncoderError + */ + func expandEncodedTransaction(_ encodedTransaction: EncodedTransaction) throws -> TransactionEntity } diff --git a/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift b/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift index 8961f57e..13075020 100644 --- a/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift +++ b/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift @@ -11,12 +11,11 @@ class WalletTransactionEncoder: TransactionEncoder { var rustBackend: ZcashRustBackendWelding.Type var repository: TransactionRepository -// var initializer: Initializer var queue: DispatchQueue private var outputParamsURL: URL private var spendParamsURL: URL private var dataDbURL: URL - + init(rust: ZcashRustBackendWelding.Type, dataDb: URL, repository: TransactionRepository, @@ -75,7 +74,7 @@ class WalletTransactionEncoder: TransactionEncoder { guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else { throw TransactionEncoderError.missingParams } - + let txId = rustBackend.createToAddress(dbData: self.dataDbURL, account: Int32(accountIndex), extsk: spendingKey, to: address, value: Int64(zatoshi), memo: memo, spendParamsPath: self.spendParamsURL.path, outputParamsPath: self.outputParamsURL.path) guard txId > 0 else { @@ -92,4 +91,17 @@ class WalletTransactionEncoder: TransactionEncoder { return readableSpend && readableOutput // Todo: change this to something that makes sense } + + /** + Fetch the Transaction Entity from the encoded representation + - Parameter encodedTransaction: The encoded transaction to expand + - Returns: a TransactionEntity based on the given Encoded Transaction + - Throws: a TransactionEncoderError + */ + func expandEncodedTransaction(_ encodedTransaction: EncodedTransaction) throws -> TransactionEntity { + guard let t = try? repository.findBy(rawId: encodedTransaction.transactionId) else { + throw TransactionEncoderError.couldNotExpand(txId: encodedTransaction.transactionId) + } + return t + } } diff --git a/ZcashLightClientKitTests/BalanceTests.swift b/ZcashLightClientKitTests/BalanceTests.swift index 04656260..66eb4b5b 100644 --- a/ZcashLightClientKitTests/BalanceTests.swift +++ b/ZcashLightClientKitTests/BalanceTests.swift @@ -174,11 +174,11 @@ class BalanceTests: XCTestCase { } // (previous available funds - spent note + change) equals to (previous available funds - sent amount) - self.verifiedBalanceValidation(previousBalance: presendBalance, - spentNoteValue: Int64(sentNote.value), - changeValue: Int64(receivedNote.value), - sentAmount: Int64(self.sendAmount), - currentVerifiedBalance: self.coordinator.synchronizer.initializer.getVerifiedBalance()) + self.verifiedBalanceValidation(previousBalance: presendBalance, + spentNoteValue: Int64(sentNote.value), + changeValue: Int64(receivedNote.value), + sentAmount: Int64(self.sendAmount), + currentVerifiedBalance: self.coordinator.synchronizer.initializer.getVerifiedBalance()) } @@ -200,7 +200,7 @@ class BalanceTests: XCTestCase { */ func testVerifyTotalBalanceDuringSend() throws { try FakeChainBuilder.buildChain(darksideWallet: coordinator.service) - + try coordinator.applyStaged(blockheight: defaultLatestHeight) sleep(2) @@ -222,9 +222,9 @@ class BalanceTests: XCTestCase { var error: Error? coordinator.synchronizer.sendToAddress(spendingKey: spendingKey, - zatoshi: Int64(sendAmount), - toAddress: testRecipientAddress, - memo: "test send \(self.description) \(Date().description)", + zatoshi: Int64(sendAmount), + toAddress: testRecipientAddress, + memo: "test send \(self.description) \(Date().description)", from: 0) { result in switch result { case .failure(let e): @@ -255,7 +255,7 @@ class BalanceTests: XCTestCase { XCTAssertEqual(Int64(transaction.value), self.sendAmount) XCTAssertEqual(self.coordinator.synchronizer.initializer.getBalance(), presendBalance - Int64(self.sendAmount)) - + XCTAssertNil(transaction.errorCode) let latestHeight = try coordinator.latestHeight() @@ -356,7 +356,7 @@ class BalanceTests: XCTestCase { wait(for: [syncedExpectation], timeout: 6) - + let previousVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance() let previousTotalBalance = coordinator.synchronizer.initializer.getBalance() @@ -460,17 +460,17 @@ class BalanceTests: XCTestCase { */ self.verifiedBalanceValidation( - previousBalance: previousVerifiedBalance, - spentNoteValue: Int64(sentNote.value), - changeValue: Int64(receivedNote.value), - sentAmount: Int64(self.sendAmount), - currentVerifiedBalance: synchronizer.initializer.getVerifiedBalance()) + previousBalance: previousVerifiedBalance, + spentNoteValue: Int64(sentNote.value), + changeValue: Int64(receivedNote.value), + sentAmount: Int64(self.sendAmount), + currentVerifiedBalance: synchronizer.initializer.getVerifiedBalance()) self.totalBalanceValidation(totalBalance: synchronizer.initializer.getBalance(), - previousTotalbalance: previousTotalBalance, - sentAmount: Int64(self.sendAmount)) + previousTotalbalance: previousTotalBalance, + sentAmount: Int64(self.sendAmount)) syncToMinedheightExpectation.fulfill() }, error: self.handleError) @@ -544,41 +544,44 @@ class BalanceTests: XCTestCase { let expirationSyncExpectation = XCTestExpectation(description: "expiration sync expectation") let expiryHeight = pendingTransaction.expiryHeight let blockCount = abs(self.defaultLatestHeight - expiryHeight) - try coordinator.stageBlockCreate(height: self.defaultLatestHeight + 1, count: blockCount) - try coordinator.applyStaged(blockheight: expiryHeight) + try coordinator.stageBlockCreate(height: self.defaultLatestHeight + 1, count: blockCount) + try coordinator.applyStaged(blockheight: expiryHeight + 1) sleep(2) try coordinator.sync(completion: { (synchronizer) in - - /* - Verified Balance is equal to verified balance previously shown before sending the expired transaction - */ - XCTAssertEqual(synchronizer.initializer.getVerifiedBalance(), previousVerifiedBalance) - - /* - Total Balance is equal to total balance previously shown before sending the expired transaction - */ - XCTAssertEqual(synchronizer.initializer.getBalance(), previousTotalBalance) - - let pendingRepo = PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: synchronizer.initializer.pendingDbURL.absoluteString)) - - guard let expiredPending = try? pendingRepo.find(by: pendingTransaction.id!), - let id = expiredPending.id, - let sentExpired = try? synchronizer.allSentTransactions().first(where: { $0.id == id}) else { - XCTFail("expired transaction not found") - return - } - /* - There’s a pending transaction that has expired - */ - XCTAssertEqual(expiredPending.minedHeight, -1) - XCTAssertEqual(sentExpired.expiryHeight,expiryHeight) - XCTAssertEqual(Int64(sentExpired.value), self.sendAmount) - expirationSyncExpectation.fulfill() }, error: self.handleError) - wait(for: [expirationSyncExpectation], timeout: 10) + wait(for: [expirationSyncExpectation], timeout: 5) + + /* + Verified Balance is equal to verified balance previously shown before sending the expired transaction + */ + XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), previousVerifiedBalance) + + /* + Total Balance is equal to total balance previously shown before sending the expired transaction + */ + XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), previousTotalBalance) + + let pendingRepo = PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: coordinator.synchronizer.initializer.pendingDbURL.absoluteString)) + + guard let expiredPending = try? pendingRepo.find(by: pendingTransaction.id!), + let id = expiredPending.id else { + XCTFail("pending transaction not found") + return + } + + /* + there no sent transaction displayed + */ + + XCTAssertNil( try coordinator.synchronizer.allSentTransactions().first(where: { $0.id == id})) + /* + There’s a pending transaction that has expired + */ + XCTAssertEqual(expiredPending.minedHeight, -1) + } func handleError(_ error: Error?) {