diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 15d91fe3..587a1805 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -176,8 +176,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "7c801be1f445402a433b32835a50d832e8a50437", - "version" : "0.6.0" + "revision" : "4eccfb5ea825f5f0ebb3cab964d08ef21103feb4" } } ], diff --git a/Package.resolved b/Package.resolved index 6ed2aaaf..176c726a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -122,8 +122,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "7c801be1f445402a433b32835a50d832e8a50437", - "version" : "0.6.0" + "revision" : "4eccfb5ea825f5f0ebb3cab964d08ef21103feb4" } } ], diff --git a/Package.swift b/Package.swift index f86b18e9..3aa8d8cf 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.19.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", exact: "0.6.0") + // Compiled from revision `c2ac47300d062b76134c515589301b202277e3fa`. + .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "4eccfb5ea825f5f0ebb3cab964d08ef21103feb4") ], targets: [ .target( diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index 4eb567ef..81f48917 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -28,7 +28,6 @@ actor CompactBlockProcessor { private let configProvider: ConfigProvider private var afterSyncHooksManager = AfterSyncHooksManager() - private let accountRepository: AccountRepository var blockDownloaderService: BlockDownloaderService private var latestBlocksDataProvider: LatestBlocksDataProvider private let logger: Logger @@ -142,20 +141,6 @@ actor CompactBlockProcessor { } } - /// Initializes a CompactBlockProcessor instance - /// - Parameters: - /// - service: concrete implementation of `LightWalletService` protocol - /// - storage: concrete implementation of `CompactBlockRepository` protocol - /// - backend: a class that complies to `ZcashRustBackendWelding` - /// - config: `Configuration` struct for this processor - init(container: DIContainer, config: Configuration) { - self.init( - container: container, - config: config, - accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true, logger: container.resolve(Logger.self)) - ) - } - /// Initializes a CompactBlockProcessor instance from an Initialized object /// - Parameters: /// - initializer: an instance that complies to CompactBlockDownloading protocol @@ -171,20 +156,23 @@ actor CompactBlockProcessor { saplingParamsSourceURL: initializer.saplingParamsSourceURL, walletBirthdayProvider: walletBirthdayProvider, network: initializer.network - ), - accountRepository: initializer.accountRepository + ) ) } + /// Initializes a CompactBlockProcessor instance + /// - Parameters: + /// - service: concrete implementation of `LightWalletService` protocol + /// - storage: concrete implementation of `CompactBlockRepository` protocol + /// - backend: a class that complies to `ZcashRustBackendWelding` + /// - config: `Configuration` struct for this processor init( container: DIContainer, - config: Configuration, - accountRepository: AccountRepository + config: Configuration ) { Dependencies.setupCompactBlockProcessor( in: container, - config: config, - accountRepository: accountRepository + config: config ) let configProvider = ConfigProvider(config: config) @@ -200,7 +188,6 @@ actor CompactBlockProcessor { self.storage = container.resolve(CompactBlockRepository.self) self.config = config self.transactionRepository = container.resolve(TransactionRepository.self) - self.accountRepository = accountRepository self.fileManager = container.resolve(ZcashFileManager.self) self.configProvider = configProvider } diff --git a/Sources/ZcashLightClientKit/Block/FetchUnspentTxOutputs/UTXOFetcher.swift b/Sources/ZcashLightClientKit/Block/FetchUnspentTxOutputs/UTXOFetcher.swift index bbff3986..f9c51229 100644 --- a/Sources/ZcashLightClientKit/Block/FetchUnspentTxOutputs/UTXOFetcher.swift +++ b/Sources/ZcashLightClientKit/Block/FetchUnspentTxOutputs/UTXOFetcher.swift @@ -23,7 +23,6 @@ protocol UTXOFetcher { } struct UTXOFetcherImpl { - let accountRepository: AccountRepository let blockDownloaderService: BlockDownloaderService let config: UTXOFetcherConfig let rustBackend: ZcashRustBackendWelding @@ -37,8 +36,7 @@ extension UTXOFetcherImpl: UTXOFetcher { ) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) { try Task.checkCancellation() - let accounts = try accountRepository.getAll() - .map { $0.account } + let accounts = try await rustBackend.listAccounts() var tAddresses: [TransparentAddress] = [] for account in accounts { diff --git a/Sources/ZcashLightClientKit/Entity/AccountEntity.swift b/Sources/ZcashLightClientKit/Entity/AccountEntity.swift index 32e7c710..17d83742 100644 --- a/Sources/ZcashLightClientKit/Entity/AccountEntity.swift +++ b/Sources/ZcashLightClientKit/Entity/AccountEntity.swift @@ -14,11 +14,6 @@ protocol AccountEntity { } struct DbAccount: AccountEntity, Encodable, Decodable { - enum CodingKeys: String, CodingKey { - case account - case ufvk - } - let account: Int let ufvk: String } @@ -38,171 +33,3 @@ extension DbAccount: Hashable { return true } } - -protocol AccountRepository { - func getAll() throws -> [AccountEntity] - func findBy(account: Int) throws -> AccountEntity? - func update(_ account: AccountEntity) throws -} - -class AccountSQDAO: AccountRepository { - enum TableColums { - static let account = Expression("account") - static let extfvk = Expression("ufvk") - } - - let table = Table("accounts") - - let dbProvider: ConnectionProvider - let logger: Logger - - init(dbProvider: ConnectionProvider, logger: Logger) { - self.dbProvider = dbProvider - self.logger = logger - } - - /// - Throws: - /// - `accountDAOGetAllCantDecode` if account data fetched from the db can't be decoded to the `Account` object. - /// - `accountDAOGetAll` if sqlite query fetching account data failed. - func getAll() throws -> [AccountEntity] { - do { - globalDBLock.lock() - defer { globalDBLock.unlock() } - - return try dbProvider.connection() - .prepare(table) - .map { row -> DbAccount in - do { - return try row.decode() - } catch { - throw ZcashError.accountDAOGetAllCantDecode(error) - } - } - } catch { - if let error = error as? ZcashError { - throw error - } else { - throw ZcashError.accountDAOGetAll(error) - } - } - } - - /// - Throws: - /// - `accountDAOFindByCantDecode` if account data fetched from the db can't be decoded to the `Account` object. - /// - `accountDAOFindBy` if sqlite query fetching account data failed. - func findBy(account: Int) throws -> AccountEntity? { - let query = table.filter(TableColums.account == account).limit(1) - do { - globalDBLock.lock() - defer { globalDBLock.unlock() } - - return try dbProvider.connection() - .prepare(query) - .map { - do { - return try $0.decode() as DbAccount - } catch { - throw ZcashError.accountDAOFindByCantDecode(error) - } - } - .first - } catch { - if let error = error as? ZcashError { - throw error - } else { - throw ZcashError.accountDAOFindBy(error) - } - } - } - - /// - Throws: - /// - `accountDAOUpdate` if sqlite query updating account failed. - /// - `accountDAOUpdatedZeroRows` if sqlite query updating account pass but it affects 0 rows. - func update(_ account: AccountEntity) throws { - guard let acc = account as? DbAccount else { - throw ZcashError.accountDAOUpdateInvalidAccount - } - - let updatedRows: Int - do { - globalDBLock.lock() - defer { globalDBLock.unlock() } - - updatedRows = try dbProvider.connection().run(table.filter(TableColums.account == acc.account).update(acc)) - } catch { - throw ZcashError.accountDAOUpdate(error) - } - - if updatedRows == 0 { - logger.error("attempted to update pending transactions but no rows were updated") - throw ZcashError.accountDAOUpdatedZeroRows - } - } -} - -class CachingAccountDao: AccountRepository { - let dao: AccountRepository - lazy var cache: [Int: AccountEntity] = { - var accountCache: [Int: AccountEntity] = [:] - guard let all = try? dao.getAll() else { - return accountCache - } - - for acc in all { - accountCache[acc.account] = acc - } - - return accountCache - }() - - init(dao: AccountRepository) { - self.dao = dao - } - - func getAll() throws -> [AccountEntity] { - guard cache.isEmpty else { - return cache.values.sorted(by: { $0.account < $1.account }) - } - - let all = try dao.getAll() - - for acc in all { - cache[acc.account] = acc - } - - return all - } - - func findBy(account: Int) throws -> AccountEntity? { - if let acc = cache[account] { - return acc - } - - let acc = try dao.findBy(account: account) - cache[account] = acc - - return acc - } - - func update(_ account: AccountEntity) throws { - try dao.update(account) - } -} - -enum AccountRepositoryBuilder { - static func build(dataDbURL: URL, readOnly: Bool = false, caching: Bool = false, logger: Logger) -> AccountRepository { - if caching { - return CachingAccountDao( - dao: AccountSQDAO( - dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: readOnly), - logger: logger - ) - ) - } else { - return AccountSQDAO( - dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: readOnly), - logger: logger - ) - } - } -} diff --git a/Sources/ZcashLightClientKit/Error/ZcashError.swift b/Sources/ZcashLightClientKit/Error/ZcashError.swift index 069ef7c3..2f7fb20e 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashError.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashError.swift @@ -325,6 +325,10 @@ public enum ZcashError: Equatable, Error { /// - `rustError` contains error generated by the rust layer. /// ZRUST0057 case rustProposeTransferFromURI(_ rustError: String) + /// Error from rust layer when calling ZcashRustBackend. + /// - `rustError` contains error generated by the rust layer. + /// ZRUST0058 + case rustListAccounts(_ rustError: String) /// SQLite query failed when fetching all accounts from the database. /// - `sqliteError` is error produced by SQLite library. /// ZADAO0001 @@ -681,6 +685,7 @@ public enum ZcashError: Equatable, Error { case .rustScanProgressOutOfRange: return "Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%." case .rustGetWalletSummary: return "Error from rust layer when calling ZcashRustBackend.getWalletSummary" case .rustProposeTransferFromURI: return "Error from rust layer when calling ZcashRustBackend." + case .rustListAccounts: return "Error from rust layer when calling ZcashRustBackend." case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database." case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them." case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database." @@ -856,6 +861,7 @@ public enum ZcashError: Equatable, Error { case .rustScanProgressOutOfRange: return .rustScanProgressOutOfRange case .rustGetWalletSummary: return .rustGetWalletSummary case .rustProposeTransferFromURI: return .rustProposeTransferFromURI + case .rustListAccounts: return .rustListAccounts case .accountDAOGetAll: return .accountDAOGetAll case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode case .accountDAOFindBy: return .accountDAOFindBy diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift index 8290036a..3eea20a3 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift @@ -177,6 +177,8 @@ public enum ZcashErrorCode: String { case rustGetWalletSummary = "ZRUST0056" /// Error from rust layer when calling ZcashRustBackend. case rustProposeTransferFromURI = "ZRUST0057" + /// Error from rust layer when calling ZcashRustBackend. + case rustListAccounts = "ZRUST0058" /// SQLite query failed when fetching all accounts from the database. case accountDAOGetAll = "ZADAO0001" /// Fetched accounts from SQLite but can't decode them. diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift index de2c84b3..f574dc1f 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift @@ -352,6 +352,10 @@ enum ZcashErrorDefinition { /// - `rustError` contains error generated by the rust layer. // sourcery: code="ZRUST0057" case rustProposeTransferFromURI(_ rustError: String) + /// Error from rust layer when calling ZcashRustBackend. + /// - `rustError` contains error generated by the rust layer. + // sourcery: code="ZRUST0058" + case rustListAccounts(_ rustError: String) // MARK: - Account DAO diff --git a/Sources/ZcashLightClientKit/Initializer.swift b/Sources/ZcashLightClientKit/Initializer.swift index db4e3c2a..d8af044b 100644 --- a/Sources/ZcashLightClientKit/Initializer.swift +++ b/Sources/ZcashLightClientKit/Initializer.swift @@ -120,7 +120,6 @@ public class Initializer { let saplingParamsSourceURL: SaplingParamsSourceURL var lightWalletService: LightWalletService let transactionRepository: TransactionRepository - let accountRepository: AccountRepository let storage: CompactBlockRepository var blockDownloaderService: BlockDownloaderService let network: ZcashNetwork @@ -272,12 +271,6 @@ public class Initializer { self.alias = alias self.lightWalletService = container.resolve(LightWalletService.self) self.transactionRepository = container.resolve(TransactionRepository.self) - self.accountRepository = AccountRepositoryBuilder.build( - dataDbURL: urls.dataDbURL, - readOnly: true, - caching: true, - logger: container.resolve(Logger.self) - ) self.storage = container.resolve(CompactBlockRepository.self) self.blockDownloaderService = container.resolve(BlockDownloaderService.self) self.network = network @@ -419,7 +412,7 @@ public class Initializer { self.walletBirthday = checkpoint.height // If there are no accounts it must be created, the default amount of accounts is 1 - if let seed, try accountRepository.getAll().isEmpty { + if let seed, try await rustBackend.listAccounts().isEmpty { var chainTip: UInt32? if walletMode == .restoreWallet { diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift index bb169dfd..3e0b9e15 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -49,6 +49,31 @@ actor ZcashRustBackend: ZcashRustBackendWelding { } } + func listAccounts() async throws -> [Int32] { + globalDBLock.lock() + let accountsPtr = zcashlc_list_accounts( + dbData.0, + dbData.1, + networkType.networkId + ) + globalDBLock.unlock() + + guard let accountsPtr else { + throw ZcashError.rustListAccounts(lastErrorMessage(fallback: "`listAccounts` failed with unknown error")) + } + + defer { zcashlc_free_accounts(accountsPtr) } + + var accounts: [Int32] = [] + + for i in (0 ..< Int(accountsPtr.pointee.len)) { + let account = accountsPtr.pointee.ptr.advanced(by: i).pointee + accounts.append(Int32(account.account_index)) + } + + return accounts + } + func createAccount(seed: [UInt8], treeState: TreeState, recoverUntil: UInt32?) async throws -> UnifiedSpendingKey { var rUntil: Int64 = -1 diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift index 64e9793b..807a2114 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift @@ -20,6 +20,9 @@ public enum DbInitResult { // sourcery: mockActor protocol ZcashRustBackendWelding { + /// Returns a list of the accounts in the wallet. + func listAccounts() async throws -> [Int32] + /// Adds the next available account-level spend authority, given the current set of [ZIP 316] /// account identifiers known, to the wallet database. /// diff --git a/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift b/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift index 05c48053..8405a332 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift @@ -100,8 +100,7 @@ enum Dependencies { static func setupCompactBlockProcessor( in container: DIContainer, - config: CompactBlockProcessor.Configuration, - accountRepository: AccountRepository + config: CompactBlockProcessor.Configuration ) { container.register(type: BlockDownloader.self, isSingleton: true) { di in let service = di.resolve(LightWalletService.self) @@ -163,7 +162,6 @@ enum Dependencies { let logger = di.resolve(Logger.self) return UTXOFetcherImpl( - accountRepository: accountRepository, blockDownloaderService: blockDownloaderService, config: utxoFetcherConfig, rustBackend: rustBackend, diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 0102bffe..a78c5f82 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -667,8 +667,7 @@ public class SDKSynchronizer: Synchronizer { // CompactBlockProcessor dependency update Dependencies.setupCompactBlockProcessor( in: initializer.container, - config: await blockProcessor.config, - accountRepository: initializer.accountRepository + config: await blockProcessor.config ) // INITIALIZER diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index 51612a02..12d12d34 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -2213,6 +2213,37 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { self.consensusBranchIdForHeightClosure = consensusBranchIdForHeightClosure } + // MARK: - listAccounts + + var listAccountsThrowableError: Error? + func setListAccountsThrowableError(_ param: Error?) async { + listAccountsThrowableError = param + } + var listAccountsCallsCount = 0 + var listAccountsCalled: Bool { + return listAccountsCallsCount > 0 + } + var listAccountsReturnValue: [Int32]! + func setListAccountsReturnValue(_ param: [Int32]) async { + listAccountsReturnValue = param + } + var listAccountsClosure: (() async throws -> [Int32])? + func setListAccountsClosure(_ param: (() async throws -> [Int32])?) async { + listAccountsClosure = param + } + + func listAccounts() async throws -> [Int32] { + if let error = listAccountsThrowableError { + throw error + } + listAccountsCallsCount += 1 + if let closure = listAccountsClosure { + return try await closure() + } else { + return listAccountsReturnValue + } + } + // MARK: - createAccount var createAccountSeedTreeStateRecoverUntilThrowableError: Error?