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 759b2cbf..00000000 Binary files a/Tests/TestUtils/Resources/darkside_pending.db and /dev/null differ diff --git a/Tests/TestUtils/SDKSynchronizer+Utils.swift b/Tests/TestUtils/SDKSynchronizer+Utils.swift index c5f90852..e4af245c 100644 --- a/Tests/TestUtils/SDKSynchronizer+Utils.swift +++ b/Tests/TestUtils/SDKSynchronizer+Utils.swift @@ -18,7 +18,7 @@ extension SDKSynchronizer { 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( diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index 7e48a5e5..a400794e 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -36,22 +36,22 @@ class SynchronizerMock: Synchronizer { get { return underlyingMetrics } } var underlyingMetrics: SDKMetrics! - var pendingTransactions: [PendingTransactionEntity] { + var pendingTransactions: [ZcashTransaction.Overview] { get async { return underlyingPendingTransactions } } - var underlyingPendingTransactions: [PendingTransactionEntity] = [] - var clearedTransactions: [ZcashTransaction.Overview] { - get async { return underlyingClearedTransactions } + var underlyingPendingTransactions: [ZcashTransaction.Overview] = [] + var transactions: [ZcashTransaction.Overview] { + get async { return underlyingTransactions } } - var underlyingClearedTransactions: [ZcashTransaction.Overview] = [] - var sentTransactions: [ZcashTransaction.Sent] { + var underlyingTransactions: [ZcashTransaction.Overview] = [] + var sentTransactions: [ZcashTransaction.Overview] { get async { return underlyingSentTransactions } } - var underlyingSentTransactions: [ZcashTransaction.Sent] = [] - var receivedTransactions: [ZcashTransaction.Received] { + var underlyingSentTransactions: [ZcashTransaction.Overview] = [] + var receivedTransactions: [ZcashTransaction.Overview] { get async { return underlyingReceivedTransactions } } - var underlyingReceivedTransactions: [ZcashTransaction.Received] = [] + var underlyingReceivedTransactions: [ZcashTransaction.Overview] = [] // MARK: - prepare @@ -189,10 +189,10 @@ class SynchronizerMock: Synchronizer { return sendToAddressSpendingKeyZatoshiToAddressMemoCallsCount > 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.