Remove `AccountRepository`

This removes the last direct access to the `accounts` table; all access
now goes through the Rust FFI.
This commit is contained in:
Jack Grigg 2024-03-15 15:26:44 +00:00
parent dd9942b6ab
commit 86defc8b4a
15 changed files with 88 additions and 216 deletions

View File

@ -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"
}
}
],

View File

@ -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"
}
}
],

View File

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

View File

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

View File

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

View File

@ -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<Int>("account")
static let extfvk = Expression<String>("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
)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.
///

View File

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

View File

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

View File

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