From 54ba41020e60cc93d4c4886832c0107bc2d3683f Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 29 Jan 2024 16:19:11 -0500 Subject: [PATCH] Add CashuDB interface and SQLiteDB implementation --- .../main/kotlin/me/tb/cashuclient/Wallet.kt | 50 +++++-------------- .../kotlin/me/tb/cashuclient/db/CashuDB.kt | 11 ++++ .../kotlin/me/tb/cashuclient/db/SQLiteDB.kt | 40 +++++++++++++++ 3 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 lib/src/main/kotlin/me/tb/cashuclient/db/CashuDB.kt create mode 100644 lib/src/main/kotlin/me/tb/cashuclient/db/SQLiteDB.kt diff --git a/lib/src/main/kotlin/me/tb/cashuclient/Wallet.kt b/lib/src/main/kotlin/me/tb/cashuclient/Wallet.kt index 52db7d6..8195dcd 100644 --- a/lib/src/main/kotlin/me/tb/cashuclient/Wallet.kt +++ b/lib/src/main/kotlin/me/tb/cashuclient/Wallet.kt @@ -28,8 +28,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json -import me.tb.cashuclient.db.DBProof -import me.tb.cashuclient.db.DBSettings +import me.tb.cashuclient.db.CashuDB +import me.tb.cashuclient.db.SQLiteDB import me.tb.cashuclient.melt.MeltQuoteRequest import me.tb.cashuclient.melt.MeltQuoteResponse import me.tb.cashuclient.melt.MeltRequest @@ -54,13 +54,6 @@ import me.tb.cashuclient.types.PreRequestBundle import me.tb.cashuclient.types.Proof import me.tb.cashuclient.types.SpecificKeysetResponse import me.tb.cashuclient.types.SwapRequired -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.LoggerFactory public typealias NewAvailableDenominations = List @@ -71,13 +64,15 @@ public typealias MintInfo = InfoResponse * with a single mint and a single unit. * * @param activeKeyset The [Keyset] that is currently active for the mint. - * @param mintUrl The URL of the mint. - * @param unit The underlying unit used with the ecash tokens for this wallet. + * @param mintUrl The URL of the mint. + * @param unit The underlying unit used with the ecash tokens for this wallet. + * @param db The implementation of [CashuDB] used for this wallet (by default, a [SQLiteDB]). */ public class Wallet( public var activeKeyset: Keyset? = null, private val mintUrl: String, private val unit: EcashUnit, + private val db: CashuDB = SQLiteDB() ) { public val inactiveKeysets: MutableList = mutableListOf() private val logger = LoggerFactory.getLogger(Wallet::class.java) @@ -245,13 +240,8 @@ public class Wallet( ?.truncateToSatoshi() ?.toULong() ?: throw Exception("Payment request does not have an amount.") - val availableProofs: List = transaction(DBSettings.db) { - SchemaUtils.create(DBProof) - DBProof - .selectAll() - .map { it[DBProof.amount] } - } - val totalBalance = availableProofs.sum() + val availableNoteSizes: List = db.spendableNoteSizes() + val totalBalance = availableNoteSizes.sum() val totalCost = quote.amount + quote.feeReserve if (totalBalance < totalCost) { @@ -259,7 +249,7 @@ public class Wallet( } val isSwapRequired: SwapRequired = isSwapRequired( - allDenominations = availableProofs, + allDenominations = availableNoteSizes, targetAmount = quote.amount + quote.feeReserve ) @@ -331,10 +321,8 @@ public class Wallet( // TODO: Should we add the preimage to the database? // TODO: Does the inList operator delete _all_ proofs that match the condition or just the first one? In this // case secrets should always be unique anyway, but still I'm wondering how the API works. - transaction(DBSettings.db) { - SchemaUtils.create(DBProof) - val secretsToDelete = preMeltBundle.proofs.map { it.secret } - DBProof.deleteWhere { secret inList secretsToDelete } + preMeltBundle.proofs.map { proof -> + db.deleteProof(proof) } } @@ -399,24 +387,12 @@ public class Wallet( script = null ) - transaction(DBSettings.db) { - SchemaUtils.create(DBProof) - - DBProof.insert { - it[amount] = proof.amount - it[secret] = proof.secret - it[C] = proof.C - it[id] = proof.id - it[script] = null - } - } + db.insertProof(proof) } if (requestBundle is PreSwapBundle) { - SchemaUtils.create(DBProof) requestBundle.proofsToSwap.forEach { proof -> - val secretToDelete = proof.secret - DBProof.deleteWhere { secret eq secretToDelete } + db.deleteProof(proof) } } } diff --git a/lib/src/main/kotlin/me/tb/cashuclient/db/CashuDB.kt b/lib/src/main/kotlin/me/tb/cashuclient/db/CashuDB.kt new file mode 100644 index 0000000..40abee6 --- /dev/null +++ b/lib/src/main/kotlin/me/tb/cashuclient/db/CashuDB.kt @@ -0,0 +1,11 @@ +package me.tb.cashuclient.db + +import me.tb.cashuclient.types.Proof + +public interface CashuDB { + public fun insertProof(proof: Proof): Unit + + public fun deleteProof(proof: Proof): Unit + + public fun spendableNoteSizes(): List +} diff --git a/lib/src/main/kotlin/me/tb/cashuclient/db/SQLiteDB.kt b/lib/src/main/kotlin/me/tb/cashuclient/db/SQLiteDB.kt new file mode 100644 index 0000000..13cba5e --- /dev/null +++ b/lib/src/main/kotlin/me/tb/cashuclient/db/SQLiteDB.kt @@ -0,0 +1,40 @@ +package me.tb.cashuclient.db + +import me.tb.cashuclient.types.Proof +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction + +public class SQLiteDB : CashuDB { + override fun insertProof(proof: Proof) { + transaction(DBSettings.db) { + SchemaUtils.create(DBProof) + + DBProof.insert { + it[amount] = proof.amount + it[secret] = proof.secret + it[C] = proof.C + it[id] = proof.id + it[script] = null + } + } + } + + override fun deleteProof(proof: Proof) { + transaction(DBSettings.db) { + SchemaUtils.create(DBProof) + val secretOfProofToDelete = proof.secret + DBProof.deleteWhere { secret eq secretOfProofToDelete } + } + } + + override fun spendableNoteSizes(): List { + return transaction(DBSettings.db) { + SchemaUtils.create(DBProof) + DBProof.selectAll().map { it[DBProof.amount] } + } + } +}