From f5e7c027af4479bc6c7bc300c268d321a638a9f6 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Fri, 5 May 2023 14:30:47 -0300 Subject: [PATCH] [#1001] Remove PendingDb in favor of `v_transactions` and `v_tx_output` Views (#1001) Removes `PendingTransactionEntity` and all of its related components. Pending items are still tracked and visualized by the existing APIs but they are retrieved from the `TransactionRepository` instead by returning `ZcashTransaction.Overview` instead. `pendingDbURL` is removed from every place it was required. Its deletion is responsibility of wallet developers. `ClearedTransactions` are now just `transactions`. `MigrationManager` is deleted. Now all migrations are in charge of the rust welding layer. `PendingTransactionDao.swift` is removed. Implementation of `AccountEntity` called `Account` is now `DbAccount` `ZcashTransaction.Overview` can be checked for "pending-ness" by calling `.isPending(latestHeight:)` latest height must be provided so that minedHeight can be compared with the lastest and the `defaultStaleTolerance` constant. `TransactionRecipient` is now a public type. protocol `PendingTransactionRepository` is removed. `TransactionManagerError` and `PersistentTransactionManager` are deleted. `OutboundTransactionManager` is deleted and replaced by `TransactionEncoder` which now incorporates `submit(encoded:)` functionality `WalletTransactionEncoder` now uses a `LightWalletService` to submit the encoded transactions. Add changelog changes Delete references to PendingDb from tests and documentation. Fixes some typos. Adds the ability to trace transaction repository SQL queries from test Fix rebase conflicts and generate code [#837] Memo tests regarding transparent address Closes #837 Add model for transaction output Point to FFI branch Fix issue where sync wouldn't resume after wipe. Becasue GRPC channel would be closed Fix Tests Fix testPendingTransactionMinedHeightUpdated Fix testLastStates [#921] Fix broken SynchronizerDarksideTests Add ZcashTransaction.Output API to Synchronizer Changelog + comment fix Add Assertions for transaction outputs and recipients Point to FFI 0.3.1 Fix Demo App Compiler errors Fix Demo App Compiler errors fix cacheDb warnings Fix Tests and compiler errors of rebase build demo app Remove `ZcashTransaction.Sent` and `.Received`. Add `.State` and tests Fix SPM warning PR Suggestions Removes errors that are not used anymore fix warnings --- CHANGELOG.md | 67 ++++ .../ZcashLightClientSample/AppDelegate.swift | 8 +- .../TransactionsDataSource.swift | 12 +- .../SyncBlocksListViewController.swift | 1 - .../TransactionDetailViewController.swift | 18 +- MIGRATING.md | 8 + Package.resolved | 4 +- Package.swift | 5 +- .../Block/CompactBlockProcessor.swift | 4 - .../DatabaseMigrationManager.swift | 177 -------- .../Block/Utils/AfterSyncHooksManager.swift | 2 - .../ClosureSynchronizer.swift | 18 +- .../CombineSynchronizer.swift | 21 +- .../Constants/ZcashSDK.swift | 12 - .../ZcashLightClientKit/DAO/NotesDao.swift | 201 ---------- .../DAO/PendingTransactionDao.swift | 378 ------------------ .../DAO/TransactionDao.swift | 89 +++-- .../Entity/AccountEntity.swift | 10 +- .../Entity/EncodedTransactionEntity.swift | 2 +- .../Entity/PendingTransactionEntity.swift | 220 ---------- .../Entity/ReceivedNoteEntity.swift | 22 - .../Entity/TransactionEntity.swift | 177 +++++--- .../Error/ZcashError.swift | 186 +-------- .../Error/ZcashErrorCode.swift | 68 +--- .../Error/ZcashErrorCodeDefinition.swift | 131 +----- Sources/ZcashLightClientKit/Initializer.swift | 23 +- Sources/ZcashLightClientKit/Model/Memo.swift | 3 +- .../Model/WalletTypes.swift | 5 + .../Providers/ResourceProvider.swift | 13 - .../PendingTransactionRepository.swift | 19 - .../Repository/TransactionRepository.swift | 10 +- .../ZcashLightClientKit/Synchronizer.swift | 49 +-- .../Synchronizer/ClosureSDKSynchronizer.swift | 38 +- .../Synchronizer/CombineSDKSynchronizer.swift | 42 +- .../Synchronizer/SDKSynchronizer.swift | 206 ++++------ .../PersistentTransactionManager.swift | 255 ------------ .../Transaction/TransactionEncoder.swift | 15 + .../Transaction/TransactionManager.swift | 59 --- .../WalletTransactionEncoder.swift | 29 ++ .../SDKSynchronizerAliasDarksideTests.swift | 1 - Tests/DarksideTests/AdvancedReOrgTests.swift | 65 ++- Tests/DarksideTests/BalanceTests.swift | 196 ++++----- .../DarksideSanityCheckTests.swift | 1 - .../InternalStateConsistencyTests.swift | 3 +- .../PendingTransactionUpdatesTest.swift | 19 +- Tests/DarksideTests/ReOrgTests.swift | 1 - Tests/DarksideTests/RewindRescanTests.swift | 35 +- Tests/DarksideTests/ShieldFundsTests.swift | 9 +- .../SynchronizerDarksideTests.swift | 108 +++-- Tests/DarksideTests/SynchronizerTests.swift | 22 +- Tests/DarksideTests/Z2TReceiveTests.swift | 3 +- .../ClosureSynchronizerOfflineTests.swift | 136 +------ .../CombineSynchronizerOfflineTests.swift | 184 +-------- .../InitializerOfflineTests.swift | 39 -- Tests/OfflineTests/NotesRepositoryTests.swift | 40 -- .../PendingTransactionRepositoryTests.swift | 181 --------- .../SynchronizerOfflineTests.swift | 29 +- .../TransactionRepositoryTests.swift | 34 +- Tests/OfflineTests/WalletTests.swift | 1 - .../ZcashTransactionStateTests.swift | 87 ++++ .../PerformanceTests/SynchronizerTests.swift | 2 - Tests/TestUtils/DarkSideWalletService.swift | 4 - .../TestUtils/MockTransactionRepository.swift | 24 +- Tests/TestUtils/Resources/darkside_pending.db | Bin 16384 -> 0 bytes Tests/TestUtils/SDKSynchronizer+Utils.swift | 2 +- .../AutoMockable.generated.swift | 172 +++----- Tests/TestUtils/TestCoordinator.swift | 66 ++- Tests/TestUtils/TestDbBuilder.swift | 16 +- Tests/TestUtils/TestsData.swift | 47 ++- docs/development_process.md | 2 +- 70 files changed, 1008 insertions(+), 3128 deletions(-) delete mode 100644 Sources/ZcashLightClientKit/Block/DatabaseStorage/DatabaseMigrationManager.swift delete mode 100644 Sources/ZcashLightClientKit/DAO/NotesDao.swift delete mode 100644 Sources/ZcashLightClientKit/DAO/PendingTransactionDao.swift delete mode 100644 Sources/ZcashLightClientKit/Entity/PendingTransactionEntity.swift delete mode 100644 Sources/ZcashLightClientKit/Entity/ReceivedNoteEntity.swift delete mode 100644 Sources/ZcashLightClientKit/Repository/PendingTransactionRepository.swift delete mode 100644 Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift delete mode 100644 Sources/ZcashLightClientKit/Transaction/TransactionManager.swift delete mode 100644 Tests/OfflineTests/NotesRepositoryTests.swift delete mode 100644 Tests/OfflineTests/PendingTransactionRepositoryTests.swift create mode 100644 Tests/OfflineTests/ZcashTransactionStateTests.swift delete mode 100644 Tests/TestUtils/Resources/darkside_pending.db diff --git a/CHANGELOG.md b/CHANGELOG.md index e52eccba..9a55594b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,72 @@ # Unreleased +### Changed +- `WalletTransactionEncoder` now uses a `LightWalletService` to submit the +encoded transactions. + +- Functions returning or receiving `ZcashTransaction.Sent` or `ZcashTransaction.Received` now +will be simplified by returning `ZcashTransaction.Overview` or be replaced by their Overview +counterparts + +### Added + +- `ZcashTransaction.Overview` can be checked for "pending-ness" by calling`.isPending(latestHeight:)` latest height must be provided so that minedHeight +can be compared with the lastest and the `defaultStaleTolerance` constant. + +`TransactionRecipient` is now a public type. + +- `ZcashTransaction.Output` can be queried to know the inner details of a +`ZcashTransaction.Overview`. It will return an array with all the tracked +outputs for that transaction so that they can be shown to users who request them + +- `ZcashTransaction.Overview.State` is introduced to represent `confirmed`, +`pending` or `expired` states. This State is relative to the current height +of the chain that is passed to the function `getState(for currentHeight: BlockHeight)`. + +State should be a transient value and it's not adviced to store it unless +transactions have stale values such as `confirmed` or `expired`. + + +#### Synchronizer + +- `public func getTransactionOutputs(transaction) async -> [ZcashTransaction.Output]` is added to +get the outputs related to the given transaction. You can use this to know every detail of the +transaction Overview and show it in a more fine-grained UI. + +- `TransactionRecipient` is returned on `getRecipients(for:)`. +### Renamed +- `AccountEntity` called `Account` is now `DbAccount` + +### Removed +- `ZcashTransaction.Received` and `ZcashTransaction.Sent` are removed +and replaced by `Overview` since the notion of Sent and received is +not entirely applicable to Zcash transactions where value can be +sent and received at the same time. Transactions with negative value +will be considered as "sent" but that won't be enforced with a type +anymore +- `cancelSpend()`: support for cancel spend was removed since its +completion was not guaranteed +- `PendingTransactionEntity` and all of its related components. +Pending items are still tracked and visualized by the existing APIs +but they are retrieved from the `TransactionRepository` instead by +returning `ZcashTransaction.Overview` instead. +- `pendingDbURL` is removed from every place it was required. Its +deletion is responsibility of wallet developers. +- `ClearedTransactions` are now just `transactions`.`MigrationManager` +is deleted. Now all migrations are in charge of the rust welding layer. +- `PendingTransactionDao.swift` is removed. +- `PendingTransactionRepository` protocol is removed. +- `TransactionManagerError` +- `PersistentTransactionManager` +- `OutboundTransactionManager` is deleted and replaced by `TransactionEncoder` +which now incorporates `submit(encoded:)` functionality +- `DatabaseMigrationManager` is remove since it's no longer needed all Database +migrations shall be hanlded by the rust layer. +- `ZcashSDK.defaultPendingDbName` along with any sibling members +- `TransactionRepository` + - `findMemos(for receivedTransaction: ZcashTransaction.Received)` + - `findMemos(for sentTransaction: ZcashTransaction.Sent)` + ### [#1013] Enable more granular control over logging behavior Now the SDK allows for more fine-tuning of its logging behavior. The `LoggingPolicy` enum diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/AppDelegate.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/AppDelegate.swift index b6d82329..c4103330 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/AppDelegate.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/AppDelegate.swift @@ -47,7 +47,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { cacheDbURL: nil, fsBlockDbRoot: try! fsBlockDbRootURLHelper(), dataDbURL: try! dataDbURLHelper(), - pendingDbURL: try! pendingDbURLHelper(), endpoint: DemoAppConfig.endpoint, network: kZcashNetwork, spendParamsURL: try! spendParamsURLHelper(), @@ -74,7 +73,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { .store(in: &cancellables) } - func txMined(_ transaction: PendingTransactionEntity) { + func txMined(_ transaction: ZcashTransaction.Overview) { NotificationBubble.display( in: window!.rootViewController!.view, options: NotificationBubble.sucessOptions( @@ -189,11 +188,6 @@ func dataDbURLHelper() throws -> URL { ) } -func pendingDbURLHelper() throws -> URL { - try documentsDirectoryHelper() - .appendingPathComponent(kZcashNetwork.constants.defaultDbNamePrefix + ZcashSDK.defaultPendingDbName) -} - func spendParamsURLHelper() throws -> URL { try documentsDirectoryHelper().appendingPathComponent("sapling-spend.params") } diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/List Transactions/TransactionsDataSource.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/List Transactions/TransactionsDataSource.swift index 5bb39210..028ace01 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/List Transactions/TransactionsDataSource.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/List Transactions/TransactionsDataSource.swift @@ -39,14 +39,12 @@ class TransactionsDataSource: NSObject { case .pending: let rawTransactions = await synchronizer.pendingTransactions for pendingTransaction in rawTransactions { - let defaultFee: Zatoshi = kZcashNetwork.constants.defaultFee(for: pendingTransaction.minedHeight) - let transaction = pendingTransaction.makeTransactionEntity(defaultFee: defaultFee) - let memos = try await synchronizer.getMemos(for: transaction) + let memos = try await synchronizer.getMemos(for: pendingTransaction) transactions.append(TransactionDetailModel(pendingTransaction: pendingTransaction, memos: memos)) } case .cleared: - let rawTransactions = await synchronizer.clearedTransactions + let rawTransactions = await synchronizer.transactions for transaction in rawTransactions { let memos = try await synchronizer.getMemos(for: transaction) transactions.append(TransactionDetailModel(transaction: transaction, memos: memos)) @@ -66,13 +64,11 @@ class TransactionsDataSource: NSObject { case .all: let rawPendingTransactions = await synchronizer.pendingTransactions for pendingTransaction in rawPendingTransactions { - let defaultFee: Zatoshi = kZcashNetwork.constants.defaultFee(for: pendingTransaction.minedHeight) - let transaction = pendingTransaction.makeTransactionEntity(defaultFee: defaultFee) - let memos = try await synchronizer.getMemos(for: transaction) + let memos = try await synchronizer.getMemos(for: pendingTransaction) transactions.append(TransactionDetailModel(pendingTransaction: pendingTransaction, memos: memos)) } - let rawClearedTransactions = await synchronizer.clearedTransactions + let rawClearedTransactions = await synchronizer.transactions for transaction in rawClearedTransactions { let memos = try await synchronizer.getMemos(for: transaction) transactions.append(TransactionDetailModel(transaction: transaction, memos: memos)) diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift index 0e7a415b..6929d175 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift @@ -105,7 +105,6 @@ class SyncBlocksListViewController: UIViewController { cacheDbURL: nil, fsBlockDbRoot: try! fsBlockDbRootURLHelper(), dataDbURL: try! dataDbURLHelper(), - pendingDbURL: try! pendingDbURLHelper(), endpoint: DemoAppConfig.endpoint, network: kZcashNetwork, spendParamsURL: try! spendParamsURLHelper(), diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Transaction Detail/TransactionDetailViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Transaction Detail/TransactionDetailViewController.swift index f9e56ba0..d6c458e0 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Transaction Detail/TransactionDetailViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Transaction Detail/TransactionDetailViewController.swift @@ -11,9 +11,9 @@ import ZcashLightClientKit final class TransactionDetailModel { enum Transaction { - case sent(ZcashTransaction.Sent) - case received(ZcashTransaction.Received) - case pending(PendingTransactionEntity) + case sent(ZcashTransaction.Overview) + case received(ZcashTransaction.Overview) + case pending(ZcashTransaction.Overview) case cleared(ZcashTransaction.Overview) } @@ -25,7 +25,7 @@ final class TransactionDetailModel { var zatoshi: Zatoshi var memo: Memo? - init(sendTransaction transaction: ZcashTransaction.Sent, memos: [Memo]) { + init(sendTransaction transaction: ZcashTransaction.Overview, memos: [Memo]) { self.transaction = .sent(transaction) self.id = transaction.rawID self.minedHeight = transaction.minedHeight @@ -41,22 +41,22 @@ final class TransactionDetailModel { } } - init(receivedTransaction transaction: ZcashTransaction.Received, memos: [Memo]) { + init(receivedTransaction transaction: ZcashTransaction.Overview, memos: [Memo]) { self.transaction = .received(transaction) self.id = transaction.rawID self.minedHeight = transaction.minedHeight self.expiryHeight = transaction.expiryHeight self.zatoshi = transaction.value self.memo = memos.first - self.created = Date(timeIntervalSince1970: transaction.blockTime) + self.created = Date(timeIntervalSince1970: transaction.blockTime ?? Date().timeIntervalSince1970) } - init(pendingTransaction transaction: PendingTransactionEntity, memos: [Memo]) { + init(pendingTransaction transaction: ZcashTransaction.Overview, memos: [Memo]) { self.transaction = .pending(transaction) - self.id = transaction.rawTransactionId + self.id = transaction.rawID self.minedHeight = transaction.minedHeight self.expiryHeight = transaction.expiryHeight - self.created = Date(timeIntervalSince1970: transaction.createTime) + self.created = Date(timeIntervalSince1970: transaction.blockTime ?? Date().timeIntervalSince1970) self.zatoshi = transaction.value self.memo = memos.first } diff --git a/MIGRATING.md b/MIGRATING.md index de4f1d5f..cb0f47d4 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -1,3 +1,11 @@ +# Migrating from previous versions to _Unreleased_ +PendingDb is no longer used. Wallet developers should take care about deleting +the database file since the SDK will no longer require it or any of the +information stored. + +Failed transactions will be treated as "Expired-Unmined" instead. The SDK won't +track failures on its own. Wallet developers would have to account for those. + # Migrating from previous versions to 0.20.0-beta The `SDKSynchronizer` no longer uses `NotificationCenter` to send notifications. Notifications are replaced with `Combine` publishers. diff --git a/Package.resolved b/Package.resolved index 3e64c940..209b6731 100644 --- a/Package.resolved +++ b/Package.resolved @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "bf5992c2e53749ad11c1e85ad5d9c63e39bdf3cc", - "version" : "0.3.0" + "revision" : "75821e2b859600707318e4a788abbe27e6615833", + "version" : "0.3.1" } } ], diff --git a/Package.swift b/Package.swift index aecda6d9..015bc22a 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.14.0"), .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"), - .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", from: "0.3.0") + .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", from: "0.3.1") ], targets: [ .target( @@ -42,14 +42,13 @@ let package = Package( exclude: [ "proto/darkside.proto", "Sourcery/AutoMockable.stencil", - "Sourcery/generateMocks" + "Sourcery/generateMocks.sh" ], resources: [ .copy("Resources/test_data.db"), .copy("Resources/cache.db"), .copy("Resources/darkside_caches.db"), .copy("Resources/darkside_data.db"), - .copy("Resources/darkside_pending.db"), .copy("Resources/sandblasted_mainnet_block.json"), .copy("Resources/txBase64String.txt"), .copy("Resources/txFromAndroidSDK.txt"), diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index 69339385..cca39377 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -676,10 +676,6 @@ actor CompactBlockProcessor { try fileManager.removeItem(at: config.dataDb) } - if fileManager.fileExists(atPath: context.pendingDbURL.path) { - try fileManager.removeItem(at: context.pendingDbURL) - } - await context.completion(nil) } catch { await context.completion(error) diff --git a/Sources/ZcashLightClientKit/Block/DatabaseStorage/DatabaseMigrationManager.swift b/Sources/ZcashLightClientKit/Block/DatabaseStorage/DatabaseMigrationManager.swift deleted file mode 100644 index af20dc94..00000000 --- a/Sources/ZcashLightClientKit/Block/DatabaseStorage/DatabaseMigrationManager.swift +++ /dev/null @@ -1,177 +0,0 @@ -// -// DatabaseMigrationManager.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 3/31/21. -// - -import Foundation -import SQLite - -class MigrationManager { - // swiftlint:disable identifier_name - enum PendingDbMigration: Int32, CaseIterable { - case none = 0 - case v1 = 1 - case v2 = 2 - } - - static let nextPendingDbMigration = PendingDbMigration.v2 - - let pendingDb: ConnectionProvider - let network: NetworkType - let logger: Logger - - init( - pendingDbConnection: ConnectionProvider, - networkType: NetworkType, - logger: Logger - ) { - self.pendingDb = pendingDbConnection - self.network = networkType - self.logger = logger - } - - func performMigration() throws { - try migratePendingDb() - } -} - -private extension MigrationManager { - /// - Throws: - /// - `dbMigrationGenericFailure` when can't read current version of the pending DB. - /// - `dbMigrationInvalidVersion` when unknown version is read from the current pending DB. - /// - `dbMigrationV1` when migration to version 1 fails. - /// - `dbMigrationV2` when migration to version 2 fails. - func migratePendingDb() throws { - // getUserVersion returns a default value of zero for an unmigrated database. - let currentPendingDbVersion: Int32 - do { - currentPendingDbVersion = try pendingDb.connection().getUserVersion() - } catch { - throw ZcashError.dbMigrationGenericFailure(error) - } - - logger.debug( - "Attempting to perform migration for pending Db - currentVersion: \(currentPendingDbVersion)." + - "Latest version is: \(Self.nextPendingDbMigration.rawValue - 1)" - ) - - for version in (currentPendingDbVersion.. Int32 { - guard let version = try scalar("PRAGMA user_version") as? Int64 else { - return 0 - } - return Int32(version) - } - - func setUserVersion(_ version: Int32) throws { - try execute("PRAGMA user_version = \(version)") - } -} diff --git a/Sources/ZcashLightClientKit/Block/Utils/AfterSyncHooksManager.swift b/Sources/ZcashLightClientKit/Block/Utils/AfterSyncHooksManager.swift index 559b1eeb..63ce4601 100644 --- a/Sources/ZcashLightClientKit/Block/Utils/AfterSyncHooksManager.swift +++ b/Sources/ZcashLightClientKit/Block/Utils/AfterSyncHooksManager.swift @@ -9,7 +9,6 @@ import Foundation class AfterSyncHooksManager { struct WipeContext { - let pendingDbURL: URL let prewipe: () -> Void let completion: (Error?) async -> Void } @@ -52,7 +51,6 @@ class AfterSyncHooksManager { static var emptyWipe: Hook { return .wipe( WipeContext( - pendingDbURL: URL(fileURLWithPath: "/"), prewipe: { }, completion: { _ in } ) diff --git a/Sources/ZcashLightClientKit/ClosureSynchronizer.swift b/Sources/ZcashLightClientKit/ClosureSynchronizer.swift index 43e2db50..9a2f199e 100644 --- a/Sources/ZcashLightClientKit/ClosureSynchronizer.swift +++ b/Sources/ZcashLightClientKit/ClosureSynchronizer.swift @@ -41,31 +41,23 @@ public protocol ClosureSynchronizer { zatoshi: Zatoshi, toAddress: Recipient, memo: Memo?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) func shieldFunds( spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) - func cancelSpend(transaction: PendingTransactionEntity, completion: @escaping (Bool) -> Void) - - func pendingTransactions(completion: @escaping ([PendingTransactionEntity]) -> Void) + func pendingTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) - func sentTranscations(completion: @escaping ([ZcashTransaction.Sent]) -> Void) - func receivedTransactions(completion: @escaping ([ZcashTransaction.Received]) -> Void) - + func sentTranscations(completion: @escaping ([ZcashTransaction.Overview]) -> Void) + func receivedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository - func getMemos(for transaction: ZcashTransaction.Overview, completion: @escaping (Result<[Memo], Error>) -> Void) - func getMemos(for receivedTransaction: ZcashTransaction.Received, completion: @escaping (Result<[Memo], Error>) -> Void) - func getMemos(for sentTransaction: ZcashTransaction.Sent, completion: @escaping (Result<[Memo], Error>) -> Void) - func getRecipients(for transaction: ZcashTransaction.Overview, completion: @escaping ([TransactionRecipient]) -> Void) - func getRecipients(for transaction: ZcashTransaction.Sent, completion: @escaping ([TransactionRecipient]) -> Void) func allConfirmedTransactions( from transaction: ZcashTransaction.Overview, diff --git a/Sources/ZcashLightClientKit/CombineSynchronizer.swift b/Sources/ZcashLightClientKit/CombineSynchronizer.swift index c3d8b7c1..35b86441 100644 --- a/Sources/ZcashLightClientKit/CombineSynchronizer.swift +++ b/Sources/ZcashLightClientKit/CombineSynchronizer.swift @@ -40,31 +40,28 @@ public protocol CombineSynchronizer { zatoshi: Zatoshi, toAddress: Recipient, memo: Memo? - ) -> SinglePublisher + ) -> SinglePublisher func shieldFunds( spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi - ) -> SinglePublisher + ) -> SinglePublisher - func cancelSpend(transaction: PendingTransactionEntity) -> SinglePublisher - - var pendingTransactions: SinglePublisher<[PendingTransactionEntity], Never> { get } - var clearedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } - var sentTransactions: SinglePublisher<[ZcashTransaction.Sent], Never> { get } - var receivedTransactions: SinglePublisher<[ZcashTransaction.Received], Never> { get } + var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } + var pendingTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } + var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } + var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository func getMemos(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[Memo], Error> - func getMemos(for receivedTransaction: ZcashTransaction.Received) -> SinglePublisher<[Memo], Error> - func getMemos(for sentTransaction: ZcashTransaction.Sent) -> SinglePublisher<[Memo], Error> func getRecipients(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[TransactionRecipient], Never> - func getRecipients(for transaction: ZcashTransaction.Sent) -> SinglePublisher<[TransactionRecipient], Never> - func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) -> SinglePublisher<[ZcashTransaction.Overview], Error> + func allPendingTransactions() -> SinglePublisher<[ZcashTransaction.Overview], Error> + + func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) -> SinglePublisher<[ZcashTransaction.Overview], Error> func latestHeight() -> SinglePublisher diff --git a/Sources/ZcashLightClientKit/Constants/ZcashSDK.swift b/Sources/ZcashLightClientKit/Constants/ZcashSDK.swift index ea612782..0ee0384d 100644 --- a/Sources/ZcashLightClientKit/Constants/ZcashSDK.swift +++ b/Sources/ZcashLightClientKit/Constants/ZcashSDK.swift @@ -128,9 +128,6 @@ public enum ZcashSDK { /// Default Name for Compact Block caches db public static let defaultCacheDbName = "caches.db" - /// Default name for pending transactions db - public static let defaultPendingDbName = "pending.db" - /// The Url that is used by default in zcashd. /// We'll want to make this externally configurable, rather than baking it into the SDK but /// this will do for now, since we're using a cloudfront URL that already redirects. @@ -161,9 +158,6 @@ public protocol NetworkConstants { @available(*, deprecated, message: "use this name to clean up the sqlite compact block database") static var defaultCacheDbName: String { get } - /// Default name for pending transactions db - static var defaultPendingDbName: String { get } - /// Default prefix for db filenames static var defaultDbNamePrefix: String { get } @@ -195,9 +189,6 @@ public enum ZcashSDKMainnetConstants: NetworkConstants { /// Default Name for Compact Block caches db public static let defaultCacheDbName = "caches.db" - - /// Default name for pending transactions db - public static let defaultPendingDbName = "pending.db" public static let defaultDbNamePrefix = "ZcashSdk_mainnet_" @@ -216,9 +207,6 @@ public enum ZcashSDKTestnetConstants: NetworkConstants { public static let defaultCacheDbName = "caches.db" public static let defaultFsBlockDbRootName = "fs_cache" - - /// Default name for pending transactions db - public static let defaultPendingDbName = "pending.db" public static let defaultDbNamePrefix = "ZcashSdk_testnet_" diff --git a/Sources/ZcashLightClientKit/DAO/NotesDao.swift b/Sources/ZcashLightClientKit/DAO/NotesDao.swift deleted file mode 100644 index 28f0cb4d..00000000 --- a/Sources/ZcashLightClientKit/DAO/NotesDao.swift +++ /dev/null @@ -1,201 +0,0 @@ -// -// NotesDao.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 11/18/19. -// - -import Foundation -import SQLite - -struct ReceivedNote: ReceivedNoteEntity, Codable { - enum CodingKeys: String, CodingKey { - case id = "id_note" - case diversifier - case rcm - case nf - case isChange = "is_change" - case transactionId = "id_tx" - case outputIndex = "output_index" - case account - case value - case memo - case spent - case tx - } - let id: Int - let diversifier: Data - let rcm: Data - let nf: Data - let isChange: Bool - let transactionId: Int - let outputIndex: Int - let account: Int - let value: Int - let memo: Data? - let spent: Int? - let tx: Int -} - -class ReceivedNotesSQLDAO: ReceivedNoteRepository { - let table = Table("received_notes") - - let dbProvider: ConnectionProvider - - init(dbProvider: ConnectionProvider) { - self.dbProvider = dbProvider - } - - /// Throws `notesDAOReceivedCount` if sqlite query fetching count fails. - func count() throws -> Int { - do { - return try dbProvider.connection().scalar(table.count) - } catch { - throw ZcashError.notesDAOReceivedCount(error) - } - } - - /// - Throws: - /// - `notesDAOReceivedCantDecode` if fetched note data from the db can't be decoded to the `ReceivedNote` object. - /// - `notesDAOReceivedNote` if sqlite query fetching note data fails. - func receivedNote(byRawTransactionId: Data) throws -> ReceivedNoteEntity? { - let transactions = Table("transactions") - let idTx = Expression("id_tx") - let transaction = Expression("tx") - let txid = Expression("txid") - let joinStatement = table - .join( - .inner, - transactions, - on: transactions[idTx] == table[transaction] - ) - .where(transactions[txid] == Blob(bytes: byRawTransactionId.bytes)) - .limit(1) - - do { - return try dbProvider.connection() - .prepare(joinStatement) - .map { row -> ReceivedNote in - do { - return try row.decode() - } catch { - throw ZcashError.notesDAOReceivedCantDecode(error) - } - } - .first - } catch { - if let error = error as? ZcashError { - throw error - } else { - throw ZcashError.notesDAOReceivedNote(error) - } - } - } -} - -struct SentNote: SentNoteEntity, Codable { - enum CodingKeys: String, CodingKey { - case id = "id_note" - case transactionId = "tx" - case outputPool = "output_pool" - case outputIndex = "output_index" - case fromAccount = "from_account" - case toAddress = "to_address" - case toAccount = "to_account" - case value - case memo - } - - let id: Int - let transactionId: Int - let outputPool: Int - let outputIndex: Int - let fromAccount: Int - let toAddress: String? - let toAccount: Int? - let value: Int - let memo: Data? -} - -class SentNotesSQLDAO: SentNotesRepository { - let table = Table("sent_notes") - - let dbProvider: ConnectionProvider - - init(dbProvider: ConnectionProvider) { - self.dbProvider = dbProvider - } - - /// - Throws: `notesDAOSentCount` if sqlite query fetching count fails. - func count() throws -> Int { - do { - return try dbProvider.connection().scalar(table.count) - } catch { - throw ZcashError.notesDAOSentCount(error) - } - } - - /// - Throws: - /// - `notesDAOSentCantDecode` if fetched note data from the db can't be decoded to the `SentNote` object. - /// - `notesDAOSentNote` if sqlite query fetching note data fails. - func sentNote(byRawTransactionId: Data) throws -> SentNoteEntity? { - let transactions = Table("transactions") - let idTx = Expression("id_tx") - let transaction = Expression("tx") - let txid = Expression("txid") - let joinStatement = table - .join( - .inner, - transactions, - on: transactions[idTx] == table[transaction] - ) - .where(transactions[txid] == Blob(bytes: byRawTransactionId.bytes)) - .limit(1) - - do { - return try dbProvider.connection() - .prepare(joinStatement) - .map { row -> SentNote in - do { - return try row.decode() - } catch { - throw ZcashError.notesDAOSentCantDecode(error) - } - } - .first - } catch { - if let error = error as? ZcashError { - throw error - } else { - throw ZcashError.notesDAOSentNote(error) - } - } - } - - func getRecipients(for id: Int) -> [TransactionRecipient] { - guard let result = try? dbProvider.connection().prepare( - table.where(id == table[Expression("tx")]) - ) else { return [] } - - guard let rows = try? result.map({ row -> SentNote in - try row.decode() - }) else { return [] } - - return rows.compactMap { sentNote -> TransactionRecipient? in - if sentNote.toAccount == nil { - guard - let toAddress = sentNote.toAddress, - let recipient = Recipient.forEncodedAddress(encoded: toAddress) - else { return nil } - - return TransactionRecipient.address(recipient.0) - } else { - guard let toAccount = sentNote.toAccount else { - return nil - } - - return TransactionRecipient.internalAccount(UInt32(toAccount)) - } - } - } -} diff --git a/Sources/ZcashLightClientKit/DAO/PendingTransactionDao.swift b/Sources/ZcashLightClientKit/DAO/PendingTransactionDao.swift deleted file mode 100644 index 07a4b7b1..00000000 --- a/Sources/ZcashLightClientKit/DAO/PendingTransactionDao.swift +++ /dev/null @@ -1,378 +0,0 @@ -// -// PendingTransactionDao.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 11/19/19. -// - -import Foundation -import SQLite - -struct PendingTransaction: PendingTransactionEntity, Decodable, Encodable { - enum CodingKeys: String, CodingKey { - case toAddress = "to_address" - case toInternalAccount = "to_internal" - case accountIndex = "account_index" - case minedHeight = "mined_height" - case expiryHeight = "expiry_height" - case cancelled - case encodeAttempts = "encode_attempts" - case submitAttempts = "submit_attempts" - case errorMessage = "error_message" - case errorCode = "error_code" - case createTime = "create_time" - case raw - case id - case value - case memo - case rawTransactionId = "txid" - case fee - } - - let recipient: PendingTransactionRecipient - let accountIndex: Int - var minedHeight: BlockHeight - var expiryHeight: BlockHeight - var cancelled: Int - var encodeAttempts: Int - var submitAttempts: Int - var errorMessage: String? - var errorCode: Int? - let createTime: TimeInterval - var raw: Data? - var id: Int? - let value: Zatoshi - let memo: Data? - var rawTransactionId: Data? - let fee: Zatoshi? - - static func from(entity: PendingTransactionEntity) -> PendingTransaction { - PendingTransaction( - recipient: entity.recipient, - accountIndex: entity.accountIndex, - minedHeight: entity.minedHeight, - expiryHeight: entity.expiryHeight, - cancelled: entity.cancelled, - encodeAttempts: entity.encodeAttempts, - submitAttempts: entity.submitAttempts, - errorMessage: entity.errorMessage, - errorCode: entity.errorCode, - createTime: entity.createTime, - raw: entity.raw, - id: entity.id, - value: entity.value, - memo: entity.memo, - rawTransactionId: entity.raw, - fee: entity.fee - ) - } - - init( - recipient: PendingTransactionRecipient, - accountIndex: Int, - minedHeight: BlockHeight, - expiryHeight: BlockHeight, - cancelled: Int, - encodeAttempts: Int, - submitAttempts: Int, - errorMessage: String?, - errorCode: Int?, - createTime: TimeInterval, - raw: Data?, - id: Int?, - value: Zatoshi, - memo: Data?, - rawTransactionId: Data?, - fee: Zatoshi? - ) { - self.recipient = recipient - self.accountIndex = accountIndex - self.minedHeight = minedHeight - self.expiryHeight = expiryHeight - self.cancelled = cancelled - self.encodeAttempts = encodeAttempts - self.submitAttempts = submitAttempts - self.errorMessage = errorMessage - self.errorCode = errorCode - self.createTime = createTime - self.raw = raw - self.id = id - self.value = value - self.memo = memo - self.rawTransactionId = rawTransactionId - self.fee = fee - } - - /// - Throws: - /// - `pendingTransactionDecodeInvalidData` if some of the fields contain invalid data. - /// - `pendingTransactionCantDecode` if decoding fails. - init(from decoder: Decoder) throws { - do { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let toAddress: String? = try container.decodeIfPresent(String.self, forKey: .toAddress) - let toInternalAccount: Int? = try container.decodeIfPresent(Int.self, forKey: .toInternalAccount) - - switch (toAddress, toInternalAccount) { - case let (.some(address), nil): - guard let recipient = Recipient.forEncodedAddress(encoded: address) else { - throw ZcashError.pendingTransactionDecodeInvalidData(["toAddress"]) - } - self.recipient = .address(recipient.0) - case let (nil, .some(accountId)): - self.recipient = .internalAccount(UInt32(accountId)) - default: - throw ZcashError.pendingTransactionDecodeInvalidData(["toAddress", "toInternalAccount"]) - } - - self.accountIndex = try container.decode(Int.self, forKey: .accountIndex) - self.minedHeight = try container.decode(BlockHeight.self, forKey: .minedHeight) - self.expiryHeight = try container.decode(BlockHeight.self, forKey: .expiryHeight) - self.cancelled = try container.decode(Int.self, forKey: .cancelled) - self.encodeAttempts = try container.decode(Int.self, forKey: .encodeAttempts) - self.submitAttempts = try container.decode(Int.self, forKey: .submitAttempts) - self.errorMessage = try container.decodeIfPresent(String.self, forKey: .errorMessage) - self.errorCode = try container.decodeIfPresent(Int.self, forKey: .errorCode) - self.createTime = try container.decode(TimeInterval.self, forKey: .createTime) - self.raw = try container.decodeIfPresent(Data.self, forKey: .raw) - self.id = try container.decodeIfPresent(Int.self, forKey: .id) - - let zatoshiValue = try container.decode(Int64.self, forKey: .value) - self.value = Zatoshi(zatoshiValue) - self.memo = try container.decodeIfPresent(Data.self, forKey: .memo) - self.rawTransactionId = try container.decodeIfPresent(Data.self, forKey: .rawTransactionId) - if let feeValue = try container.decodeIfPresent(Int64.self, forKey: .fee) { - self.fee = Zatoshi(feeValue) - } else { - self.fee = nil - } - } catch { - if let error = error as? ZcashError { - throw error - } else { - throw ZcashError.pendingTransactionCantDecode(error) - } - } - } - - /// - Throws: - /// - `pendingTransactionCantEncode` if encoding fails. - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - var toAddress: String? - var accountId: Int? - switch recipient { - case .address(let recipient): - toAddress = recipient.stringEncoded - case .internalAccount(let acct): - accountId = Int(acct) - } - - do { - try container.encodeIfPresent(toAddress, forKey: .toAddress) - try container.encodeIfPresent(accountId, forKey: .toInternalAccount) - try container.encode(self.accountIndex, forKey: .accountIndex) - try container.encode(self.minedHeight, forKey: .minedHeight) - try container.encode(self.expiryHeight, forKey: .expiryHeight) - try container.encode(self.cancelled, forKey: .cancelled) - try container.encode(self.encodeAttempts, forKey: .encodeAttempts) - try container.encode(self.submitAttempts, forKey: .submitAttempts) - try container.encodeIfPresent(self.errorMessage, forKey: .errorMessage) - try container.encodeIfPresent(self.errorCode, forKey: .errorCode) - try container.encode(self.createTime, forKey: .createTime) - try container.encodeIfPresent(self.raw, forKey: .raw) - try container.encodeIfPresent(self.id, forKey: .id) - try container.encode(self.value.amount, forKey: .value) - try container.encodeIfPresent(self.memo, forKey: .memo) - try container.encodeIfPresent(self.rawTransactionId, forKey: .rawTransactionId) - try container.encodeIfPresent(self.fee?.amount, forKey: .fee) - } catch { - throw ZcashError.pendingTransactionCantEncode(error) - } - } - - func isSameTransactionId(other: T) -> Bool where T: RawIdentifiable { - self.rawTransactionId == other.rawTransactionId - } -} - -extension PendingTransaction { - init(value: Zatoshi, recipient: PendingTransactionRecipient, memo: MemoBytes?, account index: Int) { - var memoData: Data? - - if let memo { - memoData = Data(memo.bytes) - } - - self = PendingTransaction( - recipient: recipient, - accountIndex: index, - minedHeight: -1, - expiryHeight: -1, - cancelled: 0, - encodeAttempts: 0, - submitAttempts: 0, - errorMessage: nil, - errorCode: nil, - createTime: Date().timeIntervalSince1970, - raw: nil, - id: nil, - value: value, - memo: memoData, - rawTransactionId: nil, - fee: nil - ) - } -} - -class PendingTransactionSQLDAO: PendingTransactionRepository { - enum TableColumns { - static let toAddress = Expression("to_address") - static let toInternalAccount = Expression("to_internal") - static let accountIndex = Expression("account_index") - static let minedHeight = Expression("mined_height") - static let expiryHeight = Expression("expiry_height") - static let cancelled = Expression("cancelled") - static let encodeAttempts = Expression("encode_attempts") - static let errorMessage = Expression("error_message") - static let submitAttempts = Expression("submit_attempts") - static let errorCode = Expression("error_code") - static let createTime = Expression("create_time") - static let raw = Expression("raw") - static let id = Expression("id") - static let value = Expression("value") - static let memo = Expression("memo") - static let rawTransactionId = Expression("txid") - static let fee = Expression("fee") - } - - static let table = Table("pending_transactions") - - let dbProvider: ConnectionProvider - let logger: Logger - - init(dbProvider: ConnectionProvider, logger: Logger) { - self.dbProvider = dbProvider - self.logger = logger - } - - func closeDBConnection() { - dbProvider.close() - } - - /// - Throws: - /// - `pendingTransactionDAOCreate` if sqlite fails. - func create(_ transaction: PendingTransactionEntity) throws -> Int { - do { - let pendingTx = transaction as? PendingTransaction ?? PendingTransaction.from(entity: transaction) - return try Int(dbProvider.connection().run(Self.table.insert(pendingTx))) - } catch { - throw ZcashError.pendingTransactionDAOCreate(error) - } - } - - /// - Throws: - /// - `pendingTransactionDAOUpdateMissingID` if `transaction` (or entity created from `transaction`) is missing id. - /// - `pendingTransactionDAOUpdate` if sqlite fails - func update(_ transaction: PendingTransactionEntity) throws { - let pendingTx = transaction as? PendingTransaction ?? PendingTransaction.from(entity: transaction) - guard let id = pendingTx.id else { - throw ZcashError.pendingTransactionDAOUpdateMissingID - } - - do { - let updatedRows = try dbProvider.connection().run(Self.table.filter(TableColumns.id == id).update(pendingTx)) - if updatedRows == 0 { - logger.error("attempted to update pending transactions but no rows were updated") - } - } catch { - throw ZcashError.pendingTransactionDAOUpdate(error) - } - } - - /// - Throws: - /// - `pendingTransactionDAODeleteMissingID` if `transaction` (or entity created from `transaction`) is missing id. - /// - `pendingTransactionDAODelete` if sqlite fails - func delete(_ transaction: PendingTransactionEntity) throws { - guard let id = transaction.id else { - throw ZcashError.pendingTransactionDAODeleteMissingID - } - - do { - try dbProvider.connection().run(Self.table.filter(TableColumns.id == id).delete()) - } catch { - throw ZcashError.pendingTransactionDAODelete(error) - } - } - - /// - Throws: - /// - `pendingTransactionDAOCancelMissingID` if `transaction` (or entity created from `transaction`) is missing id. - /// - `pendingTransactionDAOCancel` if sqlite fails - func cancel(_ transaction: PendingTransactionEntity) throws { - var pendingTx = transaction as? PendingTransaction ?? PendingTransaction.from(entity: transaction) - pendingTx.cancelled = 1 - guard let txId = pendingTx.id else { - throw ZcashError.pendingTransactionDAOCancelMissingID - } - - do { - try dbProvider.connection().run(Self.table.filter(TableColumns.id == txId).update(pendingTx)) - } catch { - throw ZcashError.pendingTransactionDAOCancel(error) - } - } - - /// - Throws: - /// - `pendingTransactionDAOFind` if sqlite fails - func find(by id: Int) throws -> PendingTransactionEntity? { - do { - guard let row = try dbProvider.connection().pluck(Self.table.filter(TableColumns.id == id).limit(1)) else { - return nil - } - - let pendingTx: PendingTransaction = try row.decode() - return pendingTx - } catch { - if let error = error as? ZcashError { - throw error - } else { - throw ZcashError.pendingTransactionDAOFind(error) - } - } - } - - /// - Throws: - /// - `pendingTransactionDAOGetAll` if sqlite fails - func getAll() throws -> [PendingTransactionEntity] { - do { - let allTxs: [PendingTransaction] = try dbProvider.connection().prepare(Self.table).map { row in - try row.decode() - } - return allTxs - } catch { - if let error = error as? ZcashError { - throw error - } else { - throw ZcashError.pendingTransactionDAOGetAll(error) - } - } - } - - /// - Throws: - /// - `pendingTransactionDAOApplyMinedHeight` if sqlite fails - func applyMinedHeight(_ height: BlockHeight, id: Int) throws { - do { - let transaction = Self.table.filter(TableColumns.id == id) - - let updatedRows = try dbProvider.connection() - .run(transaction.update([TableColumns.minedHeight <- height])) - - if updatedRows == 0 { - logger.error("attempted to update a row but none was updated") - } - } catch { - throw ZcashError.pendingTransactionDAOApplyMinedHeight(error) - } - } -} diff --git a/Sources/ZcashLightClientKit/DAO/TransactionDao.swift b/Sources/ZcashLightClientKit/DAO/TransactionDao.swift index 16d6d8ec..61f4a0b3 100644 --- a/Sources/ZcashLightClientKit/DAO/TransactionDao.swift +++ b/Sources/ZcashLightClientKit/DAO/TransactionDao.swift @@ -15,19 +15,15 @@ class TransactionSQLDAO: TransactionRepository { } let dbProvider: ConnectionProvider - let transactions = Table("transactions") private let blockDao: BlockSQLDAO - private let sentNotesRepository: SentNotesRepository private let transactionsView = View("v_transactions") - private let receivedNotesTable = Table("received_notes") - private let sentNotesTable = Table("sent_notes") + private let txOutputsView = View("v_tx_outputs") private let traceClosure: ((String) -> Void)? init(dbProvider: ConnectionProvider, traceClosure: ((String) -> Void)? = nil) { self.dbProvider = dbProvider self.blockDao = BlockSQLDAO(dbProvider: dbProvider) - self.sentNotesRepository = SentNotesSQLDAO(dbProvider: dbProvider) self.traceClosure = traceClosure } @@ -59,7 +55,7 @@ class TransactionSQLDAO: TransactionRepository { func countAll() async throws -> Int { do { - return try connection().scalar(transactions.count) + return try connection().scalar(transactionsView.count) } catch { throw ZcashError.transactionRepositoryCountAll(error) } @@ -67,7 +63,7 @@ class TransactionSQLDAO: TransactionRepository { func countUnmined() async throws -> Int { do { - return try connection().scalar(transactions.filter(ZcashTransaction.Overview.Column.minedHeight == nil).count) + return try connection().scalar(transactionsView.filter(ZcashTransaction.Overview.Column.minedHeight == nil).count) } catch { throw ZcashError.transactionRepositoryCountUnmined(error) } @@ -131,66 +127,53 @@ class TransactionSQLDAO: TransactionRepository { return try execute(query) { try ZcashTransaction.Overview(row: $0) } } - func findReceived(offset: Int, limit: Int) async throws -> [ZcashTransaction.Received] { + func findReceived(offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview] { let query = transactionsView .filterQueryFor(kind: .received) .order(ZcashTransaction.Overview.Column.id.desc, (ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc) .limit(limit, offset: offset) return try execute(query) { try ZcashTransaction.Overview(row: $0) } - .compactMap { ZcashTransaction.Received(overview: $0) } } - func findSent(offset: Int, limit: Int) async throws -> [ZcashTransaction.Sent] { + func findSent(offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview] { let query = transactionsView .filterQueryFor(kind: .sent) .order((ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc, ZcashTransaction.Overview.Column.id.desc) .limit(limit, offset: offset) return try execute(query) { try ZcashTransaction.Overview(row: $0) } - .compactMap { ZcashTransaction.Sent(overview: $0) } + } + + func findPendingTransactions(latestHeight: BlockHeight, offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview] { + let query = transactionsView + .filterPendingFrom(latestHeight) + .order((ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc, ZcashTransaction.Overview.Column.id.desc) + .limit(limit, offset: offset) + + return try execute(query) { try ZcashTransaction.Overview(row: $0) } } func findMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo] { - if transaction.isSentTransaction { - return try await findMemos(for: transaction.id, table: sentNotesTable) - } else { - return try await findMemos(for: transaction.id, table: receivedNotesTable) - } - } - - func findMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo] { - return try await findMemos(for: receivedTransaction.id, table: receivedNotesTable) - } - - func findMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo] { - return try await findMemos(for: sentTransaction.id, table: sentNotesTable) - } - - func getRecipients(for id: Int) async -> [TransactionRecipient] { - return self.sentNotesRepository.getRecipients(for: id) - } - - private func findMemos(for transactionID: Int, table: Table) async throws -> [Memo] { - let query = table - .filter(NotesTableStructure.transactionID == transactionID) - do { - let memos = try connection().prepare(query).compactMap { row in - do { - let rawMemo = try row.get(NotesTableStructure.memo) - return try Memo(bytes: rawMemo.bytes) - } catch { - return nil - } - } - - return memos + return try await getTransactionOutputs(for: transaction.id) + .compactMap { $0.memo } } catch { throw ZcashError.transactionRepositoryFindMemos(error) } } + func getTransactionOutputs(for id: Int) async throws -> [ZcashTransaction.Output] { + let query = self.txOutputsView + .filter(ZcashTransaction.Output.Column.idTx == id) + + return try execute(query) { try ZcashTransaction.Output(row: $0) } + } + + func getRecipients(for id: Int) async throws -> [TransactionRecipient] { + try await getTransactionOutputs(for: id).map { $0.recipient } + } + private func execute(_ query: View, createEntity: (Row) throws -> Entity) throws -> Entity { let entities: [Entity] = try execute(query, createEntity: createEntity) guard let entity = entities.first else { throw ZcashError.transactionRepositoryEntityNotFound } @@ -214,6 +197,24 @@ class TransactionSQLDAO: TransactionRepository { } } +private extension View { + func filterPendingFrom(_ latestHeight: BlockHeight) -> View { + filter( + // transaction has not expired + ZcashTransaction.Overview.Column.expiredUnmined == false && + // transaction is "sent" + ZcashTransaction.Overview.Column.value < 0 && + // transaction has not been mined yet OR + // it has been within the latest `defaultStaleTolerance` blocks + ( + ZcashTransaction.Overview.Column.minedHeight == nil || + ZcashTransaction.Overview.Column.minedHeight > (latestHeight - ZcashSDK.defaultStaleTolerance) + ) + ) + + } +} + private extension View { func filterQueryFor(kind: TransactionKind) -> View { switch kind { diff --git a/Sources/ZcashLightClientKit/Entity/AccountEntity.swift b/Sources/ZcashLightClientKit/Entity/AccountEntity.swift index 730bc116..7e829bb9 100644 --- a/Sources/ZcashLightClientKit/Entity/AccountEntity.swift +++ b/Sources/ZcashLightClientKit/Entity/AccountEntity.swift @@ -13,7 +13,7 @@ protocol AccountEntity { var ufvk: String { get } } -struct Account: AccountEntity, Encodable, Decodable { +struct DbAccount: AccountEntity, Encodable, Decodable { enum CodingKeys: String, CodingKey { case account case ufvk @@ -23,7 +23,7 @@ struct Account: AccountEntity, Encodable, Decodable { let ufvk: String } -extension Account: Hashable { +extension DbAccount: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(account) hasher.combine(ufvk) @@ -68,7 +68,7 @@ class AccountSQDAO: AccountRepository { do { return try dbProvider.connection() .prepare(table) - .map { row -> Account in + .map { row -> DbAccount in do { return try row.decode() } catch { @@ -94,7 +94,7 @@ class AccountSQDAO: AccountRepository { .prepare(query) .map { do { - return try $0.decode() as Account + return try $0.decode() as DbAccount } catch { throw ZcashError.accountDAOFindByCantDecode(error) } @@ -113,7 +113,7 @@ class AccountSQDAO: AccountRepository { /// - `accountDAOUpdate` if sqlite query updating account failed. /// - `accountDAOUpdatedZeroRows` if sqlite query updating account pass but it affects 0 rows. func update(_ account: AccountEntity) throws { - guard let acc = account as? Account else { + guard let acc = account as? DbAccount else { throw ZcashError.accountDAOUpdateInvalidAccount } diff --git a/Sources/ZcashLightClientKit/Entity/EncodedTransactionEntity.swift b/Sources/ZcashLightClientKit/Entity/EncodedTransactionEntity.swift index cffb4078..63d01a54 100644 --- a/Sources/ZcashLightClientKit/Entity/EncodedTransactionEntity.swift +++ b/Sources/ZcashLightClientKit/Entity/EncodedTransactionEntity.swift @@ -9,7 +9,7 @@ import Foundation struct EncodedTransaction { let transactionId: Data - let raw: Data? + let raw: Data } extension EncodedTransaction: Hashable { diff --git a/Sources/ZcashLightClientKit/Entity/PendingTransactionEntity.swift b/Sources/ZcashLightClientKit/Entity/PendingTransactionEntity.swift deleted file mode 100644 index 33535573..00000000 --- a/Sources/ZcashLightClientKit/Entity/PendingTransactionEntity.swift +++ /dev/null @@ -1,220 +0,0 @@ -// -// PendingTransactionEntity.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 11/19/19. -// - -import Foundation - -public typealias TransactionRecipient = PendingTransactionRecipient - -public enum PendingTransactionRecipient: Equatable { - case address(Recipient) - case internalAccount(UInt32) -} - -/** -Represents a sent transaction that has not been confirmed yet on the blockchain -*/ -public protocol PendingTransactionEntity: RawIdentifiable { - /** - internal id for this transaction - */ - var id: Int? { get set } - - /** - value in zatoshi - */ - var value: Zatoshi { get } - - /** - data containing the memo if any - */ - var memo: Data? { get } - - var fee: Zatoshi? { get } - - var raw: Data? { get set } - - /** - recipient address - */ - var recipient: PendingTransactionRecipient { get } - - /** - index of the account from which the funds were sent - */ - var accountIndex: Int { get } - - /** - height which the block was mined at. - -1 when block has not been mined yet - */ - var minedHeight: BlockHeight { get set } - - /** - height for which the represented transaction would be considered expired - */ - var expiryHeight: BlockHeight { get set } - - /** - value is 1 if the transaction was cancelled - */ - var cancelled: Int { get set } - - /** - how many times this transaction encoding was attempted - */ - var encodeAttempts: Int { get set } - - /** - How many attempts to send this transaction have been done - */ - var submitAttempts: Int { get set } - - /** - Error message if available. - */ - var errorMessage: String? { get set } - - /** - error code, if available - */ - var errorCode: Int? { get set } - - /** - create time of the represented transaction - - - Note: represented in timeIntervalySince1970 - */ - var createTime: TimeInterval { get } - - /** - Checks whether this transaction is the same as the given transaction - */ - func isSameTransactionId (other: T) -> Bool - - /** - returns whether the represented transaction is pending based on the provided block height - */ - func isPending(currentHeight: Int) -> Bool - - /** - if the represented transaction is being created - */ - var isCreating: Bool { get } - - /** - returns whether the represented transaction has failed to be encoded - */ - var isFailedEncoding: Bool { get } - - /** - returns whether the represented transaction has failed to be submitted - */ - var isFailedSubmit: Bool { get } - - /** - returns whether the represented transaction presents some kind of error - */ - var isFailure: Bool { get } - - /** - returns whether the represented transaction has been cancelled by the user - */ - var isCancelled: Bool { get } - - /** - returns whether the represented transaction has been successfully mined - */ - var isMined: Bool { get } - - /** - returns whether the represented transaction has been submitted - */ - var isSubmitted: Bool { get } - - /** - returns whether the represented transaction has been submitted successfully - */ - var isSubmitSuccess: Bool { get } -} - -public extension PendingTransactionEntity { - func isSameTransaction(other: T) -> Bool { - guard let selfId = self.rawTransactionId, let otherId = other.rawTransactionId else { return false } - return selfId == otherId - } - - var isCreating: Bool { - (raw?.isEmpty ?? true) != false && submitAttempts <= 0 && !isFailedSubmit && !isFailedEncoding - } - - var isFailedEncoding: Bool { - (raw?.isEmpty ?? true) != false && encodeAttempts > 0 - } - - var isFailedSubmit: Bool { - errorMessage != nil || (errorCode != nil && (errorCode ?? 0) < 0) - } - - var isFailure: Bool { - isFailedEncoding || isFailedSubmit - } - - var isCancelled: Bool { - cancelled > 0 - } - - var isMined: Bool { - minedHeight > 0 - } - - var isSubmitted: Bool { - submitAttempts > 0 - } - - func isPending(currentHeight: Int = -1) -> Bool { - // not mined and not expired and successfully created - isSubmitSuccess && !isConfirmed(currentHeight: currentHeight) && (expiryHeight == -1 || expiryHeight > currentHeight) && raw != nil - } - - var isSubmitSuccess: Bool { - submitAttempts > 0 && (errorCode == nil || (errorCode ?? 0) >= 0) && errorMessage == nil - } - - func isConfirmed(currentHeight: Int = -1 ) -> Bool { - guard minedHeight > 0 else { - return false - } - - guard currentHeight > 0 else { - return false - } - - return abs(currentHeight - minedHeight) >= ZcashSDK.defaultStaleTolerance - } -} - -public extension PendingTransactionEntity { - func makeTransactionEntity(defaultFee: Zatoshi) -> ZcashTransaction.Overview { - return ZcashTransaction.Overview( - accountId: 0, - blockTime: createTime, - expiryHeight: expiryHeight, - fee: fee, - id: id ?? -1, - index: nil, - hasChange: false, - memoCount: 0, - minedHeight: minedHeight, - raw: raw, - rawID: rawTransactionId ?? Data(), - receivedNoteCount: 0, - sentNoteCount: 0, - value: value, - isExpiredUmined: false - ) - } -} diff --git a/Sources/ZcashLightClientKit/Entity/ReceivedNoteEntity.swift b/Sources/ZcashLightClientKit/Entity/ReceivedNoteEntity.swift deleted file mode 100644 index 11ffe09b..00000000 --- a/Sources/ZcashLightClientKit/Entity/ReceivedNoteEntity.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// ReceivedNotesEntity.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 11/14/19. -// - -import Foundation - -protocol ReceivedNoteEntity { - var id: Int { get } - var transactionId: Int { get } - var outputIndex: Int { get } - var account: Int { get } - var value: Int { get } - var memo: Data? { get } - var spent: Int? { get } - var diversifier: Data { get } - var rcm: Data { get } - var nf: Data { get } - var isChange: Bool { get } -} diff --git a/Sources/ZcashLightClientKit/Entity/TransactionEntity.swift b/Sources/ZcashLightClientKit/Entity/TransactionEntity.swift index e0373793..4091ea43 100644 --- a/Sources/ZcashLightClientKit/Entity/TransactionEntity.swift +++ b/Sources/ZcashLightClientKit/Entity/TransactionEntity.swift @@ -10,6 +10,39 @@ import SQLite public enum ZcashTransaction { public struct Overview { + /// Represents the transaction state based on current height of the chain, + /// mined height and expiry height of a transaction. + public enum State { + /// transaction has a `minedHeight` that's greater or equal than + /// `ZcashSDK.defaultStaleTolerance` confirmations. + case confirmed + /// transaction has no `minedHeight` but current known height is less than + /// `expiryHeight`. + case pending + /// transaction has no + case expired + + init(currentHeight: BlockHeight, + minedHeight: BlockHeight?, + expiredUnmined: Bool + ) { + guard !expiredUnmined else { + self = .expired + return + } + + if let minedHeight, (currentHeight - minedHeight) >= ZcashSDK.defaultStaleTolerance { + self = .confirmed + } else if let minedHeight, (currentHeight - minedHeight) < ZcashSDK.defaultStaleTolerance { + self = .pending + } else if minedHeight == nil { + self = .pending + } else { + self = .expired + } + } + } + public let accountId: Int public let blockTime: TimeInterval? public let expiryHeight: BlockHeight? @@ -28,32 +61,31 @@ public enum ZcashTransaction { public let isExpiredUmined: Bool } - public struct Received { - public let blockTime: TimeInterval - public let expiryHeight: BlockHeight? - public let fromAccount: Int - public let id: Int - public let index: Int - public let memoCount: Int - public let minedHeight: BlockHeight - public let noteCount: Int - public let raw: Data? - public let rawID: Data? - public let value: Zatoshi - } + public struct Output { + public enum Pool { + case transaparent + case sapling + case other(Int) + init(rawValue: Int) { + switch rawValue { + case 0: + self = .transaparent + case 2: + self = .sapling + default: + self = .other(rawValue) + } + } + } - public struct Sent { - public let blockTime: TimeInterval? - public let expiryHeight: BlockHeight? - public let fromAccount: Int - public let id: Int - public let index: Int? - public let memoCount: Int - public let minedHeight: BlockHeight? - public let noteCount: Int - public let raw: Data? - public let rawID: Data? + public let idTx: Int + public let pool: Pool + public let index: Int + public let fromAccount: Int? + public let recipient: TransactionRecipient public let value: Zatoshi + public let isChange: Bool + public let memo: Memo? } /// Used when fetching blocks from the lightwalletd @@ -64,6 +96,50 @@ public enum ZcashTransaction { } } +extension ZcashTransaction.Output { + enum Column { + static let idTx = Expression("id_tx") + static let pool = Expression("output_pool") + static let index = Expression("output_index") + static let toAccount = Expression("to_account") + static let fromAccount = Expression("from_account") + static let toAddress = Expression("to_address") + static let value = Expression("value") + static let isChange = Expression("is_change") + static let memo = Expression("memo") + } + + init(row: Row) throws { + do { + idTx = try row.get(Column.idTx) + pool = .init(rawValue: try row.get(Column.pool)) + index = try row.get(Column.index) + fromAccount = try row.get(Column.fromAccount) + value = Zatoshi(try row.get(Column.value)) + isChange = try row.get(Column.isChange) + + if + let outputRecipient = try row.get(Column.toAddress), + let metadata = DerivationTool.getAddressMetadata(outputRecipient) + { + recipient = TransactionRecipient.address(try Recipient(outputRecipient, network: metadata.networkType)) + } else if let toAccount = try row.get(Column.toAccount) { + recipient = .internalAccount(UInt32(toAccount)) + } else { + throw ZcashError.zcashTransactionOutputInconsistentRecipient + } + + if let memoData = try row.get(Column.memo) { + memo = try Memo(bytes: memoData.bytes) + } else { + memo = nil + } + } catch { + throw ZcashError.zcashTransactionOutputInit(error) + } + } +} + extension ZcashTransaction.Overview { enum Column { static let accountId = Expression("account_id") @@ -97,6 +173,7 @@ extension ZcashTransaction.Overview { self.sentNoteCount = try row.get(Column.sentNoteCount) self.value = Zatoshi(try row.get(Column.value)) self.isExpiredUmined = try row.get(Column.expiredUnmined) + if let blockTime = try row.get(Column.blockTime) { self.blockTime = TimeInterval(blockTime) } else { @@ -134,51 +211,21 @@ extension ZcashTransaction.Overview { } } -extension ZcashTransaction.Received { - /// Attempts to create a `ZcashTransaction.Received` from an `Overview` - /// given that the transaction might not be a "sent" transaction, so it won't have the necessary - /// data to actually create it as it is currently defined, this initializer is optional - /// - returns: Optional. `Some` if the values present suffice to create a received - /// transaction otherwise `.none` - init?(overview: ZcashTransaction.Overview) { - guard - !overview.isSentTransaction, - let txBlocktime = overview.blockTime, - let txIndex = overview.index, - let txMinedHeight = overview.minedHeight - else { return nil } +/// extension to handle pending states +public extension ZcashTransaction.Overview { + func getState(for currentHeight: BlockHeight) -> State { + State( + currentHeight: currentHeight, + minedHeight: minedHeight, + expiredUnmined: self.isExpiredUmined + ) + } - self.blockTime = txBlocktime - self.expiryHeight = overview.expiryHeight - self.fromAccount = overview.accountId - self.id = overview.id - self.index = txIndex - self.memoCount = overview.memoCount - self.minedHeight = txMinedHeight - self.noteCount = overview.receivedNoteCount - self.value = overview.value - self.raw = overview.raw - self.rawID = overview.rawID + func isPending(currentHeight: BlockHeight) -> Bool { + getState(for: currentHeight) == .pending } } -extension ZcashTransaction.Sent { - init?(overview: ZcashTransaction.Overview) { - guard overview.isSentTransaction else { return nil } - - self.blockTime = overview.blockTime - self.expiryHeight = overview.expiryHeight - self.fromAccount = overview.accountId - self.id = overview.id - self.index = overview.index - self.memoCount = overview.memoCount - self.minedHeight = overview.minedHeight - self.noteCount = overview.sentNoteCount - self.value = overview.value - self.raw = overview.raw - self.rawID = overview.rawID - } -} /** Capabilities of an entity that can be uniquely identified by a raw transaction id diff --git a/Sources/ZcashLightClientKit/Error/ZcashError.swift b/Sources/ZcashLightClientKit/Error/ZcashError.swift index d509dfe0..68193463 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashError.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashError.swift @@ -44,18 +44,6 @@ public enum ZcashError: Equatable, Error { /// LightWalletService.blockStream failed. /// ZSRVC0000 case serviceBlockStreamFailed(_ error: LightWalletServiceError) - /// Migration of the pending DB failed because of unspecific reason. - /// ZDBMG0001 - case dbMigrationGenericFailure(_ error: Error) - /// Migration of the pending DB failed because unknown version of the existing database. - /// ZDBMG00002 - case dbMigrationInvalidVersion - /// Migration of the pending DB to version 1 failed. - /// ZDBMG00003 - case dbMigrationV1(_ dbError: Error) - /// Migration of the pending DB to version 2 failed. - /// ZDBMG00004 - case dbMigrationV2(_ dbError: Error) /// SimpleConnectionProvider init of Connection failed. /// ZSCPC0001 case simpleConnectionProvider(_ error: Error) @@ -76,30 +64,6 @@ public enum ZcashError: Equatable, Error { /// - `destination` is filesystem URL pointing to location where downloaded file should be moved. /// ZSAPP0004 case saplingParamsCantMoveDownloadedFile(_ error: Error, _ downloadURL: URL, _ destination: URL) - /// SQLite query failed when fetching received notes count from the database. - /// - `sqliteError` is error produced by SQLite library. - /// ZNDAO0001 - case notesDAOReceivedCount(_ sqliteError: Error) - /// SQLite query failed when fetching received notes from the database. - /// - `sqliteError` is error produced by SQLite library. - /// ZNDAO0002 - case notesDAOReceivedNote(_ sqliteError: Error) - /// Fetched note from the SQLite but can't decode that. - /// - `error` is decoding error. - /// ZNDAO0003 - case notesDAOReceivedCantDecode(_ error: Error) - /// SQLite query failed when fetching sent notes count from the database. - /// - `sqliteError` is error produced by SQLite library. - /// ZNDAO0004 - case notesDAOSentCount(_ sqliteError: Error) - /// SQLite query failed when fetching sent notes from the database. - /// - `sqliteError` is error produced by SQLite library. - /// ZNDAO0005 - case notesDAOSentNote(_ sqliteError: Error) - /// Fetched note from the SQLite but can't decode that. - /// - `error` is decoding error. - /// ZNDAO0006 - case notesDAOSentCantDecode(_ error: Error) /// SQLite query failed when fetching block information from database. /// - `sqliteError` is error produced by SQLite library. /// ZBDAO0001 @@ -361,6 +325,12 @@ public enum ZcashError: Equatable, Error { /// Initialization of `ZcashTransaction.Sent` failed. /// ZTEZT0003 case zcashTransactionSentInit(_ error: Error) + /// Initialization of `ZcashTransaction.Output` failed. + /// ZTEZT0004 + case zcashTransactionOutputInit(_ error: Error) + /// Initialization of `ZcashTransaction.Output` failed because there an inconsistency in the output recipient. + /// ZTEZT0005 + case zcashTransactionOutputInconsistentRecipient /// Entity not found in the database, result of `createEntity` execution. /// ZTREE0001 case transactionRepositoryEntityNotFound @@ -403,55 +373,6 @@ public enum ZcashError: Equatable, Error { /// Failed to decode `Checkpoint` object. /// ZCHKP0002 case checkpointDecode(_ error: Error) - /// Decoding of `PendingTransaction` failed because of specific invalid data. - /// - `field` is list of fields names that contain invalid data. - /// ZPETR0001 - case pendingTransactionDecodeInvalidData(_ fields: [String]) - /// Can't decode `PendingTransaction`. - /// - `error` is error which described why decoding failed. - /// ZPETR0002 - case pendingTransactionCantDecode(_ error: Error) - /// Can't encode `PendingTransaction`. - /// - `error` is error which described why encoding failed. - /// ZPETR0003 - case pendingTransactionCantEncode(_ error: Error) - /// SQLite query failed when creating pending transaction. - /// - `sqliteError` is error produced by SQLite library. - /// ZPETR0004 - case pendingTransactionDAOCreate(_ sqliteError: Error) - /// Pending transaction which should be updated is missing ID. - /// ZPETR0005 - case pendingTransactionDAOUpdateMissingID - /// SQLite query failed when updating pending transaction. - /// - `sqliteError` is error produced by SQLite library. - /// ZPETR0006 - case pendingTransactionDAOUpdate(_ sqliteError: Error) - /// Pending transaction which should be deleted is missing ID. - /// ZPETR0007 - case pendingTransactionDAODeleteMissingID - /// SQLite query failed when deleting pending transaction. - /// - `sqliteError` is error produced by SQLite library. - /// ZPETR0008 - case pendingTransactionDAODelete(_ sqliteError: Error) - /// Pending transaction which should be canceled is missing ID. - /// ZPETR0009 - case pendingTransactionDAOCancelMissingID - /// SQLite query failed when canceling pending transaction. - /// - `sqliteError` is error produced by SQLite library. - /// ZPETR0010 - case pendingTransactionDAOCancel(_ sqliteError: Error) - /// SQLite query failed when seaching for pending transaction. - /// - `sqliteError` is error produced by SQLite library. - /// ZPETR0011 - case pendingTransactionDAOFind(_ sqliteError: Error) - /// SQLite query failed when getting pending transactions. - /// - `sqliteError` is error produced by SQLite library. - /// ZPETR0012 - case pendingTransactionDAOGetAll(_ sqliteError: Error) - /// SQLite query failed when applying mined height. - /// - `sqliteError` is error produced by SQLite library. - /// ZPETR0013 - case pendingTransactionDAOApplyMinedHeight(_ sqliteError: Error) /// Invalid account when trying to derive spending key /// ZDRVT0001 case derivationToolSpendingKeyInvalidAccount @@ -506,33 +427,6 @@ public enum ZcashError: Equatable, Error { /// WalletTransactionEncoder wants to shield funds but files with sapling parameters are not present on disk. /// ZWLTE0002 case walletTransEncoderShieldFundsMissingSaplingParams - /// PersistentTransactionsManager cant create transaction. - /// ZPTRM0001 - case persistentTransManagerCantCreateTransaction(_ recipient: PendingTransactionRecipient, _ account: Int, _ zatoshi: Zatoshi) - /// PersistentTransactionsManager cant get to address from pending transaction. - /// ZPTRM0002 - case persistentTransManagerEncodeUknownToAddress(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing id. - /// ZPTRM0003 - case persistentTransManagerSubmitTransactionIDMissing(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing id. Transaction is probably not stored. - /// ZPTRM0004 - case persistentTransManagerSubmitTransactionNotFound(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but transaction is canceled. - /// ZPTRM0005 - case persistentTransManagerSubmitTransactionCanceled(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing raw data. - /// ZPTRM0006 - case persistentTransManagerSubmitTransactionRawDataMissing(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but submit API call failed. - /// ZPTRM0007 - case persistentTransManagerSubmitFailed(_ entity: PendingTransactionEntity, _ serviceErrorCode: Int) - /// PersistentTransactionsManager wants to apply mined height to transaction but transaction is missing id. Transaction is probably not stored. - /// ZPTRM0008 - case persistentTransManagerApplyMinedHeightTransactionIDMissing(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to apply mined height to transaction but transaction is not found in storage. Transaction is probably not stored. - /// ZPTRM0009 - case persistentTransManagerApplyMinedHeightTransactionNotFound(_ entity: PendingTransactionEntity) /// Initiatilzation fo `Zatoshi` from a decoder failed. /// ZTSHO0001 case zatoshiDecode(_ error: Error) @@ -622,21 +516,11 @@ public enum ZcashError: Equatable, Error { case .serviceFetchTransactionFailed: return "LightWalletService.fetchTransaction failed." case .serviceFetchUTXOsFailed: return "LightWalletService.fetchUTXOs failed." case .serviceBlockStreamFailed: return "LightWalletService.blockStream failed." - case .dbMigrationGenericFailure: return "Migration of the pending DB failed because of unspecific reason." - case .dbMigrationInvalidVersion: return "Migration of the pending DB failed because unknown version of the existing database." - case .dbMigrationV1: return "Migration of the pending DB to version 1 failed." - case .dbMigrationV2: return "Migration of the pending DB to version 2 failed." case .simpleConnectionProvider: return "SimpleConnectionProvider init of Connection failed." case .saplingParamsInvalidSpendParams: return "Downloaded file with sapling spending parameters isn't valid." case .saplingParamsInvalidOutputParams: return "Downloaded file with sapling output parameters isn't valid." case .saplingParamsDownload: return "Failed to download sapling parameters file" case .saplingParamsCantMoveDownloadedFile: return "Failed to move sapling parameters file to final destination after download." - case .notesDAOReceivedCount: return "SQLite query failed when fetching received notes count from the database." - case .notesDAOReceivedNote: return "SQLite query failed when fetching received notes from the database." - case .notesDAOReceivedCantDecode: return "Fetched note from the SQLite but can't decode that." - case .notesDAOSentCount: return "SQLite query failed when fetching sent notes count from the database." - case .notesDAOSentNote: return "SQLite query failed when fetching sent notes from the database." - case .notesDAOSentCantDecode: return "Fetched note from the SQLite but can't decode that." case .blockDAOBlock: return "SQLite query failed when fetching block information from database." case .blockDAOCantDecode: return "Fetched block information from DB but can't decode them." case .blockDAOLatestBlockHeight: return "SQLite query failed when fetching height of the latest block from the database." @@ -709,6 +593,8 @@ public enum ZcashError: Equatable, Error { case .zcashTransactionOverviewInit: return "Initialization of `ZcashTransaction.Overview` failed." case .zcashTransactionReceivedInit: return "Initialization of `ZcashTransaction.Received` failed." case .zcashTransactionSentInit: return "Initialization of `ZcashTransaction.Sent` failed." + case .zcashTransactionOutputInit: return "Initialization of `ZcashTransaction.Output` failed." + case .zcashTransactionOutputInconsistentRecipient: return "Initialization of `ZcashTransaction.Output` failed because there an inconsistency in the output recipient." case .transactionRepositoryEntityNotFound: return "Entity not found in the database, result of `createEntity` execution." case .transactionRepositoryTransactionMissingRequiredFields: return "`Find` call is missing fields, required fields are transaction `index` and `blockTime`." case .transactionRepositoryCountAll: return "Counting all transactions failed." @@ -723,19 +609,6 @@ public enum ZcashError: Equatable, Error { case .memoBytesInvalidUTF8: return "Invalid UTF-8 Bytes where detected when attempting to convert MemoBytes to Memo." case .checkpointCantLoadFromDisk: return "Failed to load JSON with checkpoint from disk." case .checkpointDecode: return "Failed to decode `Checkpoint` object." - case .pendingTransactionDecodeInvalidData: return "Decoding of `PendingTransaction` failed because of specific invalid data." - case .pendingTransactionCantDecode: return "Can't decode `PendingTransaction`." - case .pendingTransactionCantEncode: return "Can't encode `PendingTransaction`." - case .pendingTransactionDAOCreate: return "SQLite query failed when creating pending transaction." - case .pendingTransactionDAOUpdateMissingID: return "Pending transaction which should be updated is missing ID." - case .pendingTransactionDAOUpdate: return "SQLite query failed when updating pending transaction." - case .pendingTransactionDAODeleteMissingID: return "Pending transaction which should be deleted is missing ID." - case .pendingTransactionDAODelete: return "SQLite query failed when deleting pending transaction." - case .pendingTransactionDAOCancelMissingID: return "Pending transaction which should be canceled is missing ID." - case .pendingTransactionDAOCancel: return "SQLite query failed when canceling pending transaction." - case .pendingTransactionDAOFind: return "SQLite query failed when seaching for pending transaction." - case .pendingTransactionDAOGetAll: return "SQLite query failed when getting pending transactions." - case .pendingTransactionDAOApplyMinedHeight: return "SQLite query failed when applying mined height." case .derivationToolSpendingKeyInvalidAccount: return "Invalid account when trying to derive spending key" case .unspentTransactionOutputDAOCreateTable: return "Creation of the table for unspent transaction output failed." case .unspentTransactionOutputDAOStore: return "SQLite query failed when storing unspent transaction output." @@ -752,15 +625,6 @@ public enum ZcashError: Equatable, Error { case .recipientInvalidInput: return "Can't create `Recipient` because input is invalid." case .walletTransEncoderCreateTransactionMissingSaplingParams: return "WalletTransactionEncoder wants to create transaction but files with sapling parameters are not present on disk." case .walletTransEncoderShieldFundsMissingSaplingParams: return "WalletTransactionEncoder wants to shield funds but files with sapling parameters are not present on disk." - case .persistentTransManagerCantCreateTransaction: return "PersistentTransactionsManager cant create transaction." - case .persistentTransManagerEncodeUknownToAddress: return "PersistentTransactionsManager cant get to address from pending transaction." - case .persistentTransManagerSubmitTransactionIDMissing: return "PersistentTransactionsManager wants to submit pending transaction but transaction is missing id." - case .persistentTransManagerSubmitTransactionNotFound: return "PersistentTransactionsManager wants to submit pending transaction but transaction is missing id. Transaction is probably not stored." - case .persistentTransManagerSubmitTransactionCanceled: return "PersistentTransactionsManager wants to submit pending transaction but transaction is canceled." - case .persistentTransManagerSubmitTransactionRawDataMissing: return "PersistentTransactionsManager wants to submit pending transaction but transaction is missing raw data." - case .persistentTransManagerSubmitFailed: return "PersistentTransactionsManager wants to submit pending transaction but submit API call failed." - case .persistentTransManagerApplyMinedHeightTransactionIDMissing: return "PersistentTransactionsManager wants to apply mined height to transaction but transaction is missing id. Transaction is probably not stored." - case .persistentTransManagerApplyMinedHeightTransactionNotFound: return "PersistentTransactionsManager wants to apply mined height to transaction but transaction is not found in storage. Transaction is probably not stored." case .zatoshiDecode: return "Initiatilzation fo `Zatoshi` from a decoder failed." case .zatoshiEncode: return "Encode of `Zatoshi` failed." case .unspentTransactionFetcherStream: return "Awaiting transactions from the stream failed." @@ -802,21 +666,11 @@ public enum ZcashError: Equatable, Error { case .serviceFetchTransactionFailed: return .serviceFetchTransactionFailed case .serviceFetchUTXOsFailed: return .serviceFetchUTXOsFailed case .serviceBlockStreamFailed: return .serviceBlockStreamFailed - case .dbMigrationGenericFailure: return .dbMigrationGenericFailure - case .dbMigrationInvalidVersion: return .dbMigrationInvalidVersion - case .dbMigrationV1: return .dbMigrationV1 - case .dbMigrationV2: return .dbMigrationV2 case .simpleConnectionProvider: return .simpleConnectionProvider case .saplingParamsInvalidSpendParams: return .saplingParamsInvalidSpendParams case .saplingParamsInvalidOutputParams: return .saplingParamsInvalidOutputParams case .saplingParamsDownload: return .saplingParamsDownload case .saplingParamsCantMoveDownloadedFile: return .saplingParamsCantMoveDownloadedFile - case .notesDAOReceivedCount: return .notesDAOReceivedCount - case .notesDAOReceivedNote: return .notesDAOReceivedNote - case .notesDAOReceivedCantDecode: return .notesDAOReceivedCantDecode - case .notesDAOSentCount: return .notesDAOSentCount - case .notesDAOSentNote: return .notesDAOSentNote - case .notesDAOSentCantDecode: return .notesDAOSentCantDecode case .blockDAOBlock: return .blockDAOBlock case .blockDAOCantDecode: return .blockDAOCantDecode case .blockDAOLatestBlockHeight: return .blockDAOLatestBlockHeight @@ -889,6 +743,8 @@ public enum ZcashError: Equatable, Error { case .zcashTransactionOverviewInit: return .zcashTransactionOverviewInit case .zcashTransactionReceivedInit: return .zcashTransactionReceivedInit case .zcashTransactionSentInit: return .zcashTransactionSentInit + case .zcashTransactionOutputInit: return .zcashTransactionOutputInit + case .zcashTransactionOutputInconsistentRecipient: return .zcashTransactionOutputInconsistentRecipient case .transactionRepositoryEntityNotFound: return .transactionRepositoryEntityNotFound case .transactionRepositoryTransactionMissingRequiredFields: return .transactionRepositoryTransactionMissingRequiredFields case .transactionRepositoryCountAll: return .transactionRepositoryCountAll @@ -903,19 +759,6 @@ public enum ZcashError: Equatable, Error { case .memoBytesInvalidUTF8: return .memoBytesInvalidUTF8 case .checkpointCantLoadFromDisk: return .checkpointCantLoadFromDisk case .checkpointDecode: return .checkpointDecode - case .pendingTransactionDecodeInvalidData: return .pendingTransactionDecodeInvalidData - case .pendingTransactionCantDecode: return .pendingTransactionCantDecode - case .pendingTransactionCantEncode: return .pendingTransactionCantEncode - case .pendingTransactionDAOCreate: return .pendingTransactionDAOCreate - case .pendingTransactionDAOUpdateMissingID: return .pendingTransactionDAOUpdateMissingID - case .pendingTransactionDAOUpdate: return .pendingTransactionDAOUpdate - case .pendingTransactionDAODeleteMissingID: return .pendingTransactionDAODeleteMissingID - case .pendingTransactionDAODelete: return .pendingTransactionDAODelete - case .pendingTransactionDAOCancelMissingID: return .pendingTransactionDAOCancelMissingID - case .pendingTransactionDAOCancel: return .pendingTransactionDAOCancel - case .pendingTransactionDAOFind: return .pendingTransactionDAOFind - case .pendingTransactionDAOGetAll: return .pendingTransactionDAOGetAll - case .pendingTransactionDAOApplyMinedHeight: return .pendingTransactionDAOApplyMinedHeight case .derivationToolSpendingKeyInvalidAccount: return .derivationToolSpendingKeyInvalidAccount case .unspentTransactionOutputDAOCreateTable: return .unspentTransactionOutputDAOCreateTable case .unspentTransactionOutputDAOStore: return .unspentTransactionOutputDAOStore @@ -932,15 +775,6 @@ public enum ZcashError: Equatable, Error { case .recipientInvalidInput: return .recipientInvalidInput case .walletTransEncoderCreateTransactionMissingSaplingParams: return .walletTransEncoderCreateTransactionMissingSaplingParams case .walletTransEncoderShieldFundsMissingSaplingParams: return .walletTransEncoderShieldFundsMissingSaplingParams - case .persistentTransManagerCantCreateTransaction: return .persistentTransManagerCantCreateTransaction - case .persistentTransManagerEncodeUknownToAddress: return .persistentTransManagerEncodeUknownToAddress - case .persistentTransManagerSubmitTransactionIDMissing: return .persistentTransManagerSubmitTransactionIDMissing - case .persistentTransManagerSubmitTransactionNotFound: return .persistentTransManagerSubmitTransactionNotFound - case .persistentTransManagerSubmitTransactionCanceled: return .persistentTransManagerSubmitTransactionCanceled - case .persistentTransManagerSubmitTransactionRawDataMissing: return .persistentTransManagerSubmitTransactionRawDataMissing - case .persistentTransManagerSubmitFailed: return .persistentTransManagerSubmitFailed - case .persistentTransManagerApplyMinedHeightTransactionIDMissing: return .persistentTransManagerApplyMinedHeightTransactionIDMissing - case .persistentTransManagerApplyMinedHeightTransactionNotFound: return .persistentTransManagerApplyMinedHeightTransactionNotFound case .zatoshiDecode: return .zatoshiDecode case .zatoshiEncode: return .zatoshiEncode case .unspentTransactionFetcherStream: return .unspentTransactionFetcherStream diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift index 29e52cd3..b9329122 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift @@ -31,14 +31,6 @@ public enum ZcashErrorCode: String { case serviceFetchUTXOsFailed = "ZSRVC0008" /// LightWalletService.blockStream failed. case serviceBlockStreamFailed = "ZSRVC0000" - /// Migration of the pending DB failed because of unspecific reason. - case dbMigrationGenericFailure = "ZDBMG0001" - /// Migration of the pending DB failed because unknown version of the existing database. - case dbMigrationInvalidVersion = "ZDBMG00002" - /// Migration of the pending DB to version 1 failed. - case dbMigrationV1 = "ZDBMG00003" - /// Migration of the pending DB to version 2 failed. - case dbMigrationV2 = "ZDBMG00004" /// SimpleConnectionProvider init of Connection failed. case simpleConnectionProvider = "ZSCPC0001" /// Downloaded file with sapling spending parameters isn't valid. @@ -49,18 +41,6 @@ public enum ZcashErrorCode: String { case saplingParamsDownload = "ZSAPP0003" /// Failed to move sapling parameters file to final destination after download. case saplingParamsCantMoveDownloadedFile = "ZSAPP0004" - /// SQLite query failed when fetching received notes count from the database. - case notesDAOReceivedCount = "ZNDAO0001" - /// SQLite query failed when fetching received notes from the database. - case notesDAOReceivedNote = "ZNDAO0002" - /// Fetched note from the SQLite but can't decode that. - case notesDAOReceivedCantDecode = "ZNDAO0003" - /// SQLite query failed when fetching sent notes count from the database. - case notesDAOSentCount = "ZNDAO0004" - /// SQLite query failed when fetching sent notes from the database. - case notesDAOSentNote = "ZNDAO0005" - /// Fetched note from the SQLite but can't decode that. - case notesDAOSentCantDecode = "ZNDAO0006" /// SQLite query failed when fetching block information from database. case blockDAOBlock = "ZBDAO0001" /// Fetched block information from DB but can't decode them. @@ -205,6 +185,10 @@ public enum ZcashErrorCode: String { case zcashTransactionReceivedInit = "ZTEZT0002" /// Initialization of `ZcashTransaction.Sent` failed. case zcashTransactionSentInit = "ZTEZT0003" + /// Initialization of `ZcashTransaction.Output` failed. + case zcashTransactionOutputInit = "ZTEZT0004" + /// Initialization of `ZcashTransaction.Output` failed because there an inconsistency in the output recipient. + case zcashTransactionOutputInconsistentRecipient = "ZTEZT0005" /// Entity not found in the database, result of `createEntity` execution. case transactionRepositoryEntityNotFound = "ZTREE0001" /// `Find` call is missing fields, required fields are transaction `index` and `blockTime`. @@ -233,32 +217,6 @@ public enum ZcashErrorCode: String { case checkpointCantLoadFromDisk = "ZCHKP0001" /// Failed to decode `Checkpoint` object. case checkpointDecode = "ZCHKP0002" - /// Decoding of `PendingTransaction` failed because of specific invalid data. - case pendingTransactionDecodeInvalidData = "ZPETR0001" - /// Can't decode `PendingTransaction`. - case pendingTransactionCantDecode = "ZPETR0002" - /// Can't encode `PendingTransaction`. - case pendingTransactionCantEncode = "ZPETR0003" - /// SQLite query failed when creating pending transaction. - case pendingTransactionDAOCreate = "ZPETR0004" - /// Pending transaction which should be updated is missing ID. - case pendingTransactionDAOUpdateMissingID = "ZPETR0005" - /// SQLite query failed when updating pending transaction. - case pendingTransactionDAOUpdate = "ZPETR0006" - /// Pending transaction which should be deleted is missing ID. - case pendingTransactionDAODeleteMissingID = "ZPETR0007" - /// SQLite query failed when deleting pending transaction. - case pendingTransactionDAODelete = "ZPETR0008" - /// Pending transaction which should be canceled is missing ID. - case pendingTransactionDAOCancelMissingID = "ZPETR0009" - /// SQLite query failed when canceling pending transaction. - case pendingTransactionDAOCancel = "ZPETR0010" - /// SQLite query failed when seaching for pending transaction. - case pendingTransactionDAOFind = "ZPETR0011" - /// SQLite query failed when getting pending transactions. - case pendingTransactionDAOGetAll = "ZPETR0012" - /// SQLite query failed when applying mined height. - case pendingTransactionDAOApplyMinedHeight = "ZPETR0013" /// Invalid account when trying to derive spending key case derivationToolSpendingKeyInvalidAccount = "ZDRVT0001" /// Creation of the table for unspent transaction output failed. @@ -291,24 +249,6 @@ public enum ZcashErrorCode: String { case walletTransEncoderCreateTransactionMissingSaplingParams = "ZWLTE0001" /// WalletTransactionEncoder wants to shield funds but files with sapling parameters are not present on disk. case walletTransEncoderShieldFundsMissingSaplingParams = "ZWLTE0002" - /// PersistentTransactionsManager cant create transaction. - case persistentTransManagerCantCreateTransaction = "ZPTRM0001" - /// PersistentTransactionsManager cant get to address from pending transaction. - case persistentTransManagerEncodeUknownToAddress = "ZPTRM0002" - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing id. - case persistentTransManagerSubmitTransactionIDMissing = "ZPTRM0003" - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing id. Transaction is probably not stored. - case persistentTransManagerSubmitTransactionNotFound = "ZPTRM0004" - /// PersistentTransactionsManager wants to submit pending transaction but transaction is canceled. - case persistentTransManagerSubmitTransactionCanceled = "ZPTRM0005" - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing raw data. - case persistentTransManagerSubmitTransactionRawDataMissing = "ZPTRM0006" - /// PersistentTransactionsManager wants to submit pending transaction but submit API call failed. - case persistentTransManagerSubmitFailed = "ZPTRM0007" - /// PersistentTransactionsManager wants to apply mined height to transaction but transaction is missing id. Transaction is probably not stored. - case persistentTransManagerApplyMinedHeightTransactionIDMissing = "ZPTRM0008" - /// PersistentTransactionsManager wants to apply mined height to transaction but transaction is not found in storage. Transaction is probably not stored. - case persistentTransManagerApplyMinedHeightTransactionNotFound = "ZPTRM0009" /// Initiatilzation fo `Zatoshi` from a decoder failed. case zatoshiDecode = "ZTSHO0001" /// Encode of `Zatoshi` failed. diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift index 3c111e78..ec1914eb 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift @@ -63,21 +63,6 @@ enum ZcashErrorDefinition { // sourcery: code="ZSRVC0000" case serviceBlockStreamFailed(_ error: LightWalletServiceError) - // MARK: - DB Migration - - /// Migration of the pending DB failed because of unspecific reason. - // sourcery: code="ZDBMG0001" - case dbMigrationGenericFailure(_ error: Error) - /// Migration of the pending DB failed because unknown version of the existing database. - // sourcery: code="ZDBMG00002" - case dbMigrationInvalidVersion - /// Migration of the pending DB to version 1 failed. - // sourcery: code="ZDBMG00003" - case dbMigrationV1(_ dbError: Error) - /// Migration of the pending DB to version 2 failed. - // sourcery: code="ZDBMG00004" - case dbMigrationV2(_ dbError: Error) - // MARK: SQLite connection /// SimpleConnectionProvider init of Connection failed. @@ -104,33 +89,6 @@ enum ZcashErrorDefinition { // sourcery: code="ZSAPP0004" case saplingParamsCantMoveDownloadedFile(_ error: Error, _ downloadURL: URL, _ destination: URL) - // MARK: - NotesDAO - - /// SQLite query failed when fetching received notes count from the database. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZNDAO0001" - case notesDAOReceivedCount(_ sqliteError: Error) - /// SQLite query failed when fetching received notes from the database. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZNDAO0002" - case notesDAOReceivedNote(_ sqliteError: Error) - /// Fetched note from the SQLite but can't decode that. - /// - `error` is decoding error. - // sourcery: code="ZNDAO0003" - case notesDAOReceivedCantDecode(_ error: Error) - /// SQLite query failed when fetching sent notes count from the database. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZNDAO0004" - case notesDAOSentCount(_ sqliteError: Error) - /// SQLite query failed when fetching sent notes from the database. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZNDAO0005" - case notesDAOSentNote(_ sqliteError: Error) - /// Fetched note from the SQLite but can't decode that. - /// - `error` is decoding error. - // sourcery: code="ZNDAO0006" - case notesDAOSentCantDecode(_ error: Error) - // MARK: - BlockDAO /// SQLite query failed when fetching block information from database. @@ -412,6 +370,13 @@ enum ZcashErrorDefinition { /// Initialization of `ZcashTransaction.Sent` failed. // sourcery: code="ZTEZT0003" case zcashTransactionSentInit(_ error: Error) + /// Initialization of `ZcashTransaction.Output` failed. + // sourcery: code="ZTEZT0004" + case zcashTransactionOutputInit(_ error: Error) + + /// Initialization of `ZcashTransaction.Output` failed because there an inconsistency in the output recipient. + // sourcery: code="ZTEZT0005" + case zcashTransactionOutputInconsistentRecipient // MARK: - Transaction Repository @@ -467,58 +432,6 @@ enum ZcashErrorDefinition { // sourcery: code="ZCHKP0002" case checkpointDecode(_ error: Error) - // MARK: - PendingTransactionDAO - - /// Decoding of `PendingTransaction` failed because of specific invalid data. - /// - `field` is list of fields names that contain invalid data. - // sourcery: code="ZPETR0001" - case pendingTransactionDecodeInvalidData(_ fields: [String]) - /// Can't decode `PendingTransaction`. - /// - `error` is error which described why decoding failed. - // sourcery: code="ZPETR0002" - case pendingTransactionCantDecode(_ error: Error) - /// Can't encode `PendingTransaction`. - /// - `error` is error which described why encoding failed. - // sourcery: code="ZPETR0003" - case pendingTransactionCantEncode(_ error: Error) - /// SQLite query failed when creating pending transaction. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZPETR0004" - case pendingTransactionDAOCreate(_ sqliteError: Error) - /// Pending transaction which should be updated is missing ID. - // sourcery: code="ZPETR0005" - case pendingTransactionDAOUpdateMissingID - /// SQLite query failed when updating pending transaction. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZPETR0006" - case pendingTransactionDAOUpdate(_ sqliteError: Error) - /// Pending transaction which should be deleted is missing ID. - // sourcery: code="ZPETR0007" - case pendingTransactionDAODeleteMissingID - /// SQLite query failed when deleting pending transaction. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZPETR0008" - case pendingTransactionDAODelete(_ sqliteError: Error) - /// Pending transaction which should be canceled is missing ID. - // sourcery: code="ZPETR0009" - case pendingTransactionDAOCancelMissingID - /// SQLite query failed when canceling pending transaction. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZPETR0010" - case pendingTransactionDAOCancel(_ sqliteError: Error) - /// SQLite query failed when seaching for pending transaction. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZPETR0011" - case pendingTransactionDAOFind(_ sqliteError: Error) - /// SQLite query failed when getting pending transactions. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZPETR0012" - case pendingTransactionDAOGetAll(_ sqliteError: Error) - /// SQLite query failed when applying mined height. - /// - `sqliteError` is error produced by SQLite library. - // sourcery: code="ZPETR0013" - case pendingTransactionDAOApplyMinedHeight(_ sqliteError: Error) - // MARK: - DerivationTool /// Invalid account when trying to derive spending key @@ -584,36 +497,6 @@ enum ZcashErrorDefinition { /// WalletTransactionEncoder wants to shield funds but files with sapling parameters are not present on disk. // sourcery: code="ZWLTE0002" case walletTransEncoderShieldFundsMissingSaplingParams - - // MARK: - PersistentTransactionManager - - /// PersistentTransactionsManager cant create transaction. - // sourcery: code="ZPTRM0001" - case persistentTransManagerCantCreateTransaction(_ recipient: PendingTransactionRecipient, _ account: Int, _ zatoshi: Zatoshi) - /// PersistentTransactionsManager cant get to address from pending transaction. - // sourcery: code="ZPTRM0002" - case persistentTransManagerEncodeUknownToAddress(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing id. - // sourcery: code="ZPTRM0003" - case persistentTransManagerSubmitTransactionIDMissing(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing id. Transaction is probably not stored. - // sourcery: code="ZPTRM0004" - case persistentTransManagerSubmitTransactionNotFound(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but transaction is canceled. - // sourcery: code="ZPTRM0005" - case persistentTransManagerSubmitTransactionCanceled(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but transaction is missing raw data. - // sourcery: code="ZPTRM0006" - case persistentTransManagerSubmitTransactionRawDataMissing(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to submit pending transaction but submit API call failed. - // sourcery: code="ZPTRM0007" - case persistentTransManagerSubmitFailed(_ entity: PendingTransactionEntity, _ serviceErrorCode: Int) - /// PersistentTransactionsManager wants to apply mined height to transaction but transaction is missing id. Transaction is probably not stored. - // sourcery: code="ZPTRM0008" - case persistentTransManagerApplyMinedHeightTransactionIDMissing(_ entity: PendingTransactionEntity) - /// PersistentTransactionsManager wants to apply mined height to transaction but transaction is not found in storage. Transaction is probably not stored. - // sourcery: code="ZPTRM0009" - case persistentTransManagerApplyMinedHeightTransactionNotFound(_ entity: PendingTransactionEntity) // MARK: - Zatoshi diff --git a/Sources/ZcashLightClientKit/Initializer.swift b/Sources/ZcashLightClientKit/Initializer.swift index 149b7801..5a7e0e7b 100644 --- a/Sources/ZcashLightClientKit/Initializer.swift +++ b/Sources/ZcashLightClientKit/Initializer.swift @@ -89,7 +89,6 @@ public class Initializer { struct URLs { let fsBlockDbRoot: URL let dataDbURL: URL - let pendingDbURL: URL let spendParamsURL: URL let outputParamsURL: URL } @@ -112,7 +111,6 @@ public class Initializer { let endpoint: LightWalletEndpoint let fsBlockDbRoot: URL let dataDbURL: URL - let pendingDbURL: URL let spendParamsURL: URL let outputParamsURL: URL let saplingParamsSourceURL: SaplingParamsSourceURL @@ -143,7 +141,6 @@ public class Initializer { /// just pass `nil` here. /// - fsBlockDbRoot: location of the compact blocks cache /// - dataDbURL: Location of the data db - /// - pendingDbURL: location of the pending transactions database /// - endpoint: the endpoint representing the lightwalletd instance you want to point to /// - spendParamsURL: location of the spend parameters /// - outputParamsURL: location of the output parameters @@ -151,7 +148,6 @@ public class Initializer { cacheDbURL: URL?, fsBlockDbRoot: URL, dataDbURL: URL, - pendingDbURL: URL, endpoint: LightWalletEndpoint, network: ZcashNetwork, spendParamsURL: URL, @@ -163,7 +159,6 @@ public class Initializer { let urls = URLs( fsBlockDbRoot: fsBlockDbRoot, dataDbURL: dataDbURL, - pendingDbURL: pendingDbURL, spendParamsURL: spendParamsURL, outputParamsURL: outputParamsURL ) @@ -244,7 +239,6 @@ public class Initializer { self.rustBackend = rustBackend self.fsBlockDbRoot = urls.fsBlockDbRoot self.dataDbURL = urls.dataDbURL - self.pendingDbURL = urls.pendingDbURL self.endpoint = endpoint self.spendParamsURL = urls.spendParamsURL self.outputParamsURL = urls.outputParamsURL @@ -261,7 +255,7 @@ public class Initializer { self.logger = logger } - private static func makeLightWalletServiceFactory(endpoint: LightWalletEndpoint) -> LightWalletServiceFactory { + static func makeLightWalletServiceFactory(endpoint: LightWalletEndpoint) -> LightWalletServiceFactory { return LightWalletServiceFactory(endpoint: endpoint) } @@ -273,7 +267,7 @@ public class Initializer { /// - /some/path/to/directory -> /some/path/to/c_anotherInstance_directory /// /// If any of the URLs can't be parsed then returned error isn't nil. - private static func tryToUpdateURLs( + static func tryToUpdateURLs( with alias: ZcashSynchronizerAlias, urls: URLs ) -> (URLs, ZcashError?) { @@ -307,10 +301,6 @@ public class Initializer { return .failure(.initializerCantUpdateURLWithAlias(urls.dataDbURL)) } - guard let updatedPendingDbURL = urls.pendingDbURL.updateLastPathComponent(with: alias) else { - return .failure(.initializerCantUpdateURLWithAlias(urls.pendingDbURL)) - } - guard let updatedSpendParamsURL = urls.spendParamsURL.updateLastPathComponent(with: alias) else { return .failure(.initializerCantUpdateURLWithAlias(urls.spendParamsURL)) } @@ -323,7 +313,6 @@ public class Initializer { URLs( fsBlockDbRoot: updatedFsBlockDbRoot, dataDbURL: updatedDataDbURL, - pendingDbURL: updatedPendingDbURL, spendParamsURL: updatedSpendParamsURL, outputParamsURL: updateOutputParamsURL ) @@ -374,14 +363,6 @@ public class Initializer { throw error } - let migrationManager = MigrationManager( - pendingDbConnection: SimpleConnectionProvider(path: pendingDbURL.path), - networkType: self.network.networkType, - logger: logger - ) - - try migrationManager.performMigration() - return .success } diff --git a/Sources/ZcashLightClientKit/Model/Memo.swift b/Sources/ZcashLightClientKit/Model/Memo.swift index 457922a7..daa8a818 100644 --- a/Sources/ZcashLightClientKit/Model/Memo.swift +++ b/Sources/ZcashLightClientKit/Model/Memo.swift @@ -165,7 +165,8 @@ public extension MemoBytes { func intoMemo() throws -> Memo { switch self.bytes[0] { case 0x00 ... 0xF4: - guard let validatedUTF8String = String(validatingUTF8: self.unpaddedRawBytes()) else { + let unpadded = self.unpaddedRawBytes() + guard let validatedUTF8String = String(validatingUTF8: unpadded) else { throw ZcashError.memoBytesInvalidUTF8 } diff --git a/Sources/ZcashLightClientKit/Model/WalletTypes.swift b/Sources/ZcashLightClientKit/Model/WalletTypes.swift index 4d99711f..2bba971b 100644 --- a/Sources/ZcashLightClientKit/Model/WalletTypes.swift +++ b/Sources/ZcashLightClientKit/Model/WalletTypes.swift @@ -213,6 +213,11 @@ public struct UnifiedAddress: Equatable, StringEncoded { } } +public enum TransactionRecipient: Equatable { + case address(Recipient) + case internalAccount(UInt32) +} + /// Represents a valid recipient of Zcash public enum Recipient: Equatable, StringEncoded { case transparent(TransparentAddress) diff --git a/Sources/ZcashLightClientKit/Providers/ResourceProvider.swift b/Sources/ZcashLightClientKit/Providers/ResourceProvider.swift index a886aa63..a19441b9 100644 --- a/Sources/ZcashLightClientKit/Providers/ResourceProvider.swift +++ b/Sources/ZcashLightClientKit/Providers/ResourceProvider.swift @@ -14,9 +14,6 @@ public enum ResourceProviderError: Error { public protocol ResourceProvider { var dataDbURL: URL { get } var fsCacheURL: URL { get } - - @available(*, deprecated, message: "you should use this to delete the existing cache.db sqlite.") - var cacheDbURL: URL { get } } /** Convenience provider for a data db and cache db resources. @@ -43,16 +40,6 @@ public struct DefaultResourceProvider: ResourceProvider { return URL(fileURLWithPath: "file://\(constants.defaultFsBlockDbRootName)") } } - - public var cacheDbURL: URL { - let constants = network.constants - do { - let path = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) - return path.appendingPathComponent(constants.defaultCacheDbName) - } catch { - return URL(fileURLWithPath: "file://\(constants.defaultCacheDbName)") - } - } public var spendParamsURL: URL { do { diff --git a/Sources/ZcashLightClientKit/Repository/PendingTransactionRepository.swift b/Sources/ZcashLightClientKit/Repository/PendingTransactionRepository.swift deleted file mode 100644 index b888747a..00000000 --- a/Sources/ZcashLightClientKit/Repository/PendingTransactionRepository.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// PendingTransactionRepository.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 11/19/19. -// - -import Foundation - -protocol PendingTransactionRepository { - func closeDBConnection() - func create(_ transaction: PendingTransactionEntity) throws -> Int - func update(_ transaction: PendingTransactionEntity) throws - func delete(_ transaction: PendingTransactionEntity) throws - func cancel(_ transaction: PendingTransactionEntity) throws - func find(by id: Int) throws -> PendingTransactionEntity? - func getAll() throws -> [PendingTransactionEntity] - func applyMinedHeight(_ height: BlockHeight, id: Int) throws -} diff --git a/Sources/ZcashLightClientKit/Repository/TransactionRepository.swift b/Sources/ZcashLightClientKit/Repository/TransactionRepository.swift index 75e381bf..6fef89df 100644 --- a/Sources/ZcashLightClientKit/Repository/TransactionRepository.swift +++ b/Sources/ZcashLightClientKit/Repository/TransactionRepository.swift @@ -20,10 +20,10 @@ protocol TransactionRepository { func find(offset: Int, limit: Int, kind: TransactionKind) async throws -> [ZcashTransaction.Overview] func find(in range: CompactBlockRange, limit: Int, kind: TransactionKind) async throws -> [ZcashTransaction.Overview] func find(from: ZcashTransaction.Overview, limit: Int, kind: TransactionKind) async throws -> [ZcashTransaction.Overview] - func findReceived(offset: Int, limit: Int) async throws -> [ZcashTransaction.Received] - func findSent(offset: Int, limit: Int) async throws -> [ZcashTransaction.Sent] + func findPendingTransactions(latestHeight: BlockHeight, offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview] + func findReceived(offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview] + func findSent(offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview] func findMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo] - func findMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo] - func findMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo] - func getRecipients(for id: Int) async -> [TransactionRecipient] + func getRecipients(for id: Int) async throws -> [TransactionRecipient] + func getTransactionOutputs(for id: Int) async throws -> [ZcashTransaction.Output] } diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index 88af9eb1..d97cfdd0 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -64,7 +64,7 @@ public struct SynchronizerState: Equatable { public enum SynchronizerEvent { // Sent when the synchronizer finds a pendingTransaction that hast been newly mined. - case minedTransaction(PendingTransactionEntity) + case minedTransaction(ZcashTransaction.Overview) // Sent when the synchronizer finds a mined transaction case foundTransactions(_ transactions: [ZcashTransaction.Overview], _ inRange: CompactBlockRange) // Sent when the synchronizer fetched utxos from lightwalletd attempted to store them. @@ -163,7 +163,7 @@ public protocol Synchronizer: AnyObject { zatoshi: Zatoshi, toAddress: Recipient, memo: Memo? - ) async throws -> PendingTransactionEntity + ) async throws -> ZcashTransaction.Overview /// Shields transparent funds from the given private key into the best shielded pool of the account associated to the given `UnifiedSpendingKey`. /// - Parameter spendingKey: the `UnifiedSpendingKey` that allows to spend transparent funds @@ -175,25 +175,19 @@ public protocol Synchronizer: AnyObject { spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi - ) async throws -> PendingTransactionEntity - - /// Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only - /// an option if the transaction has not yet been submitted to the server. - /// - Parameter transaction: the transaction to cancel. - /// - Returns: true when the cancellation request was successful. False when it is too late. - func cancelSpend(transaction: PendingTransactionEntity) async -> Bool + ) async throws -> ZcashTransaction.Overview /// all outbound pending transactions that have been sent but are awaiting confirmations - var pendingTransactions: [PendingTransactionEntity] { get async } + var pendingTransactions: [ZcashTransaction.Overview] { get async } /// all the transactions that are on the blockchain - var clearedTransactions: [ZcashTransaction.Overview] { get async } + var transactions: [ZcashTransaction.Overview] { get async } /// All transactions that are related to sending funds - var sentTransactions: [ZcashTransaction.Sent] { get async } + var sentTransactions: [ZcashTransaction.Overview] { get async } /// all transactions related to receiving funds - var receivedTransactions: [ZcashTransaction.Received] { get async } + var receivedTransactions: [ZcashTransaction.Overview] { get async } /// A repository serving transactions in a paginated manner /// - Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository @@ -204,16 +198,6 @@ public protocol Synchronizer: AnyObject { // sourcery: mockedName="getMemosForClearedTransaction" func getMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo] - /// Get all memos for `receivedTransaction`. - /// - // sourcery: mockedName="getMemosForReceivedTransaction" - func getMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo] - - /// Get all memos for `sentTransaction`. - /// - // sourcery: mockedName="getMemosForSentTransaction" - func getMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo] - /// Attempt to get recipients from a Transaction Overview. /// - parameter transaction: A transaction overview /// - returns the recipients or an empty array if no recipients are found on this transaction because it's not an outgoing @@ -221,21 +205,24 @@ public protocol Synchronizer: AnyObject { /// // sourcery: mockedName="getRecipientsForClearedTransaction" func getRecipients(for transaction: ZcashTransaction.Overview) async -> [TransactionRecipient] - - /// Get the recipients for the given a sent transaction + + /// Attempt to get outputs involved in a given Transaction. /// - parameter transaction: A transaction overview - /// - returns the recipients or an empty array if no recipients are found on this transaction because it's not an outgoing - /// transaction + /// - returns the array of outputs involved in this transaction. Transparent outputs might not be tracked /// - // sourcery: mockedName="getRecipientsForSentTransaction" - func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient] + // sourcery: mockedName="getTransactionOutputsForTransaction" + func getTransactionOutputs(for transaction: ZcashTransaction.Overview) async -> [ZcashTransaction.Output] /// Returns a list of confirmed transactions that preceed the given transaction with a limit count. /// - Parameters: /// - from: the confirmed transaction from which the query should start from or nil to retrieve from the most recent transaction /// - limit: the maximum amount of items this should return if available - /// - Returns: an array with the given Transactions or nil - func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] + /// - Returns: an array with the given Transactions or an empty array + func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] + + /// Fetch all pending transactions + /// - Returns: an array of transactions which are considered pending confirmation. can be empty + func allPendingTransactions() async throws -> [ZcashTransaction.Overview] /// Returns the latest block height from the provided Lightwallet endpoint func latestHeight() async throws -> BlockHeight diff --git a/Sources/ZcashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift index 27d330ab..6921b3fd 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift @@ -75,7 +75,7 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer { zatoshi: Zatoshi, toAddress: Recipient, memo: Memo?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { AsyncToClosureGateway.executeThrowingAction(completion) { try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo) @@ -86,20 +86,14 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer { spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { AsyncToClosureGateway.executeThrowingAction(completion) { try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) } } - public func cancelSpend(transaction: PendingTransactionEntity, completion: @escaping (Bool) -> Void) { - AsyncToClosureGateway.executeAction(completion) { - await self.synchronizer.cancelSpend(transaction: transaction) - } - } - - public func pendingTransactions(completion: @escaping ([PendingTransactionEntity]) -> Void) { + public func pendingTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) { AsyncToClosureGateway.executeAction(completion) { await self.synchronizer.pendingTransactions } @@ -107,17 +101,17 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer { public func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) { AsyncToClosureGateway.executeAction(completion) { - await self.synchronizer.clearedTransactions + await self.synchronizer.transactions } } - public func sentTranscations(completion: @escaping ([ZcashTransaction.Sent]) -> Void) { + public func sentTranscations(completion: @escaping ([ZcashTransaction.Overview]) -> Void) { AsyncToClosureGateway.executeAction(completion) { await self.synchronizer.sentTransactions } } - public func receivedTransactions(completion: @escaping ([ZcashTransaction.Received]) -> Void) { + public func receivedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) { AsyncToClosureGateway.executeAction(completion) { await self.synchronizer.receivedTransactions } @@ -131,37 +125,19 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer { } } - public func getMemos(for receivedTransaction: ZcashTransaction.Received, completion: @escaping (Result<[Memo], Error>) -> Void) { - AsyncToClosureGateway.executeThrowingAction(completion) { - try await self.synchronizer.getMemos(for: receivedTransaction) - } - } - - public func getMemos(for sentTransaction: ZcashTransaction.Sent, completion: @escaping (Result<[Memo], Error>) -> Void) { - AsyncToClosureGateway.executeThrowingAction(completion) { - try await self.synchronizer.getMemos(for: sentTransaction) - } - } - public func getRecipients(for transaction: ZcashTransaction.Overview, completion: @escaping ([TransactionRecipient]) -> Void) { AsyncToClosureGateway.executeAction(completion) { await self.synchronizer.getRecipients(for: transaction) } } - public func getRecipients(for transaction: ZcashTransaction.Sent, completion: @escaping ([TransactionRecipient]) -> Void) { - AsyncToClosureGateway.executeAction(completion) { - await self.synchronizer.getRecipients(for: transaction) - } - } - public func allConfirmedTransactions( from transaction: ZcashTransaction.Overview, limit: Int, completion: @escaping (Result<[ZcashTransaction.Overview], Error>) -> Void ) { AsyncToClosureGateway.executeThrowingAction(completion) { - try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit) + try await self.synchronizer.allTransactions(from: transaction, limit: limit) } } diff --git a/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift index 8b78fc3f..a14e5ffc 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift @@ -74,7 +74,7 @@ extension CombineSDKSynchronizer: CombineSynchronizer { zatoshi: Zatoshi, toAddress: Recipient, memo: Memo? - ) -> SinglePublisher { + ) -> SinglePublisher { AsyncToCombineGateway.executeThrowingAction() { try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo) } @@ -84,37 +84,31 @@ extension CombineSDKSynchronizer: CombineSynchronizer { spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi - ) -> SinglePublisher { + ) -> SinglePublisher { AsyncToCombineGateway.executeThrowingAction() { try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) } } - public func cancelSpend(transaction: PendingTransactionEntity) -> SinglePublisher { - AsyncToCombineGateway.executeAction() { - await self.synchronizer.cancelSpend(transaction: transaction) - } - } - - public var pendingTransactions: SinglePublisher<[PendingTransactionEntity], Never> { + public var pendingTransactions: AnyPublisher<[ZcashTransaction.Overview], Never> { AsyncToCombineGateway.executeAction() { await self.synchronizer.pendingTransactions } } - public var clearedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { + public var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { AsyncToCombineGateway.executeAction() { - await self.synchronizer.clearedTransactions + await self.synchronizer.transactions } } - public var sentTransactions: SinglePublisher<[ZcashTransaction.Sent], Never> { + public var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { AsyncToCombineGateway.executeAction() { await self.synchronizer.sentTransactions } } - public var receivedTransactions: SinglePublisher<[ZcashTransaction.Received], Never> { + public var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { AsyncToCombineGateway.executeAction() { await self.synchronizer.receivedTransactions } @@ -128,33 +122,21 @@ extension CombineSDKSynchronizer: CombineSynchronizer { } } - public func getMemos(for receivedTransaction: ZcashTransaction.Received) -> SinglePublisher<[Memo], Error> { - AsyncToCombineGateway.executeThrowingAction() { - try await self.synchronizer.getMemos(for: receivedTransaction) - } - } - - public func getMemos(for sentTransaction: ZcashTransaction.Sent) -> SinglePublisher<[Memo], Error> { - AsyncToCombineGateway.executeThrowingAction() { - try await self.synchronizer.getMemos(for: sentTransaction) - } - } - public func getRecipients(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[TransactionRecipient], Never> { AsyncToCombineGateway.executeAction() { await self.synchronizer.getRecipients(for: transaction) } } - public func getRecipients(for transaction: ZcashTransaction.Sent) -> SinglePublisher<[TransactionRecipient], Never> { - AsyncToCombineGateway.executeAction() { - await self.synchronizer.getRecipients(for: transaction) + public func allPendingTransactions() -> AnyPublisher<[ZcashTransaction.Overview], Error> { + AsyncToCombineGateway.executeThrowingAction() { + try await self.synchronizer.allPendingTransactions() } } - public func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) -> SinglePublisher<[ZcashTransaction.Overview], Error> { + public func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) -> SinglePublisher<[ZcashTransaction.Overview], Error> { AsyncToCombineGateway.executeThrowingAction() { - try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit) + try await self.synchronizer.allTransactions(from: transaction, limit: limit) } } diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 4de71450..07b9805b 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -37,7 +37,7 @@ public class SDKSynchronizer: Synchronizer { public let initializer: Initializer public var connectionState: ConnectionState public let network: ZcashNetwork - private let transactionManager: OutboundTransactionManager + private let transactionEncoder: TransactionEncoder private let transactionRepository: TransactionRepository private let utxoRepository: UnspentTransactionOutputRepository @@ -58,7 +58,7 @@ public class SDKSynchronizer: Synchronizer { self.init( status: .unprepared, initializer: initializer, - transactionManager: OutboundTransactionManagerBuilder.build(initializer: initializer), + transactionEncoder: WalletTransactionEncoder(initializer: initializer), transactionRepository: initializer.transactionRepository, utxoRepository: UTXORepositoryBuilder.build(initializer: initializer), blockProcessor: CompactBlockProcessor( @@ -78,7 +78,7 @@ public class SDKSynchronizer: Synchronizer { init( status: SyncStatus, initializer: Initializer, - transactionManager: OutboundTransactionManager, + transactionEncoder: TransactionEncoder, transactionRepository: TransactionRepository, utxoRepository: UnspentTransactionOutputRepository, blockProcessor: CompactBlockProcessor, @@ -90,7 +90,7 @@ public class SDKSynchronizer: Synchronizer { self.connectionState = .idle self.underlyingStatus = GenericActor(status) self.initializer = initializer - self.transactionManager = transactionManager + self.transactionEncoder = transactionEncoder self.transactionRepository = transactionRepository self.utxoRepository = utxoRepository self.blockProcessor = blockProcessor @@ -224,7 +224,8 @@ public class SDKSynchronizer: Synchronizer { self?.foundTransactions(transactions: transactions, in: range) case let .handledReorg(reorgHeight, rewindHeight): - await self?.handledReorg(reorgHeight: reorgHeight, rewindHeight: rewindHeight) + // log reorg information + self?.logger.info("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)") case let .progressUpdated(progress): await self?.progressUpdated(progress: progress) @@ -255,8 +256,7 @@ public class SDKSynchronizer: Synchronizer { private func finished(lastScannedHeight: BlockHeight, foundBlocks: Bool) async { await latestBlocksDataProvider.updateScannedData() - // FIX: Pending transaction updates fail if done from another thread. Improvement needed: explicitly define queues for sql repositories see: https://github.com/zcash/ZcashLightClientKit/issues/450 - await refreshPendingTransactions() + await updateStatus(.synced) if let syncStartDate { @@ -273,16 +273,6 @@ public class SDKSynchronizer: Synchronizer { } } - private func handledReorg(reorgHeight: BlockHeight, rewindHeight: BlockHeight) async { - logger.debug("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)") - - do { - try await transactionManager.handleReorg(at: rewindHeight) - } catch { - logger.debug("error handling reorg: \(error)") - } - } - private func progressUpdated(progress: CompactBlockProgress) async { let newStatus = SyncStatus(progress) await updateStatus(newStatus) @@ -301,9 +291,13 @@ public class SDKSynchronizer: Synchronizer { zatoshi: Zatoshi, toAddress: Recipient, memo: Memo? - ) async throws -> PendingTransactionEntity { + ) async throws -> ZcashTransaction.Overview { try throwIfUnprepared() + if case Recipient.transparent = toAddress, memo != nil { + throw ZcashError.synchronizerSendMemoToTransparentAddress + } + try await SaplingParameterDownloader.downloadParamsIfnotPresent( spendURL: initializer.spendParamsURL, spendSourceURL: initializer.saplingParamsSourceURL.spendParamFileURL, @@ -312,10 +306,6 @@ public class SDKSynchronizer: Synchronizer { logger: logger ) - if case Recipient.transparent = toAddress, memo != nil { - throw ZcashError.synchronizerSendMemoToTransparentAddress - } - return try await createToAddress( spendingKey: spendingKey, zatoshi: zatoshi, @@ -328,33 +318,30 @@ public class SDKSynchronizer: Synchronizer { spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi - ) async throws -> PendingTransactionEntity { + ) async throws -> ZcashTransaction.Overview { try throwIfUnprepared() // let's see if there are funds to shield let accountIndex = Int(spendingKey.account) let tBalance = try await self.getTransparentBalance(accountIndex: accountIndex) - - // Verify that at least there are funds for the fee. Ideally this logic will be improved by the shielding wallet. + + // Verify that at least there are funds for the fee. Ideally this logic will be improved by the shielding wallet. guard tBalance.verified >= self.network.constants.defaultFee(for: await self.latestBlocksDataProvider.latestScannedHeight) else { throw ZcashError.synchronizerShieldFundsInsuficientTransparentFunds } - - let shieldingSpend = try await transactionManager.initSpend( - zatoshi: tBalance.verified, - recipient: .internalAccount(spendingKey.account), - memo: try memo.asMemoBytes(), - from: accountIndex - ) - - // TODO: [#487] Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487 - let transaction = try await transactionManager.encodeShieldingTransaction( + + let transaction = try await transactionEncoder.createShieldingTransaction( spendingKey: spendingKey, shieldingThreshold: shieldingThreshold, - pendingTransaction: shieldingSpend + memoBytes: memo.asMemoBytes(), + from: Int(spendingKey.account) ) - - return try await transactionManager.submit(pendingTransaction: transaction) + + let encodedTx = try transaction.encodedTransaction() + + try await transactionEncoder.submit(transaction: encodedTx) + + return transaction } func createToAddress( @@ -362,43 +349,51 @@ public class SDKSynchronizer: Synchronizer { zatoshi: Zatoshi, recipient: Recipient, memo: Memo? - ) async throws -> PendingTransactionEntity { - let spend = try await transactionManager.initSpend( - zatoshi: zatoshi, - recipient: .address(recipient), - memo: memo?.asMemoBytes(), - from: Int(spendingKey.account) - ) - - let transaction = try await transactionManager.encode( - spendingKey: spendingKey, - pendingTransaction: spend - ) - let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) - return submittedTx + ) async throws -> ZcashTransaction.Overview { + do { + if + case .transparent = recipient, + memo != nil { + throw ZcashError.synchronizerSendMemoToTransparentAddress + } + + let transaction = try await transactionEncoder.createTransaction( + spendingKey: spendingKey, + zatoshi: zatoshi, + to: recipient.stringEncoded, + memoBytes: memo?.asMemoBytes(), + from: Int(spendingKey.account) + ) + + let encodedTransaction = try transaction.encodedTransaction() + + try await transactionEncoder.submit(transaction: encodedTransaction) + + return transaction + } catch { + throw error + } } - public func cancelSpend(transaction: PendingTransactionEntity) async -> Bool { - await transactionManager.cancel(pendingTransaction: transaction) - } - - public func allReceivedTransactions() async throws -> [ZcashTransaction.Received] { + public func allReceivedTransactions() async throws -> [ZcashTransaction.Overview] { try await transactionRepository.findReceived(offset: 0, limit: Int.max) } - public func allPendingTransactions() async throws -> [PendingTransactionEntity] { - try await transactionManager.allPendingTransactions() + public func allPendingTransactions() async throws -> [ZcashTransaction.Overview] { + let latestScannedHeight = self.latestState.latestScannedHeight + + return try await transactionRepository.findPendingTransactions(latestHeight: latestScannedHeight, offset: 0, limit: .max) } - public func allClearedTransactions() async throws -> [ZcashTransaction.Overview] { + public func allTransactions() async throws -> [ZcashTransaction.Overview] { return try await transactionRepository.find(offset: 0, limit: Int.max, kind: .all) } - public func allSentTransactions() async throws -> [ZcashTransaction.Sent] { + public func allSentTransactions() async throws -> [ZcashTransaction.Overview] { return try await transactionRepository.findSent(offset: 0, limit: Int.max) } - public func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] { + public func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] { return try await transactionRepository.find(from: transaction, limit: limit, kind: .all) } @@ -410,20 +405,12 @@ public class SDKSynchronizer: Synchronizer { return try await transactionRepository.findMemos(for: transaction) } - public func getMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo] { - return try await transactionRepository.findMemos(for: receivedTransaction) - } - - public func getMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo] { - return try await transactionRepository.findMemos(for: sentTransaction) - } - public func getRecipients(for transaction: ZcashTransaction.Overview) async -> [TransactionRecipient] { - return await transactionRepository.getRecipients(for: transaction.id) + return (try? await transactionRepository.getRecipients(for: transaction.id)) ?? [] } - public func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient] { - return await transactionRepository.getRecipients(for: transaction.id) + public func getTransactionOutputs(for transaction: ZcashTransaction.Overview) async -> [ZcashTransaction.Output] { + return (try? await transactionRepository.getTransactionOutputs(for: transaction.id)) ?? [] } public func latestHeight() async throws -> BlockHeight { @@ -515,15 +502,10 @@ public class SDKSynchronizer: Synchronizer { let context = AfterSyncHooksManager.RewindContext( height: height, - completion: { [weak self] result in + completion: { result in switch result { - case let .success(rewindHeight): - do { - try await self?.transactionManager.handleReorg(at: rewindHeight) - subject.send(completion: .finished) - } catch { - subject.send(completion: .failure(error)) - } + case .success: + subject.send(completion: .finished) case let .failure(error): subject.send(completion: .failure(error)) @@ -547,9 +529,8 @@ public class SDKSynchronizer: Synchronizer { } let context = AfterSyncHooksManager.WipeContext( - pendingDbURL: initializer.pendingDbURL, prewipe: { [weak self] in - self?.transactionManager.closeDBConnection() + self?.transactionEncoder.closeDBConnection() self?.transactionRepository.closeDBConnection() }, completion: { [weak self] possibleError in @@ -617,44 +598,7 @@ public class SDKSynchronizer: Synchronizer { } } - // MARK: book keeping - - private func updateMinedTransactions() async throws { - let transactions = try await transactionManager.allPendingTransactions() - .filter { $0.isSubmitSuccess && !$0.isMined } - - for pendingTx in transactions { - guard let rawID = pendingTx.rawTransactionId else { return } - let transaction = try await transactionRepository.find(rawID: rawID) - guard let minedHeight = transaction.minedHeight else { return } - - let minedTx = try await transactionManager.applyMinedHeight(pendingTransaction: pendingTx, minedHeight: minedHeight) - - notifyMinedTransaction(minedTx) - } - } - - private func removeConfirmedTransactions() async throws { - let latestHeight = try await transactionRepository.lastScannedHeight() - - let transactions = try await transactionManager.allPendingTransactions() - .filter { $0.minedHeight > 0 && abs($0.minedHeight - latestHeight) >= ZcashSDK.defaultStaleTolerance } - - for transaction in transactions { - try await transactionManager.delete(pendingTransaction: transaction) - } - } - - private func refreshPendingTransactions() async { - do { - try await updateMinedTransactions() - try await removeConfirmedTransactions() - } catch { - logger.debug("error refreshing pending transactions: \(error)") - } - } - - private func notifyMinedTransaction(_ transaction: PendingTransactionEntity) { + private func notifyMinedTransaction(_ transaction: ZcashTransaction.Overview) { streamsUpdateQueue.async { [weak self] in self?.eventSubject.send(.minedTransaction(transaction)) } @@ -662,29 +606,29 @@ public class SDKSynchronizer: Synchronizer { } extension SDKSynchronizer { - public var pendingTransactions: [PendingTransactionEntity] { + public var transactions: [ZcashTransaction.Overview] { get async { - (try? await self.allPendingTransactions()) ?? [PendingTransactionEntity]() + (try? await self.allTransactions()) ?? [] } } - public var clearedTransactions: [ZcashTransaction.Overview] { - get async { - (try? await allClearedTransactions()) ?? [] - } - } - - public var sentTransactions: [ZcashTransaction.Sent] { + public var sentTransactions: [ZcashTransaction.Overview] { get async { (try? await allSentTransactions()) ?? [] } } - public var receivedTransactions: [ZcashTransaction.Received] { + public var receivedTransactions: [ZcashTransaction.Overview] { get async { (try? await allReceivedTransactions()) ?? [] } } + + public var pendingTransactions: [ZcashTransaction.Overview] { + get async { + (try? await allPendingTransactions()) ?? [] + } + } } extension SyncStatus { diff --git a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift deleted file mode 100644 index be404699..00000000 --- a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift +++ /dev/null @@ -1,255 +0,0 @@ -// -// PendingTransactionsManager.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 11/26/19. -// - -import Foundation - -class PersistentTransactionManager: OutboundTransactionManager { - let repository: PendingTransactionRepository - let encoder: TransactionEncoder - let service: LightWalletService - let queue: DispatchQueue - let network: NetworkType - let logger: Logger - - init( - encoder: TransactionEncoder, - service: LightWalletService, - repository: PendingTransactionRepository, - networkType: NetworkType, - logger: Logger - ) { - self.repository = repository - self.encoder = encoder - self.service = service - self.network = networkType - self.queue = DispatchQueue.init(label: "PersistentTransactionManager.serial.queue", qos: .userInitiated) - self.logger = logger - } - - func initSpend( - zatoshi: Zatoshi, - recipient: PendingTransactionRecipient, - memo: MemoBytes?, - from accountIndex: Int - ) throws -> PendingTransactionEntity { - guard let insertedTx = try repository.find( - by: try repository.create( - PendingTransaction( - value: zatoshi, - recipient: recipient, - memo: memo, - account: accountIndex - ) - ) - ) else { - throw ZcashError.persistentTransManagerCantCreateTransaction(recipient, accountIndex, zatoshi) - } - logger.debug("pending transaction \(String(describing: insertedTx.id)) created") - return insertedTx - } - - func encodeShieldingTransaction( - spendingKey: UnifiedSpendingKey, - shieldingThreshold: Zatoshi, - pendingTransaction: PendingTransactionEntity - ) async throws -> PendingTransactionEntity { - let transaction = try await self.encoder.createShieldingTransaction( - spendingKey: spendingKey, - shieldingThreshold: shieldingThreshold, - memoBytes: try pendingTransaction.memo?.intoMemoBytes(), - from: pendingTransaction.accountIndex - ) - - var pending = pendingTransaction - pending.encodeAttempts += 1 - pending.raw = transaction.raw - pending.rawTransactionId = transaction.rawID - pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() - pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() - - try self.repository.update(pending) - - return pending - } - - func encode( - spendingKey: UnifiedSpendingKey, - pendingTransaction: PendingTransactionEntity - ) async throws -> PendingTransactionEntity { - do { - var toAddress: String? - switch pendingTransaction.recipient { - case .address(let addr): - toAddress = addr.stringEncoded - case .internalAccount: - break - } - - guard let toAddress else { - throw ZcashError.persistentTransManagerEncodeUknownToAddress(pendingTransaction) - } - - let transaction = try await self.encoder.createTransaction( - spendingKey: spendingKey, - zatoshi: pendingTransaction.value, - to: toAddress, - memoBytes: try pendingTransaction.memo?.intoMemoBytes(), - from: pendingTransaction.accountIndex - ) - - var pending = pendingTransaction - pending.encodeAttempts += 1 - pending.raw = transaction.raw - pending.rawTransactionId = transaction.rawID - pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() - pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() - - try self.repository.update(pending) - - return pending - } catch { - try await self.updateOnFailure(transaction: pendingTransaction, error: error) - throw error - } - } - - func submit( - pendingTransaction: PendingTransactionEntity - ) async throws -> PendingTransactionEntity { - guard let txId = pendingTransaction.id else { - // this transaction is not stored - throw ZcashError.persistentTransManagerSubmitTransactionIDMissing(pendingTransaction) - } - - do { - guard let storedTx = try self.repository.find(by: txId) else { - throw ZcashError.persistentTransManagerSubmitTransactionNotFound(pendingTransaction) - } - - guard !storedTx.isCancelled else { - logger.debug("ignoring cancelled transaction \(storedTx)") - throw ZcashError.persistentTransManagerSubmitTransactionCanceled(storedTx) - } - - guard let raw = storedTx.raw else { - logger.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data") - throw ZcashError.persistentTransManagerSubmitTransactionRawDataMissing(storedTx) - } - - let response = try await self.service.submit(spendTransaction: raw) - let transaction = try await self.update(transaction: storedTx, on: response) - - guard response.errorCode >= 0 else { - throw ZcashError.persistentTransManagerSubmitFailed(transaction, Int(response.errorCode)) - } - - return transaction - } catch { - try await self.updateOnFailure(transaction: pendingTransaction, error: error) - throw error - } - } - - func applyMinedHeight(pendingTransaction: PendingTransactionEntity, minedHeight: BlockHeight) async throws -> PendingTransactionEntity { - guard let id = pendingTransaction.id else { - throw ZcashError.persistentTransManagerApplyMinedHeightTransactionIDMissing(pendingTransaction) - } - - guard var transaction = try repository.find(by: id) else { - throw ZcashError.persistentTransManagerApplyMinedHeightTransactionNotFound(pendingTransaction) - } - - transaction.minedHeight = minedHeight - guard let pendingTxId = pendingTransaction.id else { - throw ZcashError.persistentTransManagerApplyMinedHeightTransactionIDMissing(pendingTransaction) - } - try repository.applyMinedHeight(minedHeight, id: pendingTxId) - return transaction - } - - func handleReorg(at height: BlockHeight) async throws { - let affectedTxs = try await allPendingTransactions() - .filter({ $0.minedHeight >= height }) - - try affectedTxs - .map { transaction -> PendingTransactionEntity in - var updatedTx = transaction - updatedTx.minedHeight = -1 - return updatedTx - } - .forEach { try self.repository.update($0) } - } - - func cancel(pendingTransaction: PendingTransactionEntity) async -> Bool { - guard let id = pendingTransaction.id else { return false } - - guard let transaction = try? repository.find(by: id) else { return false } - - guard !transaction.isSubmitted else { return false } - - guard (try? repository.cancel(transaction)) != nil else { return false } - - return true - } - - func allPendingTransactions() async throws -> [PendingTransactionEntity] { - try repository.getAll() - } - - // MARK: other functions - private func updateOnFailure(transaction: PendingTransactionEntity, error: Error) async throws { - var pending = transaction - pending.errorMessage = "\(error)" - pending.encodeAttempts = transaction.encodeAttempts + 1 - try self.repository.update(pending) - } - - private func update(transaction: PendingTransactionEntity, on sendResponse: LightWalletServiceResponse) async throws -> PendingTransactionEntity { - var pendingTx = transaction - pendingTx.submitAttempts += 1 - let error = sendResponse.errorCode < 0 - pendingTx.errorCode = error ? Int(sendResponse.errorCode) : nil - pendingTx.errorMessage = error ? sendResponse.errorMessage : nil - try repository.update(pendingTx) - return pendingTx - } - - func delete(pendingTransaction: PendingTransactionEntity) async throws { - try repository.delete(pendingTransaction) - } - - func closeDBConnection() { - repository.closeDBConnection() - } -} - -enum OutboundTransactionManagerBuilder { - static func build(initializer: Initializer) -> OutboundTransactionManager { - PersistentTransactionManager( - encoder: TransactionEncoderbuilder.build(initializer: initializer), - service: initializer.lightWalletService, - repository: PendingTransactionRepositoryBuilder.build(initializer: initializer), - networkType: initializer.network.networkType, - logger: initializer.logger - ) - } -} - -enum PendingTransactionRepositoryBuilder { - static func build(initializer: Initializer) -> PendingTransactionRepository { - PendingTransactionSQLDAO( - dbProvider: SimpleConnectionProvider(path: initializer.pendingDbURL.path, readonly: false), - logger: initializer.logger - ) - } -} - -enum TransactionEncoderbuilder { - static func build(initializer: Initializer) -> TransactionEncoder { - WalletTransactionEncoder(initializer: initializer) - } -} diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift index f192131a..0844b59f 100644 --- a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift @@ -9,6 +9,15 @@ import Foundation typealias TransactionEncoderResultBlock = (_ result: Result) -> Void +public enum TransactionEncoderError: Error { + case notFound(transactionId: Int) + case notEncoded(transactionId: Int) + case missingParams + case spendingKeyWrongNetwork + case couldNotExpand(txId: Data) + case submitError(code: Int, message: String) +} + protocol TransactionEncoder { /// Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation /// doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using @@ -48,4 +57,10 @@ protocol TransactionEncoder { memoBytes: MemoBytes?, from accountIndex: Int ) async throws -> ZcashTransaction.Overview + + /// submits a transaction to the Zcash peer-to-peer network. + /// - Parameter transaction: a transaction overview + func submit(transaction: EncodedTransaction) async throws + + func closeDBConnection() } diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift deleted file mode 100644 index ca9fb7d4..00000000 --- a/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// TransactionManager.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 11/26/19. -// - -import Foundation - -/** -Manage outbound transactions with the main purpose of reporting which ones are still pending, -particularly after failed attempts or dropped connectivity. The intent is to help see outbound -transactions through to completion. -*/ - -protocol OutboundTransactionManager { - func initSpend( - zatoshi: Zatoshi, - recipient: PendingTransactionRecipient, - memo: MemoBytes?, - from accountIndex: Int - ) async throws -> PendingTransactionEntity - - func encodeShieldingTransaction( - spendingKey: UnifiedSpendingKey, - shieldingThreshold: Zatoshi, - pendingTransaction: PendingTransactionEntity - ) async throws -> PendingTransactionEntity - - func encode( - spendingKey: UnifiedSpendingKey, - pendingTransaction: PendingTransactionEntity - ) async throws -> PendingTransactionEntity - - func submit( - pendingTransaction: PendingTransactionEntity - ) async throws -> PendingTransactionEntity - - func applyMinedHeight( - pendingTransaction: PendingTransactionEntity, - minedHeight: BlockHeight - ) async throws -> PendingTransactionEntity - - /** - Attempts to Cancel a transaction. Returns true if successful - */ - func cancel(pendingTransaction: PendingTransactionEntity) async -> Bool - - func allPendingTransactions() async throws -> [PendingTransactionEntity] - - func handleReorg(at blockHeight: BlockHeight) async throws - - /** - Deletes a pending transaction from the database - */ - func delete(pendingTransaction: PendingTransactionEntity) async throws - - func closeDBConnection() -} diff --git a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift index 7ccde20c..b55bb1d9 100644 --- a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift @@ -8,6 +8,7 @@ import Foundation class WalletTransactionEncoder: TransactionEncoder { + let lightWalletService: LightWalletService let rustBackend: ZcashRustBackendWelding let repository: TransactionRepository let logger: Logger @@ -22,6 +23,7 @@ class WalletTransactionEncoder: TransactionEncoder { rustBackend: ZcashRustBackendWelding, dataDb: URL, fsBlockDbRoot: URL, + service: LightWalletService, repository: TransactionRepository, outputParams: URL, spendParams: URL, @@ -31,6 +33,7 @@ class WalletTransactionEncoder: TransactionEncoder { self.rustBackend = rustBackend self.dataDbURL = dataDb self.fsBlockDbRoot = fsBlockDbRoot + self.lightWalletService = service self.repository = repository self.outputParamsURL = outputParams self.spendParamsURL = spendParams @@ -43,6 +46,7 @@ class WalletTransactionEncoder: TransactionEncoder { rustBackend: initializer.rustBackend, dataDb: initializer.dataDbURL, fsBlockDbRoot: initializer.fsBlockDbRoot, + service: initializer.lightWalletService, repository: initializer.transactionRepository, outputParams: initializer.outputParamsURL, spendParams: initializer.spendParamsURL, @@ -126,6 +130,17 @@ class WalletTransactionEncoder: TransactionEncoder { return Int(txId) } + + func submit( + transaction: EncodedTransaction + ) async throws { + let response = try await self.lightWalletService.submit(spendTransaction: transaction.raw) + + guard response.errorCode >= 0 else { + throw TransactionEncoderError.submitError(code: Int(response.errorCode) , message: response.errorMessage) + } + } + func ensureParams(spend: URL, output: URL) -> Bool { let readableSpend = FileManager.default.isReadableFile(atPath: spend.path) @@ -134,4 +149,18 @@ class WalletTransactionEncoder: TransactionEncoder { // TODO: [#713] change this to something that makes sense, https://github.com/zcash/ZcashLightClientKit/issues/713 return readableSpend && readableOutput } + + func closeDBConnection() { + self.repository.closeDBConnection() + } +} + +extension ZcashTransaction.Overview { + func encodedTransaction() throws -> EncodedTransaction { + guard let raw else { + throw TransactionEncoderError.notEncoded(transactionId: self.id) + } + + return EncodedTransaction(transactionId: self.rawID, raw: raw) + } } diff --git a/Tests/AliasDarksideTests/SDKSynchronizerAliasDarksideTests.swift b/Tests/AliasDarksideTests/SDKSynchronizerAliasDarksideTests.swift index 84ccff66..7f373706 100644 --- a/Tests/AliasDarksideTests/SDKSynchronizerAliasDarksideTests.swift +++ b/Tests/AliasDarksideTests/SDKSynchronizerAliasDarksideTests.swift @@ -70,7 +70,6 @@ class SDKSynchronizerAliasDarksideTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } coordinators = [] } diff --git a/Tests/DarksideTests/AdvancedReOrgTests.swift b/Tests/DarksideTests/AdvancedReOrgTests.swift index 2eef1b40..bfe9543f 100644 --- a/Tests/DarksideTests/AdvancedReOrgTests.swift +++ b/Tests/DarksideTests/AdvancedReOrgTests.swift @@ -28,7 +28,12 @@ class AdvancedReOrgTests: XCTestCase { override func setUp() async throws { try await super.setUp() // don't use an exact birthday, users never do. - self.coordinator = try await TestCoordinator(walletBirthday: birthday + 50, network: network) + self.coordinator = try await TestCoordinator( + walletBirthday: birthday + 50, + network: network, + dbTracingClosure: { logger.debug($0) } + ) + try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName) } @@ -41,7 +46,6 @@ class AdvancedReOrgTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func handleReorg(event: CompactBlockProcessor.Event) { @@ -305,7 +309,7 @@ class AdvancedReOrgTests: XCTestCase { await fulfillment(of: [preTxExpectation], timeout: 5) let sendExpectation = XCTestExpectation(description: "sendToAddress") - var pendingEntity: PendingTransactionEntity? + var pendingEntity: ZcashTransaction.Overview? var testError: Error? let sendAmount = Zatoshi(10000) @@ -436,13 +440,14 @@ class AdvancedReOrgTests: XCTestCase { await fulfillment(of: [lastSyncExpectation], timeout: 5) - let expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance() + let expectedVerifiedBalance = initialTotalBalance + pendingTx.value + let currentVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance() let expectedPendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count XCTAssertEqual(expectedPendingTransactionsCount, 0) - XCTAssertEqual(initialTotalBalance - pendingTx.value - Zatoshi(1000), expectedVerifiedBalance) + XCTAssertEqual(expectedVerifiedBalance, currentVerifiedBalance) let resultingBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() - XCTAssertEqual(resultingBalance, expectedVerifiedBalance) + XCTAssertEqual(resultingBalance, currentVerifiedBalance) } func testIncomingTransactionIndexChange() async throws { @@ -571,6 +576,27 @@ class AdvancedReOrgTests: XCTestCase { XCTAssertEqual(afterReOrgBalance, initialBalance) XCTAssertEqual(afterReOrgVerifiedBalance, initialVerifiedBalance) + + guard + let receivedTransaction = try await coordinator.synchronizer.allTransactions().first + else { + XCTFail("expected to have a received transaction, but found none") + return + } + + let transactionOutputs = await coordinator.synchronizer.getTransactionOutputs( + for: receivedTransaction + ) + + guard transactionOutputs.count == 1 else { + XCTFail("expected output count to be 1") + return + } + + let output = transactionOutputs[0] + + XCTAssertEqual(output.recipient, .internalAccount(0)) + XCTAssertEqual(output.value, Zatoshi(100000)) } /// Steps: @@ -598,7 +624,7 @@ class AdvancedReOrgTests: XCTestCase { var initialBalance = Zatoshi(-1) var initialVerifiedBalance = Zatoshi(-1) - var incomingTx: ZcashTransaction.Received! + var incomingTx: ZcashTransaction.Overview! try await coordinator.sync( completion: { _ in @@ -765,16 +791,18 @@ class AdvancedReOrgTests: XCTestCase { let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() let sendExpectation = XCTestExpectation(description: "send expectation") - var pendingEntity: PendingTransactionEntity? + var pendingEntity: ZcashTransaction.Overview? /* 2. send transaction to recipient address */ + let recipient = try Recipient(Environment.testRecipientAddress, network: self.network.networkType) + do { let pendingTx = try await coordinator.synchronizer.sendToAddress( spendingKey: self.coordinator.spendingKey, zatoshi: Zatoshi(20000), - toAddress: try Recipient(Environment.testRecipientAddress, network: self.network.networkType), + toAddress: recipient, memo: try Memo(string: "this is a test") ) pendingEntity = pendingTx @@ -889,7 +917,7 @@ class AdvancedReOrgTests: XCTestCase { return } - XCTAssertEqual(newPendingTx.minedHeight, BlockHeight.empty()) + XCTAssertNil(newPendingTx.minedHeight) /* 11. applyHeight(sentTxHeight + 2) @@ -920,7 +948,7 @@ class AdvancedReOrgTests: XCTestCase { */ pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count XCTAssertEqual(pendingTransactionsCount, 1) - guard let newlyPendingTx = try await coordinator.synchronizer.allPendingTransactions().first else { + guard let newlyPendingTx = try await coordinator.synchronizer.allPendingTransactions().first(where: { $0.isSentTransaction }) else { XCTFail("no pending transaction") try await coordinator.stop() return @@ -962,7 +990,7 @@ class AdvancedReOrgTests: XCTestCase { let sentTransactions = await coordinator.synchronizer.sentTransactions .first( where: { transaction in - return transaction.rawID == newlyPendingTx.rawTransactionId + return transaction.rawID == newlyPendingTx.rawID } ) @@ -975,14 +1003,19 @@ class AdvancedReOrgTests: XCTestCase { let expectedBalance = try await coordinator.synchronizer.getShieldedBalance() XCTAssertEqual( - initialTotalBalance - newlyPendingTx.value - Zatoshi(1000), + initialTotalBalance + newlyPendingTx.value, // Note: sent transactions have negative values expectedBalance ) XCTAssertEqual( - initialTotalBalance - newlyPendingTx.value - Zatoshi(1000), + initialTotalBalance + newlyPendingTx.value, // Note: sent transactions have negative values expectedVerifiedBalance ) + + let txRecipients = await coordinator.synchronizer.getRecipients(for: newPendingTx) + XCTAssertEqual(txRecipients.count, 2) + XCTAssertNotNil(txRecipients.first(where: { $0 == .internalAccount(0) })) + XCTAssertNotNil(txRecipients.first(where: { $0 == .address(recipient) })) } /// Uses the zcash-hackworks data set. @@ -1180,7 +1213,7 @@ class AdvancedReOrgTests: XCTestCase { let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() let sendExpectation = XCTestExpectation(description: "send expectation") - var pendingEntity: PendingTransactionEntity? + var pendingEntity: ZcashTransaction.Overview? /* 2. send transaction to recipient address @@ -1274,7 +1307,7 @@ class AdvancedReOrgTests: XCTestCase { return } - XCTAssertFalse(pendingTx.isMined) + XCTAssertNil(pendingTx.minedHeight) LoggerProxy.info("applyStaged(blockheight: \(sentTxHeight + extraBlocks - 1))") try coordinator.applyStaged(blockheight: sentTxHeight + extraBlocks - 1) diff --git a/Tests/DarksideTests/BalanceTests.swift b/Tests/DarksideTests/BalanceTests.swift index 9a38c3eb..666f31a4 100644 --- a/Tests/DarksideTests/BalanceTests.swift +++ b/Tests/DarksideTests/BalanceTests.swift @@ -38,7 +38,6 @@ class BalanceTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } /** @@ -85,7 +84,7 @@ class BalanceTests: XCTestCase { // 4 send the transaction let spendingKey = coordinator.spendingKey - var pendingTx: PendingTransactionEntity? + var pendingTx: ZcashTransaction.Overview? do { let transaction = try await coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, @@ -106,9 +105,9 @@ class BalanceTests: XCTestCase { } notificationHandler.synchronizerMinedTransaction = { transaction in - XCTAssertNotNil(transaction.rawTransactionId) - XCTAssertNotNil(pendingTx.rawTransactionId) - XCTAssertEqual(transaction.rawTransactionId, pendingTx.rawTransactionId) + XCTAssertNotNil(transaction.rawID) + XCTAssertNotNil(pendingTx.rawID) + XCTAssertEqual(transaction.rawID, pendingTx.rawID) transactionMinedExpectation.fulfill() } @@ -123,7 +122,7 @@ class BalanceTests: XCTestCase { let sentTxHeight = latestHeight + 1 notificationHandler.transactionsFound = { txs in - let foundTx = txs.first(where: { $0.rawID == pendingTx.rawTransactionId }) + let foundTx = txs.first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNotNil(foundTx) XCTAssertEqual(foundTx?.minedHeight, sentTxHeight) @@ -139,9 +138,9 @@ class BalanceTests: XCTestCase { do { try await coordinator.sync( completion: { synchronizer in - let pendingEntity = await synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now") - XCTAssertTrue(pendingEntity?.isMined ?? false) + XCTAssertNotNil(pendingEntity?.minedHeight) XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight) mineExpectation.fulfill() }, @@ -181,7 +180,7 @@ class BalanceTests: XCTestCase { await fulfillment(of: [confirmExpectation], timeout: 5) let confirmedPending = try await coordinator.synchronizer.allPendingTransactions() - .first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + .first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNil(confirmedPending, "pending, now confirmed transaction found") @@ -234,7 +233,7 @@ class BalanceTests: XCTestCase { // 3 create a transaction for the max amount possible // 4 send the transaction let spendingKey = coordinator.spendingKey - var pendingTx: PendingTransactionEntity? + var pendingTx: ZcashTransaction.Overview? do { let transaction = try await coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, @@ -255,9 +254,9 @@ class BalanceTests: XCTestCase { } notificationHandler.synchronizerMinedTransaction = { transaction in - XCTAssertNotNil(transaction.rawTransactionId) - XCTAssertNotNil(pendingTx.rawTransactionId) - XCTAssertEqual(transaction.rawTransactionId, pendingTx.rawTransactionId) + XCTAssertNotNil(transaction.rawID) + XCTAssertNotNil(pendingTx.rawID) + XCTAssertEqual(transaction.rawID, pendingTx.rawID) transactionMinedExpectation.fulfill() } @@ -272,7 +271,7 @@ class BalanceTests: XCTestCase { let sentTxHeight = latestHeight + 1 notificationHandler.transactionsFound = { txs in - let foundTx = txs.first(where: { $0.rawID == pendingTx.rawTransactionId }) + let foundTx = txs.first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNotNil(foundTx) XCTAssertEqual(foundTx?.minedHeight, sentTxHeight) @@ -288,9 +287,9 @@ class BalanceTests: XCTestCase { do { try await coordinator.sync( completion: { synchronizer in - let pendingEntity = await synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now") - XCTAssertTrue(pendingEntity?.isMined ?? false) + XCTAssertNotNil(pendingEntity?.minedHeight) XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight) mineExpectation.fulfill() }, @@ -303,8 +302,9 @@ class BalanceTests: XCTestCase { await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5) // 7 advance to confirmation - - try coordinator.applyStaged(blockheight: sentTxHeight + 10) + + let advanceToConfirmationHeight = sentTxHeight + 10 + try coordinator.applyStaged(blockheight: advanceToConfirmationHeight) sleep(2) @@ -331,7 +331,7 @@ class BalanceTests: XCTestCase { let confirmedPending = try await coordinator.synchronizer .allPendingTransactions() - .first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + .first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNil(confirmedPending, "pending, now confirmed transaction found") @@ -384,7 +384,7 @@ class BalanceTests: XCTestCase { // 3 create a transaction for the max amount possible // 4 send the transaction let spendingKey = coordinator.spendingKey - var pendingTx: PendingTransactionEntity? + var pendingTx: ZcashTransaction.Overview? do { let transaction = try await coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, @@ -405,9 +405,9 @@ class BalanceTests: XCTestCase { } notificationHandler.synchronizerMinedTransaction = { transaction in - XCTAssertNotNil(transaction.rawTransactionId) - XCTAssertNotNil(pendingTx.rawTransactionId) - XCTAssertEqual(transaction.rawTransactionId, pendingTx.rawTransactionId) + XCTAssertNotNil(transaction.rawID) + XCTAssertNotNil(pendingTx.rawID) + XCTAssertEqual(transaction.rawID, pendingTx.rawID) transactionMinedExpectation.fulfill() } @@ -422,7 +422,7 @@ class BalanceTests: XCTestCase { let sentTxHeight = latestHeight + 1 notificationHandler.transactionsFound = { txs in - let foundTx = txs.first(where: { $0.rawID == pendingTx.rawTransactionId }) + let foundTx = txs.first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNotNil(foundTx) XCTAssertEqual(foundTx?.minedHeight, sentTxHeight) @@ -438,9 +438,9 @@ class BalanceTests: XCTestCase { do { try await coordinator.sync( completion: { synchronizer in - let pendingEntity = await synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now") - XCTAssertTrue(pendingEntity?.isMined ?? false) + XCTAssertTrue(pendingEntity?.minedHeight != nil) XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight) mineExpectation.fulfill() }, @@ -453,8 +453,9 @@ class BalanceTests: XCTestCase { await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5) // 7 advance to confirmation - - try coordinator.applyStaged(blockheight: sentTxHeight + 10) + let advanceToConfirmation = sentTxHeight + 10 + + try coordinator.applyStaged(blockheight: advanceToConfirmation) sleep(2) @@ -481,7 +482,7 @@ class BalanceTests: XCTestCase { let confirmedPending = try await coordinator.synchronizer .allPendingTransactions() - .first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + .first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNil(confirmedPending, "pending, now confirmed transaction found") @@ -537,7 +538,8 @@ class BalanceTests: XCTestCase { */ XCTAssertTrue(presendVerifiedBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount) - var pendingTx: PendingTransactionEntity? + var pendingTx: ZcashTransaction.Overview? + do { let transaction = try await coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, @@ -609,59 +611,29 @@ class BalanceTests: XCTestCase { basic health check */ XCTAssertEqual(transaction.value, self.sendAmount) - - /* - build up repos to get data - */ - guard let txid = transaction.rawTransactionId else { - XCTFail("sent transaction has no internal id") + + let outputs = await coordinator.synchronizer.getTransactionOutputs(for: transaction) + + guard outputs.count == 2 else { + XCTFail("Expected sent transaction to have 2 outputs") return } - let sentNoteDAO = SentNotesSQLDAO( - dbProvider: SimpleConnectionProvider( - path: self.coordinator.synchronizer.initializer.dataDbURL.absoluteString, - readonly: true - ) - ) - - let receivedNoteDAO = ReceivedNotesSQLDAO( - dbProvider: SimpleConnectionProvider( - path: self.coordinator.synchronizer.initializer.dataDbURL.absoluteString, - readonly: true - ) - ) - var sentEntity: SentNoteEntity? - do { - sentEntity = try sentNoteDAO.sentNote(byRawTransactionId: txid) - } catch { - XCTFail("error retrieving sent note: \(error)") - } - - guard let sentNote = sentEntity else { - XCTFail("could not find sent note for this transaction") + guard let changeOutput = outputs.first(where: { $0.isChange }) else { + XCTFail("Sent transaction has no change") return } - var receivedEntity: ReceivedNoteEntity? - - do { - receivedEntity = try receivedNoteDAO.receivedNote(byRawTransactionId: txid) - } catch { - XCTFail("error retrieving received note: \(error)") - } - - guard let receivedNote = receivedEntity else { - XCTFail("could not find sent note for this transaction") + guard let sentOutput = outputs.first(where: { !$0.isChange }) else { + XCTFail("sent transaction does not have a 'sent' output") return } // (previous available funds - spent note + change) equals to (previous available funds - sent amount) - self.verifiedBalanceValidation( previousBalance: presendVerifiedBalance, - spentNoteValue: Zatoshi(Int64(sentNote.value)), - changeValue: Zatoshi(Int64(receivedNote.value)), + spentNoteValue: sentOutput.value, + changeValue: changeOutput.value, sentAmount: self.sendAmount, currentVerifiedBalance: try await coordinator.synchronizer.getShieldedVerifiedBalance() ) @@ -709,7 +681,7 @@ class BalanceTests: XCTestCase { // there's more zatoshi to send than network fee XCTAssertTrue(presendBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount) - var pendingTx: PendingTransactionEntity? + var pendingTx: ZcashTransaction.Overview? var testError: Error? do { @@ -750,8 +722,6 @@ class BalanceTests: XCTestCase { presendBalance - self.sendAmount - network.constants.defaultFee(for: defaultLatestHeight) ) - XCTAssertNil(transaction.errorCode) - let latestHeight = try await coordinator.latestHeight() let sentTxHeight = latestHeight + 1 try coordinator.stageBlockCreate(height: sentTxHeight) @@ -814,7 +784,7 @@ class BalanceTests: XCTestCase { await fulfillment(of: [syncedExpectation], timeout: 5) - let clearedTransactions = await coordinator.synchronizer.clearedTransactions + let clearedTransactions = await coordinator.synchronizer.transactions let expectedBalance = try await coordinator.synchronizer.getShieldedBalance() XCTAssertEqual(clearedTransactions.count, 2) XCTAssertEqual(expectedBalance, Zatoshi(200000)) @@ -878,7 +848,8 @@ class BalanceTests: XCTestCase { Send */ let memo = try Memo(string: "shielding is fun!") - var pendingTx: PendingTransactionEntity? + var pendingTx: ZcashTransaction.Overview? + do { let transaction = try await coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, @@ -918,8 +889,8 @@ class BalanceTests: XCTestCase { completion: { synchronizer in let confirmedTx: ZcashTransaction.Overview! do { - confirmedTx = try await synchronizer.allClearedTransactions().first(where: { confirmed -> Bool in - confirmed.rawID == pendingTx?.rawTransactionId + confirmedTx = try await synchronizer.allTransactions().first(where: { confirmed -> Bool in + confirmed.rawID == pendingTx?.rawID }) } catch { XCTFail("Error retrieving cleared transactions") @@ -937,30 +908,21 @@ class BalanceTests: XCTestCase { /* Find out what note was used */ - let sentNotesRepo = SentNotesSQLDAO( - dbProvider: SimpleConnectionProvider( - path: synchronizer.initializer.dataDbURL.absoluteString, - readonly: true - ) - ) - guard let sentNote = try? sentNotesRepo.sentNote(byRawTransactionId: confirmedTx.rawID) else { - XCTFail("Could not finde sent note with transaction Id \(confirmedTx.rawID)") + let outputs = await self.coordinator.synchronizer.getTransactionOutputs(for: confirmedTx) + + guard outputs.count == 2 else { + XCTFail("Expected sent transaction to have 2 outputs") return } - let receivedNotesRepo = ReceivedNotesSQLDAO( - dbProvider: SimpleConnectionProvider( - path: self.coordinator.synchronizer.initializer.dataDbURL.absoluteString, - readonly: true - ) - ) + guard let changeOutput = outputs.first(where: { $0.isChange }) else { + XCTFail("Sent transaction has no change") + return + } - /* - get change note - */ - guard let receivedNote = try? receivedNotesRepo.receivedNote(byRawTransactionId: confirmedTx.rawID) else { - XCTFail("Could not find received not with change for transaction Id \(confirmedTx.rawID)") + guard let sentOutput = outputs.first(where: { !$0.isChange }) else { + XCTFail("sent transaction does not have a 'sent' output") return } @@ -969,7 +931,7 @@ class BalanceTests: XCTestCase { */ XCTAssertEqual( previousVerifiedBalance - self.sendAmount - self.network.constants.defaultFee(for: self.defaultLatestHeight), - Zatoshi(Int64(receivedNote.value)) + changeOutput.value ) /* @@ -977,8 +939,8 @@ class BalanceTests: XCTestCase { */ self.verifiedBalanceValidation( previousBalance: previousVerifiedBalance, - spentNoteValue: Zatoshi(Int64(sentNote.value)), - changeValue: Zatoshi(Int64(receivedNote.value)), + spentNoteValue: sentOutput.value, + changeValue: changeOutput.value, sentAmount: self.sendAmount, currentVerifiedBalance: try await synchronizer.getShieldedVerifiedBalance() ) @@ -1046,7 +1008,7 @@ class BalanceTests: XCTestCase { let previousVerifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance() let previousTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() let sendExpectation = XCTestExpectation(description: "send expectation") - var pendingTx: PendingTransactionEntity? + var pendingTx: ZcashTransaction.Overview? do { let pending = try await coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, @@ -1067,13 +1029,13 @@ class BalanceTests: XCTestCase { await fulfillment(of: [sendExpectation], timeout: 12) - guard let pendingTransaction = pendingTx, pendingTransaction.expiryHeight > defaultLatestHeight else { + guard let pendingTransaction = pendingTx, let expiryHeight = pendingTransaction.expiryHeight, expiryHeight > defaultLatestHeight else { XCTFail("No pending transaction") return } let expirationSyncExpectation = XCTestExpectation(description: "expiration sync expectation") - let expiryHeight = pendingTransaction.expiryHeight + try coordinator.applyStaged(blockheight: expiryHeight + 1) sleep(2) @@ -1102,32 +1064,24 @@ class BalanceTests: XCTestCase { Total Balance is equal to total balance previously shown before sending the expired transaction */ XCTAssertEqual(expectedBalance, previousTotalBalance) - - let pendingRepo = PendingTransactionSQLDAO( - dbProvider: SimpleConnectionProvider( - path: coordinator.synchronizer.initializer.pendingDbURL.absoluteString - ), - logger: logger + + let transactionRepo = TransactionSQLDAO(dbProvider: SimpleConnectionProvider( + path: coordinator.synchronizer.initializer.dataDbURL.absoluteString + ) ) - - guard - let expiredPending = try? pendingRepo.find(by: pendingTransaction.id!), - let id = expiredPending.id - else { - XCTFail("pending transaction not found") - return - } + + let expiredPending = try await transactionRepo.find(id: pendingTransaction.id) /* there no sent transaction displayed */ let sentTransactions = try await coordinator.synchronizer.allSentTransactions() - XCTAssertNil(sentTransactions.first(where: { $0.id == id })) + XCTAssertNil(sentTransactions.first(where: { $0.id == pendingTransaction.id })) /* There’s a pending transaction that has expired */ - XCTAssertEqual(expiredPending.minedHeight, -1) + XCTAssertNil(expiredPending.minedHeight) } func handleError(_ error: Error?) { @@ -1162,7 +1116,7 @@ class BalanceTests: XCTestCase { class SDKSynchonizerListener { var transactionsFound: (([ZcashTransaction.Overview]) -> Void)? - var synchronizerMinedTransaction: ((PendingTransactionEntity) -> Void)? + var synchronizerMinedTransaction: ((ZcashTransaction.Overview) -> Void)? var cancellables: [AnyCancellable] = [] func subscribeToSynchronizer(_ synchronizer: SDKSynchronizer) { @@ -1194,7 +1148,7 @@ class SDKSynchonizerListener { } } - func txMined(_ transaction: PendingTransactionEntity) { + func txMined(_ transaction: ZcashTransaction.Overview) { DispatchQueue.main.async { [weak self] in self?.synchronizerMinedTransaction?(transaction) } diff --git a/Tests/DarksideTests/DarksideSanityCheckTests.swift b/Tests/DarksideTests/DarksideSanityCheckTests.swift index 7bae92de..521b9ec9 100644 --- a/Tests/DarksideTests/DarksideSanityCheckTests.swift +++ b/Tests/DarksideTests/DarksideSanityCheckTests.swift @@ -41,7 +41,6 @@ class DarksideSanityCheckTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func testDarkside() async throws { diff --git a/Tests/DarksideTests/InternalStateConsistencyTests.swift b/Tests/DarksideTests/InternalStateConsistencyTests.swift index 9b15c2ad..92a89e59 100644 --- a/Tests/DarksideTests/InternalStateConsistencyTests.swift +++ b/Tests/DarksideTests/InternalStateConsistencyTests.swift @@ -41,7 +41,6 @@ final class InternalStateConsistencyTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func testInternalStateIsConsistentWhenMigrating() async throws { @@ -70,7 +69,7 @@ final class InternalStateConsistencyTests: XCTestCase { let coordinator = self.coordinator! DispatchQueue.global().asyncAfter(deadline: .now() + 1) { Task(priority: .userInitiated) { - await coordinator.synchronizer.stop() + coordinator.synchronizer.stop() } } diff --git a/Tests/DarksideTests/PendingTransactionUpdatesTest.swift b/Tests/DarksideTests/PendingTransactionUpdatesTest.swift index eb17d1e8..cd4dabf2 100644 --- a/Tests/DarksideTests/PendingTransactionUpdatesTest.swift +++ b/Tests/DarksideTests/PendingTransactionUpdatesTest.swift @@ -37,7 +37,6 @@ class PendingTransactionUpdatesTest: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func testPendingTransactionMinedHeightUpdated() async throws { @@ -72,7 +71,7 @@ class PendingTransactionUpdatesTest: XCTestCase { sleep(1) let sendExpectation = XCTestExpectation(description: "send expectation") - var pendingEntity: PendingTransactionEntity? + var pendingEntity: ZcashTransaction.Overview? /* 2. send transaction to recipient address @@ -99,12 +98,12 @@ class PendingTransactionUpdatesTest: XCTestCase { return } - XCTAssertFalse( - pendingUnconfirmedTx.isConfirmed(currentHeight: 663188), + XCTAssertTrue( + pendingUnconfirmedTx.isPending(currentHeight: 633188), "pending transaction evaluated as confirmed when it shouldn't" ) - XCTAssertFalse( - pendingUnconfirmedTx.isMined, + XCTAssertNil( + pendingUnconfirmedTx.minedHeight, "pending transaction evaluated as mined when it shouldn't" ) @@ -171,7 +170,7 @@ class PendingTransactionUpdatesTest: XCTestCase { */ LoggerProxy.info("6a. verify that there's a pending transaction with a mined height of \(sentTxHeight)") XCTAssertEqual(afterStagePendingTx.minedHeight, sentTxHeight) - XCTAssertTrue(afterStagePendingTx.isMined, "pending transaction shown as unmined when it has been mined") + XCTAssertNotNil(afterStagePendingTx.minedHeight, "pending transaction shown as unmined when it has been mined") XCTAssertTrue(afterStagePendingTx.isPending(currentHeight: sentTxHeight)) /* @@ -207,11 +206,11 @@ class PendingTransactionUpdatesTest: XCTestCase { let supposedlyPendingUnexistingTransaction = try await coordinator.synchronizer.allPendingTransactions().first let clearedTransactions = await coordinator.synchronizer - .clearedTransactions + .transactions - let clearedTransaction = clearedTransactions.first(where: { $0.rawID == afterStagePendingTx.rawTransactionId }) + let clearedTransaction = clearedTransactions.first(where: { $0.rawID == afterStagePendingTx.rawID } ) - XCTAssertEqual(clearedTransaction!.value.amount + clearedTransaction!.fee!.amount, -afterStagePendingTx.value.amount) + XCTAssertEqual(clearedTransaction!.value.amount, afterStagePendingTx.value.amount) XCTAssertNil(supposedlyPendingUnexistingTransaction) } diff --git a/Tests/DarksideTests/ReOrgTests.swift b/Tests/DarksideTests/ReOrgTests.swift index 5916c593..76670625 100644 --- a/Tests/DarksideTests/ReOrgTests.swift +++ b/Tests/DarksideTests/ReOrgTests.swift @@ -69,7 +69,6 @@ class ReOrgTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func handleReOrgNotification(event: CompactBlockProcessor.Event) { diff --git a/Tests/DarksideTests/RewindRescanTests.swift b/Tests/DarksideTests/RewindRescanTests.swift index 31496198..bd19863e 100644 --- a/Tests/DarksideTests/RewindRescanTests.swift +++ b/Tests/DarksideTests/RewindRescanTests.swift @@ -43,7 +43,6 @@ class RewindRescanTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func handleError(_ error: Error?) { @@ -271,7 +270,7 @@ class RewindRescanTests: XCTestCase { XCTAssertEqual(verifiedBalance, totalBalance) // rewind to transaction - guard let transaction = try await coordinator.synchronizer.allClearedTransactions().first else { + guard let transaction = try await coordinator.synchronizer.allTransactions().first else { XCTFail("failed to get a transaction to rewind to") return } @@ -362,7 +361,7 @@ class RewindRescanTests: XCTestCase { // 3 create a transaction for the max amount possible // 4 send the transaction let spendingKey = coordinator.spendingKey - var pendingTx: PendingTransactionEntity? + var pendingTx: ZcashTransaction.Overview? do { let transaction = try await coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, @@ -382,9 +381,9 @@ class RewindRescanTests: XCTestCase { } notificationHandler.synchronizerMinedTransaction = { transaction in - XCTAssertNotNil(transaction.rawTransactionId) - XCTAssertNotNil(pendingTx.rawTransactionId) - XCTAssertEqual(transaction.rawTransactionId, pendingTx.rawTransactionId) + XCTAssertNotNil(transaction.rawID) + XCTAssertNotNil(pendingTx.rawID) + XCTAssertEqual(transaction.rawID, pendingTx.rawID) transactionMinedExpectation.fulfill() } @@ -399,7 +398,7 @@ class RewindRescanTests: XCTestCase { let sentTxHeight = latestHeight + 1 notificationHandler.transactionsFound = { txs in - let foundTx = txs.first(where: { $0.rawID == pendingTx.rawTransactionId }) + let foundTx = txs.first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNotNil(foundTx) XCTAssertEqual(foundTx?.minedHeight, sentTxHeight) @@ -416,10 +415,10 @@ class RewindRescanTests: XCTestCase { do { try await coordinator.sync( completion: { synchronizer in - let pendingTransaction = await synchronizer.pendingTransactions - .first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + let pendingTransaction = try await synchronizer.allPendingTransactions() + .first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNotNil(pendingTransaction, "pending transaction should have been mined by now") - XCTAssertTrue(pendingTransaction?.isMined ?? false) + XCTAssertNotNil(pendingTransaction?.minedHeight) XCTAssertEqual(pendingTransaction?.minedHeight, sentTxHeight) mineExpectation.fulfill() }, error: self.handleError @@ -431,16 +430,18 @@ class RewindRescanTests: XCTestCase { await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5) // 7 advance to confirmation - - try coordinator.applyStaged(blockheight: sentTxHeight + 10) + let advanceToConfirmationHeight = sentTxHeight + 10 + + try coordinator.applyStaged(blockheight: advanceToConfirmationHeight) sleep(2) let rewindExpectation = XCTestExpectation(description: "RewindExpectation") + let rewindHeight = sentTxHeight - 5 try await withCheckedThrowingContinuation { continuation in // rewind 5 blocks prior to sending - coordinator.synchronizer.rewind(.height(blockheight: sentTxHeight - 5)) + coordinator.synchronizer.rewind(.height(blockheight: rewindHeight)) .sink( receiveCompletion: { result in rewindExpectation.fulfill() @@ -462,13 +463,13 @@ class RewindRescanTests: XCTestCase { guard let pendingEntity = try await coordinator.synchronizer.allPendingTransactions() - .first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + .first(where: { $0.rawID == pendingTx.rawID }) else { XCTFail("sent pending transaction not found after rewind") return } - XCTAssertFalse(pendingEntity.isMined) + XCTAssertNil(pendingEntity.minedHeight) let confirmExpectation = XCTestExpectation(description: "confirm expectation") notificationHandler.transactionsFound = { txs in @@ -477,7 +478,7 @@ class RewindRescanTests: XCTestCase { XCTFail("should have found sent transaction but didn't") return } - XCTAssertEqual(transaction.rawID, pendingTx.rawTransactionId, "should have mined sent transaction but didn't") + XCTAssertEqual(transaction.rawID, pendingTx.rawID, "should have mined sent transaction but didn't") } notificationHandler.synchronizerMinedTransaction = { transaction in @@ -498,7 +499,7 @@ class RewindRescanTests: XCTestCase { await fulfillment(of: [confirmExpectation], timeout: 10) let confirmedPending = try await coordinator.synchronizer.allPendingTransactions() - .first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) + .first(where: { $0.rawID == pendingTx.rawID }) XCTAssertNil(confirmedPending, "pending, now confirmed transaction found") diff --git a/Tests/DarksideTests/ShieldFundsTests.swift b/Tests/DarksideTests/ShieldFundsTests.swift index d3308730..5007d755 100644 --- a/Tests/DarksideTests/ShieldFundsTests.swift +++ b/Tests/DarksideTests/ShieldFundsTests.swift @@ -38,7 +38,6 @@ class ShieldFundsTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } /// Tests shielding funds from a UTXO @@ -206,7 +205,7 @@ class ShieldFundsTests: XCTestCase { shouldContinue = false - var shieldingPendingTx: PendingTransactionEntity? + var shieldingPendingTx: ZcashTransaction.Overview? // shield the funds do { @@ -216,7 +215,7 @@ class ShieldFundsTests: XCTestCase { shieldingThreshold: Zatoshi(10000) ) shouldContinue = true - XCTAssertEqual(pendingTx.value, Zatoshi(10000)) + XCTAssertEqual(pendingTx.value, Zatoshi(10000) - pendingTx.fee!) shieldingPendingTx = pendingTx shieldFundsExpectation.fulfill() } catch { @@ -320,8 +319,8 @@ class ShieldFundsTests: XCTestCase { guard shouldContinue else { return } // verify that there's a confirmed transaction that's the shielding transaction - let clearedTransaction = await coordinator.synchronizer.clearedTransactions.first( - where: { $0.rawID == shieldingPendingTx?.rawTransactionId } + let clearedTransaction = await coordinator.synchronizer.transactions.first( + where: { $0.rawID == shieldingPendingTx?.rawID } ) XCTAssertNotNil(clearedTransaction) diff --git a/Tests/DarksideTests/SynchronizerDarksideTests.swift b/Tests/DarksideTests/SynchronizerDarksideTests.swift index fd2374b8..a3d03fa8 100644 --- a/Tests/DarksideTests/SynchronizerDarksideTests.swift +++ b/Tests/DarksideTests/SynchronizerDarksideTests.swift @@ -45,7 +45,6 @@ class SynchronizerDarksideTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func testFoundTransactions() async throws { @@ -181,7 +180,7 @@ class SynchronizerDarksideTests: XCTestCase { transparentBalance: .zero, syncStatus: .disconnected, latestScannedHeight: 663150, - latestBlockHeight: 663189, + latestBlockHeight: 0, latestScannedTime: 1576821833 ), SynchronizerState( @@ -190,13 +189,12 @@ class SynchronizerDarksideTests: XCTestCase { transparentBalance: .zero, syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)), latestScannedHeight: 663150, - latestBlockHeight: 663189, + latestBlockHeight: 0, latestScannedTime: 1576821833 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: .zero, -// shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), + shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), transparentBalance: .zero, syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)), latestScannedHeight: 663189, @@ -296,7 +294,15 @@ class SynchronizerDarksideTests: XCTestCase { ) ] - XCTAssertEqual(states, expectedStates) + XCTAssertEqual(states.count, expectedStates.count) + XCTAssertEqual(states[0], expectedStates[0]) + XCTAssertEqual(states[1], expectedStates[1]) + XCTAssertEqual(states[2], expectedStates[2]) + XCTAssertEqual(states[3], expectedStates[3]) + XCTAssertEqual(states[4], expectedStates[4]) + XCTAssertEqual(states[5], expectedStates[5]) + XCTAssertEqual(states[6], expectedStates[6]) + XCTAssertEqual(states[7], expectedStates[7]) } func testSyncSessionUpdates() async throws { @@ -338,7 +344,7 @@ class SynchronizerDarksideTests: XCTestCase { syncStatus: .disconnected, latestScannedHeight: 663150, latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedTime: 1576821833.0 ), SynchronizerState( syncSessionID: uuids[0], @@ -347,25 +353,25 @@ class SynchronizerDarksideTests: XCTestCase { syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)), latestScannedHeight: 663150, latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedTime: 1576821833.0 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: .zero, + shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), transparentBalance: .zero, syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)), - latestScannedHeight: 663150, - latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedHeight: 663189, + latestBlockHeight: 663189, + latestScannedTime: 1 ), SynchronizerState( syncSessionID: uuids[0], shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)), - latestScannedHeight: 663150, - latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedHeight: 663189, + latestBlockHeight: 663189, + latestScannedTime: 1 ), SynchronizerState( syncSessionID: uuids[0], @@ -379,7 +385,7 @@ class SynchronizerDarksideTests: XCTestCase { accountId: 0, blockTime: 1.0, expiryHeight: 663206, - fee: Zatoshi(0), + fee: nil, id: 2, index: 1, hasChange: false, @@ -395,9 +401,9 @@ class SynchronizerDarksideTests: XCTestCase { range: 663150...663189 ) ), - latestScannedHeight: 663150, - latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedHeight: 663189, + latestBlockHeight: 663189, + latestScannedTime: 1 ), SynchronizerState( syncSessionID: uuids[0], @@ -411,7 +417,7 @@ class SynchronizerDarksideTests: XCTestCase { accountId: 0, blockTime: 1.0, expiryHeight: 663192, - fee: Zatoshi(0), + fee: nil, id: 1, index: 1, hasChange: false, @@ -427,18 +433,18 @@ class SynchronizerDarksideTests: XCTestCase { range: 663150...663189 ) ), - latestScannedHeight: 663150, - latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedHeight: 663189, + latestBlockHeight: 663189, + latestScannedTime: 1 ), SynchronizerState( syncSessionID: uuids[0], shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), syncStatus: .fetching, - latestScannedHeight: 663150, - latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedHeight: 663189, + latestBlockHeight: 663189, + latestScannedTime: 1 ), SynchronizerState( syncSessionID: uuids[0], @@ -446,12 +452,20 @@ class SynchronizerDarksideTests: XCTestCase { transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), syncStatus: .synced, latestScannedHeight: 663189, - latestBlockHeight: 0, - latestScannedTime: 0 + latestBlockHeight: 663189, + latestScannedTime: 1 ) ] - XCTAssertEqual(states, expectedStates) + XCTAssertEqual(states.count, expectedStates.count) + XCTAssertEqual(states[0], expectedStates[0]) + XCTAssertEqual(states[1], expectedStates[1]) + XCTAssertEqual(states[2], expectedStates[2]) + XCTAssertEqual(states[3], expectedStates[3]) + XCTAssertEqual(states[4], expectedStates[4]) + XCTAssertEqual(states[5], expectedStates[5]) + XCTAssertEqual(states[7], expectedStates[7]) + try coordinator.service.applyStaged(nextLatestHeight: 663_200) @@ -477,35 +491,35 @@ class SynchronizerDarksideTests: XCTestCase { transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)), latestScannedHeight: 663189, - latestBlockHeight: 0, - latestScannedTime: 0 + latestBlockHeight: 663189, + latestScannedTime: 1.0 ), SynchronizerState( syncSessionID: uuids[1], - shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), + shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), syncStatus: .syncing(BlockProgress(startHeight: 663190, targetHeight: 663200, progressHeight: 663200)), - latestScannedHeight: 663189, - latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedHeight: 663200, + latestBlockHeight: 663200, + latestScannedTime: 1 ), SynchronizerState( syncSessionID: uuids[1], shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)), - latestScannedHeight: 663189, - latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedHeight: 663200, + latestBlockHeight: 663200, + latestScannedTime: 1 ), SynchronizerState( syncSessionID: uuids[1], shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), syncStatus: .fetching, - latestScannedHeight: 663189, - latestBlockHeight: 0, - latestScannedTime: 0 + latestScannedHeight: 663200, + latestBlockHeight: 663200, + latestScannedTime: 1 ), SynchronizerState( syncSessionID: uuids[1], @@ -513,12 +527,18 @@ class SynchronizerDarksideTests: XCTestCase { transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), syncStatus: .synced, latestScannedHeight: 663200, - latestBlockHeight: 0, - latestScannedTime: 0 + latestBlockHeight: 663200, + latestScannedTime: 1 ) ] - XCTAssertEqual(states, secondBatchOfExpectedStates) + XCTAssertEqual(states.count, secondBatchOfExpectedStates.count) + XCTAssertEqual(states[0], secondBatchOfExpectedStates[0]) + XCTAssertEqual(states[1], secondBatchOfExpectedStates[1]) + XCTAssertEqual(states[2], secondBatchOfExpectedStates[2]) + XCTAssertEqual(states[3], secondBatchOfExpectedStates[3]) + XCTAssertEqual(states[4], secondBatchOfExpectedStates[4]) + } func testSyncAfterWipeWorks() async throws { diff --git a/Tests/DarksideTests/SynchronizerTests.swift b/Tests/DarksideTests/SynchronizerTests.swift index 8e85f4e0..918097c8 100644 --- a/Tests/DarksideTests/SynchronizerTests.swift +++ b/Tests/DarksideTests/SynchronizerTests.swift @@ -51,7 +51,6 @@ final class SynchronizerTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func handleReorg(event: CompactBlockProcessor.Event) { @@ -92,7 +91,7 @@ final class SynchronizerTests: XCTestCase { ) try await Task.sleep(nanoseconds: 5_000_000_000) - await self.coordinator.synchronizer.stop() + self.coordinator.synchronizer.stop() await fulfillment(of: [syncStoppedExpectation], timeout: 6) @@ -221,7 +220,7 @@ final class SynchronizerTests: XCTestCase { let storage = await self.coordinator.synchronizer.blockProcessor.storage as! FSCompactBlockRepository let fm = FileManager.default print(coordinator.synchronizer.initializer.dataDbURL.path) - XCTAssertFalse(fm.fileExists(atPath: coordinator.synchronizer.initializer.pendingDbURL.path), "Pending DB should be deleted") + XCTAssertFalse(fm.fileExists(atPath: coordinator.synchronizer.initializer.dataDbURL.path), "Data DB should be deleted.") XCTAssertTrue(fm.fileExists(atPath: storage.blocksDirectory.path), "FS Cache directory should exist") XCTAssertEqual(try fm.contentsOfDirectory(atPath: storage.blocksDirectory.path), [], "FS Cache directory should be empty") @@ -262,7 +261,7 @@ final class SynchronizerTests: XCTestCase { // 1 sync and get spendable funds try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName) - try coordinator.applyStaged(blockheight: defaultLatestHeight) + try coordinator.applyStaged(blockheight: 663200) let initialVerifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance() let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() sleep(1) @@ -281,6 +280,12 @@ final class SynchronizerTests: XCTestCase { await fulfillment(of: [firstSyncExpectation], timeout: 12) + let verifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance() + let totalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() + // 2 check that there are no unconfirmed funds + XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight)) + XCTAssertEqual(verifiedBalance, totalBalance) + // Add more blocks to the chain so the long sync can start. try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName, length: 10000) try coordinator.applyStaged(blockheight: birthday + 10000) @@ -305,19 +310,12 @@ final class SynchronizerTests: XCTestCase { await fulfillment(of: [waitExpectation], timeout: 1) - let verifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance() - let totalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() - // 2 check that there are no unconfirmed funds - XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight)) - XCTAssertEqual(verifiedBalance, totalBalance) - let rewindExpectation = XCTestExpectation(description: "RewindExpectation") // rewind to birthday coordinator.synchronizer.rewind(.birthday) .sink( receiveCompletion: { result in - rewindExpectation.fulfill() switch result { case .finished: break @@ -326,7 +324,7 @@ final class SynchronizerTests: XCTestCase { } rewindExpectation.fulfill() }, - receiveValue: { _ in } + receiveValue: { _ in rewindExpectation.fulfill() } ) .store(in: &cancellables) diff --git a/Tests/DarksideTests/Z2TReceiveTests.swift b/Tests/DarksideTests/Z2TReceiveTests.swift index dfcc460e..304cf930 100644 --- a/Tests/DarksideTests/Z2TReceiveTests.swift +++ b/Tests/DarksideTests/Z2TReceiveTests.swift @@ -41,7 +41,6 @@ class Z2TReceiveTests: XCTestCase { try await coordinator.stop() try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.dataDB) - try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) } func subscribeToFoundTransactions() { @@ -134,7 +133,7 @@ class Z2TReceiveTests: XCTestCase { await fulfillment(of: [preTxExpectation, foundTransactionsExpectation], timeout: 5) let sendExpectation = XCTestExpectation(description: "sendToAddress") - var pendingEntity: PendingTransactionEntity? + var pendingEntity: ZcashTransaction.Overview? var testError: Error? let sendAmount = Zatoshi(10000) /* diff --git a/Tests/OfflineTests/ClosureSynchronizerOfflineTests.swift b/Tests/OfflineTests/ClosureSynchronizerOfflineTests.swift index ab6cd22d..dde6dec0 100644 --- a/Tests/OfflineTests/ClosureSynchronizerOfflineTests.swift +++ b/Tests/OfflineTests/ClosureSynchronizerOfflineTests.swift @@ -307,7 +307,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase { } func testSendToAddressSucceed() throws { - let amount = Zatoshi(100) + let amount = Zatoshi(10) let recipient: Recipient = .transparent(data.transparentAddress) let memo: Memo = .text(try MemoText("Some message")) let mockedSpendingKey = data.spendingKey @@ -326,7 +326,8 @@ class ClosureSynchronizerOfflineTests: XCTestCase { synchronizer.sendToAddress(spendingKey: mockedSpendingKey, zatoshi: amount, toAddress: recipient, memo: memo) { result in switch result { case let .success(receivedEntity): - XCTAssertEqual(receivedEntity.recipient, self.data.pendingTransactionEntity.recipient) + + XCTAssertEqual(receivedEntity.value, amount) expectation.fulfill() case let .failure(error): XCTFail("Unpected failure with error: \(error)") @@ -377,7 +378,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase { synchronizer.shieldFunds(spendingKey: mockedSpendingKey, memo: memo, shieldingThreshold: shieldingThreshold) { result in switch result { case let .success(receivedEntity): - XCTAssertEqual(receivedEntity.recipient, self.data.pendingTransactionEntity.recipient) + XCTAssertEqual(receivedEntity.value, self.data.pendingTransactionEntity.value) expectation.fulfill() case let .failure(error): XCTFail("Unpected failure with error: \(error)") @@ -410,22 +411,6 @@ class ClosureSynchronizerOfflineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } - func testCancelSpendSucceed() { - synchronizerMock.cancelSpendTransactionClosure = { receivedTransaction in - XCTAssertEqual(receivedTransaction.recipient, self.data.pendingTransactionEntity.recipient) - return true - } - - let expectation = XCTestExpectation() - - synchronizer.cancelSpend(transaction: data.pendingTransactionEntity) { result in - XCTAssertTrue(result) - expectation.fulfill() - } - - wait(for: [expectation], timeout: 0.5) - } - func testPendingTransactionsSucceed() { synchronizerMock.underlyingPendingTransactions = [data.pendingTransactionEntity] @@ -433,7 +418,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase { synchronizer.pendingTransactions() { transactions in XCTAssertEqual(transactions.count, 1) - XCTAssertEqual(transactions[0].recipient, self.data.pendingTransactionEntity.recipient) + XCTAssertEqual(transactions[0].id, self.data.pendingTransactionEntity.id) expectation.fulfill() } @@ -441,7 +426,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase { } func testClearedTransactionsSucceed() { - synchronizerMock.underlyingClearedTransactions = [data.clearedTransaction] + synchronizerMock.underlyingTransactions = [data.clearedTransaction] let expectation = XCTestExpectation() @@ -525,92 +510,6 @@ class ClosureSynchronizerOfflineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } - func testGetMemosForReceivedTransactionSucceed() throws { - let memo: Memo = .text(try MemoText("Some message")) - - synchronizerMock.getMemosForReceivedTransactionClosure = { receivedTransaction in - XCTAssertEqual(receivedTransaction.id, self.data.receivedTransaction.id) - return [memo] - } - - let expectation = XCTestExpectation() - - synchronizer.getMemos(for: data.receivedTransaction) { result in - switch result { - case let .success(memos): - XCTAssertEqual(memos.count, 1) - XCTAssertEqual(memos[0], memo) - expectation.fulfill() - case let .failure(error): - XCTFail("Unpected failure with error: \(error)") - } - } - - wait(for: [expectation], timeout: 0.5) - } - - func testGetMemosForReceivedTransactionThrowsError() { - synchronizerMock.getMemosForReceivedTransactionClosure = { _ in - throw "Some error" - } - - let expectation = XCTestExpectation() - - synchronizer.getMemos(for: data.receivedTransaction) { result in - switch result { - case .success: - XCTFail("Error should be thrown.") - case .failure: - expectation.fulfill() - } - } - - wait(for: [expectation], timeout: 0.5) - } - - func testGetMemosForSentTransactionSucceed() throws { - let memo: Memo = .text(try MemoText("Some message")) - - synchronizerMock.getMemosForSentTransactionClosure = { receivedTransaction in - XCTAssertEqual(receivedTransaction.id, self.data.sentTransaction.id) - return [memo] - } - - let expectation = XCTestExpectation() - - synchronizer.getMemos(for: data.sentTransaction) { result in - switch result { - case let .success(memos): - XCTAssertEqual(memos.count, 1) - XCTAssertEqual(memos[0], memo) - expectation.fulfill() - case let .failure(error): - XCTFail("Unpected failure with error: \(error)") - } - } - - wait(for: [expectation], timeout: 0.5) - } - - func testGetMemosForSentTransactionThrowsError() { - synchronizerMock.getMemosForSentTransactionClosure = { _ in - throw "Some error" - } - - let expectation = XCTestExpectation() - - synchronizer.getMemos(for: data.sentTransaction) { result in - switch result { - case .success: - XCTFail("Error should be thrown.") - case .failure: - expectation.fulfill() - } - } - - wait(for: [expectation], timeout: 0.5) - } - func testGetRecipientsForClearedTransaction() { let expectedRecipient: TransactionRecipient = .address(.transparent(data.transparentAddress)) @@ -630,27 +529,8 @@ class ClosureSynchronizerOfflineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } - func testGetRecipientsForSentTransaction() { - let expectedRecipient: TransactionRecipient = .address(.transparent(data.transparentAddress)) - - synchronizerMock.getRecipientsForSentTransactionClosure = { receivedTransaction in - XCTAssertEqual(receivedTransaction.id, self.data.sentTransaction.id) - return [expectedRecipient] - } - - let expectation = XCTestExpectation() - - synchronizer.getRecipients(for: data.sentTransaction) { recipients in - XCTAssertEqual(recipients.count, 1) - XCTAssertEqual(recipients[0], expectedRecipient) - expectation.fulfill() - } - - wait(for: [expectation], timeout: 0.5) - } - func testAllConfirmedTransactionsSucceed() throws { - synchronizerMock.allConfirmedTransactionsFromLimitClosure = { receivedTransaction, limit in + synchronizerMock.allTransactionsFromLimitClosure = { receivedTransaction, limit in XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(limit, 3) return [self.data.clearedTransaction] @@ -673,7 +553,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase { } func testAllConfirmedTransactionsThrowsError() throws { - synchronizerMock.allConfirmedTransactionsFromLimitClosure = { _, _ in + synchronizerMock.allTransactionsFromLimitClosure = { _, _ in throw "Some error" } diff --git a/Tests/OfflineTests/CombineSynchronizerOfflineTests.swift b/Tests/OfflineTests/CombineSynchronizerOfflineTests.swift index 79d6f010..8ae884b0 100644 --- a/Tests/OfflineTests/CombineSynchronizerOfflineTests.swift +++ b/Tests/OfflineTests/CombineSynchronizerOfflineTests.swift @@ -329,7 +329,7 @@ class CombineSynchronizerOfflineTests: XCTestCase { } }, receiveValue: { value in - XCTAssertEqual(value.recipient, self.data.pendingTransactionEntity.recipient) + XCTAssertEqual(value.value, self.data.pendingTransactionEntity.value) } ) .store(in: &cancellables) @@ -393,7 +393,7 @@ class CombineSynchronizerOfflineTests: XCTestCase { } }, receiveValue: { value in - XCTAssertEqual(value.recipient, self.data.pendingTransactionEntity.recipient) + XCTAssertEqual(value.id, self.data.pendingTransactionEntity.id) } ) .store(in: &cancellables) @@ -431,33 +431,6 @@ class CombineSynchronizerOfflineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } - func testCancelSpendSucceed() { - synchronizerMock.cancelSpendTransactionClosure = { receivedTransaction in - XCTAssertEqual(receivedTransaction.recipient, self.data.pendingTransactionEntity.recipient) - return true - } - - let expectation = XCTestExpectation() - - synchronizer.cancelSpend(transaction: data.pendingTransactionEntity) - .sink( - receiveCompletion: { result in - switch result { - case .finished: - expectation.fulfill() - case let .failure(error): - XCTFail("Unpected failure with error: \(error)") - } - }, - receiveValue: { value in - XCTAssertTrue(value) - } - ) - .store(in: &cancellables) - - wait(for: [expectation], timeout: 0.5) - } - func testPendingTransactionsSucceed() { synchronizerMock.underlyingPendingTransactions = [data.pendingTransactionEntity] @@ -483,11 +456,11 @@ class CombineSynchronizerOfflineTests: XCTestCase { } func testClearedTransactionsSucceed() { - synchronizerMock.underlyingClearedTransactions = [data.clearedTransaction] + synchronizerMock.underlyingTransactions = [data.clearedTransaction] let expectation = XCTestExpectation() - synchronizer.clearedTransactions + synchronizer.allTransactions .sink( receiveCompletion: { result in switch result { @@ -609,118 +582,8 @@ class CombineSynchronizerOfflineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } - func testGetMemosForReceivedTransactionSucceed() throws { - let memo: Memo = .text(try MemoText("Some message")) - - synchronizerMock.getMemosForReceivedTransactionClosure = { receivedTransaction in - XCTAssertEqual(receivedTransaction.id, self.data.receivedTransaction.id) - return [memo] - } - - let expectation = XCTestExpectation() - - synchronizer.getMemos(for: data.receivedTransaction) - .sink( - receiveCompletion: { result in - switch result { - case .finished: - expectation.fulfill() - case let .failure(error): - XCTFail("Unpected failure with error: \(error)") - } - }, - receiveValue: { value in - XCTAssertEqual(value, [memo]) - } - ) - .store(in: &cancellables) - - wait(for: [expectation], timeout: 0.5) - } - - func testGetMemosForReceivedTransactionThrowsError() { - synchronizerMock.getMemosForReceivedTransactionClosure = { _ in - throw "Some error" - } - - let expectation = XCTestExpectation() - - synchronizer.getMemos(for: data.receivedTransaction) - .sink( - receiveCompletion: { result in - switch result { - case .finished: - XCTFail("Error should be thrown.") - case .failure: - expectation.fulfill() - } - }, - receiveValue: { _ in - XCTFail("No value is expected") - } - ) - .store(in: &cancellables) - - wait(for: [expectation], timeout: 0.5) - } - - func testGetMemosForSentTransactionSucceed() throws { - let memo: Memo = .text(try MemoText("Some message")) - - synchronizerMock.getMemosForSentTransactionClosure = { receivedTransaction in - XCTAssertEqual(receivedTransaction.id, self.data.sentTransaction.id) - return [memo] - } - - let expectation = XCTestExpectation() - - synchronizer.getMemos(for: data.sentTransaction) - .sink( - receiveCompletion: { result in - switch result { - case .finished: - expectation.fulfill() - case let .failure(error): - XCTFail("Unpected failure with error: \(error)") - } - }, - receiveValue: { value in - XCTAssertEqual(value, [memo]) - } - ) - .store(in: &cancellables) - - wait(for: [expectation], timeout: 0.5) - } - - func testGetMemosForSentTransactionThrowsError() { - synchronizerMock.getMemosForSentTransactionClosure = { _ in - throw "Some error" - } - - let expectation = XCTestExpectation() - - synchronizer.getMemos(for: data.sentTransaction) - .sink( - receiveCompletion: { result in - switch result { - case .finished: - XCTFail("Error should be thrown.") - case .failure: - expectation.fulfill() - } - }, - receiveValue: { _ in - XCTFail("No value is expected") - } - ) - .store(in: &cancellables) - - wait(for: [expectation], timeout: 0.5) - } - func testGetRecipientsForClearedTransaction() { - let expectedRecipient: TransactionRecipient = .address(.transparent(data.transparentAddress)) + let expectedRecipient: TransactionRecipient = TransactionRecipient.address(.transparent(self.data.transparentAddress)) synchronizerMock.getRecipientsForClearedTransactionClosure = { receivedTransaction in XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) @@ -748,37 +611,8 @@ class CombineSynchronizerOfflineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } - func testGetRecipientsForSentTransaction() { - let expectedRecipient: TransactionRecipient = .address(.transparent(data.transparentAddress)) - - synchronizerMock.getRecipientsForSentTransactionClosure = { receivedTransaction in - XCTAssertEqual(receivedTransaction.id, self.data.sentTransaction.id) - return [expectedRecipient] - } - - let expectation = XCTestExpectation() - - synchronizer.getRecipients(for: data.sentTransaction) - .sink( - receiveCompletion: { result in - switch result { - case .finished: - expectation.fulfill() - case let .failure(error): - XCTFail("Unpected failure with error: \(error)") - } - }, - receiveValue: { value in - XCTAssertEqual(value, [expectedRecipient]) - } - ) - .store(in: &cancellables) - - wait(for: [expectation], timeout: 0.5) - } - func testAllConfirmedTransactionsSucceed() throws { - synchronizerMock.allConfirmedTransactionsFromLimitClosure = { receivedTransaction, limit in + synchronizerMock.allTransactionsFromLimitClosure = { receivedTransaction, limit in XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(limit, 3) return [self.data.clearedTransaction] @@ -786,7 +620,7 @@ class CombineSynchronizerOfflineTests: XCTestCase { let expectation = XCTestExpectation() - synchronizer.allConfirmedTransactions(from: data.clearedTransaction, limit: 3) + synchronizer.allTransactions(from: data.clearedTransaction, limit: 3) .sink( receiveCompletion: { result in switch result { @@ -806,13 +640,13 @@ class CombineSynchronizerOfflineTests: XCTestCase { } func testAllConfirmedTransactionsThrowsError() throws { - synchronizerMock.allConfirmedTransactionsFromLimitClosure = { _, _ in + synchronizerMock.allTransactionsFromLimitClosure = { _, _ in throw "Some error" } let expectation = XCTestExpectation() - synchronizer.allConfirmedTransactions(from: data.clearedTransaction, limit: 3) + synchronizer.allTransactions(from: data.clearedTransaction, limit: 3) .sink( receiveCompletion: { result in switch result { diff --git a/Tests/OfflineTests/InitializerOfflineTests.swift b/Tests/OfflineTests/InitializerOfflineTests.swift index b4fbd286..cfe67f8b 100644 --- a/Tests/OfflineTests/InitializerOfflineTests.swift +++ b/Tests/OfflineTests/InitializerOfflineTests.swift @@ -20,7 +20,6 @@ class InitializerOfflineTests: XCTestCase { private func makeInitializer( fsBlockDbRoot: URL, dataDbURL: URL, - pendingDbURL: URL, spendParamsURL: URL, outputParamsURL: URL, alias: ZcashSynchronizerAlias @@ -29,7 +28,6 @@ class InitializerOfflineTests: XCTestCase { cacheDbURL: nil, fsBlockDbRoot: fsBlockDbRoot, dataDbURL: dataDbURL, - pendingDbURL: pendingDbURL, endpoint: LightWalletEndpointBuilder.default, network: ZcashNetworkBuilder.network(for: .testnet), spendParamsURL: spendParamsURL, @@ -54,7 +52,6 @@ class InitializerOfflineTests: XCTestCase { private func genericTestForURLsParsingFailures( fsBlockDbRoot: URL, dataDbURL: URL, - pendingDbURL: URL, spendParamsURL: URL, outputParamsURL: URL, alias: ZcashSynchronizerAlias, @@ -63,7 +60,6 @@ class InitializerOfflineTests: XCTestCase { let initializer = makeInitializer( fsBlockDbRoot: fsBlockDbRoot, dataDbURL: dataDbURL, - pendingDbURL: pendingDbURL, spendParamsURL: spendParamsURL, outputParamsURL: outputParamsURL, alias: alias @@ -77,7 +73,6 @@ class InitializerOfflineTests: XCTestCase { XCTAssertEqual(initializer.fsBlockDbRoot, fsBlockDbRoot, "Failing \(function)") XCTAssertEqual(initializer.dataDbURL, dataDbURL, "Failing \(function)") - XCTAssertEqual(initializer.pendingDbURL, pendingDbURL, "Failing \(function)") XCTAssertEqual(initializer.spendParamsURL, spendParamsURL, "Failing \(function)") XCTAssertEqual(initializer.outputParamsURL, outputParamsURL, "Failing \(function)") } @@ -86,7 +81,6 @@ class InitializerOfflineTests: XCTestCase { let initializer = makeInitializer( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, - pendingDbURL: validFileURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, alias: .default @@ -95,7 +89,6 @@ class InitializerOfflineTests: XCTestCase { XCTAssertNil(initializer.urlsParsingError) XCTAssertEqual(initializer.fsBlockDbRoot, validDirectoryURL) XCTAssertEqual(initializer.dataDbURL, validFileURL) - XCTAssertEqual(initializer.pendingDbURL, validFileURL) XCTAssertEqual(initializer.spendParamsURL, validFileURL) XCTAssertEqual(initializer.outputParamsURL, validFileURL) } @@ -104,7 +97,6 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: invalidPathURL, dataDbURL: validFileURL, - pendingDbURL: validFileURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, alias: .default @@ -115,18 +107,6 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: invalidPathURL, - pendingDbURL: validFileURL, - spendParamsURL: validFileURL, - outputParamsURL: validFileURL, - alias: .default - ) - } - - func test__defaultAlias__invalidPendingDbURL__errorIsGenerated() { - genericTestForURLsParsingFailures( - fsBlockDbRoot: validDirectoryURL, - dataDbURL: validFileURL, - pendingDbURL: invalidPathURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, alias: .default @@ -137,7 +117,6 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, - pendingDbURL: validFileURL, spendParamsURL: invalidPathURL, outputParamsURL: validFileURL, alias: .default @@ -148,7 +127,6 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, - pendingDbURL: validFileURL, spendParamsURL: validFileURL, outputParamsURL: invalidPathURL, alias: .default @@ -160,7 +138,6 @@ class InitializerOfflineTests: XCTestCase { let initializer = makeInitializer( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, - pendingDbURL: validFileURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, alias: alias @@ -169,7 +146,6 @@ class InitializerOfflineTests: XCTestCase { XCTAssertNil(initializer.urlsParsingError) XCTAssertEqual(initializer.fsBlockDbRoot, update(url: validDirectoryURL, with: alias)) XCTAssertEqual(initializer.dataDbURL, update(url: validFileURL, with: alias)) - XCTAssertEqual(initializer.pendingDbURL, update(url: validFileURL, with: alias)) XCTAssertEqual(initializer.spendParamsURL, update(url: validFileURL, with: alias)) XCTAssertEqual(initializer.outputParamsURL, update(url: validFileURL, with: alias)) } @@ -178,7 +154,6 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: invalidPathURL, dataDbURL: validFileURL, - pendingDbURL: validFileURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, alias: .custom("alias") @@ -189,18 +164,6 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: invalidPathURL, - pendingDbURL: validFileURL, - spendParamsURL: validFileURL, - outputParamsURL: validFileURL, - alias: .custom("alias") - ) - } - - func test__customAlias__invalidPendingDbURL__errorIsGenerated() { - genericTestForURLsParsingFailures( - fsBlockDbRoot: validDirectoryURL, - dataDbURL: validFileURL, - pendingDbURL: invalidPathURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, alias: .custom("alias") @@ -211,7 +174,6 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, - pendingDbURL: validFileURL, spendParamsURL: invalidPathURL, outputParamsURL: validFileURL, alias: .custom("alias") @@ -222,7 +184,6 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, - pendingDbURL: validFileURL, spendParamsURL: validFileURL, outputParamsURL: invalidPathURL, alias: .custom("alias") diff --git a/Tests/OfflineTests/NotesRepositoryTests.swift b/Tests/OfflineTests/NotesRepositoryTests.swift deleted file mode 100644 index 7fbe0214..00000000 --- a/Tests/OfflineTests/NotesRepositoryTests.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// NotesRepositoryTests.swift -// ZcashLightClientKit-Unit-Tests -// -// Created by Francisco Gindre on 11/18/19. -// - -import XCTest -@testable import TestUtils -@testable import ZcashLightClientKit - -class NotesRepositoryTests: XCTestCase { - var sentNotesRepository: SentNotesRepository! - var receivedNotesRepository: ReceivedNoteRepository! - - override func setUp() async throws { - try await super.setUp() - let rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: Environment.uniqueTestTempDirectory, networkType: .testnet) - sentNotesRepository = try! await TestDbBuilder.sentNotesRepository(rustBackend: rustBackend) - receivedNotesRepository = try! await TestDbBuilder.receivedNotesRepository(rustBackend: rustBackend) - } - - override func tearDown() { - super.tearDown() - sentNotesRepository = nil - receivedNotesRepository = nil - } - - func testSentCount() { - var count: Int? - XCTAssertNoThrow(try { count = try sentNotesRepository.count() }()) - XCTAssertEqual(count, 13) - } - - func testReceivedCount() { - var count: Int? - XCTAssertNoThrow(try { count = try receivedNotesRepository.count() }()) - XCTAssertEqual(count, 22) - } -} diff --git a/Tests/OfflineTests/PendingTransactionRepositoryTests.swift b/Tests/OfflineTests/PendingTransactionRepositoryTests.swift deleted file mode 100644 index d9f1e2e1..00000000 --- a/Tests/OfflineTests/PendingTransactionRepositoryTests.swift +++ /dev/null @@ -1,181 +0,0 @@ -// -// PendingTransactionRepositoryTests.swift -// ZcashLightClientKit-Unit-Tests -// -// Created by Francisco Gindre on 11/27/19. -// - -import XCTest -@testable import TestUtils -@testable import ZcashLightClientKit - -class PendingTransactionRepositoryTests: XCTestCase { - let dbUrl = try! TestDbBuilder.pendingTransactionsDbURL() - let recipient = SaplingAddress(validatedEncoding: "ztestsapling1ctuamfer5xjnnrdr3xdazenljx0mu0gutcf9u9e74tr2d3jwjnt0qllzxaplu54hgc2tyjdc2p6") - - var pendingRepository: PendingTransactionRepository! - - override func setUp() { - super.setUp() - cleanUpDb() - let pendingDbProvider = SimpleConnectionProvider(path: try! TestDbBuilder.pendingTransactionsDbURL().absoluteString) - let migrations = MigrationManager(pendingDbConnection: pendingDbProvider, networkType: .testnet, logger: logger) - try! migrations.performMigration() - pendingRepository = PendingTransactionSQLDAO(dbProvider: pendingDbProvider, logger: logger) - } - - override func tearDown() { - super.tearDown() - cleanUpDb() - pendingRepository = nil - } - - func cleanUpDb() { - try? FileManager.default.removeItem(at: TestDbBuilder.pendingTransactionsDbURL()) - } - - func testCreate() { - let transaction = createAndStoreMockedTransaction() - - guard let id = transaction.id, id >= 0 else { - XCTFail("failed to create mocked transaction that was just inserted") - return - } - - var expectedTx: PendingTransactionEntity? - XCTAssertNoThrow(try { expectedTx = try pendingRepository.find(by: id) }()) - - guard let expected = expectedTx else { - XCTFail("failed to retrieve mocked transaction by id \(id) that was just inserted") - return - } - - XCTAssertEqual(transaction.accountIndex, expected.accountIndex) - XCTAssertEqual(transaction.value, expected.value) - XCTAssertEqual(transaction.recipient, expected.recipient) - } - - func testFindById() { - let transaction = createAndStoreMockedTransaction() - - var expected: PendingTransactionEntity? - - guard let id = transaction.id else { - XCTFail("transaction with no id") - return - } - - XCTAssertNoThrow(try { expected = try pendingRepository.find(by: id) }()) - XCTAssertNotNil(expected) - } - - func testCancel() { - let transaction = createAndStoreMockedTransaction() - - guard let id = transaction.id else { - XCTFail("transaction with no id") - return - } - - guard id >= 0 else { - XCTFail("failed to create mocked transaction that was just inserted") - return - } - - XCTAssertNoThrow(try pendingRepository.cancel(transaction)) - } - - func testDelete() { - let transaction = createAndStoreMockedTransaction() - - guard let id = transaction.id else { - XCTFail("transaction with no id") - return - } - - guard id >= 0 else { - XCTFail("failed to create mocked transaction that was just inserted") - return - } - - XCTAssertNoThrow(try pendingRepository.delete(transaction)) - - var unexpectedTx: PendingTransactionEntity? - - XCTAssertNoThrow(try { unexpectedTx = try pendingRepository.find(by: id) }()) - XCTAssertNil(unexpectedTx) - } - - func testGetAll() { - var mockTransactions: [PendingTransactionEntity] = [] - for _ in 1...100 { - mockTransactions.append(createAndStoreMockedTransaction()) - } - - var all: [PendingTransactionEntity]? - - XCTAssertNoThrow(try { all = try pendingRepository.getAll() }()) - - guard let allTxs = all else { - XCTFail("failed to get all transactions") - return - } - - XCTAssertEqual(mockTransactions.count, allTxs.count) - } - - func testUpdate() { - let transaction = createAndStoreMockedTransaction() - - guard let id = transaction.id else { - XCTFail("transaction with no id") - return - } - - var stored: PendingTransactionEntity? - - XCTAssertNoThrow(try { stored = try pendingRepository.find(by: id) }()) - - guard stored != nil else { - XCTFail("failed to store tx") - return - } - - let oldEncodeAttempts = stored!.encodeAttempts - let oldSubmitAttempts = stored!.submitAttempts - - stored!.encodeAttempts += 1 - stored!.submitAttempts += 5 - - XCTAssertNoThrow(try pendingRepository.update(stored!)) - - guard let updatedTransaction = try? pendingRepository.find(by: stored!.id!) else { - XCTFail("failed to retrieve updated transaction with id: \(stored!.id!)") - return - } - - XCTAssertEqual(updatedTransaction.encodeAttempts, oldEncodeAttempts + 1) - XCTAssertEqual(updatedTransaction.submitAttempts, oldSubmitAttempts + 5) - XCTAssertEqual(updatedTransaction.recipient, stored!.recipient) - } - - func createAndStoreMockedTransaction(with value: Zatoshi = Zatoshi(1000)) -> PendingTransactionEntity { - var transaction = mockTransaction(with: value) - var id: Int? - - XCTAssertNoThrow(try { id = try pendingRepository.create(transaction) }()) - transaction.id = Int(id ?? -1) - return transaction - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - _ = try! pendingRepository.getAll() - } - } - - private func mockTransaction(with value: Zatoshi = Zatoshi(1000)) -> PendingTransactionEntity { - PendingTransaction(value: value, recipient: .address(.sapling(recipient)), memo: .empty(), account: 0) - } -} diff --git a/Tests/OfflineTests/SynchronizerOfflineTests.swift b/Tests/OfflineTests/SynchronizerOfflineTests.swift index 9f8ad130..d2909c32 100644 --- a/Tests/OfflineTests/SynchronizerOfflineTests.swift +++ b/Tests/OfflineTests/SynchronizerOfflineTests.swift @@ -175,6 +175,25 @@ class SynchronizerOfflineTests: XCTestCase { } } + func testSendToWithTransparentAddressThrowsError() async throws { + let testCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: true) + + do { + _ = try await testCoordinator.synchronizer.sendToAddress( + spendingKey: testCoordinator.spendingKey, + zatoshi: Zatoshi(1), + toAddress: .transparent(data.transparentAddress), + memo: try .init(string: "hello") + ) + XCTFail("Send to address should fail.") + } catch { + if let error = error as? ZcashError, case .synchronizerSendMemoToTransparentAddress = error { + } else { + XCTFail("Send to address failed with unexpected error: \(error)") + } + } + } + func testShieldFundsCalledWithoutPrepareThrowsError() async throws { let testCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false) @@ -233,7 +252,7 @@ class SynchronizerOfflineTests: XCTestCase { await fulfillment(of: [expectation], timeout: 1) } - func testURLsParsingFailsInInitialierPrepareThenThrowsError() async throws { + func testURLsParsingFailsInInitializerPrepareThenThrowsError() async throws { let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file") let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory") let invalidPathURL = URL(string: "https://whatever")! @@ -241,8 +260,7 @@ class SynchronizerOfflineTests: XCTestCase { let initializer = Initializer( cacheDbURL: nil, fsBlockDbRoot: validDirectoryURL, - dataDbURL: validFileURL, - pendingDbURL: invalidPathURL, + dataDbURL: invalidPathURL, endpoint: LightWalletEndpointBuilder.default, network: ZcashNetworkBuilder.network(for: .testnet), spendParamsURL: validFileURL, @@ -274,7 +292,7 @@ class SynchronizerOfflineTests: XCTestCase { } } - func testURLsParsingFailsInInitialierWipeThenThrowsError() async throws { + func testURLsParsingFailsInInitializerWipeThenThrowsError() async throws { let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file") let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory") let invalidPathURL = URL(string: "https://whatever")! @@ -282,8 +300,7 @@ class SynchronizerOfflineTests: XCTestCase { let initializer = Initializer( cacheDbURL: nil, fsBlockDbRoot: validDirectoryURL, - dataDbURL: validFileURL, - pendingDbURL: invalidPathURL, + dataDbURL: invalidPathURL, endpoint: LightWalletEndpointBuilder.default, network: ZcashNetworkBuilder.network(for: .testnet), spendParamsURL: validFileURL, diff --git a/Tests/OfflineTests/TransactionRepositoryTests.swift b/Tests/OfflineTests/TransactionRepositoryTests.swift index 1cb0906c..44188480 100644 --- a/Tests/OfflineTests/TransactionRepositoryTests.swift +++ b/Tests/OfflineTests/TransactionRepositoryTests.swift @@ -134,18 +134,22 @@ class TransactionRepositoryTests: XCTestCase { } func testFindMemoForReceivedTransaction() async throws { - let transaction = ZcashTransaction.Received( + let transaction = ZcashTransaction.Overview( + accountId: 0, blockTime: 1, expiryHeight: nil, - fromAccount: 0, + fee: nil, id: 5, index: 0, - memoCount: 0, + hasChange: false, + memoCount: 1, minedHeight: 0, - noteCount: 0, raw: nil, - rawID: nil, - value: Zatoshi.zero + rawID: Data(), + receivedNoteCount: 1, + sentNoteCount: 0, + value: .zero, + isExpiredUmined: false ) let memos = try await self.transactionRepository.findMemos(for: transaction) @@ -154,18 +158,22 @@ class TransactionRepositoryTests: XCTestCase { } func testFindMemoForSentTransaction() async throws { - let transaction = ZcashTransaction.Sent( + let transaction = ZcashTransaction.Overview( + accountId: 0, blockTime: 1, expiryHeight: nil, - fromAccount: 0, + fee: nil, id: 9, index: 0, - memoCount: 0, - minedHeight: 0, - noteCount: 0, + hasChange: false, + memoCount: 1, + minedHeight: nil, raw: nil, - rawID: nil, - value: Zatoshi.zero + rawID: Data(), + receivedNoteCount: 0, + sentNoteCount: 2, + value: .zero, + isExpiredUmined: false ) let memos = try await self.transactionRepository.findMemos(for: transaction) diff --git a/Tests/OfflineTests/WalletTests.swift b/Tests/OfflineTests/WalletTests.swift index b75ba014..65548621 100644 --- a/Tests/OfflineTests/WalletTests.swift +++ b/Tests/OfflineTests/WalletTests.swift @@ -44,7 +44,6 @@ class WalletTests: XCTestCase { cacheDbURL: nil, fsBlockDbRoot: testTempDirectory, dataDbURL: try __dataDbURL(), - pendingDbURL: try TestDbBuilder.pendingTransactionsDbURL(), endpoint: LightWalletEndpointBuilder.default, network: network, spendParamsURL: try __spendParamsURL(), diff --git a/Tests/OfflineTests/ZcashTransactionStateTests.swift b/Tests/OfflineTests/ZcashTransactionStateTests.swift new file mode 100644 index 00000000..a7a77fd8 --- /dev/null +++ b/Tests/OfflineTests/ZcashTransactionStateTests.swift @@ -0,0 +1,87 @@ +// +// ZcashTransactionStateTests.swift +// +// +// Created by Francisco Gindre on 5/3/23. +// + +import XCTest +import TestUtils +@testable import ZcashLightClientKit + +final class ZcashTransactionStateTests: XCTestCase { + func testExpiredUnminedState() throws { + let currentHeight = 1010 + + XCTAssertEqual( + ZcashTransaction.Overview.State( + currentHeight: currentHeight, + minedHeight: nil, + expiredUnmined: true + ), + .expired + ) + } + + func testConfirmationsBelowStaleConstantIsPending() { + let currentHeight = 1010 + + XCTAssertEqual( + ZcashTransaction.Overview.State( + currentHeight: currentHeight, + minedHeight: currentHeight, + expiredUnmined: false + ), + .pending + ) + + XCTAssertEqual( + ZcashTransaction.Overview.State( + currentHeight: currentHeight, + minedHeight: currentHeight - ZcashSDK.defaultStaleTolerance + 1, + expiredUnmined: false + ), + .pending + ) + + XCTAssertNotEqual( + ZcashTransaction.Overview.State( + currentHeight: currentHeight, + minedHeight: currentHeight - ZcashSDK.defaultStaleTolerance, + expiredUnmined: false + ), + .pending + ) + } + + func testMinedHeightAboveOrEqualToStaleConstantIsConfirmed() { + let currentHeight = 1010 + + XCTAssertEqual( + ZcashTransaction.Overview.State( + currentHeight: currentHeight, + minedHeight: currentHeight - ZcashSDK.defaultStaleTolerance, + expiredUnmined: false + ), + .confirmed + ) + + XCTAssertEqual( + ZcashTransaction.Overview.State( + currentHeight: currentHeight, + minedHeight: currentHeight - ZcashSDK.defaultStaleTolerance - 1, + expiredUnmined: false + ), + .confirmed + ) + + XCTAssertNotEqual( + ZcashTransaction.Overview.State( + currentHeight: currentHeight, + minedHeight: currentHeight - ZcashSDK.defaultStaleTolerance + 1, + expiredUnmined: false + ), + .confirmed + ) + } +} diff --git a/Tests/PerformanceTests/SynchronizerTests.swift b/Tests/PerformanceTests/SynchronizerTests.swift index 4e6ac8d3..81659229 100644 --- a/Tests/PerformanceTests/SynchronizerTests.swift +++ b/Tests/PerformanceTests/SynchronizerTests.swift @@ -72,7 +72,6 @@ class SynchronizerTests: XCTestCase { cacheDbURL: nil, fsBlockDbRoot: databases.fsCacheDbRoot, dataDbURL: databases.dataDB, - pendingDbURL: databases.pendingDB, endpoint: endpoint, network: network, spendParamsURL: try __spendParamsURL(), @@ -84,7 +83,6 @@ class SynchronizerTests: XCTestCase { try? FileManager.default.removeItem(at: databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: databases.dataDB) - try? FileManager.default.removeItem(at: databases.pendingDB) synchronizer = SDKSynchronizer(initializer: initializer) diff --git a/Tests/TestUtils/DarkSideWalletService.swift b/Tests/TestUtils/DarkSideWalletService.swift index 0ae8f575..3730cbe4 100644 --- a/Tests/TestUtils/DarkSideWalletService.swift +++ b/Tests/TestUtils/DarkSideWalletService.swift @@ -195,10 +195,6 @@ enum DarksideWalletDConstants: NetworkConstants { ZcashSDKMainnetConstants.defaultCacheDbName } - static var defaultPendingDbName: String { - ZcashSDKMainnetConstants.defaultPendingDbName - } - static var defaultDbNamePrefix: String { ZcashSDKMainnetConstants.defaultDbNamePrefix } diff --git a/Tests/TestUtils/MockTransactionRepository.swift b/Tests/TestUtils/MockTransactionRepository.swift index 01fa0516..ab29b7aa 100644 --- a/Tests/TestUtils/MockTransactionRepository.swift +++ b/Tests/TestUtils/MockTransactionRepository.swift @@ -26,8 +26,8 @@ class MockTransactionRepository { var network: ZcashNetwork var transactions: [ZcashTransaction.Overview] = [] - var receivedTransactions: [ZcashTransaction.Received] = [] - var sentTransactions: [ZcashTransaction.Sent] = [] + var receivedTransactions: [ZcashTransaction.Overview] = [] + var sentTransactions: [ZcashTransaction.Overview] = [] var allCount: Int { receivedCount + sentCount @@ -73,6 +73,14 @@ extension MockTransactionRepository.Kind: Equatable {} // MARK: - TransactionRepository extension MockTransactionRepository: TransactionRepository { + func getTransactionOutputs(for id: Int) async throws -> [ZcashLightClientKit.ZcashTransaction.Output] { + [] + } + + func findPendingTransactions(latestHeight: ZcashLightClientKit.BlockHeight, offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview] { + [] + } + func getRecipients(for id: Int) -> [TransactionRecipient] { [] } @@ -203,25 +211,17 @@ extension MockTransactionRepository: TransactionRepository { throw MockTransactionRepositoryError.notImplemented } - func findReceived(offset: Int, limit: Int) throws -> [ZcashTransaction.Received] { + func findReceived(offset: Int, limit: Int) throws -> [ZcashTransaction.Overview] { throw MockTransactionRepositoryError.notImplemented } - func findSent(offset: Int, limit: Int) throws -> [ZcashTransaction.Sent] { + func findSent(offset: Int, limit: Int) throws -> [ZcashTransaction.Overview] { throw MockTransactionRepositoryError.notImplemented } func findMemos(for transaction: ZcashLightClientKit.ZcashTransaction.Overview) throws -> [ZcashLightClientKit.Memo] { throw MockTransactionRepositoryError.notImplemented } - - func findMemos(for receivedTransaction: ZcashLightClientKit.ZcashTransaction.Received) throws -> [ZcashLightClientKit.Memo] { - throw MockTransactionRepositoryError.notImplemented - } - - func findMemos(for sentTransaction: ZcashLightClientKit.ZcashTransaction.Sent) throws -> [ZcashLightClientKit.Memo] { - throw MockTransactionRepositoryError.notImplemented - } } extension Array { diff --git a/Tests/TestUtils/Resources/darkside_pending.db b/Tests/TestUtils/Resources/darkside_pending.db deleted file mode 100644 index 759b2cbfc9fc85209d4e4de3270b907c9b0c2da5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI2&2G~`6oto0RcKLxT^4LcW|OEDLPA|uA}Iz%xJe0)L|4|-o~D-kliCvq5?d*BNugtsLoYv$8%=x%zXDKktM%>XL^Fuizp75pfAW{qG{wMrG$`< zJhnDhZSy_7FCuMA`}{3BE7!d z9%|p73U==EB@bOMT-YLJVZt2YMPbs`wskx+hL%CC;m9;-TaW%OmEpPRxs36|GTs<7 zdO91Q3})7UV8E8Qmu&&3mxwJyx3h$(Rq{BFVmpwVV+$_lFE^JfX_|Chjg=*<^S~3g$nV6Q z32qB7$kWJ@6!6C|}b@=C3xBR%dRMC1#&8wK$dI10F<0YkDn}3tr8u_Ov^A z_(c2NJ37*?qZ=(~N50!;vb4QZv!Z@eH=Xu%&(&2mZs9QKFAtumc*7S*e^ zQXLMcUUO7cP;RO=He95nnvG=As)y7fOaG{GasdtFD5;UMVjG=io{=D=Vf4J1RL?w8 zj;?H7`a< 0 } var sendToAddressSpendingKeyZatoshiToAddressMemoReceivedArguments: (spendingKey: UnifiedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, memo: Memo?)? - var sendToAddressSpendingKeyZatoshiToAddressMemoReturnValue: PendingTransactionEntity! - var sendToAddressSpendingKeyZatoshiToAddressMemoClosure: ((UnifiedSpendingKey, Zatoshi, Recipient, Memo?) async throws -> PendingTransactionEntity)? + var sendToAddressSpendingKeyZatoshiToAddressMemoReturnValue: ZcashTransaction.Overview! + var sendToAddressSpendingKeyZatoshiToAddressMemoClosure: ((UnifiedSpendingKey, Zatoshi, Recipient, Memo?) async throws -> ZcashTransaction.Overview)? - func sendToAddress(spendingKey: UnifiedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, memo: Memo?) async throws -> PendingTransactionEntity { + func sendToAddress(spendingKey: UnifiedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, memo: Memo?) async throws -> ZcashTransaction.Overview { if let error = sendToAddressSpendingKeyZatoshiToAddressMemoThrowableError { throw error } @@ -213,10 +213,10 @@ class SynchronizerMock: Synchronizer { return shieldFundsSpendingKeyMemoShieldingThresholdCallsCount > 0 } var shieldFundsSpendingKeyMemoShieldingThresholdReceivedArguments: (spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi)? - var shieldFundsSpendingKeyMemoShieldingThresholdReturnValue: PendingTransactionEntity! - var shieldFundsSpendingKeyMemoShieldingThresholdClosure: ((UnifiedSpendingKey, Memo, Zatoshi) async throws -> PendingTransactionEntity)? + var shieldFundsSpendingKeyMemoShieldingThresholdReturnValue: ZcashTransaction.Overview! + var shieldFundsSpendingKeyMemoShieldingThresholdClosure: ((UnifiedSpendingKey, Memo, Zatoshi) async throws -> ZcashTransaction.Overview)? - func shieldFunds(spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi) async throws -> PendingTransactionEntity { + func shieldFunds(spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi) async throws -> ZcashTransaction.Overview { if let error = shieldFundsSpendingKeyMemoShieldingThresholdThrowableError { throw error } @@ -229,26 +229,6 @@ class SynchronizerMock: Synchronizer { } } - // MARK: - cancelSpend - - var cancelSpendTransactionCallsCount = 0 - var cancelSpendTransactionCalled: Bool { - return cancelSpendTransactionCallsCount > 0 - } - var cancelSpendTransactionReceivedTransaction: PendingTransactionEntity? - var cancelSpendTransactionReturnValue: Bool! - var cancelSpendTransactionClosure: ((PendingTransactionEntity) async -> Bool)? - - func cancelSpend(transaction: PendingTransactionEntity) async -> Bool { - cancelSpendTransactionCallsCount += 1 - cancelSpendTransactionReceivedTransaction = transaction - if let closure = cancelSpendTransactionClosure { - return await closure(transaction) - } else { - return cancelSpendTransactionReturnValue - } - } - // MARK: - paginatedTransactions var paginatedTransactionsOfCallsCount = 0 @@ -293,54 +273,6 @@ class SynchronizerMock: Synchronizer { } } - // MARK: - getMemos - - var getMemosForReceivedTransactionThrowableError: Error? - var getMemosForReceivedTransactionCallsCount = 0 - var getMemosForReceivedTransactionCalled: Bool { - return getMemosForReceivedTransactionCallsCount > 0 - } - var getMemosForReceivedTransactionReceivedReceivedTransaction: ZcashTransaction.Received? - var getMemosForReceivedTransactionReturnValue: [Memo]! - var getMemosForReceivedTransactionClosure: ((ZcashTransaction.Received) async throws -> [Memo])? - - func getMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo] { - if let error = getMemosForReceivedTransactionThrowableError { - throw error - } - getMemosForReceivedTransactionCallsCount += 1 - getMemosForReceivedTransactionReceivedReceivedTransaction = receivedTransaction - if let closure = getMemosForReceivedTransactionClosure { - return try await closure(receivedTransaction) - } else { - return getMemosForReceivedTransactionReturnValue - } - } - - // MARK: - getMemos - - var getMemosForSentTransactionThrowableError: Error? - var getMemosForSentTransactionCallsCount = 0 - var getMemosForSentTransactionCalled: Bool { - return getMemosForSentTransactionCallsCount > 0 - } - var getMemosForSentTransactionReceivedSentTransaction: ZcashTransaction.Sent? - var getMemosForSentTransactionReturnValue: [Memo]! - var getMemosForSentTransactionClosure: ((ZcashTransaction.Sent) async throws -> [Memo])? - - func getMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo] { - if let error = getMemosForSentTransactionThrowableError { - throw error - } - getMemosForSentTransactionCallsCount += 1 - getMemosForSentTransactionReceivedSentTransaction = sentTransaction - if let closure = getMemosForSentTransactionClosure { - return try await closure(sentTransaction) - } else { - return getMemosForSentTransactionReturnValue - } - } - // MARK: - getRecipients var getRecipientsForClearedTransactionCallsCount = 0 @@ -361,47 +293,69 @@ class SynchronizerMock: Synchronizer { } } - // MARK: - getRecipients + // MARK: - getTransactionOutputs - var getRecipientsForSentTransactionCallsCount = 0 - var getRecipientsForSentTransactionCalled: Bool { - return getRecipientsForSentTransactionCallsCount > 0 + var getTransactionOutputsForTransactionCallsCount = 0 + var getTransactionOutputsForTransactionCalled: Bool { + return getTransactionOutputsForTransactionCallsCount > 0 } - var getRecipientsForSentTransactionReceivedTransaction: ZcashTransaction.Sent? - var getRecipientsForSentTransactionReturnValue: [TransactionRecipient]! - var getRecipientsForSentTransactionClosure: ((ZcashTransaction.Sent) async -> [TransactionRecipient])? + var getTransactionOutputsForTransactionReceivedTransaction: ZcashTransaction.Overview? + var getTransactionOutputsForTransactionReturnValue: [ZcashTransaction.Output]! + var getTransactionOutputsForTransactionClosure: ((ZcashTransaction.Overview) async -> [ZcashTransaction.Output])? - func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient] { - getRecipientsForSentTransactionCallsCount += 1 - getRecipientsForSentTransactionReceivedTransaction = transaction - if let closure = getRecipientsForSentTransactionClosure { + func getTransactionOutputs(for transaction: ZcashTransaction.Overview) async -> [ZcashTransaction.Output] { + getTransactionOutputsForTransactionCallsCount += 1 + getTransactionOutputsForTransactionReceivedTransaction = transaction + if let closure = getTransactionOutputsForTransactionClosure { return await closure(transaction) } else { - return getRecipientsForSentTransactionReturnValue + return getTransactionOutputsForTransactionReturnValue } } - // MARK: - allConfirmedTransactions + // MARK: - allTransactions - var allConfirmedTransactionsFromLimitThrowableError: Error? - var allConfirmedTransactionsFromLimitCallsCount = 0 - var allConfirmedTransactionsFromLimitCalled: Bool { - return allConfirmedTransactionsFromLimitCallsCount > 0 + var allTransactionsFromLimitThrowableError: Error? + var allTransactionsFromLimitCallsCount = 0 + var allTransactionsFromLimitCalled: Bool { + return allTransactionsFromLimitCallsCount > 0 } - var allConfirmedTransactionsFromLimitReceivedArguments: (transaction: ZcashTransaction.Overview, limit: Int)? - var allConfirmedTransactionsFromLimitReturnValue: [ZcashTransaction.Overview]! - var allConfirmedTransactionsFromLimitClosure: ((ZcashTransaction.Overview, Int) async throws -> [ZcashTransaction.Overview])? + var allTransactionsFromLimitReceivedArguments: (transaction: ZcashTransaction.Overview, limit: Int)? + var allTransactionsFromLimitReturnValue: [ZcashTransaction.Overview]! + var allTransactionsFromLimitClosure: ((ZcashTransaction.Overview, Int) async throws -> [ZcashTransaction.Overview])? - func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] { - if let error = allConfirmedTransactionsFromLimitThrowableError { + func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] { + if let error = allTransactionsFromLimitThrowableError { throw error } - allConfirmedTransactionsFromLimitCallsCount += 1 - allConfirmedTransactionsFromLimitReceivedArguments = (transaction: transaction, limit: limit) - if let closure = allConfirmedTransactionsFromLimitClosure { + allTransactionsFromLimitCallsCount += 1 + allTransactionsFromLimitReceivedArguments = (transaction: transaction, limit: limit) + if let closure = allTransactionsFromLimitClosure { return try await closure(transaction, limit) } else { - return allConfirmedTransactionsFromLimitReturnValue + return allTransactionsFromLimitReturnValue + } + } + + // MARK: - allPendingTransactions + + var allPendingTransactionsThrowableError: Error? + var allPendingTransactionsCallsCount = 0 + var allPendingTransactionsCalled: Bool { + return allPendingTransactionsCallsCount > 0 + } + var allPendingTransactionsReturnValue: [ZcashTransaction.Overview]! + var allPendingTransactionsClosure: (() async throws -> [ZcashTransaction.Overview])? + + func allPendingTransactions() async throws -> [ZcashTransaction.Overview] { + if let error = allPendingTransactionsThrowableError { + throw error + } + allPendingTransactionsCallsCount += 1 + if let closure = allPendingTransactionsClosure { + return try await closure() + } else { + return allPendingTransactionsReturnValue } } diff --git a/Tests/TestUtils/TestCoordinator.swift b/Tests/TestUtils/TestCoordinator.swift index 640033ce..66fc21eb 100644 --- a/Tests/TestUtils/TestCoordinator.swift +++ b/Tests/TestUtils/TestCoordinator.swift @@ -59,25 +59,71 @@ class TestCoordinator { network: ZcashNetwork, callPrepareInConstructor: Bool = true, endpoint: LightWalletEndpoint = TestCoordinator.defaultEndpoint, - syncSessionIDGenerator: SyncSessionIDGenerator = UniqueSyncSessionIDGenerator() + syncSessionIDGenerator: SyncSessionIDGenerator = UniqueSyncSessionIDGenerator(), + dbTracingClosure: ((String) -> Void)? = nil ) async throws { await InternalSyncProgress(alias: alias, storage: UserDefaults.standard, logger: logger).rewind(to: 0) let databases = TemporaryDbBuilder.build() self.databases = databases - let initializer = Initializer( - cacheDbURL: nil, + let urls = Initializer.URLs( fsBlockDbRoot: databases.fsCacheDbRoot, dataDbURL: databases.dataDB, - pendingDbURL: databases.pendingDB, - endpoint: endpoint, - network: network, spendParamsURL: try __spendParamsURL(), - outputParamsURL: try __outputParamsURL(), + outputParamsURL: try __outputParamsURL() + ) + + let (updatedURLs, parsingError) = Initializer.tryToUpdateURLs(with: alias, urls: urls) + + let backend = ZcashRustBackend( + dbData: updatedURLs.dataDbURL, + fsBlockDbRoot: updatedURLs.fsBlockDbRoot, + spendParamsPath: updatedURLs.spendParamsURL, + outputParamsPath: updatedURLs.outputParamsURL, + networkType: network.networkType + ) + + let transactionRepository = TransactionSQLDAO( + dbProvider: SimpleConnectionProvider(path: updatedURLs.dataDbURL.absoluteString), + traceClosure: dbTracingClosure + ) + + let accountRepository = AccountRepositoryBuilder.build( + dataDbURL: updatedURLs.dataDbURL, + readOnly: true, + caching: true, + logger: logger + ) + + let fsBlockRepository = FSCompactBlockRepository( + fsBlockDbRoot: updatedURLs.fsBlockDbRoot, + metadataStore: .live( + fsBlockDbRoot: updatedURLs.fsBlockDbRoot, + rustBackend: backend, + logger: logger + ), + blockDescriptor: .live, + contentProvider: DirectoryListingProviders.defaultSorted, + logger: logger + ) + + let service = Initializer.makeLightWalletServiceFactory(endpoint: endpoint).make() + + let initializer = Initializer( + rustBackend: backend, + network: network, + cacheDbURL: nil, + urls: updatedURLs, + endpoint: endpoint, + service: service, + repository: transactionRepository, + accountRepository: accountRepository, + storage: fsBlockRepository, saplingParamsSourceURL: SaplingParamsSourceURL.tests, alias: alias, - loggingPolicy: .default(.debug) + urlsParsingError: parsingError, + logger: OSLogger(logLevel: .debug) ) let derivationTool = DerivationTool(networkType: network.networkType) @@ -243,7 +289,6 @@ extension TestCoordinator { struct TemporaryTestDatabases { var fsCacheDbRoot: URL var dataDB: URL - var pendingDB: URL } enum TemporaryDbBuilder { @@ -253,8 +298,7 @@ enum TemporaryDbBuilder { return TemporaryTestDatabases( fsCacheDbRoot: tempUrl.appendingPathComponent("fs_cache_\(timestamp)"), - dataDB: tempUrl.appendingPathComponent("data_db_\(timestamp).db"), - pendingDB: tempUrl.appendingPathComponent("pending_db_\(timestamp).db") + dataDB: tempUrl.appendingPathComponent("data_db_\(timestamp).db") ) } } diff --git a/Tests/TestUtils/TestDbBuilder.swift b/Tests/TestUtils/TestDbBuilder.swift index dc46339a..921ec74c 100644 --- a/Tests/TestUtils/TestDbBuilder.swift +++ b/Tests/TestUtils/TestDbBuilder.swift @@ -42,10 +42,6 @@ enum TestDbBuilder { case generalError } - static func pendingTransactionsDbURL() throws -> URL { - try __documentsDirectory().appendingPathComponent("pending.db") - } - static func prePopulatedDataDbURL() -> URL? { Bundle.module.url(forResource: "test_data", withExtension: "db") } @@ -68,7 +64,7 @@ enum TestDbBuilder { switch initResult { case .success: return provider case .seedRequired: - throw ZcashError.dbMigrationGenericFailure("Seed value required to initialize the wallet database") + throw ZcashError.compactBlockProcessorDataDbInitFailed("Seed value required to initialize the wallet database") } } @@ -83,16 +79,6 @@ enum TestDbBuilder { return TransactionSQLDAO(dbProvider: provider, traceClosure: closure) } - - static func sentNotesRepository(rustBackend: ZcashRustBackendWelding) async throws -> SentNotesRepository? { - guard let provider = try await prepopulatedDataDbProvider(rustBackend: rustBackend) else { return nil } - return SentNotesSQLDAO(dbProvider: provider) - } - - static func receivedNotesRepository(rustBackend: ZcashRustBackendWelding) async throws -> ReceivedNoteRepository? { - guard let provider = try await prepopulatedDataDbProvider(rustBackend: rustBackend) else { return nil } - return ReceivedNotesSQLDAO(dbProvider: provider) - } static func seed(db: CompactBlockRepository, with blockRange: CompactBlockRange) async throws { guard let blocks = StubBlockCreator.createBlockRange(blockRange) else { diff --git a/Tests/TestUtils/TestsData.swift b/Tests/TestUtils/TestsData.swift index 4fdd006c..885b02f3 100644 --- a/Tests/TestUtils/TestsData.swift +++ b/Tests/TestUtils/TestsData.swift @@ -16,7 +16,6 @@ class TestsData { cacheDbURL: nil, fsBlockDbRoot: URL(fileURLWithPath: "/"), dataDbURL: URL(fileURLWithPath: "/"), - pendingDbURL: URL(fileURLWithPath: "/"), endpoint: LightWalletEndpointBuilder.default, network: ZcashNetworkBuilder.network(for: networkType), spendParamsURL: URL(fileURLWithPath: "/"), @@ -34,7 +33,23 @@ class TestsData { ) let transparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz") lazy var pendingTransactionEntity = { - PendingTransaction(value: Zatoshi(10), recipient: .address(.transparent(transparentAddress)), memo: .empty(), account: 0) + ZcashTransaction.Overview( + accountId: 0, + blockTime: nil, + expiryHeight: nil, + fee: Zatoshi(1000), + id: 0, + index: nil, + hasChange: true, + memoCount: 0, + minedHeight: nil, + raw: nil, + rawID: Data(), + receivedNoteCount: 0, + sentNoteCount: 0, + value: Zatoshi(10), + isExpiredUmined: false + ) }() let clearedTransaction = { @@ -58,34 +73,42 @@ class TestsData { }() let sentTransaction = { - ZcashTransaction.Sent( + ZcashTransaction.Overview( + accountId: 0, blockTime: 1, expiryHeight: nil, - fromAccount: 0, + fee: Zatoshi(10000), id: 9, index: 0, + hasChange: true, memoCount: 0, minedHeight: 0, - noteCount: 0, raw: nil, - rawID: nil, - value: Zatoshi.zero + rawID: Data(), + receivedNoteCount: 0, + sentNoteCount: 2, + value: .zero, + isExpiredUmined: false ) }() let receivedTransaction = { - ZcashTransaction.Received( + ZcashTransaction.Overview( + accountId: 0, blockTime: 1, expiryHeight: nil, - fromAccount: 0, + fee: nil, id: 9, index: 0, + hasChange: true, memoCount: 0, minedHeight: 0, - noteCount: 0, raw: nil, - rawID: nil, - value: Zatoshi.zero + rawID: Data(), + receivedNoteCount: 0, + sentNoteCount: 2, + value: .zero, + isExpiredUmined: false ) }() diff --git a/docs/development_process.md b/docs/development_process.md index d9d49d08..f934c3d3 100644 --- a/docs/development_process.md +++ b/docs/development_process.md @@ -1,6 +1,6 @@ # Development Process -the `master` branch is considered a Development-Stable branch. This means that every PR merged to it compiles, tests pass and is considered to be _deployable_. However, SDK clients **MUST NOT** import the SDK as a dependency by pointing to `master` unless they are purposely doing so for development or feature previewing. +the `main` branch is considered a Development-Stable branch. This means that every PR merged to it compiles, tests pass and is considered to be _deployable_. However, SDK clients **MUST NOT** import the SDK as a dependency by pointing to `main` unless they are purposely doing so for development or feature previewing. Even though the SDK is currently in **beta** and developers should always use the dependencies released through published tags.