Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate database implementation to SqlDelight #11

Merged
merged 1 commit into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ plugins {
id("org.gradle.java-library")
id("org.gradle.maven-publish")
id("org.jetbrains.dokka") version "1.9.10"
id("app.cash.sqldelight") version "2.0.2"
}

sqldelight {
databases {
create("Database") {
packageName.set("me.tb.cashulib")
}
}
}

repositories {
Expand All @@ -29,14 +38,8 @@ dependencies {
implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm-darwin:0.11.0")
implementation("fr.acinq.lightning:lightning-kmp:1.5.15")

// Exposed
implementation("org.jetbrains.exposed:exposed-core:0.40.1")
implementation("org.jetbrains.exposed:exposed-dao:0.40.1")
implementation("org.jetbrains.exposed:exposed-jdbc:0.40.1")
implementation("org.jetbrains.exposed:exposed-java-time:0.40.1")

// SQLite
implementation("org.xerial:sqlite-jdbc:3.42.0.0")
// SqlDelight
implementation("app.cash.sqldelight:sqlite-driver:2.0.2")

// Ktor
implementation("io.ktor:ktor-client-core-jvm:2.3.1")
Expand Down
4 changes: 2 additions & 2 deletions lib/src/main/kotlin/me/tb/cashuclient/Wallet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ public class Wallet(
"The sum of tokens to spend must be equal to the sum of the required tokens."
}

val preMeltBundle: PreMeltBundle = PreMeltBundle.create(finalListOfProofs, quote.quoteId)
val preMeltBundle: PreMeltBundle = PreMeltBundle.create(finalListOfProofs, quote.quoteId, db)
val meltRequest: MeltRequest = preMeltBundle.buildMeltRequest()

val response = client.post("$mintUrl/melt") {
Expand Down Expand Up @@ -321,7 +321,7 @@ public class Wallet(
val client = createClient()
val scopedActiveKeyset = activeKeyset ?: throw Exception("The wallet must have an active keyset for the mint when attempting a swap operation.")

val preSwapRequestBundle = PreSwapBundle.create(availableForSwap, requiredAmount, scopedActiveKeyset.keysetId)
val preSwapRequestBundle = PreSwapBundle.create(db, availableForSwap, requiredAmount, scopedActiveKeyset.keysetId)
val swapRequest = preSwapRequestBundle.buildSwapRequest()

val response = client.post("$mintUrl$SWAP_ENDPOINT") {
Expand Down
11 changes: 11 additions & 0 deletions lib/src/main/kotlin/me/tb/cashuclient/db/CashuDB.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ public interface CashuDB {
*/
public fun insertProof(proof: Proof): Unit

/**
* Returns proofs for requested amounts.
*
* This method is used to query the database for proofs associated with the specified amounts.
* Assumes that proofs are present in the database for the requested amounts, otherwise
* throws an exception.
*
* @param amounts - A list of [ULong] representing the amounts for which proofs are requested.
*/
public fun proofsForAmounts(amounts: List<ULong>): List<Proof>
kirillzh marked this conversation as resolved.
Show resolved Hide resolved

/**
* Deletes a proof from the database.
*
Expand Down
44 changes: 0 additions & 44 deletions lib/src/main/kotlin/me/tb/cashuclient/db/DBBolt11Payment.kt

This file was deleted.

18 changes: 0 additions & 18 deletions lib/src/main/kotlin/me/tb/cashuclient/db/DBProof.kt

This file was deleted.

14 changes: 0 additions & 14 deletions lib/src/main/kotlin/me/tb/cashuclient/db/DBSettings.kt

This file was deleted.

68 changes: 41 additions & 27 deletions lib/src/main/kotlin/me/tb/cashuclient/db/SQLiteDB.kt
Original file line number Diff line number Diff line change
@@ -1,43 +1,57 @@
package me.tb.cashuclient.db

import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import fr.acinq.lightning.utils.getValue
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
import me.tb.cashulib.Database

/**
* The default implementation of the [CashuDB] interface, using SQLite.
*/
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
}
public class SQLiteDB(
private val sqlDriver: SqlDriver = sqlDriver()
) : CashuDB {

private val database by lazy {
Database.Schema.create(sqlDriver)
Database(sqlDriver)
}

override fun proofsForAmounts(amounts: List<ULong>): List<Proof> {
return amounts.map { amt ->
val record = database.proofQueries.selectProofByAmount(amt.toLong()).executeAsOne()
Proof(
amount = record.amount.toULong(),
secret = record.secret,
C = record.C,
id = record.id
)
}
}

override fun insertProof(proof: Proof) {
database.proofQueries.insert(
amount = proof.amount.toLong(),
secret = proof.secret,
C = proof.C,
id = proof.id,
script = proof.script
)
}

override fun deleteProof(proof: Proof) {
transaction(DBSettings.db) {
SchemaUtils.create(DBProof)
val secretOfProofToDelete = proof.secret
DBProof.deleteWhere { secret eq secretOfProofToDelete }
}
database.proofQueries.deleteById(proof.id)
}

override fun spendableNoteSizes(): List<ULong> {
return transaction(DBSettings.db) {
SchemaUtils.create(DBProof)
DBProof.selectAll().map { it[DBProof.amount] }
}
return database.proofQueries.selectAll().executeAsList().map { it.amount.toULong() }
}
}

/**
* Creates a new [SqlDriver] instance that connects to the SQLite database.
*/
private fun sqlDriver(): SqlDriver {
return JdbcSqliteDriver(url = "jdbc:sqlite:./cashu.sqlite3")
}
34 changes: 8 additions & 26 deletions lib/src/main/kotlin/me/tb/cashuclient/melt/PreMeltBundle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
* Copyright 2023 thunderbiscuit and contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE.txt file.
*/

package me.tb.cashuclient.melt

import fr.acinq.lightning.payment.PaymentRequest
import me.tb.cashuclient.db.DBProof
import me.tb.cashuclient.db.DBSettings
import me.tb.cashuclient.db.CashuDB
import me.tb.cashuclient.melt.PreMeltBundle.Companion.create
import me.tb.cashuclient.types.BlindedMessage
import me.tb.cashuclient.types.Proof
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction

/**
* The data bundle Alice must create prior to communicating with the mint requesting a melt.
Expand All @@ -26,7 +23,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
public class PreMeltBundle private constructor(
public val proofs: List<Proof>,
private val quoteId: String,
public val potentialChangeOutputs: List<BlindedMessage>? = null
public val potentialChangeOutputs: List<BlindedMessage>? = null,
) {
/**
* Builds a [MeltRequest] from the data in this bundle. This [MeltRequest] is the data structure that is then
Expand All @@ -49,31 +46,16 @@ public class PreMeltBundle private constructor(
*/
public fun create(
denominationsToUse: List<ULong>,
quoteId: String
quoteId: String,
db: CashuDB,
): PreMeltBundle {

DBSettings.db
// Check if we have these amounts in the database
val proofs: List<Proof> = denominationsToUse.map { amt ->
transaction {
SchemaUtils.create(DBProof)
val proof: Proof? = DBProof.select { DBProof.amount eq amt }.firstOrNull()?.let {
Proof(
amount = it[DBProof.amount],
id = it[DBProof.id],
secret = it[DBProof.secret],
C = it[DBProof.C],
script = it[DBProof.script]
)
}
proof ?: throw Exception("No proof found for amount $amt")
}
}
val proofs = db.proofsForAmounts(denominationsToUse)

return PreMeltBundle(
proofs = proofs,
quoteId = quoteId,
potentialChangeOutputs = null
potentialChangeOutputs = null,
)
}
}
Expand Down
32 changes: 6 additions & 26 deletions lib/src/main/kotlin/me/tb/cashuclient/swap/PreSwapBundle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,20 @@
* Copyright 2023 thunderbiscuit and contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE.txt file.
*/

package me.tb.cashuclient.swap

import fr.acinq.bitcoin.PrivateKey
import fr.acinq.bitcoin.PublicKey
import me.tb.cashuclient.db.CashuDB
import me.tb.cashuclient.types.KeysetId
import me.tb.cashuclient.types.Secret
import me.tb.cashuclient.db.DBProof
import me.tb.cashuclient.db.DBSettings
import me.tb.cashuclient.decomposeAmount
import me.tb.cashuclient.types.BlindedMessage
import me.tb.cashuclient.types.BlindingData
import me.tb.cashuclient.types.PreRequestBundle
import me.tb.cashuclient.types.Proof
import me.tb.cashuclient.types.createBlindingData
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction

/**
* The data bundle Alice must create prior to communicating with the mint requesting a swap. Once the mint sends a
Expand Down Expand Up @@ -60,6 +56,7 @@ public class PreSwapBundle private constructor(
* @param keysetId The [KeysetId] of the keyset the wallet expects will be signing the [BlindedMessage]s.
*/
public fun create(
db: CashuDB,
availableForSwap: List<ULong>,
requiredAmount: ULong,
keysetId: KeysetId,
Expand All @@ -78,7 +75,8 @@ public class PreSwapBundle private constructor(

val requiredDenominations: List<ULong> = decomposeAmount(requiredAmount)
val overPayment: ULong = runningTotal - requiredAmount
val changeDenominations: List<ULong> = if (overPayment > 0uL) decomposeAmount(overPayment) else emptyList()
val changeDenominations: List<ULong> =
if (overPayment > 0uL) decomposeAmount(overPayment) else emptyList()

val requestDenominations = requiredDenominations + changeDenominations

Expand All @@ -89,25 +87,7 @@ public class PreSwapBundle private constructor(
}

// Go get a proof in the database for the denominations required
DBSettings.db
val proofs: List<Proof> = transaction {
SchemaUtils.create(DBProof)

denominationsToSwap.map { denomination ->
DBProof.select { DBProof.amount eq denomination }
.limit(1)
.firstOrNull()
?.let {
Proof(
amount = it[DBProof.amount],
id = it[DBProof.id],
secret = it[DBProof.secret],
C = it[DBProof.C],
script = it[DBProof.script]
)
} ?: throw Exception("No proof found for denomination $denomination")
}
}
val proofs = db.proofsForAmounts(denominationsToSwap)

return PreSwapBundle(proofs, preSwapItem, keysetId)
}
Expand Down
12 changes: 12 additions & 0 deletions lib/src/main/sqldelight/me/tb/cashuclient/Bolt11Payment.sq
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- A table to store payment requests made by the mint.
-- Once paid, these will later be used to request tokens.
CREATE TABLE Bolt11Payment(
-- The hash here is really just a secret payment ID used to identify the payment between the mint and the client.
hash TEXT NOT NULL PRIMARY KEY,
pr TEXT NOT NULL,
value INTEGER NOT NULL
);

-- Using a hash as the identifier for an amount requested and invoice paid, return said amount
getAmountByHash:
SELECT value FROM Bolt11Payment WHERE hash = ?;
Loading
Loading