Remove `UnspentTransactionOutputRepository`
This removes the last direct access to the `utxos` table; all access now goes through the Rust FFI. `SDKSynchronizer.latestUTXOs` is removed without replacement. It was introduced during the initial addition of shielding support, but: - It is no longer used anywhere inside the SDK (when added, it was used in a few other methods). - It is not exposed in the `Synchronizer` protocol. - It is AFAICT unused in Zashi iOS, Edge, and Unstoppable. - It was functionally replaced by `refreshUTXOs`, which performs best-effort UTXO updates instead of failing on any error. (It also does not clear the `utxo`s table which makes it not equivalent.)
This commit is contained in:
parent
6207cc999b
commit
dd9942b6ab
|
@ -18,6 +18,10 @@ Possible errors:
|
|||
- `ZcashError.rustProposeTransferFromURI`
|
||||
- Other errors that `sentToAddress` can throw
|
||||
|
||||
## Removed
|
||||
|
||||
- `SDKSynchronizer.latestUTXOs`
|
||||
|
||||
# 2.0.11 - 2024-03-08
|
||||
|
||||
## Changed
|
||||
|
|
|
@ -8,25 +8,12 @@
|
|||
import Foundation
|
||||
|
||||
struct UTXO: Decodable, Encodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "id_utxo"
|
||||
case address
|
||||
case prevoutTxId = "prevout_txid"
|
||||
case prevoutIndex = "prevout_idx"
|
||||
case script
|
||||
case valueZat = "value_zat"
|
||||
case height
|
||||
case spentInTx = "spent_in_tx"
|
||||
}
|
||||
|
||||
let id: Int?
|
||||
let address: String
|
||||
var prevoutTxId: Data
|
||||
var prevoutIndex: Int
|
||||
let script: Data
|
||||
let valueZat: Int
|
||||
let height: Int
|
||||
let spentInTx: Int?
|
||||
}
|
||||
|
||||
extension UTXO: UnspentTransactionOutputEntity {
|
||||
|
@ -48,145 +35,3 @@ extension UTXO: UnspentTransactionOutputEntity {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UnspentTransactionOutputEntity {
|
||||
/**
|
||||
As UTXO, with id and spentIntTx set to __nil__
|
||||
*/
|
||||
func asUTXO() -> UTXO {
|
||||
UTXO(
|
||||
id: nil,
|
||||
address: address,
|
||||
prevoutTxId: txid,
|
||||
prevoutIndex: index,
|
||||
script: script,
|
||||
valueZat: valueZat,
|
||||
height: height,
|
||||
spentInTx: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
import SQLite
|
||||
class UnspentTransactionOutputSQLDAO: UnspentTransactionOutputRepository {
|
||||
enum TableColumns {
|
||||
static let id = Expression<Int>("id_utxo")
|
||||
static let address = Expression<String>("address")
|
||||
static let txid = Expression<Blob>("prevout_txid")
|
||||
static let index = Expression<Int>("prevout_idx")
|
||||
static let script = Expression<Blob>("script")
|
||||
static let valueZat = Expression<Int>("value_zat")
|
||||
static let height = Expression<Int>("height")
|
||||
static let spentInTx = Expression<Int?>("spent_in_tx")
|
||||
}
|
||||
|
||||
let table = Table("utxos")
|
||||
|
||||
let dbProvider: ConnectionProvider
|
||||
|
||||
init(dbProvider: ConnectionProvider) {
|
||||
self.dbProvider = dbProvider
|
||||
}
|
||||
|
||||
/// - Throws: `unspentTransactionOutputDAOCreateTable` if creation table fails.
|
||||
func initialise() async throws {
|
||||
try await createTableIfNeeded()
|
||||
}
|
||||
|
||||
private func createTableIfNeeded() async throws {
|
||||
let stringStatement =
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS utxos (
|
||||
id_utxo INTEGER PRIMARY KEY,
|
||||
address TEXT NOT NULL,
|
||||
prevout_txid BLOB NOT NULL,
|
||||
prevout_idx INTEGER NOT NULL,
|
||||
script BLOB NOT NULL,
|
||||
value_zat INTEGER NOT NULL,
|
||||
height INTEGER NOT NULL,
|
||||
spent_in_tx INTEGER,
|
||||
FOREIGN KEY (spent_in_tx) REFERENCES transactions(id_tx),
|
||||
CONSTRAINT tx_outpoint UNIQUE (prevout_txid, prevout_idx)
|
||||
)
|
||||
"""
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
try dbProvider.connection().run(stringStatement)
|
||||
} catch {
|
||||
throw ZcashError.unspentTransactionOutputDAOCreateTable(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// - Throws: `unspentTransactionOutputDAOStore` if sqlite query fails.
|
||||
func store(utxos: [UnspentTransactionOutputEntity]) async throws {
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
let db = try dbProvider.connection()
|
||||
try db.transaction {
|
||||
for utxo in utxos.map({ $0 as? UTXO ?? $0.asUTXO() }) {
|
||||
try db.run(table.insert(utxo))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
throw ZcashError.unspentTransactionOutputDAOStore(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// - Throws: `unspentTransactionOutputDAOClearAll` if sqlite query fails.
|
||||
func clearAll(address: String?) async throws {
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
if let tAddr = address {
|
||||
try dbProvider.connection().run(table.filter(TableColumns.address == tAddr).delete())
|
||||
} else {
|
||||
try dbProvider.connection().run(table.delete())
|
||||
}
|
||||
} catch {
|
||||
throw ZcashError.unspentTransactionOutputDAOClearAll(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// - Throws:
|
||||
/// - `unspentTransactionOutputDAOClearAll` if the data fetched from the DB can't be decoded to `UTXO` object.
|
||||
/// - `unspentTransactionOutputDAOGetAll` if sqlite query fails.
|
||||
func getAll(address: String?) async throws -> [UnspentTransactionOutputEntity] {
|
||||
do {
|
||||
if let tAddress = address {
|
||||
let allTxs: [UTXO] = try dbProvider.connection()
|
||||
.prepare(table.filter(TableColumns.address == tAddress))
|
||||
.map { row in
|
||||
do {
|
||||
return try row.decode()
|
||||
} catch {
|
||||
throw ZcashError.unspentTransactionOutputDAOGetAllCantDecode(error)
|
||||
}
|
||||
}
|
||||
return allTxs
|
||||
} else {
|
||||
let allTxs: [UTXO] = try dbProvider.connection()
|
||||
.prepare(table)
|
||||
.map { row in
|
||||
try row.decode()
|
||||
}
|
||||
return allTxs
|
||||
}
|
||||
} catch {
|
||||
if let error = error as? ZcashError {
|
||||
throw error
|
||||
} else {
|
||||
throw ZcashError.unspentTransactionOutputDAOGetAll(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum UTXORepositoryBuilder {
|
||||
static func build(initializer: Initializer) -> UnspentTransactionOutputRepository {
|
||||
return UnspentTransactionOutputSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.dataDbURL.path))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,14 +223,12 @@ extension LightWalletGRPCService: LightWalletService {
|
|||
do {
|
||||
guard let reply = try await iterator.next() else { return nil }
|
||||
return UTXO(
|
||||
id: nil,
|
||||
address: reply.address,
|
||||
prevoutTxId: reply.txid,
|
||||
prevoutIndex: Int(reply.index),
|
||||
script: reply.script,
|
||||
valueZat: Int(reply.valueZat),
|
||||
height: Int(reply.height),
|
||||
spentInTx: nil
|
||||
height: Int(reply.height)
|
||||
)
|
||||
} catch {
|
||||
let serviceError = error.mapToServiceError()
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// UnspentTransactionOutputRepository.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 12/11/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol UnspentTransactionOutputRepository {
|
||||
func initialise() async throws
|
||||
func getAll(address: String?) async throws -> [UnspentTransactionOutputEntity]
|
||||
func store(utxos: [UnspentTransactionOutputEntity]) async throws
|
||||
func clearAll(address: String?) async throws
|
||||
}
|
|
@ -40,7 +40,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
public let network: ZcashNetwork
|
||||
private let transactionEncoder: TransactionEncoder
|
||||
private let transactionRepository: TransactionRepository
|
||||
private let utxoRepository: UnspentTransactionOutputRepository
|
||||
|
||||
private let syncSessionIDGenerator: SyncSessionIDGenerator
|
||||
private let syncSession: SyncSession
|
||||
|
@ -55,7 +54,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
initializer: initializer,
|
||||
transactionEncoder: WalletTransactionEncoder(initializer: initializer),
|
||||
transactionRepository: initializer.transactionRepository,
|
||||
utxoRepository: UTXORepositoryBuilder.build(initializer: initializer),
|
||||
blockProcessor: CompactBlockProcessor(
|
||||
initializer: initializer,
|
||||
walletBirthdayProvider: { initializer.walletBirthday }
|
||||
|
@ -69,7 +67,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
initializer: Initializer,
|
||||
transactionEncoder: TransactionEncoder,
|
||||
transactionRepository: TransactionRepository,
|
||||
utxoRepository: UnspentTransactionOutputRepository,
|
||||
blockProcessor: CompactBlockProcessor,
|
||||
syncSessionTicker: SessionTicker
|
||||
) {
|
||||
|
@ -78,7 +75,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
self.initializer = initializer
|
||||
self.transactionEncoder = transactionEncoder
|
||||
self.transactionRepository = transactionRepository
|
||||
self.utxoRepository = utxoRepository
|
||||
self.blockProcessor = blockProcessor
|
||||
self.network = initializer.network
|
||||
self.metrics = initializer.container.resolve(SDKMetrics.self)
|
||||
|
@ -137,8 +133,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
throw error
|
||||
}
|
||||
|
||||
try await utxoRepository.initialise()
|
||||
|
||||
if case .seedRequired = try await self.initializer.initialize(with: seed, walletBirthday: walletBirthday, for: walletMode) {
|
||||
return .seedRequired
|
||||
}
|
||||
|
@ -502,25 +496,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
try await blockProcessor.latestHeight()
|
||||
}
|
||||
|
||||
public func latestUTXOs(address: String) async throws -> [UnspentTransactionOutputEntity] {
|
||||
try throwIfUnprepared()
|
||||
|
||||
guard initializer.isValidTransparentAddress(address) else {
|
||||
throw ZcashError.synchronizerLatestUTXOsInvalidTAddress
|
||||
}
|
||||
|
||||
let stream = initializer.lightWalletService.fetchUTXOs(for: address, height: network.constants.saplingActivationHeight)
|
||||
|
||||
// swiftlint:disable:next array_constructor
|
||||
var utxos: [UnspentTransactionOutputEntity] = []
|
||||
for try await transactionEntity in stream {
|
||||
utxos.append(transactionEntity)
|
||||
}
|
||||
try await self.utxoRepository.clearAll(address: address)
|
||||
try await self.utxoRepository.store(utxos: utxos)
|
||||
return utxos
|
||||
}
|
||||
|
||||
public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs {
|
||||
try throwIfUnprepared()
|
||||
return try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height)
|
||||
|
|
Loading…
Reference in New Issue