[#1001] Remove PendingDb in favor of `v_transactions` and `v_tx_output` Views (#1001)

Removes `PendingTransactionEntity` and all of its related components.
Pending items are still tracked and visualized by the existing APIs
but they are retrieved from the `TransactionRepository` instead by
returning `ZcashTransaction.Overview` instead.

`pendingDbURL` is removed from every place it was required. Its
deletion is responsibility of wallet developers.

`ClearedTransactions` are now just `transactions`.

`MigrationManager` is deleted. Now all migrations are in charge of
the rust welding layer.

`PendingTransactionDao.swift` is removed.

Implementation of `AccountEntity` called `Account` is now `DbAccount`

`ZcashTransaction.Overview` can be checked for "pending-ness" by calling
`.isPending(latestHeight:)` latest height must be provided so that minedHeight
can be compared with the lastest and the `defaultStaleTolerance` constant.

`TransactionRecipient` is now a public type.

protocol `PendingTransactionRepository` is removed.

`TransactionManagerError` and `PersistentTransactionManager` are deleted.

`OutboundTransactionManager` is deleted and replaced by `TransactionEncoder`
which now incorporates `submit(encoded:)` functionality

`WalletTransactionEncoder` now uses a `LightWalletService` to submit the
encoded transactions.

Add changelog changes

Delete references to PendingDb from tests and documentation.

Fixes some typos. Adds the ability to trace transaction repository
SQL queries from test

Fix rebase conflicts and generate code

[#837] Memo tests regarding transparent address

Closes #837

Add model for transaction output

Point to FFI branch

Fix issue where sync wouldn't resume after wipe. Becasue GRPC
channel would be closed

Fix Tests

Fix testPendingTransactionMinedHeightUpdated

Fix testLastStates

[#921] Fix  broken SynchronizerDarksideTests

Add ZcashTransaction.Output API to Synchronizer

Changelog + comment fix

Add Assertions for transaction outputs and recipients

Point to FFI 0.3.1

Fix Demo App Compiler errors

Fix Demo App Compiler errors

fix cacheDb warnings

Fix Tests and compiler errors of rebase

build demo app

Remove `ZcashTransaction.Sent` and `.Received`. Add `.State` and tests

Fix SPM warning

PR Suggestions

Removes errors that are not used anymore

fix warnings
This commit is contained in:
Francisco Gindre 2023-05-05 14:30:47 -03:00 committed by GitHub
parent 0324d9ace5
commit f5e7c027af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 1008 additions and 3128 deletions

View File

@ -1,5 +1,72 @@
# Unreleased # 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 ### [#1013] Enable more granular control over logging behavior
Now the SDK allows for more fine-tuning of its logging behavior. The `LoggingPolicy` enum Now the SDK allows for more fine-tuning of its logging behavior. The `LoggingPolicy` enum

View File

@ -47,7 +47,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: try! fsBlockDbRootURLHelper(), fsBlockDbRoot: try! fsBlockDbRootURLHelper(),
dataDbURL: try! dataDbURLHelper(), dataDbURL: try! dataDbURLHelper(),
pendingDbURL: try! pendingDbURLHelper(),
endpoint: DemoAppConfig.endpoint, endpoint: DemoAppConfig.endpoint,
network: kZcashNetwork, network: kZcashNetwork,
spendParamsURL: try! spendParamsURLHelper(), spendParamsURL: try! spendParamsURLHelper(),
@ -74,7 +73,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
.store(in: &cancellables) .store(in: &cancellables)
} }
func txMined(_ transaction: PendingTransactionEntity) { func txMined(_ transaction: ZcashTransaction.Overview) {
NotificationBubble.display( NotificationBubble.display(
in: window!.rootViewController!.view, in: window!.rootViewController!.view,
options: NotificationBubble.sucessOptions( 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 { func spendParamsURLHelper() throws -> URL {
try documentsDirectoryHelper().appendingPathComponent("sapling-spend.params") try documentsDirectoryHelper().appendingPathComponent("sapling-spend.params")
} }

View File

@ -39,14 +39,12 @@ class TransactionsDataSource: NSObject {
case .pending: case .pending:
let rawTransactions = await synchronizer.pendingTransactions let rawTransactions = await synchronizer.pendingTransactions
for pendingTransaction in rawTransactions { for pendingTransaction in rawTransactions {
let defaultFee: Zatoshi = kZcashNetwork.constants.defaultFee(for: pendingTransaction.minedHeight) let memos = try await synchronizer.getMemos(for: pendingTransaction)
let transaction = pendingTransaction.makeTransactionEntity(defaultFee: defaultFee)
let memos = try await synchronizer.getMemos(for: transaction)
transactions.append(TransactionDetailModel(pendingTransaction: pendingTransaction, memos: memos)) transactions.append(TransactionDetailModel(pendingTransaction: pendingTransaction, memos: memos))
} }
case .cleared: case .cleared:
let rawTransactions = await synchronizer.clearedTransactions let rawTransactions = await synchronizer.transactions
for transaction in rawTransactions { for transaction in rawTransactions {
let memos = try await synchronizer.getMemos(for: transaction) let memos = try await synchronizer.getMemos(for: transaction)
transactions.append(TransactionDetailModel(transaction: transaction, memos: memos)) transactions.append(TransactionDetailModel(transaction: transaction, memos: memos))
@ -66,13 +64,11 @@ class TransactionsDataSource: NSObject {
case .all: case .all:
let rawPendingTransactions = await synchronizer.pendingTransactions let rawPendingTransactions = await synchronizer.pendingTransactions
for pendingTransaction in rawPendingTransactions { for pendingTransaction in rawPendingTransactions {
let defaultFee: Zatoshi = kZcashNetwork.constants.defaultFee(for: pendingTransaction.minedHeight) let memos = try await synchronizer.getMemos(for: pendingTransaction)
let transaction = pendingTransaction.makeTransactionEntity(defaultFee: defaultFee)
let memos = try await synchronizer.getMemos(for: transaction)
transactions.append(TransactionDetailModel(pendingTransaction: pendingTransaction, memos: memos)) transactions.append(TransactionDetailModel(pendingTransaction: pendingTransaction, memos: memos))
} }
let rawClearedTransactions = await synchronizer.clearedTransactions let rawClearedTransactions = await synchronizer.transactions
for transaction in rawClearedTransactions { for transaction in rawClearedTransactions {
let memos = try await synchronizer.getMemos(for: transaction) let memos = try await synchronizer.getMemos(for: transaction)
transactions.append(TransactionDetailModel(transaction: transaction, memos: memos)) transactions.append(TransactionDetailModel(transaction: transaction, memos: memos))

View File

@ -105,7 +105,6 @@ class SyncBlocksListViewController: UIViewController {
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: try! fsBlockDbRootURLHelper(), fsBlockDbRoot: try! fsBlockDbRootURLHelper(),
dataDbURL: try! dataDbURLHelper(), dataDbURL: try! dataDbURLHelper(),
pendingDbURL: try! pendingDbURLHelper(),
endpoint: DemoAppConfig.endpoint, endpoint: DemoAppConfig.endpoint,
network: kZcashNetwork, network: kZcashNetwork,
spendParamsURL: try! spendParamsURLHelper(), spendParamsURL: try! spendParamsURLHelper(),

View File

@ -11,9 +11,9 @@ import ZcashLightClientKit
final class TransactionDetailModel { final class TransactionDetailModel {
enum Transaction { enum Transaction {
case sent(ZcashTransaction.Sent) case sent(ZcashTransaction.Overview)
case received(ZcashTransaction.Received) case received(ZcashTransaction.Overview)
case pending(PendingTransactionEntity) case pending(ZcashTransaction.Overview)
case cleared(ZcashTransaction.Overview) case cleared(ZcashTransaction.Overview)
} }
@ -25,7 +25,7 @@ final class TransactionDetailModel {
var zatoshi: Zatoshi var zatoshi: Zatoshi
var memo: Memo? var memo: Memo?
init(sendTransaction transaction: ZcashTransaction.Sent, memos: [Memo]) { init(sendTransaction transaction: ZcashTransaction.Overview, memos: [Memo]) {
self.transaction = .sent(transaction) self.transaction = .sent(transaction)
self.id = transaction.rawID self.id = transaction.rawID
self.minedHeight = transaction.minedHeight 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.transaction = .received(transaction)
self.id = transaction.rawID self.id = transaction.rawID
self.minedHeight = transaction.minedHeight self.minedHeight = transaction.minedHeight
self.expiryHeight = transaction.expiryHeight self.expiryHeight = transaction.expiryHeight
self.zatoshi = transaction.value self.zatoshi = transaction.value
self.memo = memos.first 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.transaction = .pending(transaction)
self.id = transaction.rawTransactionId self.id = transaction.rawID
self.minedHeight = transaction.minedHeight self.minedHeight = transaction.minedHeight
self.expiryHeight = transaction.expiryHeight self.expiryHeight = transaction.expiryHeight
self.created = Date(timeIntervalSince1970: transaction.createTime) self.created = Date(timeIntervalSince1970: transaction.blockTime ?? Date().timeIntervalSince1970)
self.zatoshi = transaction.value self.zatoshi = transaction.value
self.memo = memos.first self.memo = memos.first
} }

View File

@ -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 # Migrating from previous versions to 0.20.0-beta
The `SDKSynchronizer` no longer uses `NotificationCenter` to send notifications. The `SDKSynchronizer` no longer uses `NotificationCenter` to send notifications.
Notifications are replaced with `Combine` publishers. Notifications are replaced with `Combine` publishers.

View File

@ -104,8 +104,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : { "state" : {
"revision" : "bf5992c2e53749ad11c1e85ad5d9c63e39bdf3cc", "revision" : "75821e2b859600707318e4a788abbe27e6615833",
"version" : "0.3.0" "version" : "0.3.1"
} }
} }
], ],

View File

@ -16,7 +16,7 @@ let package = Package(
dependencies: [ dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.14.0"), .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/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: [ targets: [
.target( .target(
@ -42,14 +42,13 @@ let package = Package(
exclude: [ exclude: [
"proto/darkside.proto", "proto/darkside.proto",
"Sourcery/AutoMockable.stencil", "Sourcery/AutoMockable.stencil",
"Sourcery/generateMocks" "Sourcery/generateMocks.sh"
], ],
resources: [ resources: [
.copy("Resources/test_data.db"), .copy("Resources/test_data.db"),
.copy("Resources/cache.db"), .copy("Resources/cache.db"),
.copy("Resources/darkside_caches.db"), .copy("Resources/darkside_caches.db"),
.copy("Resources/darkside_data.db"), .copy("Resources/darkside_data.db"),
.copy("Resources/darkside_pending.db"),
.copy("Resources/sandblasted_mainnet_block.json"), .copy("Resources/sandblasted_mainnet_block.json"),
.copy("Resources/txBase64String.txt"), .copy("Resources/txBase64String.txt"),
.copy("Resources/txFromAndroidSDK.txt"), .copy("Resources/txFromAndroidSDK.txt"),

View File

@ -676,10 +676,6 @@ actor CompactBlockProcessor {
try fileManager.removeItem(at: config.dataDb) try fileManager.removeItem(at: config.dataDb)
} }
if fileManager.fileExists(atPath: context.pendingDbURL.path) {
try fileManager.removeItem(at: context.pendingDbURL)
}
await context.completion(nil) await context.completion(nil)
} catch { } catch {
await context.completion(error) await context.completion(error)

View File

@ -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..<Self.nextPendingDbMigration.rawValue) {
switch PendingDbMigration(rawValue: version) {
case .some(.none):
try migratePendingDbV1()
case .some(.v1):
try migratePendingDbV2()
case .some(.v2):
// we have no migrations to run after v2; this case should ordinarily be
// unreachable due to the bound on the loop.
break
case nil:
throw ZcashError.dbMigrationInvalidVersion
}
}
}
func migratePendingDbV1() throws {
let statement = PendingTransactionSQLDAO.table.create(ifNotExists: true) { createdTable in
createdTable.column(PendingTransactionSQLDAO.TableColumns.id, primaryKey: .autoincrement)
createdTable.column(PendingTransactionSQLDAO.TableColumns.toAddress)
createdTable.column(PendingTransactionSQLDAO.TableColumns.accountIndex)
createdTable.column(PendingTransactionSQLDAO.TableColumns.minedHeight)
createdTable.column(PendingTransactionSQLDAO.TableColumns.expiryHeight)
createdTable.column(PendingTransactionSQLDAO.TableColumns.cancelled)
createdTable.column(PendingTransactionSQLDAO.TableColumns.encodeAttempts, defaultValue: 0)
createdTable.column(PendingTransactionSQLDAO.TableColumns.errorMessage)
createdTable.column(PendingTransactionSQLDAO.TableColumns.errorCode)
createdTable.column(PendingTransactionSQLDAO.TableColumns.submitAttempts, defaultValue: 0)
createdTable.column(PendingTransactionSQLDAO.TableColumns.createTime)
createdTable.column(PendingTransactionSQLDAO.TableColumns.rawTransactionId)
createdTable.column(PendingTransactionSQLDAO.TableColumns.value)
createdTable.column(PendingTransactionSQLDAO.TableColumns.raw)
createdTable.column(PendingTransactionSQLDAO.TableColumns.memo)
createdTable.column(PendingTransactionSQLDAO.TableColumns.fee)
}
do {
try pendingDb.connection().transaction(.immediate) {
try pendingDb.connection().execute(statement)
try pendingDb.connection().setUserVersion(PendingDbMigration.v1.rawValue)
}
} catch {
throw ZcashError.dbMigrationV1(error)
}
}
func migratePendingDbV2() throws {
do {
try pendingDb.connection().transaction(.immediate) {
let statement =
"""
ALTER TABLE pending_transactions RENAME TO pending_transactions_old;
CREATE TABLE pending_transactions(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
to_address TEXT,
to_internal INTEGER,
account_index INTEGER NOT NULL,
mined_height INTEGER,
expiry_height INTEGER,
cancelled INTEGER,
encode_attempts INTEGER DEFAULT (0),
error_message TEXT,
error_code INTEGER,
submit_attempts INTEGER DEFAULT (0),
create_time REAL,
txid BLOB,
value INTEGER NOT NULL,
raw BLOB,
memo BLOB,
fee INTEGER
);
INSERT INTO pending_transactions
SELECT
id,
to_address,
NULL,
account_index,
mined_height,
expiry_height,
cancelled,
encode_attempts,
error_message,
error_code,
submit_attempts,
create_time,
txid,
value,
raw,
memo,
NULL
FROM pending_transactions_old;
DROP TABLE pending_transactions_old
"""
try pendingDb.connection().execute(statement)
try pendingDb.connection().setUserVersion(PendingDbMigration.v2.rawValue)
}
} catch {
throw ZcashError.dbMigrationV2(error)
}
}
}
private extension Connection {
func getUserVersion() throws -> 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)")
}
}

View File

@ -9,7 +9,6 @@ import Foundation
class AfterSyncHooksManager { class AfterSyncHooksManager {
struct WipeContext { struct WipeContext {
let pendingDbURL: URL
let prewipe: () -> Void let prewipe: () -> Void
let completion: (Error?) async -> Void let completion: (Error?) async -> Void
} }
@ -52,7 +51,6 @@ class AfterSyncHooksManager {
static var emptyWipe: Hook { static var emptyWipe: Hook {
return .wipe( return .wipe(
WipeContext( WipeContext(
pendingDbURL: URL(fileURLWithPath: "/"),
prewipe: { }, prewipe: { },
completion: { _ in } completion: { _ in }
) )

View File

@ -41,31 +41,23 @@ public protocol ClosureSynchronizer {
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo?, memo: Memo?,
completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void completion: @escaping (Result<ZcashTransaction.Overview, Error>) -> Void
) )
func shieldFunds( func shieldFunds(
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo,
shieldingThreshold: Zatoshi, shieldingThreshold: Zatoshi,
completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void completion: @escaping (Result<ZcashTransaction.Overview, Error>) -> Void
) )
func cancelSpend(transaction: PendingTransactionEntity, completion: @escaping (Bool) -> Void) func pendingTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func pendingTransactions(completion: @escaping ([PendingTransactionEntity]) -> Void)
func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func sentTranscations(completion: @escaping ([ZcashTransaction.Sent]) -> Void) func sentTranscations(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func receivedTransactions(completion: @escaping ([ZcashTransaction.Received]) -> Void) func receivedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
func getMemos(for transaction: ZcashTransaction.Overview, completion: @escaping (Result<[Memo], Error>) -> Void) 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.Overview, completion: @escaping ([TransactionRecipient]) -> Void)
func getRecipients(for transaction: ZcashTransaction.Sent, completion: @escaping ([TransactionRecipient]) -> Void)
func allConfirmedTransactions( func allConfirmedTransactions(
from transaction: ZcashTransaction.Overview, from transaction: ZcashTransaction.Overview,

View File

@ -40,31 +40,28 @@ public protocol CombineSynchronizer {
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo? memo: Memo?
) -> SinglePublisher<PendingTransactionEntity, Error> ) -> SinglePublisher<ZcashTransaction.Overview, Error>
func shieldFunds( func shieldFunds(
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo,
shieldingThreshold: Zatoshi shieldingThreshold: Zatoshi
) -> SinglePublisher<PendingTransactionEntity, Error> ) -> SinglePublisher<ZcashTransaction.Overview, Error>
func cancelSpend(transaction: PendingTransactionEntity) -> SinglePublisher<Bool, Never> var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var pendingTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var pendingTransactions: SinglePublisher<[PendingTransactionEntity], Never> { get } var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var clearedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var sentTransactions: SinglePublisher<[ZcashTransaction.Sent], Never> { get }
var receivedTransactions: SinglePublisher<[ZcashTransaction.Received], Never> { get }
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
func getMemos(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[Memo], Error> 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.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<BlockHeight, Error> func latestHeight() -> SinglePublisher<BlockHeight, Error>

View File

@ -128,9 +128,6 @@ public enum ZcashSDK {
/// Default Name for Compact Block caches db /// Default Name for Compact Block caches db
public static let defaultCacheDbName = "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. /// 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 /// 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. /// 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") @available(*, deprecated, message: "use this name to clean up the sqlite compact block database")
static var defaultCacheDbName: String { get } static var defaultCacheDbName: String { get }
/// Default name for pending transactions db
static var defaultPendingDbName: String { get }
/// Default prefix for db filenames /// Default prefix for db filenames
static var defaultDbNamePrefix: String { get } static var defaultDbNamePrefix: String { get }
@ -195,9 +189,6 @@ public enum ZcashSDKMainnetConstants: NetworkConstants {
/// Default Name for Compact Block caches db /// Default Name for Compact Block caches db
public static let defaultCacheDbName = "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_" public static let defaultDbNamePrefix = "ZcashSdk_mainnet_"
@ -216,9 +207,6 @@ public enum ZcashSDKTestnetConstants: NetworkConstants {
public static let defaultCacheDbName = "caches.db" public static let defaultCacheDbName = "caches.db"
public static let defaultFsBlockDbRootName = "fs_cache" public static let defaultFsBlockDbRootName = "fs_cache"
/// Default name for pending transactions db
public static let defaultPendingDbName = "pending.db"
public static let defaultDbNamePrefix = "ZcashSdk_testnet_" public static let defaultDbNamePrefix = "ZcashSdk_testnet_"

View File

@ -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<Int>("id_tx")
let transaction = Expression<Int>("tx")
let txid = Expression<Blob>("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<Int>("id_tx")
let transaction = Expression<Int>("tx")
let txid = Expression<Blob>("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<Int>("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))
}
}
}
}

View File

@ -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<T>(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<String?>("to_address")
static let toInternalAccount = Expression<Int?>("to_internal")
static let accountIndex = Expression<Int>("account_index")
static let minedHeight = Expression<Int?>("mined_height")
static let expiryHeight = Expression<Int?>("expiry_height")
static let cancelled = Expression<Int?>("cancelled")
static let encodeAttempts = Expression<Int?>("encode_attempts")
static let errorMessage = Expression<String?>("error_message")
static let submitAttempts = Expression<Int?>("submit_attempts")
static let errorCode = Expression<Int?>("error_code")
static let createTime = Expression<TimeInterval?>("create_time")
static let raw = Expression<Blob?>("raw")
static let id = Expression<Int>("id")
static let value = Expression<Zatoshi>("value")
static let memo = Expression<Blob?>("memo")
static let rawTransactionId = Expression<Blob?>("txid")
static let fee = Expression<Zatoshi?>("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)
}
}
}

View File

@ -15,19 +15,15 @@ class TransactionSQLDAO: TransactionRepository {
} }
let dbProvider: ConnectionProvider let dbProvider: ConnectionProvider
let transactions = Table("transactions")
private let blockDao: BlockSQLDAO private let blockDao: BlockSQLDAO
private let sentNotesRepository: SentNotesRepository
private let transactionsView = View("v_transactions") private let transactionsView = View("v_transactions")
private let receivedNotesTable = Table("received_notes") private let txOutputsView = View("v_tx_outputs")
private let sentNotesTable = Table("sent_notes")
private let traceClosure: ((String) -> Void)? private let traceClosure: ((String) -> Void)?
init(dbProvider: ConnectionProvider, traceClosure: ((String) -> Void)? = nil) { init(dbProvider: ConnectionProvider, traceClosure: ((String) -> Void)? = nil) {
self.dbProvider = dbProvider self.dbProvider = dbProvider
self.blockDao = BlockSQLDAO(dbProvider: dbProvider) self.blockDao = BlockSQLDAO(dbProvider: dbProvider)
self.sentNotesRepository = SentNotesSQLDAO(dbProvider: dbProvider)
self.traceClosure = traceClosure self.traceClosure = traceClosure
} }
@ -59,7 +55,7 @@ class TransactionSQLDAO: TransactionRepository {
func countAll() async throws -> Int { func countAll() async throws -> Int {
do { do {
return try connection().scalar(transactions.count) return try connection().scalar(transactionsView.count)
} catch { } catch {
throw ZcashError.transactionRepositoryCountAll(error) throw ZcashError.transactionRepositoryCountAll(error)
} }
@ -67,7 +63,7 @@ class TransactionSQLDAO: TransactionRepository {
func countUnmined() async throws -> Int { func countUnmined() async throws -> Int {
do { 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 { } catch {
throw ZcashError.transactionRepositoryCountUnmined(error) throw ZcashError.transactionRepositoryCountUnmined(error)
} }
@ -131,66 +127,53 @@ class TransactionSQLDAO: TransactionRepository {
return try execute(query) { try ZcashTransaction.Overview(row: $0) } 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 let query = transactionsView
.filterQueryFor(kind: .received) .filterQueryFor(kind: .received)
.order(ZcashTransaction.Overview.Column.id.desc, (ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc) .order(ZcashTransaction.Overview.Column.id.desc, (ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc)
.limit(limit, offset: offset) .limit(limit, offset: offset)
return try execute(query) { try ZcashTransaction.Overview(row: $0) } 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 let query = transactionsView
.filterQueryFor(kind: .sent) .filterQueryFor(kind: .sent)
.order((ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc, ZcashTransaction.Overview.Column.id.desc) .order((ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc, ZcashTransaction.Overview.Column.id.desc)
.limit(limit, offset: offset) .limit(limit, offset: offset)
return try execute(query) { try ZcashTransaction.Overview(row: $0) } 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] { 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 { do {
let memos = try connection().prepare(query).compactMap { row in return try await getTransactionOutputs(for: transaction.id)
do { .compactMap { $0.memo }
let rawMemo = try row.get(NotesTableStructure.memo)
return try Memo(bytes: rawMemo.bytes)
} catch {
return nil
}
}
return memos
} catch { } catch {
throw ZcashError.transactionRepositoryFindMemos(error) 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<Entity>(_ query: View, createEntity: (Row) throws -> Entity) throws -> Entity { private func execute<Entity>(_ query: View, createEntity: (Row) throws -> Entity) throws -> Entity {
let entities: [Entity] = try execute(query, createEntity: createEntity) let entities: [Entity] = try execute(query, createEntity: createEntity)
guard let entity = entities.first else { throw ZcashError.transactionRepositoryEntityNotFound } 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 { private extension View {
func filterQueryFor(kind: TransactionKind) -> View { func filterQueryFor(kind: TransactionKind) -> View {
switch kind { switch kind {

View File

@ -13,7 +13,7 @@ protocol AccountEntity {
var ufvk: String { get } var ufvk: String { get }
} }
struct Account: AccountEntity, Encodable, Decodable { struct DbAccount: AccountEntity, Encodable, Decodable {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case account case account
case ufvk case ufvk
@ -23,7 +23,7 @@ struct Account: AccountEntity, Encodable, Decodable {
let ufvk: String let ufvk: String
} }
extension Account: Hashable { extension DbAccount: Hashable {
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
hasher.combine(account) hasher.combine(account)
hasher.combine(ufvk) hasher.combine(ufvk)
@ -68,7 +68,7 @@ class AccountSQDAO: AccountRepository {
do { do {
return try dbProvider.connection() return try dbProvider.connection()
.prepare(table) .prepare(table)
.map { row -> Account in .map { row -> DbAccount in
do { do {
return try row.decode() return try row.decode()
} catch { } catch {
@ -94,7 +94,7 @@ class AccountSQDAO: AccountRepository {
.prepare(query) .prepare(query)
.map { .map {
do { do {
return try $0.decode() as Account return try $0.decode() as DbAccount
} catch { } catch {
throw ZcashError.accountDAOFindByCantDecode(error) throw ZcashError.accountDAOFindByCantDecode(error)
} }
@ -113,7 +113,7 @@ class AccountSQDAO: AccountRepository {
/// - `accountDAOUpdate` if sqlite query updating account failed. /// - `accountDAOUpdate` if sqlite query updating account failed.
/// - `accountDAOUpdatedZeroRows` if sqlite query updating account pass but it affects 0 rows. /// - `accountDAOUpdatedZeroRows` if sqlite query updating account pass but it affects 0 rows.
func update(_ account: AccountEntity) throws { func update(_ account: AccountEntity) throws {
guard let acc = account as? Account else { guard let acc = account as? DbAccount else {
throw ZcashError.accountDAOUpdateInvalidAccount throw ZcashError.accountDAOUpdateInvalidAccount
} }

View File

@ -9,7 +9,7 @@ import Foundation
struct EncodedTransaction { struct EncodedTransaction {
let transactionId: Data let transactionId: Data
let raw: Data? let raw: Data
} }
extension EncodedTransaction: Hashable { extension EncodedTransaction: Hashable {

View File

@ -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<T: RawIdentifiable> (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<T: RawIdentifiable>(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
)
}
}

View File

@ -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 }
}

View File

@ -10,6 +10,39 @@ import SQLite
public enum ZcashTransaction { public enum ZcashTransaction {
public struct Overview { 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 accountId: Int
public let blockTime: TimeInterval? public let blockTime: TimeInterval?
public let expiryHeight: BlockHeight? public let expiryHeight: BlockHeight?
@ -28,32 +61,31 @@ public enum ZcashTransaction {
public let isExpiredUmined: Bool public let isExpiredUmined: Bool
} }
public struct Received { public struct Output {
public let blockTime: TimeInterval public enum Pool {
public let expiryHeight: BlockHeight? case transaparent
public let fromAccount: Int case sapling
public let id: Int case other(Int)
public let index: Int init(rawValue: Int) {
public let memoCount: Int switch rawValue {
public let minedHeight: BlockHeight case 0:
public let noteCount: Int self = .transaparent
public let raw: Data? case 2:
public let rawID: Data? self = .sapling
public let value: Zatoshi default:
} self = .other(rawValue)
}
}
}
public struct Sent { public let idTx: Int
public let blockTime: TimeInterval? public let pool: Pool
public let expiryHeight: BlockHeight? public let index: Int
public let fromAccount: Int public let fromAccount: Int?
public let id: Int public let recipient: TransactionRecipient
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 let value: Zatoshi
public let isChange: Bool
public let memo: Memo?
} }
/// Used when fetching blocks from the lightwalletd /// Used when fetching blocks from the lightwalletd
@ -64,6 +96,50 @@ public enum ZcashTransaction {
} }
} }
extension ZcashTransaction.Output {
enum Column {
static let idTx = Expression<Int>("id_tx")
static let pool = Expression<Int>("output_pool")
static let index = Expression<Int>("output_index")
static let toAccount = Expression<Int?>("to_account")
static let fromAccount = Expression<Int?>("from_account")
static let toAddress = Expression<String?>("to_address")
static let value = Expression<Int64>("value")
static let isChange = Expression<Bool>("is_change")
static let memo = Expression<Blob?>("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 { extension ZcashTransaction.Overview {
enum Column { enum Column {
static let accountId = Expression<Int>("account_id") static let accountId = Expression<Int>("account_id")
@ -97,6 +173,7 @@ extension ZcashTransaction.Overview {
self.sentNoteCount = try row.get(Column.sentNoteCount) self.sentNoteCount = try row.get(Column.sentNoteCount)
self.value = Zatoshi(try row.get(Column.value)) self.value = Zatoshi(try row.get(Column.value))
self.isExpiredUmined = try row.get(Column.expiredUnmined) self.isExpiredUmined = try row.get(Column.expiredUnmined)
if let blockTime = try row.get(Column.blockTime) { if let blockTime = try row.get(Column.blockTime) {
self.blockTime = TimeInterval(blockTime) self.blockTime = TimeInterval(blockTime)
} else { } else {
@ -134,51 +211,21 @@ extension ZcashTransaction.Overview {
} }
} }
extension ZcashTransaction.Received { /// extension to handle pending states
/// Attempts to create a `ZcashTransaction.Received` from an `Overview` public extension ZcashTransaction.Overview {
/// given that the transaction might not be a "sent" transaction, so it won't have the necessary func getState(for currentHeight: BlockHeight) -> State {
/// data to actually create it as it is currently defined, this initializer is optional State(
/// - returns: Optional<Received>. `Some` if the values present suffice to create a received currentHeight: currentHeight,
/// transaction otherwise `.none` minedHeight: minedHeight,
init?(overview: ZcashTransaction.Overview) { expiredUnmined: self.isExpiredUmined
guard )
!overview.isSentTransaction, }
let txBlocktime = overview.blockTime,
let txIndex = overview.index,
let txMinedHeight = overview.minedHeight
else { return nil }
self.blockTime = txBlocktime func isPending(currentHeight: BlockHeight) -> Bool {
self.expiryHeight = overview.expiryHeight getState(for: currentHeight) == .pending
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
} }
} }
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 Capabilities of an entity that can be uniquely identified by a raw transaction id

View File

@ -44,18 +44,6 @@ public enum ZcashError: Equatable, Error {
/// LightWalletService.blockStream failed. /// LightWalletService.blockStream failed.
/// ZSRVC0000 /// ZSRVC0000
case serviceBlockStreamFailed(_ error: LightWalletServiceError) 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. /// SimpleConnectionProvider init of Connection failed.
/// ZSCPC0001 /// ZSCPC0001
case simpleConnectionProvider(_ error: Error) 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. /// - `destination` is filesystem URL pointing to location where downloaded file should be moved.
/// ZSAPP0004 /// ZSAPP0004
case saplingParamsCantMoveDownloadedFile(_ error: Error, _ downloadURL: URL, _ destination: URL) 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. /// SQLite query failed when fetching block information from database.
/// - `sqliteError` is error produced by SQLite library. /// - `sqliteError` is error produced by SQLite library.
/// ZBDAO0001 /// ZBDAO0001
@ -361,6 +325,12 @@ public enum ZcashError: Equatable, Error {
/// Initialization of `ZcashTransaction.Sent` failed. /// Initialization of `ZcashTransaction.Sent` failed.
/// ZTEZT0003 /// ZTEZT0003
case zcashTransactionSentInit(_ error: Error) 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. /// Entity not found in the database, result of `createEntity` execution.
/// ZTREE0001 /// ZTREE0001
case transactionRepositoryEntityNotFound case transactionRepositoryEntityNotFound
@ -403,55 +373,6 @@ public enum ZcashError: Equatable, Error {
/// Failed to decode `Checkpoint` object. /// Failed to decode `Checkpoint` object.
/// ZCHKP0002 /// ZCHKP0002
case checkpointDecode(_ error: Error) 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 /// Invalid account when trying to derive spending key
/// ZDRVT0001 /// ZDRVT0001
case derivationToolSpendingKeyInvalidAccount 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. /// WalletTransactionEncoder wants to shield funds but files with sapling parameters are not present on disk.
/// ZWLTE0002 /// ZWLTE0002
case walletTransEncoderShieldFundsMissingSaplingParams 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. /// Initiatilzation fo `Zatoshi` from a decoder failed.
/// ZTSHO0001 /// ZTSHO0001
case zatoshiDecode(_ error: Error) case zatoshiDecode(_ error: Error)
@ -622,21 +516,11 @@ public enum ZcashError: Equatable, Error {
case .serviceFetchTransactionFailed: return "LightWalletService.fetchTransaction failed." case .serviceFetchTransactionFailed: return "LightWalletService.fetchTransaction failed."
case .serviceFetchUTXOsFailed: return "LightWalletService.fetchUTXOs failed." case .serviceFetchUTXOsFailed: return "LightWalletService.fetchUTXOs failed."
case .serviceBlockStreamFailed: return "LightWalletService.blockStream 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 .simpleConnectionProvider: return "SimpleConnectionProvider init of Connection failed."
case .saplingParamsInvalidSpendParams: return "Downloaded file with sapling spending parameters isn't valid." 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 .saplingParamsInvalidOutputParams: return "Downloaded file with sapling output parameters isn't valid."
case .saplingParamsDownload: return "Failed to download sapling parameters file" case .saplingParamsDownload: return "Failed to download sapling parameters file"
case .saplingParamsCantMoveDownloadedFile: return "Failed to move sapling parameters file to final destination after download." 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 .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 .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." 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 .zcashTransactionOverviewInit: return "Initialization of `ZcashTransaction.Overview` failed."
case .zcashTransactionReceivedInit: return "Initialization of `ZcashTransaction.Received` failed." case .zcashTransactionReceivedInit: return "Initialization of `ZcashTransaction.Received` failed."
case .zcashTransactionSentInit: return "Initialization of `ZcashTransaction.Sent` 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 .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 .transactionRepositoryTransactionMissingRequiredFields: return "`Find` call is missing fields, required fields are transaction `index` and `blockTime`."
case .transactionRepositoryCountAll: return "Counting all transactions failed." 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 .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 .checkpointCantLoadFromDisk: return "Failed to load JSON with checkpoint from disk."
case .checkpointDecode: return "Failed to decode `Checkpoint` object." 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 .derivationToolSpendingKeyInvalidAccount: return "Invalid account when trying to derive spending key"
case .unspentTransactionOutputDAOCreateTable: return "Creation of the table for unspent transaction output failed." case .unspentTransactionOutputDAOCreateTable: return "Creation of the table for unspent transaction output failed."
case .unspentTransactionOutputDAOStore: return "SQLite query failed when storing unspent transaction output." 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 .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 .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 .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 .zatoshiDecode: return "Initiatilzation fo `Zatoshi` from a decoder failed."
case .zatoshiEncode: return "Encode of `Zatoshi` failed." case .zatoshiEncode: return "Encode of `Zatoshi` failed."
case .unspentTransactionFetcherStream: return "Awaiting transactions from the stream failed." case .unspentTransactionFetcherStream: return "Awaiting transactions from the stream failed."
@ -802,21 +666,11 @@ public enum ZcashError: Equatable, Error {
case .serviceFetchTransactionFailed: return .serviceFetchTransactionFailed case .serviceFetchTransactionFailed: return .serviceFetchTransactionFailed
case .serviceFetchUTXOsFailed: return .serviceFetchUTXOsFailed case .serviceFetchUTXOsFailed: return .serviceFetchUTXOsFailed
case .serviceBlockStreamFailed: return .serviceBlockStreamFailed 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 .simpleConnectionProvider: return .simpleConnectionProvider
case .saplingParamsInvalidSpendParams: return .saplingParamsInvalidSpendParams case .saplingParamsInvalidSpendParams: return .saplingParamsInvalidSpendParams
case .saplingParamsInvalidOutputParams: return .saplingParamsInvalidOutputParams case .saplingParamsInvalidOutputParams: return .saplingParamsInvalidOutputParams
case .saplingParamsDownload: return .saplingParamsDownload case .saplingParamsDownload: return .saplingParamsDownload
case .saplingParamsCantMoveDownloadedFile: return .saplingParamsCantMoveDownloadedFile 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 .blockDAOBlock: return .blockDAOBlock
case .blockDAOCantDecode: return .blockDAOCantDecode case .blockDAOCantDecode: return .blockDAOCantDecode
case .blockDAOLatestBlockHeight: return .blockDAOLatestBlockHeight case .blockDAOLatestBlockHeight: return .blockDAOLatestBlockHeight
@ -889,6 +743,8 @@ public enum ZcashError: Equatable, Error {
case .zcashTransactionOverviewInit: return .zcashTransactionOverviewInit case .zcashTransactionOverviewInit: return .zcashTransactionOverviewInit
case .zcashTransactionReceivedInit: return .zcashTransactionReceivedInit case .zcashTransactionReceivedInit: return .zcashTransactionReceivedInit
case .zcashTransactionSentInit: return .zcashTransactionSentInit case .zcashTransactionSentInit: return .zcashTransactionSentInit
case .zcashTransactionOutputInit: return .zcashTransactionOutputInit
case .zcashTransactionOutputInconsistentRecipient: return .zcashTransactionOutputInconsistentRecipient
case .transactionRepositoryEntityNotFound: return .transactionRepositoryEntityNotFound case .transactionRepositoryEntityNotFound: return .transactionRepositoryEntityNotFound
case .transactionRepositoryTransactionMissingRequiredFields: return .transactionRepositoryTransactionMissingRequiredFields case .transactionRepositoryTransactionMissingRequiredFields: return .transactionRepositoryTransactionMissingRequiredFields
case .transactionRepositoryCountAll: return .transactionRepositoryCountAll case .transactionRepositoryCountAll: return .transactionRepositoryCountAll
@ -903,19 +759,6 @@ public enum ZcashError: Equatable, Error {
case .memoBytesInvalidUTF8: return .memoBytesInvalidUTF8 case .memoBytesInvalidUTF8: return .memoBytesInvalidUTF8
case .checkpointCantLoadFromDisk: return .checkpointCantLoadFromDisk case .checkpointCantLoadFromDisk: return .checkpointCantLoadFromDisk
case .checkpointDecode: return .checkpointDecode 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 .derivationToolSpendingKeyInvalidAccount: return .derivationToolSpendingKeyInvalidAccount
case .unspentTransactionOutputDAOCreateTable: return .unspentTransactionOutputDAOCreateTable case .unspentTransactionOutputDAOCreateTable: return .unspentTransactionOutputDAOCreateTable
case .unspentTransactionOutputDAOStore: return .unspentTransactionOutputDAOStore case .unspentTransactionOutputDAOStore: return .unspentTransactionOutputDAOStore
@ -932,15 +775,6 @@ public enum ZcashError: Equatable, Error {
case .recipientInvalidInput: return .recipientInvalidInput case .recipientInvalidInput: return .recipientInvalidInput
case .walletTransEncoderCreateTransactionMissingSaplingParams: return .walletTransEncoderCreateTransactionMissingSaplingParams case .walletTransEncoderCreateTransactionMissingSaplingParams: return .walletTransEncoderCreateTransactionMissingSaplingParams
case .walletTransEncoderShieldFundsMissingSaplingParams: return .walletTransEncoderShieldFundsMissingSaplingParams 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 .zatoshiDecode: return .zatoshiDecode
case .zatoshiEncode: return .zatoshiEncode case .zatoshiEncode: return .zatoshiEncode
case .unspentTransactionFetcherStream: return .unspentTransactionFetcherStream case .unspentTransactionFetcherStream: return .unspentTransactionFetcherStream

View File

@ -31,14 +31,6 @@ public enum ZcashErrorCode: String {
case serviceFetchUTXOsFailed = "ZSRVC0008" case serviceFetchUTXOsFailed = "ZSRVC0008"
/// LightWalletService.blockStream failed. /// LightWalletService.blockStream failed.
case serviceBlockStreamFailed = "ZSRVC0000" 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. /// SimpleConnectionProvider init of Connection failed.
case simpleConnectionProvider = "ZSCPC0001" case simpleConnectionProvider = "ZSCPC0001"
/// Downloaded file with sapling spending parameters isn't valid. /// Downloaded file with sapling spending parameters isn't valid.
@ -49,18 +41,6 @@ public enum ZcashErrorCode: String {
case saplingParamsDownload = "ZSAPP0003" case saplingParamsDownload = "ZSAPP0003"
/// Failed to move sapling parameters file to final destination after download. /// Failed to move sapling parameters file to final destination after download.
case saplingParamsCantMoveDownloadedFile = "ZSAPP0004" 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. /// SQLite query failed when fetching block information from database.
case blockDAOBlock = "ZBDAO0001" case blockDAOBlock = "ZBDAO0001"
/// Fetched block information from DB but can't decode them. /// Fetched block information from DB but can't decode them.
@ -205,6 +185,10 @@ public enum ZcashErrorCode: String {
case zcashTransactionReceivedInit = "ZTEZT0002" case zcashTransactionReceivedInit = "ZTEZT0002"
/// Initialization of `ZcashTransaction.Sent` failed. /// Initialization of `ZcashTransaction.Sent` failed.
case zcashTransactionSentInit = "ZTEZT0003" 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. /// Entity not found in the database, result of `createEntity` execution.
case transactionRepositoryEntityNotFound = "ZTREE0001" case transactionRepositoryEntityNotFound = "ZTREE0001"
/// `Find` call is missing fields, required fields are transaction `index` and `blockTime`. /// `Find` call is missing fields, required fields are transaction `index` and `blockTime`.
@ -233,32 +217,6 @@ public enum ZcashErrorCode: String {
case checkpointCantLoadFromDisk = "ZCHKP0001" case checkpointCantLoadFromDisk = "ZCHKP0001"
/// Failed to decode `Checkpoint` object. /// Failed to decode `Checkpoint` object.
case checkpointDecode = "ZCHKP0002" 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 /// Invalid account when trying to derive spending key
case derivationToolSpendingKeyInvalidAccount = "ZDRVT0001" case derivationToolSpendingKeyInvalidAccount = "ZDRVT0001"
/// Creation of the table for unspent transaction output failed. /// Creation of the table for unspent transaction output failed.
@ -291,24 +249,6 @@ public enum ZcashErrorCode: String {
case walletTransEncoderCreateTransactionMissingSaplingParams = "ZWLTE0001" case walletTransEncoderCreateTransactionMissingSaplingParams = "ZWLTE0001"
/// WalletTransactionEncoder wants to shield funds but files with sapling parameters are not present on disk. /// WalletTransactionEncoder wants to shield funds but files with sapling parameters are not present on disk.
case walletTransEncoderShieldFundsMissingSaplingParams = "ZWLTE0002" 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. /// Initiatilzation fo `Zatoshi` from a decoder failed.
case zatoshiDecode = "ZTSHO0001" case zatoshiDecode = "ZTSHO0001"
/// Encode of `Zatoshi` failed. /// Encode of `Zatoshi` failed.

View File

@ -63,21 +63,6 @@ enum ZcashErrorDefinition {
// sourcery: code="ZSRVC0000" // sourcery: code="ZSRVC0000"
case serviceBlockStreamFailed(_ error: LightWalletServiceError) 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 // MARK: SQLite connection
/// SimpleConnectionProvider init of Connection failed. /// SimpleConnectionProvider init of Connection failed.
@ -104,33 +89,6 @@ enum ZcashErrorDefinition {
// sourcery: code="ZSAPP0004" // sourcery: code="ZSAPP0004"
case saplingParamsCantMoveDownloadedFile(_ error: Error, _ downloadURL: URL, _ destination: URL) 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 // MARK: - BlockDAO
/// SQLite query failed when fetching block information from database. /// SQLite query failed when fetching block information from database.
@ -412,6 +370,13 @@ enum ZcashErrorDefinition {
/// Initialization of `ZcashTransaction.Sent` failed. /// Initialization of `ZcashTransaction.Sent` failed.
// sourcery: code="ZTEZT0003" // sourcery: code="ZTEZT0003"
case zcashTransactionSentInit(_ error: Error) 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 // MARK: - Transaction Repository
@ -467,58 +432,6 @@ enum ZcashErrorDefinition {
// sourcery: code="ZCHKP0002" // sourcery: code="ZCHKP0002"
case checkpointDecode(_ error: Error) 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 // MARK: - DerivationTool
/// Invalid account when trying to derive spending key /// 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. /// WalletTransactionEncoder wants to shield funds but files with sapling parameters are not present on disk.
// sourcery: code="ZWLTE0002" // sourcery: code="ZWLTE0002"
case walletTransEncoderShieldFundsMissingSaplingParams 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 // MARK: - Zatoshi

View File

@ -89,7 +89,6 @@ public class Initializer {
struct URLs { struct URLs {
let fsBlockDbRoot: URL let fsBlockDbRoot: URL
let dataDbURL: URL let dataDbURL: URL
let pendingDbURL: URL
let spendParamsURL: URL let spendParamsURL: URL
let outputParamsURL: URL let outputParamsURL: URL
} }
@ -112,7 +111,6 @@ public class Initializer {
let endpoint: LightWalletEndpoint let endpoint: LightWalletEndpoint
let fsBlockDbRoot: URL let fsBlockDbRoot: URL
let dataDbURL: URL let dataDbURL: URL
let pendingDbURL: URL
let spendParamsURL: URL let spendParamsURL: URL
let outputParamsURL: URL let outputParamsURL: URL
let saplingParamsSourceURL: SaplingParamsSourceURL let saplingParamsSourceURL: SaplingParamsSourceURL
@ -143,7 +141,6 @@ public class Initializer {
/// just pass `nil` here. /// just pass `nil` here.
/// - fsBlockDbRoot: location of the compact blocks cache /// - fsBlockDbRoot: location of the compact blocks cache
/// - dataDbURL: Location of the data db /// - 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 /// - endpoint: the endpoint representing the lightwalletd instance you want to point to
/// - spendParamsURL: location of the spend parameters /// - spendParamsURL: location of the spend parameters
/// - outputParamsURL: location of the output parameters /// - outputParamsURL: location of the output parameters
@ -151,7 +148,6 @@ public class Initializer {
cacheDbURL: URL?, cacheDbURL: URL?,
fsBlockDbRoot: URL, fsBlockDbRoot: URL,
dataDbURL: URL, dataDbURL: URL,
pendingDbURL: URL,
endpoint: LightWalletEndpoint, endpoint: LightWalletEndpoint,
network: ZcashNetwork, network: ZcashNetwork,
spendParamsURL: URL, spendParamsURL: URL,
@ -163,7 +159,6 @@ public class Initializer {
let urls = URLs( let urls = URLs(
fsBlockDbRoot: fsBlockDbRoot, fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL, dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
spendParamsURL: spendParamsURL, spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL outputParamsURL: outputParamsURL
) )
@ -244,7 +239,6 @@ public class Initializer {
self.rustBackend = rustBackend self.rustBackend = rustBackend
self.fsBlockDbRoot = urls.fsBlockDbRoot self.fsBlockDbRoot = urls.fsBlockDbRoot
self.dataDbURL = urls.dataDbURL self.dataDbURL = urls.dataDbURL
self.pendingDbURL = urls.pendingDbURL
self.endpoint = endpoint self.endpoint = endpoint
self.spendParamsURL = urls.spendParamsURL self.spendParamsURL = urls.spendParamsURL
self.outputParamsURL = urls.outputParamsURL self.outputParamsURL = urls.outputParamsURL
@ -261,7 +255,7 @@ public class Initializer {
self.logger = logger self.logger = logger
} }
private static func makeLightWalletServiceFactory(endpoint: LightWalletEndpoint) -> LightWalletServiceFactory { static func makeLightWalletServiceFactory(endpoint: LightWalletEndpoint) -> LightWalletServiceFactory {
return LightWalletServiceFactory(endpoint: endpoint) return LightWalletServiceFactory(endpoint: endpoint)
} }
@ -273,7 +267,7 @@ public class Initializer {
/// - /some/path/to/directory -> /some/path/to/c_anotherInstance_directory /// - /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. /// 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, with alias: ZcashSynchronizerAlias,
urls: URLs urls: URLs
) -> (URLs, ZcashError?) { ) -> (URLs, ZcashError?) {
@ -307,10 +301,6 @@ public class Initializer {
return .failure(.initializerCantUpdateURLWithAlias(urls.dataDbURL)) 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 { guard let updatedSpendParamsURL = urls.spendParamsURL.updateLastPathComponent(with: alias) else {
return .failure(.initializerCantUpdateURLWithAlias(urls.spendParamsURL)) return .failure(.initializerCantUpdateURLWithAlias(urls.spendParamsURL))
} }
@ -323,7 +313,6 @@ public class Initializer {
URLs( URLs(
fsBlockDbRoot: updatedFsBlockDbRoot, fsBlockDbRoot: updatedFsBlockDbRoot,
dataDbURL: updatedDataDbURL, dataDbURL: updatedDataDbURL,
pendingDbURL: updatedPendingDbURL,
spendParamsURL: updatedSpendParamsURL, spendParamsURL: updatedSpendParamsURL,
outputParamsURL: updateOutputParamsURL outputParamsURL: updateOutputParamsURL
) )
@ -374,14 +363,6 @@ public class Initializer {
throw error throw error
} }
let migrationManager = MigrationManager(
pendingDbConnection: SimpleConnectionProvider(path: pendingDbURL.path),
networkType: self.network.networkType,
logger: logger
)
try migrationManager.performMigration()
return .success return .success
} }

View File

@ -165,7 +165,8 @@ public extension MemoBytes {
func intoMemo() throws -> Memo { func intoMemo() throws -> Memo {
switch self.bytes[0] { switch self.bytes[0] {
case 0x00 ... 0xF4: 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 throw ZcashError.memoBytesInvalidUTF8
} }

View File

@ -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 /// Represents a valid recipient of Zcash
public enum Recipient: Equatable, StringEncoded { public enum Recipient: Equatable, StringEncoded {
case transparent(TransparentAddress) case transparent(TransparentAddress)

View File

@ -14,9 +14,6 @@ public enum ResourceProviderError: Error {
public protocol ResourceProvider { public protocol ResourceProvider {
var dataDbURL: URL { get } var dataDbURL: URL { get }
var fsCacheURL: 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. Convenience provider for a data db and cache db resources.
@ -43,16 +40,6 @@ public struct DefaultResourceProvider: ResourceProvider {
return URL(fileURLWithPath: "file://\(constants.defaultFsBlockDbRootName)") 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 { public var spendParamsURL: URL {
do { do {

View File

@ -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
}

View File

@ -20,10 +20,10 @@ protocol TransactionRepository {
func find(offset: Int, limit: Int, kind: TransactionKind) async throws -> [ZcashTransaction.Overview] 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(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 find(from: ZcashTransaction.Overview, limit: Int, kind: TransactionKind) async throws -> [ZcashTransaction.Overview]
func findReceived(offset: Int, limit: Int) async throws -> [ZcashTransaction.Received] func findPendingTransactions(latestHeight: BlockHeight, offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview]
func findSent(offset: Int, limit: Int) async throws -> [ZcashTransaction.Sent] 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 transaction: ZcashTransaction.Overview) async throws -> [Memo]
func findMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo] func getRecipients(for id: Int) async throws -> [TransactionRecipient]
func findMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo] func getTransactionOutputs(for id: Int) async throws -> [ZcashTransaction.Output]
func getRecipients(for id: Int) async -> [TransactionRecipient]
} }

View File

@ -64,7 +64,7 @@ public struct SynchronizerState: Equatable {
public enum SynchronizerEvent { public enum SynchronizerEvent {
// Sent when the synchronizer finds a pendingTransaction that hast been newly mined. // 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 // Sent when the synchronizer finds a mined transaction
case foundTransactions(_ transactions: [ZcashTransaction.Overview], _ inRange: CompactBlockRange) case foundTransactions(_ transactions: [ZcashTransaction.Overview], _ inRange: CompactBlockRange)
// Sent when the synchronizer fetched utxos from lightwalletd attempted to store them. // Sent when the synchronizer fetched utxos from lightwalletd attempted to store them.
@ -163,7 +163,7 @@ public protocol Synchronizer: AnyObject {
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo? 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`. /// 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 /// - Parameter spendingKey: the `UnifiedSpendingKey` that allows to spend transparent funds
@ -175,25 +175,19 @@ public protocol Synchronizer: AnyObject {
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo,
shieldingThreshold: Zatoshi shieldingThreshold: Zatoshi
) async throws -> PendingTransactionEntity ) async throws -> ZcashTransaction.Overview
/// 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
/// all outbound pending transactions that have been sent but are awaiting confirmations /// 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 /// 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 /// 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 /// 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 /// A repository serving transactions in a paginated manner
/// - Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository /// - Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository
@ -204,16 +198,6 @@ public protocol Synchronizer: AnyObject {
// sourcery: mockedName="getMemosForClearedTransaction" // sourcery: mockedName="getMemosForClearedTransaction"
func getMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo] 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. /// Attempt to get recipients from a Transaction Overview.
/// - parameter transaction: 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 /// - 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" // sourcery: mockedName="getRecipientsForClearedTransaction"
func getRecipients(for transaction: ZcashTransaction.Overview) async -> [TransactionRecipient] 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 /// - 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 /// - returns the array of outputs involved in this transaction. Transparent outputs might not be tracked
/// transaction
/// ///
// sourcery: mockedName="getRecipientsForSentTransaction" // sourcery: mockedName="getTransactionOutputsForTransaction"
func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient] func getTransactionOutputs(for transaction: ZcashTransaction.Overview) async -> [ZcashTransaction.Output]
/// Returns a list of confirmed transactions that preceed the given transaction with a limit count. /// Returns a list of confirmed transactions that preceed the given transaction with a limit count.
/// - Parameters: /// - Parameters:
/// - from: the confirmed transaction from which the query should start from or nil to retrieve from the most recent transaction /// - 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 /// - limit: the maximum amount of items this should return if available
/// - Returns: an array with the given Transactions or nil /// - Returns: an array with the given Transactions or an empty array
func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] 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 /// Returns the latest block height from the provided Lightwallet endpoint
func latestHeight() async throws -> BlockHeight func latestHeight() async throws -> BlockHeight

View File

@ -75,7 +75,7 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo?, memo: Memo?,
completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void completion: @escaping (Result<ZcashTransaction.Overview, Error>) -> Void
) { ) {
AsyncToClosureGateway.executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo) try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo)
@ -86,20 +86,14 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo,
shieldingThreshold: Zatoshi, shieldingThreshold: Zatoshi,
completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void completion: @escaping (Result<ZcashTransaction.Overview, Error>) -> Void
) { ) {
AsyncToClosureGateway.executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold)
} }
} }
public func cancelSpend(transaction: PendingTransactionEntity, completion: @escaping (Bool) -> Void) { public func pendingTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.cancelSpend(transaction: transaction)
}
}
public func pendingTransactions(completion: @escaping ([PendingTransactionEntity]) -> Void) {
AsyncToClosureGateway.executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.pendingTransactions await self.synchronizer.pendingTransactions
} }
@ -107,17 +101,17 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
public func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) { public func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) {
AsyncToClosureGateway.executeAction(completion) { 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) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.sentTransactions await self.synchronizer.sentTransactions
} }
} }
public func receivedTransactions(completion: @escaping ([ZcashTransaction.Received]) -> Void) { public func receivedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) {
AsyncToClosureGateway.executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.receivedTransactions 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) { public func getRecipients(for transaction: ZcashTransaction.Overview, completion: @escaping ([TransactionRecipient]) -> Void) {
AsyncToClosureGateway.executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.getRecipients(for: transaction) 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( public func allConfirmedTransactions(
from transaction: ZcashTransaction.Overview, from transaction: ZcashTransaction.Overview,
limit: Int, limit: Int,
completion: @escaping (Result<[ZcashTransaction.Overview], Error>) -> Void completion: @escaping (Result<[ZcashTransaction.Overview], Error>) -> Void
) { ) {
AsyncToClosureGateway.executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit) try await self.synchronizer.allTransactions(from: transaction, limit: limit)
} }
} }

View File

@ -74,7 +74,7 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo? memo: Memo?
) -> SinglePublisher<PendingTransactionEntity, Error> { ) -> SinglePublisher<ZcashTransaction.Overview, Error> {
AsyncToCombineGateway.executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo) try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo)
} }
@ -84,37 +84,31 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo,
shieldingThreshold: Zatoshi shieldingThreshold: Zatoshi
) -> SinglePublisher<PendingTransactionEntity, Error> { ) -> SinglePublisher<ZcashTransaction.Overview, Error> {
AsyncToCombineGateway.executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold)
} }
} }
public func cancelSpend(transaction: PendingTransactionEntity) -> SinglePublisher<Bool, Never> { public var pendingTransactions: AnyPublisher<[ZcashTransaction.Overview], Never> {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.cancelSpend(transaction: transaction)
}
}
public var pendingTransactions: SinglePublisher<[PendingTransactionEntity], Never> {
AsyncToCombineGateway.executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.pendingTransactions await self.synchronizer.pendingTransactions
} }
} }
public var clearedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { public var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> {
AsyncToCombineGateway.executeAction() { 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() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.sentTransactions await self.synchronizer.sentTransactions
} }
} }
public var receivedTransactions: SinglePublisher<[ZcashTransaction.Received], Never> { public var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> {
AsyncToCombineGateway.executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.receivedTransactions 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> { public func getRecipients(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[TransactionRecipient], Never> {
AsyncToCombineGateway.executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.getRecipients(for: transaction) await self.synchronizer.getRecipients(for: transaction)
} }
} }
public func getRecipients(for transaction: ZcashTransaction.Sent) -> SinglePublisher<[TransactionRecipient], Never> { public func allPendingTransactions() -> AnyPublisher<[ZcashTransaction.Overview], Error> {
AsyncToCombineGateway.executeAction() { AsyncToCombineGateway.executeThrowingAction() {
await self.synchronizer.getRecipients(for: transaction) 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() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit) try await self.synchronizer.allTransactions(from: transaction, limit: limit)
} }
} }

View File

@ -37,7 +37,7 @@ public class SDKSynchronizer: Synchronizer {
public let initializer: Initializer public let initializer: Initializer
public var connectionState: ConnectionState public var connectionState: ConnectionState
public let network: ZcashNetwork public let network: ZcashNetwork
private let transactionManager: OutboundTransactionManager private let transactionEncoder: TransactionEncoder
private let transactionRepository: TransactionRepository private let transactionRepository: TransactionRepository
private let utxoRepository: UnspentTransactionOutputRepository private let utxoRepository: UnspentTransactionOutputRepository
@ -58,7 +58,7 @@ public class SDKSynchronizer: Synchronizer {
self.init( self.init(
status: .unprepared, status: .unprepared,
initializer: initializer, initializer: initializer,
transactionManager: OutboundTransactionManagerBuilder.build(initializer: initializer), transactionEncoder: WalletTransactionEncoder(initializer: initializer),
transactionRepository: initializer.transactionRepository, transactionRepository: initializer.transactionRepository,
utxoRepository: UTXORepositoryBuilder.build(initializer: initializer), utxoRepository: UTXORepositoryBuilder.build(initializer: initializer),
blockProcessor: CompactBlockProcessor( blockProcessor: CompactBlockProcessor(
@ -78,7 +78,7 @@ public class SDKSynchronizer: Synchronizer {
init( init(
status: SyncStatus, status: SyncStatus,
initializer: Initializer, initializer: Initializer,
transactionManager: OutboundTransactionManager, transactionEncoder: TransactionEncoder,
transactionRepository: TransactionRepository, transactionRepository: TransactionRepository,
utxoRepository: UnspentTransactionOutputRepository, utxoRepository: UnspentTransactionOutputRepository,
blockProcessor: CompactBlockProcessor, blockProcessor: CompactBlockProcessor,
@ -90,7 +90,7 @@ public class SDKSynchronizer: Synchronizer {
self.connectionState = .idle self.connectionState = .idle
self.underlyingStatus = GenericActor(status) self.underlyingStatus = GenericActor(status)
self.initializer = initializer self.initializer = initializer
self.transactionManager = transactionManager self.transactionEncoder = transactionEncoder
self.transactionRepository = transactionRepository self.transactionRepository = transactionRepository
self.utxoRepository = utxoRepository self.utxoRepository = utxoRepository
self.blockProcessor = blockProcessor self.blockProcessor = blockProcessor
@ -224,7 +224,8 @@ public class SDKSynchronizer: Synchronizer {
self?.foundTransactions(transactions: transactions, in: range) self?.foundTransactions(transactions: transactions, in: range)
case let .handledReorg(reorgHeight, rewindHeight): 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): case let .progressUpdated(progress):
await self?.progressUpdated(progress: progress) await self?.progressUpdated(progress: progress)
@ -255,8 +256,7 @@ public class SDKSynchronizer: Synchronizer {
private func finished(lastScannedHeight: BlockHeight, foundBlocks: Bool) async { private func finished(lastScannedHeight: BlockHeight, foundBlocks: Bool) async {
await latestBlocksDataProvider.updateScannedData() 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) await updateStatus(.synced)
if let syncStartDate { 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 { private func progressUpdated(progress: CompactBlockProgress) async {
let newStatus = SyncStatus(progress) let newStatus = SyncStatus(progress)
await updateStatus(newStatus) await updateStatus(newStatus)
@ -301,9 +291,13 @@ public class SDKSynchronizer: Synchronizer {
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo? memo: Memo?
) async throws -> PendingTransactionEntity { ) async throws -> ZcashTransaction.Overview {
try throwIfUnprepared() try throwIfUnprepared()
if case Recipient.transparent = toAddress, memo != nil {
throw ZcashError.synchronizerSendMemoToTransparentAddress
}
try await SaplingParameterDownloader.downloadParamsIfnotPresent( try await SaplingParameterDownloader.downloadParamsIfnotPresent(
spendURL: initializer.spendParamsURL, spendURL: initializer.spendParamsURL,
spendSourceURL: initializer.saplingParamsSourceURL.spendParamFileURL, spendSourceURL: initializer.saplingParamsSourceURL.spendParamFileURL,
@ -312,10 +306,6 @@ public class SDKSynchronizer: Synchronizer {
logger: logger logger: logger
) )
if case Recipient.transparent = toAddress, memo != nil {
throw ZcashError.synchronizerSendMemoToTransparentAddress
}
return try await createToAddress( return try await createToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: zatoshi, zatoshi: zatoshi,
@ -328,33 +318,30 @@ public class SDKSynchronizer: Synchronizer {
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo,
shieldingThreshold: Zatoshi shieldingThreshold: Zatoshi
) async throws -> PendingTransactionEntity { ) async throws -> ZcashTransaction.Overview {
try throwIfUnprepared() try throwIfUnprepared()
// let's see if there are funds to shield // let's see if there are funds to shield
let accountIndex = Int(spendingKey.account) let accountIndex = Int(spendingKey.account)
let tBalance = try await self.getTransparentBalance(accountIndex: accountIndex) 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 { guard tBalance.verified >= self.network.constants.defaultFee(for: await self.latestBlocksDataProvider.latestScannedHeight) else {
throw ZcashError.synchronizerShieldFundsInsuficientTransparentFunds throw ZcashError.synchronizerShieldFundsInsuficientTransparentFunds
} }
let shieldingSpend = try await transactionManager.initSpend( let transaction = try await transactionEncoder.createShieldingTransaction(
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(
spendingKey: spendingKey, spendingKey: spendingKey,
shieldingThreshold: shieldingThreshold, 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( func createToAddress(
@ -362,43 +349,51 @@ public class SDKSynchronizer: Synchronizer {
zatoshi: Zatoshi, zatoshi: Zatoshi,
recipient: Recipient, recipient: Recipient,
memo: Memo? memo: Memo?
) async throws -> PendingTransactionEntity { ) async throws -> ZcashTransaction.Overview {
let spend = try await transactionManager.initSpend( do {
zatoshi: zatoshi, if
recipient: .address(recipient), case .transparent = recipient,
memo: memo?.asMemoBytes(), memo != nil {
from: Int(spendingKey.account) throw ZcashError.synchronizerSendMemoToTransparentAddress
) }
let transaction = try await transactionManager.encode( let transaction = try await transactionEncoder.createTransaction(
spendingKey: spendingKey, spendingKey: spendingKey,
pendingTransaction: spend zatoshi: zatoshi,
) to: recipient.stringEncoded,
let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) memoBytes: memo?.asMemoBytes(),
return submittedTx 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 { public func allReceivedTransactions() async throws -> [ZcashTransaction.Overview] {
await transactionManager.cancel(pendingTransaction: transaction)
}
public func allReceivedTransactions() async throws -> [ZcashTransaction.Received] {
try await transactionRepository.findReceived(offset: 0, limit: Int.max) try await transactionRepository.findReceived(offset: 0, limit: Int.max)
} }
public func allPendingTransactions() async throws -> [PendingTransactionEntity] { public func allPendingTransactions() async throws -> [ZcashTransaction.Overview] {
try await transactionManager.allPendingTransactions() 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) 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) 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) 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) 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] { 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] { public func getTransactionOutputs(for transaction: ZcashTransaction.Overview) async -> [ZcashTransaction.Output] {
return await transactionRepository.getRecipients(for: transaction.id) return (try? await transactionRepository.getTransactionOutputs(for: transaction.id)) ?? []
} }
public func latestHeight() async throws -> BlockHeight { public func latestHeight() async throws -> BlockHeight {
@ -515,15 +502,10 @@ public class SDKSynchronizer: Synchronizer {
let context = AfterSyncHooksManager.RewindContext( let context = AfterSyncHooksManager.RewindContext(
height: height, height: height,
completion: { [weak self] result in completion: { result in
switch result { switch result {
case let .success(rewindHeight): case .success:
do { subject.send(completion: .finished)
try await self?.transactionManager.handleReorg(at: rewindHeight)
subject.send(completion: .finished)
} catch {
subject.send(completion: .failure(error))
}
case let .failure(error): case let .failure(error):
subject.send(completion: .failure(error)) subject.send(completion: .failure(error))
@ -547,9 +529,8 @@ public class SDKSynchronizer: Synchronizer {
} }
let context = AfterSyncHooksManager.WipeContext( let context = AfterSyncHooksManager.WipeContext(
pendingDbURL: initializer.pendingDbURL,
prewipe: { [weak self] in prewipe: { [weak self] in
self?.transactionManager.closeDBConnection() self?.transactionEncoder.closeDBConnection()
self?.transactionRepository.closeDBConnection() self?.transactionRepository.closeDBConnection()
}, },
completion: { [weak self] possibleError in completion: { [weak self] possibleError in
@ -617,44 +598,7 @@ public class SDKSynchronizer: Synchronizer {
} }
} }
// MARK: book keeping private func notifyMinedTransaction(_ transaction: ZcashTransaction.Overview) {
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) {
streamsUpdateQueue.async { [weak self] in streamsUpdateQueue.async { [weak self] in
self?.eventSubject.send(.minedTransaction(transaction)) self?.eventSubject.send(.minedTransaction(transaction))
} }
@ -662,29 +606,29 @@ public class SDKSynchronizer: Synchronizer {
} }
extension SDKSynchronizer { extension SDKSynchronizer {
public var pendingTransactions: [PendingTransactionEntity] { public var transactions: [ZcashTransaction.Overview] {
get async { get async {
(try? await self.allPendingTransactions()) ?? [PendingTransactionEntity]() (try? await self.allTransactions()) ?? []
} }
} }
public var clearedTransactions: [ZcashTransaction.Overview] { public var sentTransactions: [ZcashTransaction.Overview] {
get async {
(try? await allClearedTransactions()) ?? []
}
}
public var sentTransactions: [ZcashTransaction.Sent] {
get async { get async {
(try? await allSentTransactions()) ?? [] (try? await allSentTransactions()) ?? []
} }
} }
public var receivedTransactions: [ZcashTransaction.Received] { public var receivedTransactions: [ZcashTransaction.Overview] {
get async { get async {
(try? await allReceivedTransactions()) ?? [] (try? await allReceivedTransactions()) ?? []
} }
} }
public var pendingTransactions: [ZcashTransaction.Overview] {
get async {
(try? await allPendingTransactions()) ?? []
}
}
} }
extension SyncStatus { extension SyncStatus {

View File

@ -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)
}
}

View File

@ -9,6 +9,15 @@ import Foundation
typealias TransactionEncoderResultBlock = (_ result: Result<EncodedTransaction, Error>) -> Void typealias TransactionEncoderResultBlock = (_ result: Result<EncodedTransaction, Error>) -> 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 { protocol TransactionEncoder {
/// Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation /// 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 /// 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?, memoBytes: MemoBytes?,
from accountIndex: Int from accountIndex: Int
) async throws -> ZcashTransaction.Overview ) 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()
} }

View File

@ -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()
}

View File

@ -8,6 +8,7 @@
import Foundation import Foundation
class WalletTransactionEncoder: TransactionEncoder { class WalletTransactionEncoder: TransactionEncoder {
let lightWalletService: LightWalletService
let rustBackend: ZcashRustBackendWelding let rustBackend: ZcashRustBackendWelding
let repository: TransactionRepository let repository: TransactionRepository
let logger: Logger let logger: Logger
@ -22,6 +23,7 @@ class WalletTransactionEncoder: TransactionEncoder {
rustBackend: ZcashRustBackendWelding, rustBackend: ZcashRustBackendWelding,
dataDb: URL, dataDb: URL,
fsBlockDbRoot: URL, fsBlockDbRoot: URL,
service: LightWalletService,
repository: TransactionRepository, repository: TransactionRepository,
outputParams: URL, outputParams: URL,
spendParams: URL, spendParams: URL,
@ -31,6 +33,7 @@ class WalletTransactionEncoder: TransactionEncoder {
self.rustBackend = rustBackend self.rustBackend = rustBackend
self.dataDbURL = dataDb self.dataDbURL = dataDb
self.fsBlockDbRoot = fsBlockDbRoot self.fsBlockDbRoot = fsBlockDbRoot
self.lightWalletService = service
self.repository = repository self.repository = repository
self.outputParamsURL = outputParams self.outputParamsURL = outputParams
self.spendParamsURL = spendParams self.spendParamsURL = spendParams
@ -43,6 +46,7 @@ class WalletTransactionEncoder: TransactionEncoder {
rustBackend: initializer.rustBackend, rustBackend: initializer.rustBackend,
dataDb: initializer.dataDbURL, dataDb: initializer.dataDbURL,
fsBlockDbRoot: initializer.fsBlockDbRoot, fsBlockDbRoot: initializer.fsBlockDbRoot,
service: initializer.lightWalletService,
repository: initializer.transactionRepository, repository: initializer.transactionRepository,
outputParams: initializer.outputParamsURL, outputParams: initializer.outputParamsURL,
spendParams: initializer.spendParamsURL, spendParams: initializer.spendParamsURL,
@ -126,6 +130,17 @@ class WalletTransactionEncoder: TransactionEncoder {
return Int(txId) 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 { func ensureParams(spend: URL, output: URL) -> Bool {
let readableSpend = FileManager.default.isReadableFile(atPath: spend.path) 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 // TODO: [#713] change this to something that makes sense, https://github.com/zcash/ZcashLightClientKit/issues/713
return readableSpend && readableOutput 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)
}
} }

View File

@ -70,7 +70,6 @@ class SDKSynchronizerAliasDarksideTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
coordinators = [] coordinators = []
} }

View File

@ -28,7 +28,12 @@ class AdvancedReOrgTests: XCTestCase {
override func setUp() async throws { override func setUp() async throws {
try await super.setUp() try await super.setUp()
// don't use an exact birthday, users never do. // 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) try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
} }
@ -41,7 +46,6 @@ class AdvancedReOrgTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func handleReorg(event: CompactBlockProcessor.Event) { func handleReorg(event: CompactBlockProcessor.Event) {
@ -305,7 +309,7 @@ class AdvancedReOrgTests: XCTestCase {
await fulfillment(of: [preTxExpectation], timeout: 5) await fulfillment(of: [preTxExpectation], timeout: 5)
let sendExpectation = XCTestExpectation(description: "sendToAddress") let sendExpectation = XCTestExpectation(description: "sendToAddress")
var pendingEntity: PendingTransactionEntity? var pendingEntity: ZcashTransaction.Overview?
var testError: Error? var testError: Error?
let sendAmount = Zatoshi(10000) let sendAmount = Zatoshi(10000)
@ -436,13 +440,14 @@ class AdvancedReOrgTests: XCTestCase {
await fulfillment(of: [lastSyncExpectation], timeout: 5) 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 let expectedPendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
XCTAssertEqual(expectedPendingTransactionsCount, 0) XCTAssertEqual(expectedPendingTransactionsCount, 0)
XCTAssertEqual(initialTotalBalance - pendingTx.value - Zatoshi(1000), expectedVerifiedBalance) XCTAssertEqual(expectedVerifiedBalance, currentVerifiedBalance)
let resultingBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() let resultingBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
XCTAssertEqual(resultingBalance, expectedVerifiedBalance) XCTAssertEqual(resultingBalance, currentVerifiedBalance)
} }
func testIncomingTransactionIndexChange() async throws { func testIncomingTransactionIndexChange() async throws {
@ -571,6 +576,27 @@ class AdvancedReOrgTests: XCTestCase {
XCTAssertEqual(afterReOrgBalance, initialBalance) XCTAssertEqual(afterReOrgBalance, initialBalance)
XCTAssertEqual(afterReOrgVerifiedBalance, initialVerifiedBalance) 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: /// Steps:
@ -598,7 +624,7 @@ class AdvancedReOrgTests: XCTestCase {
var initialBalance = Zatoshi(-1) var initialBalance = Zatoshi(-1)
var initialVerifiedBalance = Zatoshi(-1) var initialVerifiedBalance = Zatoshi(-1)
var incomingTx: ZcashTransaction.Received! var incomingTx: ZcashTransaction.Overview!
try await coordinator.sync( try await coordinator.sync(
completion: { _ in completion: { _ in
@ -765,16 +791,18 @@ class AdvancedReOrgTests: XCTestCase {
let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
let sendExpectation = XCTestExpectation(description: "send expectation") let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity? var pendingEntity: ZcashTransaction.Overview?
/* /*
2. send transaction to recipient address 2. send transaction to recipient address
*/ */
let recipient = try Recipient(Environment.testRecipientAddress, network: self.network.networkType)
do { do {
let pendingTx = try await coordinator.synchronizer.sendToAddress( let pendingTx = try await coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKey, spendingKey: self.coordinator.spendingKey,
zatoshi: Zatoshi(20000), zatoshi: Zatoshi(20000),
toAddress: try Recipient(Environment.testRecipientAddress, network: self.network.networkType), toAddress: recipient,
memo: try Memo(string: "this is a test") memo: try Memo(string: "this is a test")
) )
pendingEntity = pendingTx pendingEntity = pendingTx
@ -889,7 +917,7 @@ class AdvancedReOrgTests: XCTestCase {
return return
} }
XCTAssertEqual(newPendingTx.minedHeight, BlockHeight.empty()) XCTAssertNil(newPendingTx.minedHeight)
/* /*
11. applyHeight(sentTxHeight + 2) 11. applyHeight(sentTxHeight + 2)
@ -920,7 +948,7 @@ class AdvancedReOrgTests: XCTestCase {
*/ */
pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
XCTAssertEqual(pendingTransactionsCount, 1) 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") XCTFail("no pending transaction")
try await coordinator.stop() try await coordinator.stop()
return return
@ -962,7 +990,7 @@ class AdvancedReOrgTests: XCTestCase {
let sentTransactions = await coordinator.synchronizer.sentTransactions let sentTransactions = await coordinator.synchronizer.sentTransactions
.first( .first(
where: { transaction in 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() let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()
XCTAssertEqual( XCTAssertEqual(
initialTotalBalance - newlyPendingTx.value - Zatoshi(1000), initialTotalBalance + newlyPendingTx.value, // Note: sent transactions have negative values
expectedBalance expectedBalance
) )
XCTAssertEqual( XCTAssertEqual(
initialTotalBalance - newlyPendingTx.value - Zatoshi(1000), initialTotalBalance + newlyPendingTx.value, // Note: sent transactions have negative values
expectedVerifiedBalance 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. /// Uses the zcash-hackworks data set.
@ -1180,7 +1213,7 @@ class AdvancedReOrgTests: XCTestCase {
let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
let sendExpectation = XCTestExpectation(description: "send expectation") let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity? var pendingEntity: ZcashTransaction.Overview?
/* /*
2. send transaction to recipient address 2. send transaction to recipient address
@ -1274,7 +1307,7 @@ class AdvancedReOrgTests: XCTestCase {
return return
} }
XCTAssertFalse(pendingTx.isMined) XCTAssertNil(pendingTx.minedHeight)
LoggerProxy.info("applyStaged(blockheight: \(sentTxHeight + extraBlocks - 1))") LoggerProxy.info("applyStaged(blockheight: \(sentTxHeight + extraBlocks - 1))")
try coordinator.applyStaged(blockheight: sentTxHeight + extraBlocks - 1) try coordinator.applyStaged(blockheight: sentTxHeight + extraBlocks - 1)

View File

@ -38,7 +38,6 @@ class BalanceTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) 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 // 4 send the transaction
let spendingKey = coordinator.spendingKey let spendingKey = coordinator.spendingKey
var pendingTx: PendingTransactionEntity? var pendingTx: ZcashTransaction.Overview?
do { do {
let transaction = try await coordinator.synchronizer.sendToAddress( let transaction = try await coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
@ -106,9 +105,9 @@ class BalanceTests: XCTestCase {
} }
notificationHandler.synchronizerMinedTransaction = { transaction in notificationHandler.synchronizerMinedTransaction = { transaction in
XCTAssertNotNil(transaction.rawTransactionId) XCTAssertNotNil(transaction.rawID)
XCTAssertNotNil(pendingTx.rawTransactionId) XCTAssertNotNil(pendingTx.rawID)
XCTAssertEqual(transaction.rawTransactionId, pendingTx.rawTransactionId) XCTAssertEqual(transaction.rawID, pendingTx.rawID)
transactionMinedExpectation.fulfill() transactionMinedExpectation.fulfill()
} }
@ -123,7 +122,7 @@ class BalanceTests: XCTestCase {
let sentTxHeight = latestHeight + 1 let sentTxHeight = latestHeight + 1
notificationHandler.transactionsFound = { txs in notificationHandler.transactionsFound = { txs in
let foundTx = txs.first(where: { $0.rawID == pendingTx.rawTransactionId }) let foundTx = txs.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(foundTx) XCTAssertNotNil(foundTx)
XCTAssertEqual(foundTx?.minedHeight, sentTxHeight) XCTAssertEqual(foundTx?.minedHeight, sentTxHeight)
@ -139,9 +138,9 @@ class BalanceTests: XCTestCase {
do { do {
try await coordinator.sync( try await coordinator.sync(
completion: { synchronizer in 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") XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
XCTAssertTrue(pendingEntity?.isMined ?? false) XCTAssertNotNil(pendingEntity?.minedHeight)
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight) XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
mineExpectation.fulfill() mineExpectation.fulfill()
}, },
@ -181,7 +180,7 @@ class BalanceTests: XCTestCase {
await fulfillment(of: [confirmExpectation], timeout: 5) await fulfillment(of: [confirmExpectation], timeout: 5)
let confirmedPending = try await coordinator.synchronizer.allPendingTransactions() 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") XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
@ -234,7 +233,7 @@ class BalanceTests: XCTestCase {
// 3 create a transaction for the max amount possible // 3 create a transaction for the max amount possible
// 4 send the transaction // 4 send the transaction
let spendingKey = coordinator.spendingKey let spendingKey = coordinator.spendingKey
var pendingTx: PendingTransactionEntity? var pendingTx: ZcashTransaction.Overview?
do { do {
let transaction = try await coordinator.synchronizer.sendToAddress( let transaction = try await coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
@ -255,9 +254,9 @@ class BalanceTests: XCTestCase {
} }
notificationHandler.synchronizerMinedTransaction = { transaction in notificationHandler.synchronizerMinedTransaction = { transaction in
XCTAssertNotNil(transaction.rawTransactionId) XCTAssertNotNil(transaction.rawID)
XCTAssertNotNil(pendingTx.rawTransactionId) XCTAssertNotNil(pendingTx.rawID)
XCTAssertEqual(transaction.rawTransactionId, pendingTx.rawTransactionId) XCTAssertEqual(transaction.rawID, pendingTx.rawID)
transactionMinedExpectation.fulfill() transactionMinedExpectation.fulfill()
} }
@ -272,7 +271,7 @@ class BalanceTests: XCTestCase {
let sentTxHeight = latestHeight + 1 let sentTxHeight = latestHeight + 1
notificationHandler.transactionsFound = { txs in notificationHandler.transactionsFound = { txs in
let foundTx = txs.first(where: { $0.rawID == pendingTx.rawTransactionId }) let foundTx = txs.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(foundTx) XCTAssertNotNil(foundTx)
XCTAssertEqual(foundTx?.minedHeight, sentTxHeight) XCTAssertEqual(foundTx?.minedHeight, sentTxHeight)
@ -288,9 +287,9 @@ class BalanceTests: XCTestCase {
do { do {
try await coordinator.sync( try await coordinator.sync(
completion: { synchronizer in 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") XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
XCTAssertTrue(pendingEntity?.isMined ?? false) XCTAssertNotNil(pendingEntity?.minedHeight)
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight) XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
mineExpectation.fulfill() mineExpectation.fulfill()
}, },
@ -303,8 +302,9 @@ class BalanceTests: XCTestCase {
await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5) await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// 7 advance to confirmation // 7 advance to confirmation
try coordinator.applyStaged(blockheight: sentTxHeight + 10) let advanceToConfirmationHeight = sentTxHeight + 10
try coordinator.applyStaged(blockheight: advanceToConfirmationHeight)
sleep(2) sleep(2)
@ -331,7 +331,7 @@ class BalanceTests: XCTestCase {
let confirmedPending = try await coordinator.synchronizer let confirmedPending = try await coordinator.synchronizer
.allPendingTransactions() .allPendingTransactions()
.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) .first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found") XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
@ -384,7 +384,7 @@ class BalanceTests: XCTestCase {
// 3 create a transaction for the max amount possible // 3 create a transaction for the max amount possible
// 4 send the transaction // 4 send the transaction
let spendingKey = coordinator.spendingKey let spendingKey = coordinator.spendingKey
var pendingTx: PendingTransactionEntity? var pendingTx: ZcashTransaction.Overview?
do { do {
let transaction = try await coordinator.synchronizer.sendToAddress( let transaction = try await coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
@ -405,9 +405,9 @@ class BalanceTests: XCTestCase {
} }
notificationHandler.synchronizerMinedTransaction = { transaction in notificationHandler.synchronizerMinedTransaction = { transaction in
XCTAssertNotNil(transaction.rawTransactionId) XCTAssertNotNil(transaction.rawID)
XCTAssertNotNil(pendingTx.rawTransactionId) XCTAssertNotNil(pendingTx.rawID)
XCTAssertEqual(transaction.rawTransactionId, pendingTx.rawTransactionId) XCTAssertEqual(transaction.rawID, pendingTx.rawID)
transactionMinedExpectation.fulfill() transactionMinedExpectation.fulfill()
} }
@ -422,7 +422,7 @@ class BalanceTests: XCTestCase {
let sentTxHeight = latestHeight + 1 let sentTxHeight = latestHeight + 1
notificationHandler.transactionsFound = { txs in notificationHandler.transactionsFound = { txs in
let foundTx = txs.first(where: { $0.rawID == pendingTx.rawTransactionId }) let foundTx = txs.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(foundTx) XCTAssertNotNil(foundTx)
XCTAssertEqual(foundTx?.minedHeight, sentTxHeight) XCTAssertEqual(foundTx?.minedHeight, sentTxHeight)
@ -438,9 +438,9 @@ class BalanceTests: XCTestCase {
do { do {
try await coordinator.sync( try await coordinator.sync(
completion: { synchronizer in 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") XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
XCTAssertTrue(pendingEntity?.isMined ?? false) XCTAssertTrue(pendingEntity?.minedHeight != nil)
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight) XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
mineExpectation.fulfill() mineExpectation.fulfill()
}, },
@ -453,8 +453,9 @@ class BalanceTests: XCTestCase {
await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5) await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// 7 advance to confirmation // 7 advance to confirmation
let advanceToConfirmation = sentTxHeight + 10
try coordinator.applyStaged(blockheight: sentTxHeight + 10)
try coordinator.applyStaged(blockheight: advanceToConfirmation)
sleep(2) sleep(2)
@ -481,7 +482,7 @@ class BalanceTests: XCTestCase {
let confirmedPending = try await coordinator.synchronizer let confirmedPending = try await coordinator.synchronizer
.allPendingTransactions() .allPendingTransactions()
.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) .first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found") XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
@ -537,7 +538,8 @@ class BalanceTests: XCTestCase {
*/ */
XCTAssertTrue(presendVerifiedBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount) XCTAssertTrue(presendVerifiedBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount)
var pendingTx: PendingTransactionEntity? var pendingTx: ZcashTransaction.Overview?
do { do {
let transaction = try await coordinator.synchronizer.sendToAddress( let transaction = try await coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
@ -609,59 +611,29 @@ class BalanceTests: XCTestCase {
basic health check basic health check
*/ */
XCTAssertEqual(transaction.value, self.sendAmount) XCTAssertEqual(transaction.value, self.sendAmount)
/* let outputs = await coordinator.synchronizer.getTransactionOutputs(for: transaction)
build up repos to get data
*/ guard outputs.count == 2 else {
guard let txid = transaction.rawTransactionId else { XCTFail("Expected sent transaction to have 2 outputs")
XCTFail("sent transaction has no internal id")
return return
} }
let sentNoteDAO = SentNotesSQLDAO( guard let changeOutput = outputs.first(where: { $0.isChange }) else {
dbProvider: SimpleConnectionProvider( XCTFail("Sent transaction has no change")
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")
return return
} }
var receivedEntity: ReceivedNoteEntity? guard let sentOutput = outputs.first(where: { !$0.isChange }) else {
XCTFail("sent transaction does not have a 'sent' output")
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")
return return
} }
// (previous available funds - spent note + change) equals to (previous available funds - sent amount) // (previous available funds - spent note + change) equals to (previous available funds - sent amount)
self.verifiedBalanceValidation( self.verifiedBalanceValidation(
previousBalance: presendVerifiedBalance, previousBalance: presendVerifiedBalance,
spentNoteValue: Zatoshi(Int64(sentNote.value)), spentNoteValue: sentOutput.value,
changeValue: Zatoshi(Int64(receivedNote.value)), changeValue: changeOutput.value,
sentAmount: self.sendAmount, sentAmount: self.sendAmount,
currentVerifiedBalance: try await coordinator.synchronizer.getShieldedVerifiedBalance() currentVerifiedBalance: try await coordinator.synchronizer.getShieldedVerifiedBalance()
) )
@ -709,7 +681,7 @@ class BalanceTests: XCTestCase {
// there's more zatoshi to send than network fee // there's more zatoshi to send than network fee
XCTAssertTrue(presendBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount) XCTAssertTrue(presendBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount)
var pendingTx: PendingTransactionEntity? var pendingTx: ZcashTransaction.Overview?
var testError: Error? var testError: Error?
do { do {
@ -750,8 +722,6 @@ class BalanceTests: XCTestCase {
presendBalance - self.sendAmount - network.constants.defaultFee(for: defaultLatestHeight) presendBalance - self.sendAmount - network.constants.defaultFee(for: defaultLatestHeight)
) )
XCTAssertNil(transaction.errorCode)
let latestHeight = try await coordinator.latestHeight() let latestHeight = try await coordinator.latestHeight()
let sentTxHeight = latestHeight + 1 let sentTxHeight = latestHeight + 1
try coordinator.stageBlockCreate(height: sentTxHeight) try coordinator.stageBlockCreate(height: sentTxHeight)
@ -814,7 +784,7 @@ class BalanceTests: XCTestCase {
await fulfillment(of: [syncedExpectation], timeout: 5) 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() let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()
XCTAssertEqual(clearedTransactions.count, 2) XCTAssertEqual(clearedTransactions.count, 2)
XCTAssertEqual(expectedBalance, Zatoshi(200000)) XCTAssertEqual(expectedBalance, Zatoshi(200000))
@ -878,7 +848,8 @@ class BalanceTests: XCTestCase {
Send Send
*/ */
let memo = try Memo(string: "shielding is fun!") let memo = try Memo(string: "shielding is fun!")
var pendingTx: PendingTransactionEntity? var pendingTx: ZcashTransaction.Overview?
do { do {
let transaction = try await coordinator.synchronizer.sendToAddress( let transaction = try await coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
@ -918,8 +889,8 @@ class BalanceTests: XCTestCase {
completion: { synchronizer in completion: { synchronizer in
let confirmedTx: ZcashTransaction.Overview! let confirmedTx: ZcashTransaction.Overview!
do { do {
confirmedTx = try await synchronizer.allClearedTransactions().first(where: { confirmed -> Bool in confirmedTx = try await synchronizer.allTransactions().first(where: { confirmed -> Bool in
confirmed.rawID == pendingTx?.rawTransactionId confirmed.rawID == pendingTx?.rawID
}) })
} catch { } catch {
XCTFail("Error retrieving cleared transactions") XCTFail("Error retrieving cleared transactions")
@ -937,30 +908,21 @@ class BalanceTests: XCTestCase {
/* /*
Find out what note was used 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 { let outputs = await self.coordinator.synchronizer.getTransactionOutputs(for: confirmedTx)
XCTFail("Could not finde sent note with transaction Id \(confirmedTx.rawID)")
guard outputs.count == 2 else {
XCTFail("Expected sent transaction to have 2 outputs")
return return
} }
let receivedNotesRepo = ReceivedNotesSQLDAO( guard let changeOutput = outputs.first(where: { $0.isChange }) else {
dbProvider: SimpleConnectionProvider( XCTFail("Sent transaction has no change")
path: self.coordinator.synchronizer.initializer.dataDbURL.absoluteString, return
readonly: true }
)
)
/* guard let sentOutput = outputs.first(where: { !$0.isChange }) else {
get change note XCTFail("sent transaction does not have a 'sent' output")
*/
guard let receivedNote = try? receivedNotesRepo.receivedNote(byRawTransactionId: confirmedTx.rawID) else {
XCTFail("Could not find received not with change for transaction Id \(confirmedTx.rawID)")
return return
} }
@ -969,7 +931,7 @@ class BalanceTests: XCTestCase {
*/ */
XCTAssertEqual( XCTAssertEqual(
previousVerifiedBalance - self.sendAmount - self.network.constants.defaultFee(for: self.defaultLatestHeight), 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( self.verifiedBalanceValidation(
previousBalance: previousVerifiedBalance, previousBalance: previousVerifiedBalance,
spentNoteValue: Zatoshi(Int64(sentNote.value)), spentNoteValue: sentOutput.value,
changeValue: Zatoshi(Int64(receivedNote.value)), changeValue: changeOutput.value,
sentAmount: self.sendAmount, sentAmount: self.sendAmount,
currentVerifiedBalance: try await synchronizer.getShieldedVerifiedBalance() currentVerifiedBalance: try await synchronizer.getShieldedVerifiedBalance()
) )
@ -1046,7 +1008,7 @@ class BalanceTests: XCTestCase {
let previousVerifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance() let previousVerifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance()
let previousTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() let previousTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
let sendExpectation = XCTestExpectation(description: "send expectation") let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingTx: PendingTransactionEntity? var pendingTx: ZcashTransaction.Overview?
do { do {
let pending = try await coordinator.synchronizer.sendToAddress( let pending = try await coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
@ -1067,13 +1029,13 @@ class BalanceTests: XCTestCase {
await fulfillment(of: [sendExpectation], timeout: 12) 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") XCTFail("No pending transaction")
return return
} }
let expirationSyncExpectation = XCTestExpectation(description: "expiration sync expectation") let expirationSyncExpectation = XCTestExpectation(description: "expiration sync expectation")
let expiryHeight = pendingTransaction.expiryHeight
try coordinator.applyStaged(blockheight: expiryHeight + 1) try coordinator.applyStaged(blockheight: expiryHeight + 1)
sleep(2) sleep(2)
@ -1102,32 +1064,24 @@ class BalanceTests: XCTestCase {
Total Balance is equal to total balance previously shown before sending the expired transaction Total Balance is equal to total balance previously shown before sending the expired transaction
*/ */
XCTAssertEqual(expectedBalance, previousTotalBalance) XCTAssertEqual(expectedBalance, previousTotalBalance)
let pendingRepo = PendingTransactionSQLDAO( let transactionRepo = TransactionSQLDAO(dbProvider: SimpleConnectionProvider(
dbProvider: SimpleConnectionProvider( path: coordinator.synchronizer.initializer.dataDbURL.absoluteString
path: coordinator.synchronizer.initializer.pendingDbURL.absoluteString )
),
logger: logger
) )
guard let expiredPending = try await transactionRepo.find(id: pendingTransaction.id)
let expiredPending = try? pendingRepo.find(by: pendingTransaction.id!),
let id = expiredPending.id
else {
XCTFail("pending transaction not found")
return
}
/* /*
there no sent transaction displayed there no sent transaction displayed
*/ */
let sentTransactions = try await coordinator.synchronizer.allSentTransactions() let sentTransactions = try await coordinator.synchronizer.allSentTransactions()
XCTAssertNil(sentTransactions.first(where: { $0.id == id })) XCTAssertNil(sentTransactions.first(where: { $0.id == pendingTransaction.id }))
/* /*
Theres a pending transaction that has expired Theres a pending transaction that has expired
*/ */
XCTAssertEqual(expiredPending.minedHeight, -1) XCTAssertNil(expiredPending.minedHeight)
} }
func handleError(_ error: Error?) { func handleError(_ error: Error?) {
@ -1162,7 +1116,7 @@ class BalanceTests: XCTestCase {
class SDKSynchonizerListener { class SDKSynchonizerListener {
var transactionsFound: (([ZcashTransaction.Overview]) -> Void)? var transactionsFound: (([ZcashTransaction.Overview]) -> Void)?
var synchronizerMinedTransaction: ((PendingTransactionEntity) -> Void)? var synchronizerMinedTransaction: ((ZcashTransaction.Overview) -> Void)?
var cancellables: [AnyCancellable] = [] var cancellables: [AnyCancellable] = []
func subscribeToSynchronizer(_ synchronizer: SDKSynchronizer) { 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 DispatchQueue.main.async { [weak self] in
self?.synchronizerMinedTransaction?(transaction) self?.synchronizerMinedTransaction?(transaction)
} }

View File

@ -41,7 +41,6 @@ class DarksideSanityCheckTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func testDarkside() async throws { func testDarkside() async throws {

View File

@ -41,7 +41,6 @@ final class InternalStateConsistencyTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func testInternalStateIsConsistentWhenMigrating() async throws { func testInternalStateIsConsistentWhenMigrating() async throws {
@ -70,7 +69,7 @@ final class InternalStateConsistencyTests: XCTestCase {
let coordinator = self.coordinator! let coordinator = self.coordinator!
DispatchQueue.global().asyncAfter(deadline: .now() + 1) { DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
Task(priority: .userInitiated) { Task(priority: .userInitiated) {
await coordinator.synchronizer.stop() coordinator.synchronizer.stop()
} }
} }

View File

@ -37,7 +37,6 @@ class PendingTransactionUpdatesTest: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func testPendingTransactionMinedHeightUpdated() async throws { func testPendingTransactionMinedHeightUpdated() async throws {
@ -72,7 +71,7 @@ class PendingTransactionUpdatesTest: XCTestCase {
sleep(1) sleep(1)
let sendExpectation = XCTestExpectation(description: "send expectation") let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: PendingTransactionEntity? var pendingEntity: ZcashTransaction.Overview?
/* /*
2. send transaction to recipient address 2. send transaction to recipient address
@ -99,12 +98,12 @@ class PendingTransactionUpdatesTest: XCTestCase {
return return
} }
XCTAssertFalse( XCTAssertTrue(
pendingUnconfirmedTx.isConfirmed(currentHeight: 663188), pendingUnconfirmedTx.isPending(currentHeight: 633188),
"pending transaction evaluated as confirmed when it shouldn't" "pending transaction evaluated as confirmed when it shouldn't"
) )
XCTAssertFalse( XCTAssertNil(
pendingUnconfirmedTx.isMined, pendingUnconfirmedTx.minedHeight,
"pending transaction evaluated as mined when it shouldn't" "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)") LoggerProxy.info("6a. verify that there's a pending transaction with a mined height of \(sentTxHeight)")
XCTAssertEqual(afterStagePendingTx.minedHeight, 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)) XCTAssertTrue(afterStagePendingTx.isPending(currentHeight: sentTxHeight))
/* /*
@ -207,11 +206,11 @@ class PendingTransactionUpdatesTest: XCTestCase {
let supposedlyPendingUnexistingTransaction = try await coordinator.synchronizer.allPendingTransactions().first let supposedlyPendingUnexistingTransaction = try await coordinator.synchronizer.allPendingTransactions().first
let clearedTransactions = await coordinator.synchronizer 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) XCTAssertNil(supposedlyPendingUnexistingTransaction)
} }

View File

@ -69,7 +69,6 @@ class ReOrgTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func handleReOrgNotification(event: CompactBlockProcessor.Event) { func handleReOrgNotification(event: CompactBlockProcessor.Event) {

View File

@ -43,7 +43,6 @@ class RewindRescanTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func handleError(_ error: Error?) { func handleError(_ error: Error?) {
@ -271,7 +270,7 @@ class RewindRescanTests: XCTestCase {
XCTAssertEqual(verifiedBalance, totalBalance) XCTAssertEqual(verifiedBalance, totalBalance)
// rewind to transaction // 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") XCTFail("failed to get a transaction to rewind to")
return return
} }
@ -362,7 +361,7 @@ class RewindRescanTests: XCTestCase {
// 3 create a transaction for the max amount possible // 3 create a transaction for the max amount possible
// 4 send the transaction // 4 send the transaction
let spendingKey = coordinator.spendingKey let spendingKey = coordinator.spendingKey
var pendingTx: PendingTransactionEntity? var pendingTx: ZcashTransaction.Overview?
do { do {
let transaction = try await coordinator.synchronizer.sendToAddress( let transaction = try await coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
@ -382,9 +381,9 @@ class RewindRescanTests: XCTestCase {
} }
notificationHandler.synchronizerMinedTransaction = { transaction in notificationHandler.synchronizerMinedTransaction = { transaction in
XCTAssertNotNil(transaction.rawTransactionId) XCTAssertNotNil(transaction.rawID)
XCTAssertNotNil(pendingTx.rawTransactionId) XCTAssertNotNil(pendingTx.rawID)
XCTAssertEqual(transaction.rawTransactionId, pendingTx.rawTransactionId) XCTAssertEqual(transaction.rawID, pendingTx.rawID)
transactionMinedExpectation.fulfill() transactionMinedExpectation.fulfill()
} }
@ -399,7 +398,7 @@ class RewindRescanTests: XCTestCase {
let sentTxHeight = latestHeight + 1 let sentTxHeight = latestHeight + 1
notificationHandler.transactionsFound = { txs in notificationHandler.transactionsFound = { txs in
let foundTx = txs.first(where: { $0.rawID == pendingTx.rawTransactionId }) let foundTx = txs.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(foundTx) XCTAssertNotNil(foundTx)
XCTAssertEqual(foundTx?.minedHeight, sentTxHeight) XCTAssertEqual(foundTx?.minedHeight, sentTxHeight)
@ -416,10 +415,10 @@ class RewindRescanTests: XCTestCase {
do { do {
try await coordinator.sync( try await coordinator.sync(
completion: { synchronizer in completion: { synchronizer in
let pendingTransaction = await synchronizer.pendingTransactions let pendingTransaction = try await synchronizer.allPendingTransactions()
.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) .first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(pendingTransaction, "pending transaction should have been mined by now") XCTAssertNotNil(pendingTransaction, "pending transaction should have been mined by now")
XCTAssertTrue(pendingTransaction?.isMined ?? false) XCTAssertNotNil(pendingTransaction?.minedHeight)
XCTAssertEqual(pendingTransaction?.minedHeight, sentTxHeight) XCTAssertEqual(pendingTransaction?.minedHeight, sentTxHeight)
mineExpectation.fulfill() mineExpectation.fulfill()
}, error: self.handleError }, error: self.handleError
@ -431,16 +430,18 @@ class RewindRescanTests: XCTestCase {
await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5) await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// 7 advance to confirmation // 7 advance to confirmation
let advanceToConfirmationHeight = sentTxHeight + 10
try coordinator.applyStaged(blockheight: sentTxHeight + 10)
try coordinator.applyStaged(blockheight: advanceToConfirmationHeight)
sleep(2) sleep(2)
let rewindExpectation = XCTestExpectation(description: "RewindExpectation") let rewindExpectation = XCTestExpectation(description: "RewindExpectation")
let rewindHeight = sentTxHeight - 5
try await withCheckedThrowingContinuation { continuation in try await withCheckedThrowingContinuation { continuation in
// rewind 5 blocks prior to sending // rewind 5 blocks prior to sending
coordinator.synchronizer.rewind(.height(blockheight: sentTxHeight - 5)) coordinator.synchronizer.rewind(.height(blockheight: rewindHeight))
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
rewindExpectation.fulfill() rewindExpectation.fulfill()
@ -462,13 +463,13 @@ class RewindRescanTests: XCTestCase {
guard guard
let pendingEntity = try await coordinator.synchronizer.allPendingTransactions() let pendingEntity = try await coordinator.synchronizer.allPendingTransactions()
.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId }) .first(where: { $0.rawID == pendingTx.rawID })
else { else {
XCTFail("sent pending transaction not found after rewind") XCTFail("sent pending transaction not found after rewind")
return return
} }
XCTAssertFalse(pendingEntity.isMined) XCTAssertNil(pendingEntity.minedHeight)
let confirmExpectation = XCTestExpectation(description: "confirm expectation") let confirmExpectation = XCTestExpectation(description: "confirm expectation")
notificationHandler.transactionsFound = { txs in notificationHandler.transactionsFound = { txs in
@ -477,7 +478,7 @@ class RewindRescanTests: XCTestCase {
XCTFail("should have found sent transaction but didn't") XCTFail("should have found sent transaction but didn't")
return 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 notificationHandler.synchronizerMinedTransaction = { transaction in
@ -498,7 +499,7 @@ class RewindRescanTests: XCTestCase {
await fulfillment(of: [confirmExpectation], timeout: 10) await fulfillment(of: [confirmExpectation], timeout: 10)
let confirmedPending = try await coordinator.synchronizer.allPendingTransactions() 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") XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")

View File

@ -38,7 +38,6 @@ class ShieldFundsTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
/// Tests shielding funds from a UTXO /// Tests shielding funds from a UTXO
@ -206,7 +205,7 @@ class ShieldFundsTests: XCTestCase {
shouldContinue = false shouldContinue = false
var shieldingPendingTx: PendingTransactionEntity? var shieldingPendingTx: ZcashTransaction.Overview?
// shield the funds // shield the funds
do { do {
@ -216,7 +215,7 @@ class ShieldFundsTests: XCTestCase {
shieldingThreshold: Zatoshi(10000) shieldingThreshold: Zatoshi(10000)
) )
shouldContinue = true shouldContinue = true
XCTAssertEqual(pendingTx.value, Zatoshi(10000)) XCTAssertEqual(pendingTx.value, Zatoshi(10000) - pendingTx.fee!)
shieldingPendingTx = pendingTx shieldingPendingTx = pendingTx
shieldFundsExpectation.fulfill() shieldFundsExpectation.fulfill()
} catch { } catch {
@ -320,8 +319,8 @@ class ShieldFundsTests: XCTestCase {
guard shouldContinue else { return } guard shouldContinue else { return }
// verify that there's a confirmed transaction that's the shielding transaction // verify that there's a confirmed transaction that's the shielding transaction
let clearedTransaction = await coordinator.synchronizer.clearedTransactions.first( let clearedTransaction = await coordinator.synchronizer.transactions.first(
where: { $0.rawID == shieldingPendingTx?.rawTransactionId } where: { $0.rawID == shieldingPendingTx?.rawID }
) )
XCTAssertNotNil(clearedTransaction) XCTAssertNotNil(clearedTransaction)

View File

@ -45,7 +45,6 @@ class SynchronizerDarksideTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func testFoundTransactions() async throws { func testFoundTransactions() async throws {
@ -181,7 +180,7 @@ class SynchronizerDarksideTests: XCTestCase {
transparentBalance: .zero, transparentBalance: .zero,
syncStatus: .disconnected, syncStatus: .disconnected,
latestScannedHeight: 663150, latestScannedHeight: 663150,
latestBlockHeight: 663189, latestBlockHeight: 0,
latestScannedTime: 1576821833 latestScannedTime: 1576821833
), ),
SynchronizerState( SynchronizerState(
@ -190,13 +189,12 @@ class SynchronizerDarksideTests: XCTestCase {
transparentBalance: .zero, transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)), syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663150, latestScannedHeight: 663150,
latestBlockHeight: 663189, latestBlockHeight: 0,
latestScannedTime: 1576821833 latestScannedTime: 1576821833
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[0], syncSessionID: uuids[0],
shieldedBalance: .zero, shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
// shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero, transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)), syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
latestScannedHeight: 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 { func testSyncSessionUpdates() async throws {
@ -338,7 +344,7 @@ class SynchronizerDarksideTests: XCTestCase {
syncStatus: .disconnected, syncStatus: .disconnected,
latestScannedHeight: 663150, latestScannedHeight: 663150,
latestBlockHeight: 0, latestBlockHeight: 0,
latestScannedTime: 0 latestScannedTime: 1576821833.0
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[0], syncSessionID: uuids[0],
@ -347,25 +353,25 @@ class SynchronizerDarksideTests: XCTestCase {
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)), syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663150, latestScannedHeight: 663150,
latestBlockHeight: 0, latestBlockHeight: 0,
latestScannedTime: 0 latestScannedTime: 1576821833.0
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[0], syncSessionID: uuids[0],
shieldedBalance: .zero, shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero, transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)), syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
latestScannedHeight: 663150, latestScannedHeight: 663189,
latestBlockHeight: 0, latestBlockHeight: 663189,
latestScannedTime: 0 latestScannedTime: 1
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[0], syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)), syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
latestScannedHeight: 663150, latestScannedHeight: 663189,
latestBlockHeight: 0, latestBlockHeight: 663189,
latestScannedTime: 0 latestScannedTime: 1
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[0], syncSessionID: uuids[0],
@ -379,7 +385,7 @@ class SynchronizerDarksideTests: XCTestCase {
accountId: 0, accountId: 0,
blockTime: 1.0, blockTime: 1.0,
expiryHeight: 663206, expiryHeight: 663206,
fee: Zatoshi(0), fee: nil,
id: 2, id: 2,
index: 1, index: 1,
hasChange: false, hasChange: false,
@ -395,9 +401,9 @@ class SynchronizerDarksideTests: XCTestCase {
range: 663150...663189 range: 663150...663189
) )
), ),
latestScannedHeight: 663150, latestScannedHeight: 663189,
latestBlockHeight: 0, latestBlockHeight: 663189,
latestScannedTime: 0 latestScannedTime: 1
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[0], syncSessionID: uuids[0],
@ -411,7 +417,7 @@ class SynchronizerDarksideTests: XCTestCase {
accountId: 0, accountId: 0,
blockTime: 1.0, blockTime: 1.0,
expiryHeight: 663192, expiryHeight: 663192,
fee: Zatoshi(0), fee: nil,
id: 1, id: 1,
index: 1, index: 1,
hasChange: false, hasChange: false,
@ -427,18 +433,18 @@ class SynchronizerDarksideTests: XCTestCase {
range: 663150...663189 range: 663150...663189
) )
), ),
latestScannedHeight: 663150, latestScannedHeight: 663189,
latestBlockHeight: 0, latestBlockHeight: 663189,
latestScannedTime: 0 latestScannedTime: 1
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[0], syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .fetching, syncStatus: .fetching,
latestScannedHeight: 663150, latestScannedHeight: 663189,
latestBlockHeight: 0, latestBlockHeight: 663189,
latestScannedTime: 0 latestScannedTime: 1
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[0], syncSessionID: uuids[0],
@ -446,12 +452,20 @@ class SynchronizerDarksideTests: XCTestCase {
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .synced, syncStatus: .synced,
latestScannedHeight: 663189, latestScannedHeight: 663189,
latestBlockHeight: 0, latestBlockHeight: 663189,
latestScannedTime: 0 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) try coordinator.service.applyStaged(nextLatestHeight: 663_200)
@ -477,35 +491,35 @@ class SynchronizerDarksideTests: XCTestCase {
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)), syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663189, latestScannedHeight: 663189,
latestBlockHeight: 0, latestBlockHeight: 663189,
latestScannedTime: 0 latestScannedTime: 1.0
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[1], 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)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .syncing(BlockProgress(startHeight: 663190, targetHeight: 663200, progressHeight: 663200)), syncStatus: .syncing(BlockProgress(startHeight: 663190, targetHeight: 663200, progressHeight: 663200)),
latestScannedHeight: 663189, latestScannedHeight: 663200,
latestBlockHeight: 0, latestBlockHeight: 663200,
latestScannedTime: 0 latestScannedTime: 1
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[1], syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)), shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)), syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
latestScannedHeight: 663189, latestScannedHeight: 663200,
latestBlockHeight: 0, latestBlockHeight: 663200,
latestScannedTime: 0 latestScannedTime: 1
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[1], syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)), shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .fetching, syncStatus: .fetching,
latestScannedHeight: 663189, latestScannedHeight: 663200,
latestBlockHeight: 0, latestBlockHeight: 663200,
latestScannedTime: 0 latestScannedTime: 1
), ),
SynchronizerState( SynchronizerState(
syncSessionID: uuids[1], syncSessionID: uuids[1],
@ -513,12 +527,18 @@ class SynchronizerDarksideTests: XCTestCase {
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .synced, syncStatus: .synced,
latestScannedHeight: 663200, latestScannedHeight: 663200,
latestBlockHeight: 0, latestBlockHeight: 663200,
latestScannedTime: 0 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 { func testSyncAfterWipeWorks() async throws {

View File

@ -51,7 +51,6 @@ final class SynchronizerTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func handleReorg(event: CompactBlockProcessor.Event) { func handleReorg(event: CompactBlockProcessor.Event) {
@ -92,7 +91,7 @@ final class SynchronizerTests: XCTestCase {
) )
try await Task.sleep(nanoseconds: 5_000_000_000) try await Task.sleep(nanoseconds: 5_000_000_000)
await self.coordinator.synchronizer.stop() self.coordinator.synchronizer.stop()
await fulfillment(of: [syncStoppedExpectation], timeout: 6) 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 storage = await self.coordinator.synchronizer.blockProcessor.storage as! FSCompactBlockRepository
let fm = FileManager.default let fm = FileManager.default
print(coordinator.synchronizer.initializer.dataDbURL.path) 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.") 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") 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") 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 // 1 sync and get spendable funds
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName) 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 initialVerifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance()
let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance() let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
sleep(1) sleep(1)
@ -281,6 +280,12 @@ final class SynchronizerTests: XCTestCase {
await fulfillment(of: [firstSyncExpectation], timeout: 12) 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. // 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 FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName, length: 10000)
try coordinator.applyStaged(blockheight: birthday + 10000) try coordinator.applyStaged(blockheight: birthday + 10000)
@ -305,19 +310,12 @@ final class SynchronizerTests: XCTestCase {
await fulfillment(of: [waitExpectation], timeout: 1) 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") let rewindExpectation = XCTestExpectation(description: "RewindExpectation")
// rewind to birthday // rewind to birthday
coordinator.synchronizer.rewind(.birthday) coordinator.synchronizer.rewind(.birthday)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
rewindExpectation.fulfill()
switch result { switch result {
case .finished: case .finished:
break break
@ -326,7 +324,7 @@ final class SynchronizerTests: XCTestCase {
} }
rewindExpectation.fulfill() rewindExpectation.fulfill()
}, },
receiveValue: { _ in } receiveValue: { _ in rewindExpectation.fulfill() }
) )
.store(in: &cancellables) .store(in: &cancellables)

View File

@ -41,7 +41,6 @@ class Z2TReceiveTests: XCTestCase {
try await coordinator.stop() try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
} }
func subscribeToFoundTransactions() { func subscribeToFoundTransactions() {
@ -134,7 +133,7 @@ class Z2TReceiveTests: XCTestCase {
await fulfillment(of: [preTxExpectation, foundTransactionsExpectation], timeout: 5) await fulfillment(of: [preTxExpectation, foundTransactionsExpectation], timeout: 5)
let sendExpectation = XCTestExpectation(description: "sendToAddress") let sendExpectation = XCTestExpectation(description: "sendToAddress")
var pendingEntity: PendingTransactionEntity? var pendingEntity: ZcashTransaction.Overview?
var testError: Error? var testError: Error?
let sendAmount = Zatoshi(10000) let sendAmount = Zatoshi(10000)
/* /*

View File

@ -307,7 +307,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testSendToAddressSucceed() throws { func testSendToAddressSucceed() throws {
let amount = Zatoshi(100) let amount = Zatoshi(10)
let recipient: Recipient = .transparent(data.transparentAddress) let recipient: Recipient = .transparent(data.transparentAddress)
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let mockedSpendingKey = data.spendingKey let mockedSpendingKey = data.spendingKey
@ -326,7 +326,8 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
synchronizer.sendToAddress(spendingKey: mockedSpendingKey, zatoshi: amount, toAddress: recipient, memo: memo) { result in synchronizer.sendToAddress(spendingKey: mockedSpendingKey, zatoshi: amount, toAddress: recipient, memo: memo) { result in
switch result { switch result {
case let .success(receivedEntity): case let .success(receivedEntity):
XCTAssertEqual(receivedEntity.recipient, self.data.pendingTransactionEntity.recipient)
XCTAssertEqual(receivedEntity.value, amount)
expectation.fulfill() expectation.fulfill()
case let .failure(error): case let .failure(error):
XCTFail("Unpected failure with error: \(error)") XCTFail("Unpected failure with error: \(error)")
@ -377,7 +378,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
synchronizer.shieldFunds(spendingKey: mockedSpendingKey, memo: memo, shieldingThreshold: shieldingThreshold) { result in synchronizer.shieldFunds(spendingKey: mockedSpendingKey, memo: memo, shieldingThreshold: shieldingThreshold) { result in
switch result { switch result {
case let .success(receivedEntity): case let .success(receivedEntity):
XCTAssertEqual(receivedEntity.recipient, self.data.pendingTransactionEntity.recipient) XCTAssertEqual(receivedEntity.value, self.data.pendingTransactionEntity.value)
expectation.fulfill() expectation.fulfill()
case let .failure(error): case let .failure(error):
XCTFail("Unpected failure with error: \(error)") XCTFail("Unpected failure with error: \(error)")
@ -410,22 +411,6 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) 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() { func testPendingTransactionsSucceed() {
synchronizerMock.underlyingPendingTransactions = [data.pendingTransactionEntity] synchronizerMock.underlyingPendingTransactions = [data.pendingTransactionEntity]
@ -433,7 +418,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
synchronizer.pendingTransactions() { transactions in synchronizer.pendingTransactions() { transactions in
XCTAssertEqual(transactions.count, 1) XCTAssertEqual(transactions.count, 1)
XCTAssertEqual(transactions[0].recipient, self.data.pendingTransactionEntity.recipient) XCTAssertEqual(transactions[0].id, self.data.pendingTransactionEntity.id)
expectation.fulfill() expectation.fulfill()
} }
@ -441,7 +426,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testClearedTransactionsSucceed() { func testClearedTransactionsSucceed() {
synchronizerMock.underlyingClearedTransactions = [data.clearedTransaction] synchronizerMock.underlyingTransactions = [data.clearedTransaction]
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
@ -525,92 +510,6 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) 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() { func testGetRecipientsForClearedTransaction() {
let expectedRecipient: TransactionRecipient = .address(.transparent(data.transparentAddress)) let expectedRecipient: TransactionRecipient = .address(.transparent(data.transparentAddress))
@ -630,27 +529,8 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) 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 { func testAllConfirmedTransactionsSucceed() throws {
synchronizerMock.allConfirmedTransactionsFromLimitClosure = { receivedTransaction, limit in synchronizerMock.allTransactionsFromLimitClosure = { receivedTransaction, limit in
XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id)
XCTAssertEqual(limit, 3) XCTAssertEqual(limit, 3)
return [self.data.clearedTransaction] return [self.data.clearedTransaction]
@ -673,7 +553,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testAllConfirmedTransactionsThrowsError() throws { func testAllConfirmedTransactionsThrowsError() throws {
synchronizerMock.allConfirmedTransactionsFromLimitClosure = { _, _ in synchronizerMock.allTransactionsFromLimitClosure = { _, _ in
throw "Some error" throw "Some error"
} }

View File

@ -329,7 +329,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
}, },
receiveValue: { value in receiveValue: { value in
XCTAssertEqual(value.recipient, self.data.pendingTransactionEntity.recipient) XCTAssertEqual(value.value, self.data.pendingTransactionEntity.value)
} }
) )
.store(in: &cancellables) .store(in: &cancellables)
@ -393,7 +393,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
}, },
receiveValue: { value in receiveValue: { value in
XCTAssertEqual(value.recipient, self.data.pendingTransactionEntity.recipient) XCTAssertEqual(value.id, self.data.pendingTransactionEntity.id)
} }
) )
.store(in: &cancellables) .store(in: &cancellables)
@ -431,33 +431,6 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) 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() { func testPendingTransactionsSucceed() {
synchronizerMock.underlyingPendingTransactions = [data.pendingTransactionEntity] synchronizerMock.underlyingPendingTransactions = [data.pendingTransactionEntity]
@ -483,11 +456,11 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
func testClearedTransactionsSucceed() { func testClearedTransactionsSucceed() {
synchronizerMock.underlyingClearedTransactions = [data.clearedTransaction] synchronizerMock.underlyingTransactions = [data.clearedTransaction]
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.clearedTransactions synchronizer.allTransactions
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -609,118 +582,8 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) 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() { func testGetRecipientsForClearedTransaction() {
let expectedRecipient: TransactionRecipient = .address(.transparent(data.transparentAddress)) let expectedRecipient: TransactionRecipient = TransactionRecipient.address(.transparent(self.data.transparentAddress))
synchronizerMock.getRecipientsForClearedTransactionClosure = { receivedTransaction in synchronizerMock.getRecipientsForClearedTransactionClosure = { receivedTransaction in
XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id)
@ -748,37 +611,8 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) 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 { func testAllConfirmedTransactionsSucceed() throws {
synchronizerMock.allConfirmedTransactionsFromLimitClosure = { receivedTransaction, limit in synchronizerMock.allTransactionsFromLimitClosure = { receivedTransaction, limit in
XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id)
XCTAssertEqual(limit, 3) XCTAssertEqual(limit, 3)
return [self.data.clearedTransaction] return [self.data.clearedTransaction]
@ -786,7 +620,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.allConfirmedTransactions(from: data.clearedTransaction, limit: 3) synchronizer.allTransactions(from: data.clearedTransaction, limit: 3)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -806,13 +640,13 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
func testAllConfirmedTransactionsThrowsError() throws { func testAllConfirmedTransactionsThrowsError() throws {
synchronizerMock.allConfirmedTransactionsFromLimitClosure = { _, _ in synchronizerMock.allTransactionsFromLimitClosure = { _, _ in
throw "Some error" throw "Some error"
} }
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.allConfirmedTransactions(from: data.clearedTransaction, limit: 3) synchronizer.allTransactions(from: data.clearedTransaction, limit: 3)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {

View File

@ -20,7 +20,6 @@ class InitializerOfflineTests: XCTestCase {
private func makeInitializer( private func makeInitializer(
fsBlockDbRoot: URL, fsBlockDbRoot: URL,
dataDbURL: URL, dataDbURL: URL,
pendingDbURL: URL,
spendParamsURL: URL, spendParamsURL: URL,
outputParamsURL: URL, outputParamsURL: URL,
alias: ZcashSynchronizerAlias alias: ZcashSynchronizerAlias
@ -29,7 +28,6 @@ class InitializerOfflineTests: XCTestCase {
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: fsBlockDbRoot, fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL, dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: LightWalletEndpointBuilder.default, endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: .testnet), network: ZcashNetworkBuilder.network(for: .testnet),
spendParamsURL: spendParamsURL, spendParamsURL: spendParamsURL,
@ -54,7 +52,6 @@ class InitializerOfflineTests: XCTestCase {
private func genericTestForURLsParsingFailures( private func genericTestForURLsParsingFailures(
fsBlockDbRoot: URL, fsBlockDbRoot: URL,
dataDbURL: URL, dataDbURL: URL,
pendingDbURL: URL,
spendParamsURL: URL, spendParamsURL: URL,
outputParamsURL: URL, outputParamsURL: URL,
alias: ZcashSynchronizerAlias, alias: ZcashSynchronizerAlias,
@ -63,7 +60,6 @@ class InitializerOfflineTests: XCTestCase {
let initializer = makeInitializer( let initializer = makeInitializer(
fsBlockDbRoot: fsBlockDbRoot, fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL, dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
spendParamsURL: spendParamsURL, spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL, outputParamsURL: outputParamsURL,
alias: alias alias: alias
@ -77,7 +73,6 @@ class InitializerOfflineTests: XCTestCase {
XCTAssertEqual(initializer.fsBlockDbRoot, fsBlockDbRoot, "Failing \(function)") XCTAssertEqual(initializer.fsBlockDbRoot, fsBlockDbRoot, "Failing \(function)")
XCTAssertEqual(initializer.dataDbURL, dataDbURL, "Failing \(function)") XCTAssertEqual(initializer.dataDbURL, dataDbURL, "Failing \(function)")
XCTAssertEqual(initializer.pendingDbURL, pendingDbURL, "Failing \(function)")
XCTAssertEqual(initializer.spendParamsURL, spendParamsURL, "Failing \(function)") XCTAssertEqual(initializer.spendParamsURL, spendParamsURL, "Failing \(function)")
XCTAssertEqual(initializer.outputParamsURL, outputParamsURL, "Failing \(function)") XCTAssertEqual(initializer.outputParamsURL, outputParamsURL, "Failing \(function)")
} }
@ -86,7 +81,6 @@ class InitializerOfflineTests: XCTestCase {
let initializer = makeInitializer( let initializer = makeInitializer(
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL, dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL, spendParamsURL: validFileURL,
outputParamsURL: validFileURL, outputParamsURL: validFileURL,
alias: .default alias: .default
@ -95,7 +89,6 @@ class InitializerOfflineTests: XCTestCase {
XCTAssertNil(initializer.urlsParsingError) XCTAssertNil(initializer.urlsParsingError)
XCTAssertEqual(initializer.fsBlockDbRoot, validDirectoryURL) XCTAssertEqual(initializer.fsBlockDbRoot, validDirectoryURL)
XCTAssertEqual(initializer.dataDbURL, validFileURL) XCTAssertEqual(initializer.dataDbURL, validFileURL)
XCTAssertEqual(initializer.pendingDbURL, validFileURL)
XCTAssertEqual(initializer.spendParamsURL, validFileURL) XCTAssertEqual(initializer.spendParamsURL, validFileURL)
XCTAssertEqual(initializer.outputParamsURL, validFileURL) XCTAssertEqual(initializer.outputParamsURL, validFileURL)
} }
@ -104,7 +97,6 @@ class InitializerOfflineTests: XCTestCase {
genericTestForURLsParsingFailures( genericTestForURLsParsingFailures(
fsBlockDbRoot: invalidPathURL, fsBlockDbRoot: invalidPathURL,
dataDbURL: validFileURL, dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL, spendParamsURL: validFileURL,
outputParamsURL: validFileURL, outputParamsURL: validFileURL,
alias: .default alias: .default
@ -115,18 +107,6 @@ class InitializerOfflineTests: XCTestCase {
genericTestForURLsParsingFailures( genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: invalidPathURL, dataDbURL: invalidPathURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: .default
)
}
func test__defaultAlias__invalidPendingDbURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: invalidPathURL,
spendParamsURL: validFileURL, spendParamsURL: validFileURL,
outputParamsURL: validFileURL, outputParamsURL: validFileURL,
alias: .default alias: .default
@ -137,7 +117,6 @@ class InitializerOfflineTests: XCTestCase {
genericTestForURLsParsingFailures( genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL, dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: invalidPathURL, spendParamsURL: invalidPathURL,
outputParamsURL: validFileURL, outputParamsURL: validFileURL,
alias: .default alias: .default
@ -148,7 +127,6 @@ class InitializerOfflineTests: XCTestCase {
genericTestForURLsParsingFailures( genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL, dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL, spendParamsURL: validFileURL,
outputParamsURL: invalidPathURL, outputParamsURL: invalidPathURL,
alias: .default alias: .default
@ -160,7 +138,6 @@ class InitializerOfflineTests: XCTestCase {
let initializer = makeInitializer( let initializer = makeInitializer(
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL, dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL, spendParamsURL: validFileURL,
outputParamsURL: validFileURL, outputParamsURL: validFileURL,
alias: alias alias: alias
@ -169,7 +146,6 @@ class InitializerOfflineTests: XCTestCase {
XCTAssertNil(initializer.urlsParsingError) XCTAssertNil(initializer.urlsParsingError)
XCTAssertEqual(initializer.fsBlockDbRoot, update(url: validDirectoryURL, with: alias)) XCTAssertEqual(initializer.fsBlockDbRoot, update(url: validDirectoryURL, with: alias))
XCTAssertEqual(initializer.dataDbURL, update(url: validFileURL, 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.spendParamsURL, update(url: validFileURL, with: alias))
XCTAssertEqual(initializer.outputParamsURL, update(url: validFileURL, with: alias)) XCTAssertEqual(initializer.outputParamsURL, update(url: validFileURL, with: alias))
} }
@ -178,7 +154,6 @@ class InitializerOfflineTests: XCTestCase {
genericTestForURLsParsingFailures( genericTestForURLsParsingFailures(
fsBlockDbRoot: invalidPathURL, fsBlockDbRoot: invalidPathURL,
dataDbURL: validFileURL, dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL, spendParamsURL: validFileURL,
outputParamsURL: validFileURL, outputParamsURL: validFileURL,
alias: .custom("alias") alias: .custom("alias")
@ -189,18 +164,6 @@ class InitializerOfflineTests: XCTestCase {
genericTestForURLsParsingFailures( genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: invalidPathURL, 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, spendParamsURL: validFileURL,
outputParamsURL: validFileURL, outputParamsURL: validFileURL,
alias: .custom("alias") alias: .custom("alias")
@ -211,7 +174,6 @@ class InitializerOfflineTests: XCTestCase {
genericTestForURLsParsingFailures( genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL, dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: invalidPathURL, spendParamsURL: invalidPathURL,
outputParamsURL: validFileURL, outputParamsURL: validFileURL,
alias: .custom("alias") alias: .custom("alias")
@ -222,7 +184,6 @@ class InitializerOfflineTests: XCTestCase {
genericTestForURLsParsingFailures( genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL, dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL, spendParamsURL: validFileURL,
outputParamsURL: invalidPathURL, outputParamsURL: invalidPathURL,
alias: .custom("alias") alias: .custom("alias")

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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 { func testShieldFundsCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false) 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) await fulfillment(of: [expectation], timeout: 1)
} }
func testURLsParsingFailsInInitialierPrepareThenThrowsError() async throws { func testURLsParsingFailsInInitializerPrepareThenThrowsError() async throws {
let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file") let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file")
let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory") let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory")
let invalidPathURL = URL(string: "https://whatever")! let invalidPathURL = URL(string: "https://whatever")!
@ -241,8 +260,7 @@ class SynchronizerOfflineTests: XCTestCase {
let initializer = Initializer( let initializer = Initializer(
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL, dataDbURL: invalidPathURL,
pendingDbURL: invalidPathURL,
endpoint: LightWalletEndpointBuilder.default, endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: .testnet), network: ZcashNetworkBuilder.network(for: .testnet),
spendParamsURL: validFileURL, 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 validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file")
let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory") let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory")
let invalidPathURL = URL(string: "https://whatever")! let invalidPathURL = URL(string: "https://whatever")!
@ -282,8 +300,7 @@ class SynchronizerOfflineTests: XCTestCase {
let initializer = Initializer( let initializer = Initializer(
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: validDirectoryURL, fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL, dataDbURL: invalidPathURL,
pendingDbURL: invalidPathURL,
endpoint: LightWalletEndpointBuilder.default, endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: .testnet), network: ZcashNetworkBuilder.network(for: .testnet),
spendParamsURL: validFileURL, spendParamsURL: validFileURL,

View File

@ -134,18 +134,22 @@ class TransactionRepositoryTests: XCTestCase {
} }
func testFindMemoForReceivedTransaction() async throws { func testFindMemoForReceivedTransaction() async throws {
let transaction = ZcashTransaction.Received( let transaction = ZcashTransaction.Overview(
accountId: 0,
blockTime: 1, blockTime: 1,
expiryHeight: nil, expiryHeight: nil,
fromAccount: 0, fee: nil,
id: 5, id: 5,
index: 0, index: 0,
memoCount: 0, hasChange: false,
memoCount: 1,
minedHeight: 0, minedHeight: 0,
noteCount: 0,
raw: nil, raw: nil,
rawID: nil, rawID: Data(),
value: Zatoshi.zero receivedNoteCount: 1,
sentNoteCount: 0,
value: .zero,
isExpiredUmined: false
) )
let memos = try await self.transactionRepository.findMemos(for: transaction) let memos = try await self.transactionRepository.findMemos(for: transaction)
@ -154,18 +158,22 @@ class TransactionRepositoryTests: XCTestCase {
} }
func testFindMemoForSentTransaction() async throws { func testFindMemoForSentTransaction() async throws {
let transaction = ZcashTransaction.Sent( let transaction = ZcashTransaction.Overview(
accountId: 0,
blockTime: 1, blockTime: 1,
expiryHeight: nil, expiryHeight: nil,
fromAccount: 0, fee: nil,
id: 9, id: 9,
index: 0, index: 0,
memoCount: 0, hasChange: false,
minedHeight: 0, memoCount: 1,
noteCount: 0, minedHeight: nil,
raw: nil, raw: nil,
rawID: nil, rawID: Data(),
value: Zatoshi.zero receivedNoteCount: 0,
sentNoteCount: 2,
value: .zero,
isExpiredUmined: false
) )
let memos = try await self.transactionRepository.findMemos(for: transaction) let memos = try await self.transactionRepository.findMemos(for: transaction)

View File

@ -44,7 +44,6 @@ class WalletTests: XCTestCase {
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
dataDbURL: try __dataDbURL(), dataDbURL: try __dataDbURL(),
pendingDbURL: try TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default, endpoint: LightWalletEndpointBuilder.default,
network: network, network: network,
spendParamsURL: try __spendParamsURL(), spendParamsURL: try __spendParamsURL(),

View File

@ -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
)
}
}

View File

@ -72,7 +72,6 @@ class SynchronizerTests: XCTestCase {
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: databases.fsCacheDbRoot, fsBlockDbRoot: databases.fsCacheDbRoot,
dataDbURL: databases.dataDB, dataDbURL: databases.dataDB,
pendingDbURL: databases.pendingDB,
endpoint: endpoint, endpoint: endpoint,
network: network, network: network,
spendParamsURL: try __spendParamsURL(), spendParamsURL: try __spendParamsURL(),
@ -84,7 +83,6 @@ class SynchronizerTests: XCTestCase {
try? FileManager.default.removeItem(at: databases.fsCacheDbRoot) try? FileManager.default.removeItem(at: databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: databases.dataDB) try? FileManager.default.removeItem(at: databases.dataDB)
try? FileManager.default.removeItem(at: databases.pendingDB)
synchronizer = SDKSynchronizer(initializer: initializer) synchronizer = SDKSynchronizer(initializer: initializer)

View File

@ -195,10 +195,6 @@ enum DarksideWalletDConstants: NetworkConstants {
ZcashSDKMainnetConstants.defaultCacheDbName ZcashSDKMainnetConstants.defaultCacheDbName
} }
static var defaultPendingDbName: String {
ZcashSDKMainnetConstants.defaultPendingDbName
}
static var defaultDbNamePrefix: String { static var defaultDbNamePrefix: String {
ZcashSDKMainnetConstants.defaultDbNamePrefix ZcashSDKMainnetConstants.defaultDbNamePrefix
} }

View File

@ -26,8 +26,8 @@ class MockTransactionRepository {
var network: ZcashNetwork var network: ZcashNetwork
var transactions: [ZcashTransaction.Overview] = [] var transactions: [ZcashTransaction.Overview] = []
var receivedTransactions: [ZcashTransaction.Received] = [] var receivedTransactions: [ZcashTransaction.Overview] = []
var sentTransactions: [ZcashTransaction.Sent] = [] var sentTransactions: [ZcashTransaction.Overview] = []
var allCount: Int { var allCount: Int {
receivedCount + sentCount receivedCount + sentCount
@ -73,6 +73,14 @@ extension MockTransactionRepository.Kind: Equatable {}
// MARK: - TransactionRepository // MARK: - TransactionRepository
extension MockTransactionRepository: 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] { func getRecipients(for id: Int) -> [TransactionRecipient] {
[] []
} }
@ -203,25 +211,17 @@ extension MockTransactionRepository: TransactionRepository {
throw MockTransactionRepositoryError.notImplemented throw MockTransactionRepositoryError.notImplemented
} }
func findReceived(offset: Int, limit: Int) throws -> [ZcashTransaction.Received] { func findReceived(offset: Int, limit: Int) throws -> [ZcashTransaction.Overview] {
throw MockTransactionRepositoryError.notImplemented throw MockTransactionRepositoryError.notImplemented
} }
func findSent(offset: Int, limit: Int) throws -> [ZcashTransaction.Sent] { func findSent(offset: Int, limit: Int) throws -> [ZcashTransaction.Overview] {
throw MockTransactionRepositoryError.notImplemented throw MockTransactionRepositoryError.notImplemented
} }
func findMemos(for transaction: ZcashLightClientKit.ZcashTransaction.Overview) throws -> [ZcashLightClientKit.Memo] { func findMemos(for transaction: ZcashLightClientKit.ZcashTransaction.Overview) throws -> [ZcashLightClientKit.Memo] {
throw MockTransactionRepositoryError.notImplemented 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 { extension Array {

View File

@ -18,7 +18,7 @@ extension SDKSynchronizer {
self.init( self.init(
status: .unprepared, status: .unprepared,
initializer: initializer, initializer: initializer,
transactionManager: OutboundTransactionManagerBuilder.build(initializer: initializer), transactionEncoder: WalletTransactionEncoder(initializer: initializer),
transactionRepository: initializer.transactionRepository, transactionRepository: initializer.transactionRepository,
utxoRepository: UTXORepositoryBuilder.build(initializer: initializer), utxoRepository: UTXORepositoryBuilder.build(initializer: initializer),
blockProcessor: CompactBlockProcessor( blockProcessor: CompactBlockProcessor(

View File

@ -36,22 +36,22 @@ class SynchronizerMock: Synchronizer {
get { return underlyingMetrics } get { return underlyingMetrics }
} }
var underlyingMetrics: SDKMetrics! var underlyingMetrics: SDKMetrics!
var pendingTransactions: [PendingTransactionEntity] { var pendingTransactions: [ZcashTransaction.Overview] {
get async { return underlyingPendingTransactions } get async { return underlyingPendingTransactions }
} }
var underlyingPendingTransactions: [PendingTransactionEntity] = [] var underlyingPendingTransactions: [ZcashTransaction.Overview] = []
var clearedTransactions: [ZcashTransaction.Overview] { var transactions: [ZcashTransaction.Overview] {
get async { return underlyingClearedTransactions } get async { return underlyingTransactions }
} }
var underlyingClearedTransactions: [ZcashTransaction.Overview] = [] var underlyingTransactions: [ZcashTransaction.Overview] = []
var sentTransactions: [ZcashTransaction.Sent] { var sentTransactions: [ZcashTransaction.Overview] {
get async { return underlyingSentTransactions } get async { return underlyingSentTransactions }
} }
var underlyingSentTransactions: [ZcashTransaction.Sent] = [] var underlyingSentTransactions: [ZcashTransaction.Overview] = []
var receivedTransactions: [ZcashTransaction.Received] { var receivedTransactions: [ZcashTransaction.Overview] {
get async { return underlyingReceivedTransactions } get async { return underlyingReceivedTransactions }
} }
var underlyingReceivedTransactions: [ZcashTransaction.Received] = [] var underlyingReceivedTransactions: [ZcashTransaction.Overview] = []
// MARK: - prepare // MARK: - prepare
@ -189,10 +189,10 @@ class SynchronizerMock: Synchronizer {
return sendToAddressSpendingKeyZatoshiToAddressMemoCallsCount > 0 return sendToAddressSpendingKeyZatoshiToAddressMemoCallsCount > 0
} }
var sendToAddressSpendingKeyZatoshiToAddressMemoReceivedArguments: (spendingKey: UnifiedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, memo: Memo?)? var sendToAddressSpendingKeyZatoshiToAddressMemoReceivedArguments: (spendingKey: UnifiedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, memo: Memo?)?
var sendToAddressSpendingKeyZatoshiToAddressMemoReturnValue: PendingTransactionEntity! var sendToAddressSpendingKeyZatoshiToAddressMemoReturnValue: ZcashTransaction.Overview!
var sendToAddressSpendingKeyZatoshiToAddressMemoClosure: ((UnifiedSpendingKey, Zatoshi, Recipient, Memo?) async throws -> PendingTransactionEntity)? 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 { if let error = sendToAddressSpendingKeyZatoshiToAddressMemoThrowableError {
throw error throw error
} }
@ -213,10 +213,10 @@ class SynchronizerMock: Synchronizer {
return shieldFundsSpendingKeyMemoShieldingThresholdCallsCount > 0 return shieldFundsSpendingKeyMemoShieldingThresholdCallsCount > 0
} }
var shieldFundsSpendingKeyMemoShieldingThresholdReceivedArguments: (spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi)? var shieldFundsSpendingKeyMemoShieldingThresholdReceivedArguments: (spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi)?
var shieldFundsSpendingKeyMemoShieldingThresholdReturnValue: PendingTransactionEntity! var shieldFundsSpendingKeyMemoShieldingThresholdReturnValue: ZcashTransaction.Overview!
var shieldFundsSpendingKeyMemoShieldingThresholdClosure: ((UnifiedSpendingKey, Memo, Zatoshi) async throws -> PendingTransactionEntity)? 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 { if let error = shieldFundsSpendingKeyMemoShieldingThresholdThrowableError {
throw error 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 // MARK: - paginatedTransactions
var paginatedTransactionsOfCallsCount = 0 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 // MARK: - getRecipients
var getRecipientsForClearedTransactionCallsCount = 0 var getRecipientsForClearedTransactionCallsCount = 0
@ -361,47 +293,69 @@ class SynchronizerMock: Synchronizer {
} }
} }
// MARK: - getRecipients // MARK: - getTransactionOutputs
var getRecipientsForSentTransactionCallsCount = 0 var getTransactionOutputsForTransactionCallsCount = 0
var getRecipientsForSentTransactionCalled: Bool { var getTransactionOutputsForTransactionCalled: Bool {
return getRecipientsForSentTransactionCallsCount > 0 return getTransactionOutputsForTransactionCallsCount > 0
} }
var getRecipientsForSentTransactionReceivedTransaction: ZcashTransaction.Sent? var getTransactionOutputsForTransactionReceivedTransaction: ZcashTransaction.Overview?
var getRecipientsForSentTransactionReturnValue: [TransactionRecipient]! var getTransactionOutputsForTransactionReturnValue: [ZcashTransaction.Output]!
var getRecipientsForSentTransactionClosure: ((ZcashTransaction.Sent) async -> [TransactionRecipient])? var getTransactionOutputsForTransactionClosure: ((ZcashTransaction.Overview) async -> [ZcashTransaction.Output])?
func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient] { func getTransactionOutputs(for transaction: ZcashTransaction.Overview) async -> [ZcashTransaction.Output] {
getRecipientsForSentTransactionCallsCount += 1 getTransactionOutputsForTransactionCallsCount += 1
getRecipientsForSentTransactionReceivedTransaction = transaction getTransactionOutputsForTransactionReceivedTransaction = transaction
if let closure = getRecipientsForSentTransactionClosure { if let closure = getTransactionOutputsForTransactionClosure {
return await closure(transaction) return await closure(transaction)
} else { } else {
return getRecipientsForSentTransactionReturnValue return getTransactionOutputsForTransactionReturnValue
} }
} }
// MARK: - allConfirmedTransactions // MARK: - allTransactions
var allConfirmedTransactionsFromLimitThrowableError: Error? var allTransactionsFromLimitThrowableError: Error?
var allConfirmedTransactionsFromLimitCallsCount = 0 var allTransactionsFromLimitCallsCount = 0
var allConfirmedTransactionsFromLimitCalled: Bool { var allTransactionsFromLimitCalled: Bool {
return allConfirmedTransactionsFromLimitCallsCount > 0 return allTransactionsFromLimitCallsCount > 0
} }
var allConfirmedTransactionsFromLimitReceivedArguments: (transaction: ZcashTransaction.Overview, limit: Int)? var allTransactionsFromLimitReceivedArguments: (transaction: ZcashTransaction.Overview, limit: Int)?
var allConfirmedTransactionsFromLimitReturnValue: [ZcashTransaction.Overview]! var allTransactionsFromLimitReturnValue: [ZcashTransaction.Overview]!
var allConfirmedTransactionsFromLimitClosure: ((ZcashTransaction.Overview, Int) async throws -> [ZcashTransaction.Overview])? var allTransactionsFromLimitClosure: ((ZcashTransaction.Overview, Int) async throws -> [ZcashTransaction.Overview])?
func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] { func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] {
if let error = allConfirmedTransactionsFromLimitThrowableError { if let error = allTransactionsFromLimitThrowableError {
throw error throw error
} }
allConfirmedTransactionsFromLimitCallsCount += 1 allTransactionsFromLimitCallsCount += 1
allConfirmedTransactionsFromLimitReceivedArguments = (transaction: transaction, limit: limit) allTransactionsFromLimitReceivedArguments = (transaction: transaction, limit: limit)
if let closure = allConfirmedTransactionsFromLimitClosure { if let closure = allTransactionsFromLimitClosure {
return try await closure(transaction, limit) return try await closure(transaction, limit)
} else { } 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
} }
} }

View File

@ -59,25 +59,71 @@ class TestCoordinator {
network: ZcashNetwork, network: ZcashNetwork,
callPrepareInConstructor: Bool = true, callPrepareInConstructor: Bool = true,
endpoint: LightWalletEndpoint = TestCoordinator.defaultEndpoint, endpoint: LightWalletEndpoint = TestCoordinator.defaultEndpoint,
syncSessionIDGenerator: SyncSessionIDGenerator = UniqueSyncSessionIDGenerator() syncSessionIDGenerator: SyncSessionIDGenerator = UniqueSyncSessionIDGenerator(),
dbTracingClosure: ((String) -> Void)? = nil
) async throws { ) async throws {
await InternalSyncProgress(alias: alias, storage: UserDefaults.standard, logger: logger).rewind(to: 0) await InternalSyncProgress(alias: alias, storage: UserDefaults.standard, logger: logger).rewind(to: 0)
let databases = TemporaryDbBuilder.build() let databases = TemporaryDbBuilder.build()
self.databases = databases self.databases = databases
let initializer = Initializer( let urls = Initializer.URLs(
cacheDbURL: nil,
fsBlockDbRoot: databases.fsCacheDbRoot, fsBlockDbRoot: databases.fsCacheDbRoot,
dataDbURL: databases.dataDB, dataDbURL: databases.dataDB,
pendingDbURL: databases.pendingDB,
endpoint: endpoint,
network: network,
spendParamsURL: try __spendParamsURL(), 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, saplingParamsSourceURL: SaplingParamsSourceURL.tests,
alias: alias, alias: alias,
loggingPolicy: .default(.debug) urlsParsingError: parsingError,
logger: OSLogger(logLevel: .debug)
) )
let derivationTool = DerivationTool(networkType: network.networkType) let derivationTool = DerivationTool(networkType: network.networkType)
@ -243,7 +289,6 @@ extension TestCoordinator {
struct TemporaryTestDatabases { struct TemporaryTestDatabases {
var fsCacheDbRoot: URL var fsCacheDbRoot: URL
var dataDB: URL var dataDB: URL
var pendingDB: URL
} }
enum TemporaryDbBuilder { enum TemporaryDbBuilder {
@ -253,8 +298,7 @@ enum TemporaryDbBuilder {
return TemporaryTestDatabases( return TemporaryTestDatabases(
fsCacheDbRoot: tempUrl.appendingPathComponent("fs_cache_\(timestamp)"), fsCacheDbRoot: tempUrl.appendingPathComponent("fs_cache_\(timestamp)"),
dataDB: tempUrl.appendingPathComponent("data_db_\(timestamp).db"), dataDB: tempUrl.appendingPathComponent("data_db_\(timestamp).db")
pendingDB: tempUrl.appendingPathComponent("pending_db_\(timestamp).db")
) )
} }
} }

View File

@ -42,10 +42,6 @@ enum TestDbBuilder {
case generalError case generalError
} }
static func pendingTransactionsDbURL() throws -> URL {
try __documentsDirectory().appendingPathComponent("pending.db")
}
static func prePopulatedDataDbURL() -> URL? { static func prePopulatedDataDbURL() -> URL? {
Bundle.module.url(forResource: "test_data", withExtension: "db") Bundle.module.url(forResource: "test_data", withExtension: "db")
} }
@ -68,7 +64,7 @@ enum TestDbBuilder {
switch initResult { switch initResult {
case .success: return provider case .success: return provider
case .seedRequired: 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) 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 { static func seed(db: CompactBlockRepository, with blockRange: CompactBlockRange) async throws {
guard let blocks = StubBlockCreator.createBlockRange(blockRange) else { guard let blocks = StubBlockCreator.createBlockRange(blockRange) else {

View File

@ -16,7 +16,6 @@ class TestsData {
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: URL(fileURLWithPath: "/"), fsBlockDbRoot: URL(fileURLWithPath: "/"),
dataDbURL: URL(fileURLWithPath: "/"), dataDbURL: URL(fileURLWithPath: "/"),
pendingDbURL: URL(fileURLWithPath: "/"),
endpoint: LightWalletEndpointBuilder.default, endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: networkType), network: ZcashNetworkBuilder.network(for: networkType),
spendParamsURL: URL(fileURLWithPath: "/"), spendParamsURL: URL(fileURLWithPath: "/"),
@ -34,7 +33,23 @@ class TestsData {
) )
let transparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz") let transparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz")
lazy var pendingTransactionEntity = { 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 = { let clearedTransaction = {
@ -58,34 +73,42 @@ class TestsData {
}() }()
let sentTransaction = { let sentTransaction = {
ZcashTransaction.Sent( ZcashTransaction.Overview(
accountId: 0,
blockTime: 1, blockTime: 1,
expiryHeight: nil, expiryHeight: nil,
fromAccount: 0, fee: Zatoshi(10000),
id: 9, id: 9,
index: 0, index: 0,
hasChange: true,
memoCount: 0, memoCount: 0,
minedHeight: 0, minedHeight: 0,
noteCount: 0,
raw: nil, raw: nil,
rawID: nil, rawID: Data(),
value: Zatoshi.zero receivedNoteCount: 0,
sentNoteCount: 2,
value: .zero,
isExpiredUmined: false
) )
}() }()
let receivedTransaction = { let receivedTransaction = {
ZcashTransaction.Received( ZcashTransaction.Overview(
accountId: 0,
blockTime: 1, blockTime: 1,
expiryHeight: nil, expiryHeight: nil,
fromAccount: 0, fee: nil,
id: 9, id: 9,
index: 0, index: 0,
hasChange: true,
memoCount: 0, memoCount: 0,
minedHeight: 0, minedHeight: 0,
noteCount: 0,
raw: nil, raw: nil,
rawID: nil, rawID: Data(),
value: Zatoshi.zero receivedNoteCount: 0,
sentNoteCount: 2,
value: .zero,
isExpiredUmined: false
) )
}() }()

View File

@ -1,6 +1,6 @@
# Development Process # 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. Even though the SDK is currently in **beta** and developers should always use the dependencies released through published tags.