ZcashLightClientKit/ZcashLightClientKit/DAO/UnspentTransactionOutputDao...

180 lines
5.5 KiB
Swift
Raw Normal View History

2020-12-11 12:15:29 -08:00
//
// UnspentTransactionOutputDAO.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/9/20.
//
import Foundation
2021-04-01 07:27:26 -07:00
struct UTXO: Decodable, Encodable {
2020-12-11 12:15:29 -08:00
enum CodingKeys: String, CodingKey {
2021-04-01 07:27:26 -07:00
case id = "id_utxo"
2020-12-11 12:15:29 -08:00
case address
2021-04-01 07:27:26 -07:00
case prevoutTxId = "prevout_txid"
case prevoutIndex = "prevout_idx"
2020-12-11 12:15:29 -08:00
case script
case valueZat = "value_zat"
case height
2021-04-01 07:27:26 -07:00
case spentInTx = "spent_in_tx"
2020-12-11 12:15:29 -08:00
}
var id: Int?
var address: String
2021-04-01 07:27:26 -07:00
var prevoutTxId: Data
var prevoutIndex: Int
2020-12-11 12:15:29 -08:00
var script: Data
var valueZat: Int
var height: Int
2021-04-01 07:27:26 -07:00
var spentInTx: Int?
}
extension UTXO: UnspentTransactionOutputEntity {
var txid: Data {
get {
prevoutTxId
}
set {
prevoutTxId = newValue
}
}
2020-12-11 12:15:29 -08:00
2021-04-01 07:27:26 -07:00
var index: Int {
get {
prevoutIndex
}
set {
prevoutIndex = newValue
}
}
2020-12-11 12:15:29 -08:00
}
2021-04-01 07:27:26 -07:00
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)
}
}
2020-12-11 12:15:29 -08:00
import SQLite
class UnspentTransactionOutputSQLDAO: UnspentTransactionOutputRepository {
func store(utxos: [UnspentTransactionOutputEntity]) throws {
do {
let db = try dbProvider.connection()
try dbProvider.connection().transaction {
2021-09-15 05:21:29 -07:00
for utxo in utxos.map({ (mappedUTXO) -> UTXO in
mappedUTXO as? UTXO ?? mappedUTXO.asUTXO()
2020-12-11 12:15:29 -08:00
}) {
try db.run(table.insert(utxo))
}
}
} catch {
throw StorageError.transactionFailed(underlyingError: error)
}
}
func clearAll(address: String?) throws {
if let tAddr = address {
do {
try dbProvider.connection().run(table.filter(TableColumns.address == tAddr).delete())
} catch {
throw StorageError.operationFailed
}
} else {
do {
try dbProvider.connection().run(table.delete())
} catch {
throw StorageError.operationFailed
}
}
}
let table = Table("utxos")
struct TableColumns {
2021-04-01 07:27:26 -07:00
static var id = Expression<Int>("id_utxo")
2020-12-11 12:15:29 -08:00
static var address = Expression<String>("address")
2021-04-01 07:27:26 -07:00
static var txid = Expression<Blob>("prevout_txid")
static var index = Expression<Int>("prevout_idx")
2020-12-11 12:15:29 -08:00
static var script = Expression<Blob>("script")
static var valueZat = Expression<Int>("value_zat")
static var height = Expression<Int>("height")
2021-04-01 07:27:26 -07:00
static var spentInTx = Expression<Int?>("spent_in_tx")
2020-12-11 12:15:29 -08:00
}
var dbProvider: ConnectionProvider
init (dbProvider: ConnectionProvider) {
self.dbProvider = dbProvider
}
func createTableIfNeeded() throws {
2021-04-01 07:27:26 -07:00
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)
)
"""
try dbProvider.connection().run(stringStatement)
2020-12-11 12:15:29 -08:00
}
func getAll(address: String?) throws -> [UnspentTransactionOutputEntity] {
if let tAddress = address {
let allTxs: [UTXO] = try dbProvider.connection().prepare(table.filter(TableColumns.address == tAddress)).map({ row in
try row.decode()
})
return allTxs
} else {
let allTxs: [UTXO] = try dbProvider.connection().prepare(table).map({ row in
try row.decode()
})
return allTxs
}
}
2020-12-23 15:01:09 -08:00
2021-02-17 15:02:25 -08:00
func balance(address: String, latestHeight: BlockHeight) throws -> WalletBalance {
2020-12-23 15:01:09 -08:00
do {
let verified = try dbProvider.connection().scalar(
table.select(TableColumns.valueZat.sum)
.filter(TableColumns.address == address)
2021-09-15 05:21:29 -07:00
.filter(TableColumns.height <= latestHeight - ZcashSDK.defaultStaleTolerance)) ?? 0
2021-02-15 11:15:50 -08:00
let total = try dbProvider.connection().scalar(
2020-12-23 15:01:09 -08:00
table.select(TableColumns.valueZat.sum)
.filter(TableColumns.address == address)) ?? 0
return TransparentBalance(verified: Int64(verified), total: Int64(total), address: address)
} catch {
2020-12-23 15:01:09 -08:00
throw StorageError.operationFailed
}
}
2020-12-11 12:15:29 -08:00
}
2021-02-17 15:02:25 -08:00
struct TransparentBalance: WalletBalance {
var verified: Int64
var total: Int64
var address: String
}
2020-12-11 12:15:29 -08:00
class UTXORepositoryBuilder {
static func build(initializer: Initializer) throws -> UnspentTransactionOutputRepository {
let dao = UnspentTransactionOutputSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.dataDbURL.path))
2020-12-11 12:15:29 -08:00
try dao.createTableIfNeeded()
return dao
}
}