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 {
|
|
|
|
/**
|
2021-09-17 06:49:58 -07:00
|
|
|
As UTXO, with id and spentIntTx set to __nil__
|
|
|
|
*/
|
2021-04-01 07:27:26 -07:00
|
|
|
func asUTXO() -> UTXO {
|
2021-09-17 06:49:58 -07:00
|
|
|
UTXO(
|
|
|
|
id: nil,
|
|
|
|
address: address,
|
|
|
|
prevoutTxId: txid,
|
|
|
|
prevoutIndex: index,
|
|
|
|
script: script,
|
|
|
|
valueZat: valueZat,
|
|
|
|
height: height,
|
|
|
|
spentInTx: nil
|
|
|
|
)
|
2021-04-01 07:27:26 -07:00
|
|
|
}
|
|
|
|
}
|
2020-12-11 12:15:29 -08:00
|
|
|
import SQLite
|
|
|
|
class UnspentTransactionOutputSQLDAO: UnspentTransactionOutputRepository {
|
2021-09-17 06:49:58 -07:00
|
|
|
enum TableColumns {
|
|
|
|
static var id = Expression<Int>("id_utxo")
|
|
|
|
static var address = Expression<String>("address")
|
|
|
|
static var txid = Expression<Blob>("prevout_txid")
|
|
|
|
static var index = Expression<Int>("prevout_idx")
|
|
|
|
static var script = Expression<Blob>("script")
|
|
|
|
static var valueZat = Expression<Int>("value_zat")
|
|
|
|
static var height = Expression<Int>("height")
|
|
|
|
static var spentInTx = Expression<Int?>("spent_in_tx")
|
|
|
|
}
|
|
|
|
|
|
|
|
let table = Table("utxos")
|
|
|
|
|
|
|
|
var dbProvider: ConnectionProvider
|
|
|
|
|
|
|
|
init (dbProvider: ConnectionProvider) {
|
|
|
|
self.dbProvider = dbProvider
|
|
|
|
}
|
2023-03-10 03:58:28 -08:00
|
|
|
|
|
|
|
func initialise() throws {
|
|
|
|
try createTableIfNeeded()
|
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
|
|
|
func createTableIfNeeded() 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)
|
|
|
|
)
|
|
|
|
"""
|
|
|
|
|
|
|
|
try dbProvider.connection().run(stringStatement)
|
|
|
|
}
|
|
|
|
|
2020-12-11 12:15:29 -08:00
|
|
|
func store(utxos: [UnspentTransactionOutputEntity]) throws {
|
|
|
|
do {
|
2021-09-17 06:49:58 -07:00
|
|
|
let db = try dbProvider.connection()
|
|
|
|
try dbProvider.connection().transaction {
|
|
|
|
for utxo in utxos.map({ $0 as? UTXO ?? $0.asUTXO() }) {
|
|
|
|
try db.run(table.insert(utxo))
|
|
|
|
}
|
2020-12-11 12:15:29 -08:00
|
|
|
}
|
|
|
|
} catch {
|
2023-02-02 08:58:12 -08:00
|
|
|
throw DatabaseStorageError.transactionFailed(underlyingError: error)
|
2020-12-11 12:15:29 -08:00
|
|
|
}
|
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2020-12-11 12:15:29 -08:00
|
|
|
func clearAll(address: String?) throws {
|
|
|
|
if let tAddr = address {
|
|
|
|
do {
|
|
|
|
try dbProvider.connection().run(table.filter(TableColumns.address == tAddr).delete())
|
|
|
|
} catch {
|
2023-02-02 08:58:12 -08:00
|
|
|
throw DatabaseStorageError.operationFailed
|
2020-12-11 12:15:29 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do {
|
|
|
|
try dbProvider.connection().run(table.delete())
|
|
|
|
} catch {
|
2023-02-02 08:58:12 -08:00
|
|
|
throw DatabaseStorageError.operationFailed
|
2020-12-11 12:15:29 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAll(address: String?) throws -> [UnspentTransactionOutputEntity] {
|
|
|
|
if let tAddress = address {
|
2021-09-17 06:49:58 -07:00
|
|
|
let allTxs: [UTXO] = try dbProvider.connection()
|
|
|
|
.prepare(table.filter(TableColumns.address == tAddress))
|
|
|
|
.map { row in
|
|
|
|
try row.decode()
|
|
|
|
}
|
2020-12-11 12:15:29 -08:00
|
|
|
return allTxs
|
|
|
|
} else {
|
2021-09-17 06:49:58 -07:00
|
|
|
let allTxs: [UTXO] = try dbProvider.connection()
|
|
|
|
.prepare(table)
|
|
|
|
.map { row in
|
|
|
|
try row.decode()
|
|
|
|
}
|
2020-12-11 12:15:29 -08:00
|
|
|
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 {
|
2021-01-22 13:51:48 -08:00
|
|
|
do {
|
2021-03-08 10:47:36 -08:00
|
|
|
let verified = try dbProvider.connection().scalar(
|
2021-09-17 06:49:58 -07:00
|
|
|
table.select(TableColumns.valueZat.sum)
|
|
|
|
.filter(TableColumns.address == address)
|
|
|
|
.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)
|
2021-09-17 06:49:58 -07:00
|
|
|
.filter(TableColumns.address == address)
|
|
|
|
) ?? 0
|
2021-01-22 13:51:48 -08:00
|
|
|
|
2022-06-22 12:45:37 -07:00
|
|
|
return WalletBalance(
|
2023-01-18 08:09:04 -08:00
|
|
|
verified: Zatoshi(Int64(verified)),
|
|
|
|
total: Zatoshi(Int64(total))
|
2022-06-22 12:45:37 -07:00
|
|
|
)
|
2021-01-22 13:51:48 -08:00
|
|
|
} catch {
|
2023-02-02 08:58:12 -08:00
|
|
|
throw DatabaseStorageError.operationFailed
|
2020-12-23 15:01:09 -08:00
|
|
|
}
|
|
|
|
}
|
2020-12-11 12:15:29 -08:00
|
|
|
}
|
|
|
|
|
2022-06-22 12:45:37 -07:00
|
|
|
struct TransparentBalance {
|
|
|
|
var balance: WalletBalance
|
2021-01-22 13:51:48 -08:00
|
|
|
var address: String
|
|
|
|
}
|
|
|
|
|
2021-09-17 06:49:58 -07:00
|
|
|
enum UTXORepositoryBuilder {
|
2023-03-10 03:58:28 -08:00
|
|
|
static func build(initializer: Initializer) -> UnspentTransactionOutputRepository {
|
|
|
|
return UnspentTransactionOutputSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.dataDbURL.path))
|
2020-12-11 12:15:29 -08:00
|
|
|
}
|
|
|
|
}
|