From fcc2c5c3e0e4581cd0f95581917a4c3b64b6a028 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 12 Oct 2022 23:16:08 +0200 Subject: [PATCH] lnbits migrations work --- cashu/core/migrations.py | 4 ++ cashu/mint/crud.py | 89 ++++++++++++++++++++++++++++----------- cashu/mint/ledger.py | 48 ++++++++++++--------- cashu/mint/migrations.py | 91 +++++++++++++++++++++------------------- 4 files changed, 144 insertions(+), 88 deletions(-) diff --git a/cashu/core/migrations.py b/cashu/core/migrations.py index cc5041f..29cf8c3 100644 --- a/cashu/core/migrations.py +++ b/cashu/core/migrations.py @@ -3,6 +3,10 @@ import re from cashu.core.db import COCKROACH, POSTGRES, SQLITE, Database +def table_with_schema(db, table: str): + return f"{db.references_schema if db.schema else ''}{table}" + + async def migrate_databases(db: Database, migrations_module): """Creates the necessary databases if they don't exist already; or migrates them.""" diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 0e6cd38..6ce7120 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -2,19 +2,60 @@ from typing import Any, List, Optional from cashu.core.base import Invoice, MintKeyset, Proof from cashu.core.db import Connection, Database +from cashu.core.migrations import table_with_schema + + +class LedgerCrud: + """ + Database interface for Cashu mint. + + This class needs to be overloaded by any app that imports the Cashu mint. + """ + + async def get_keyset(*args, **kwags): + + return await get_keyset(*args, **kwags) + + async def get_lightning_invoice(*args, **kwags): + + return await get_lightning_invoice(*args, **kwags) + + async def get_proofs_used(*args, **kwags): + + return await get_proofs_used(*args, **kwags) + + async def invalidate_proof(*args, **kwags): + + return await invalidate_proof(*args, **kwags) + + async def store_keyset(*args, **kwags): + + return await store_keyset(*args, **kwags) + + async def store_lightning_invoice(*args, **kwags): + + return await store_lightning_invoice(*args, **kwags) + + async def store_promise(*args, **kwags): + + return await store_promise(*args, **kwags) + + async def update_lightning_invoice(*args, **kwags): + + return await update_lightning_invoice(*args, **kwags) async def store_promise( + db: Database, amount: int, B_: str, C_: str, - db: Database, conn: Optional[Connection] = None, ): await (conn or db).execute( - """ - INSERT INTO promises + f""" + INSERT INTO {table_with_schema(db, 'promises')} (amount, B_b, C_b) VALUES (?, ?, ?) """, @@ -32,23 +73,23 @@ async def get_proofs_used( ): rows = await (conn or db).fetchall( - """ - SELECT secret from proofs_used + f""" + SELECT secret from {table_with_schema(db, 'proofs_used')} """ ) return [row[0] for row in rows] async def invalidate_proof( - proof: Proof, db: Database, + proof: Proof, conn: Optional[Connection] = None, ): # we add the proof and secret to the used list await (conn or db).execute( - """ - INSERT INTO proofs_used + f""" + INSERT INTO {table_with_schema(db, 'proofs_used')} (amount, C, secret) VALUES (?, ?, ?) """, @@ -61,14 +102,14 @@ async def invalidate_proof( async def store_lightning_invoice( - invoice: Invoice, db: Database, + invoice: Invoice, conn: Optional[Connection] = None, ): await (conn or db).execute( - """ - INSERT INTO invoices + f""" + INSERT INTO {table_with_schema(db, 'invoices')} (amount, pr, hash, issued) VALUES (?, ?, ?, ?) """, @@ -82,14 +123,14 @@ async def store_lightning_invoice( async def get_lightning_invoice( - hash: str, db: Database, + hash: str, conn: Optional[Connection] = None, ): row = await (conn or db).fetchone( - """ - SELECT * from invoices + f""" + SELECT * from {table_with_schema(db, 'invoices')} WHERE hash = ? """, (hash,), @@ -98,13 +139,13 @@ async def get_lightning_invoice( async def update_lightning_invoice( + db: Database, hash: str, issued: bool, - db: Database, conn: Optional[Connection] = None, ): await (conn or db).execute( - "UPDATE invoices SET issued = ? WHERE hash = ?", + f"UPDATE {table_with_schema(db, 'invoices')} SET issued = ? WHERE hash = ?", ( issued, hash, @@ -113,23 +154,23 @@ async def update_lightning_invoice( async def store_keyset( + db: Database, keyset: MintKeyset, - db: Database = None, conn: Optional[Connection] = None, ): await (conn or db).execute( # type: ignore - """ - INSERT INTO keysets + f""" + INSERT INTO {table_with_schema(db, 'keysets')} (id, derivation_path, valid_from, valid_to, first_seen, active, version) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( keyset.id, keyset.derivation_path, - keyset.valid_from, - keyset.valid_to, - keyset.first_seen, + keyset.valid_from or db.timestamp_now, + keyset.valid_to or db.timestamp_now, + keyset.first_seen or db.timestamp_now, True, keyset.version, ), @@ -137,9 +178,9 @@ async def store_keyset( async def get_keyset( + db: Database, id: str = None, derivation_path: str = "", - db: Database = None, conn: Optional[Connection] = None, ): clauses = [] @@ -158,7 +199,7 @@ async def get_keyset( rows = await (conn or db).fetchall( # type: ignore f""" - SELECT * from keysets + SELECT * from {table_with_schema(db, 'keysets')} {where} """, tuple(values), diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index f6efe5d..086e37d 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -27,28 +27,32 @@ from cashu.core.secp import PublicKey from cashu.core.settings import LIGHTNING, MAX_ORDER, VERSION from cashu.core.split import amount_split from cashu.lightning import WALLET -from cashu.mint.crud import ( - get_keyset, - get_lightning_invoice, - get_proofs_used, - invalidate_proof, - store_keyset, - store_lightning_invoice, - store_promise, - update_lightning_invoice, -) +from cashu.mint.crud import LedgerCrud + +# from cashu.mint.crud import ( +# get_keyset, +# get_lightning_invoice, +# get_proofs_used, +# invalidate_proof, +# store_keyset, +# store_lightning_invoice, +# store_promise, +# update_lightning_invoice, +# ) class Ledger: - def __init__(self, db: Database, seed: str, derivation_path=""): + def __init__(self, db: Database, seed: str, derivation_path="", crud=LedgerCrud): self.proofs_used: Set[str] = set() self.master_key = seed self.derivation_path = derivation_path self.db = db + self.crud = crud async def load_used_proofs(self): """Load all used proofs from database.""" - self.proofs_used = set(await get_proofs_used(db=self.db)) + proofs_used = await self.crud.get_proofs_used(db=self.db) + self.proofs_used = set(proofs_used) async def init_keysets(self): """Loads all past keysets and stores the active one if not already in db""" @@ -58,16 +62,16 @@ class Ledger: ) # check if current keyset is stored in db and store if not logger.debug(f"Loading keyset {self.keyset.id} from db.") - current_keyset_local: List[MintKeyset] = await get_keyset( + current_keyset_local: List[MintKeyset] = await self.crud.get_keyset( id=self.keyset.id, db=self.db ) if not len(current_keyset_local): logger.debug(f"Storing keyset {self.keyset.id}.") - await store_keyset(keyset=self.keyset, db=self.db) + await self.crud.store_keyset(keyset=self.keyset, db=self.db) # load all past keysets from db # this needs two steps because the types of tmp_keysets and the argument of MintKeysets() are different - tmp_keysets: List[MintKeyset] = await get_keyset(db=self.db) + tmp_keysets: List[MintKeyset] = await self.crud.get_keyset(db=self.db) self.keysets = MintKeysets(tmp_keysets) logger.debug(f"Keysets {self.keysets.keysets}") # generate all derived keys from stored derivation paths of past keysets @@ -88,7 +92,7 @@ class Ledger: """Generates a promise for given amount and returns a pair (amount, C').""" private_key_amount = self.keyset.private_keys[amount] # Get the correct key C_ = b_dhke.step2_bob(B_, private_key_amount) - await store_promise( + await self.crud.store_promise( amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db ) return BlindedSignature(amount=amount, C_=C_.serialize().hex()) @@ -221,7 +225,9 @@ class Ledger: async def _check_lightning_invoice(self, amounts, payment_hash: str): """Checks with the Lightning backend whether an invoice with this payment_hash was paid.""" - invoice: Invoice = await get_lightning_invoice(payment_hash, db=self.db) + invoice: Invoice = await self.crud.get_lightning_invoice( + payment_hash, db=self.db + ) if invoice.issued: raise Exception("tokens already issued for this invoice.") total_requested = sum(amounts) @@ -231,7 +237,9 @@ class Ledger: ) status = await WALLET.get_invoice_status(payment_hash) if status.paid: - await update_lightning_invoice(payment_hash, issued=True, db=self.db) + await self.crud.update_lightning_invoice( + payment_hash, issued=True, db=self.db + ) return status.paid async def _pay_lightning_invoice(self, invoice: str, fees_msat: int): @@ -251,7 +259,7 @@ class Ledger: self.proofs_used |= proof_msgs # store in db for p in proofs: - await invalidate_proof(p, db=self.db) + await self.crud.invalidate_proof(p, db=self.db) def _serialize_pubkeys(self): """Returns public keys for possible amounts.""" @@ -269,7 +277,7 @@ class Ledger: ) if not payment_request or not checking_id: raise Exception(f"Could not create Lightning invoice.") - await store_lightning_invoice(invoice, db=self.db) + await self.crud.store_lightning_invoice(invoice, db=self.db) return payment_request, checking_id async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None): diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index c58b3a0..51a6459 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -1,10 +1,11 @@ from cashu.core.db import Database +from cashu.core.migrations import table_with_schema async def m000_create_migrations_table(db): await db.execute( - """ - CREATE TABLE IF NOT EXISTS dbversions ( + f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'dbversions')} ( db TEXT PRIMARY KEY, version INT NOT NULL ) @@ -14,8 +15,8 @@ async def m000_create_migrations_table(db): async def m001_initial(db: Database): await db.execute( - """ - CREATE TABLE IF NOT EXISTS promises ( + f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'promises')} ( amount INTEGER NOT NULL, B_b TEXT NOT NULL, C_b TEXT NOT NULL, @@ -27,8 +28,8 @@ async def m001_initial(db: Database): ) await db.execute( - """ - CREATE TABLE IF NOT EXISTS proofs_used ( + f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} ( amount INTEGER NOT NULL, C TEXT NOT NULL, secret TEXT NOT NULL, @@ -40,8 +41,8 @@ async def m001_initial(db: Database): ) await db.execute( - """ - CREATE TABLE IF NOT EXISTS invoices ( + f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'invoices')} ( amount INTEGER NOT NULL, pr TEXT NOT NULL, hash TEXT NOT NULL, @@ -53,38 +54,38 @@ async def m001_initial(db: Database): """ ) - await db.execute( - """ - CREATE VIEW IF NOT EXISTS balance_issued AS - SELECT COALESCE(SUM(s), 0) AS balance FROM ( - SELECT SUM(amount) AS s - FROM promises - WHERE amount > 0 - ); - """ - ) + # await db.execute( + # f""" + # CREATE VIEW {table_with_schema(db, 'balance_issued')} AS + # SELECT COALESCE(SUM(s), 0) AS balance FROM ( + # SELECT SUM(amount) AS s + # FROM {table_with_schema(db, 'promises')} + # WHERE amount > 0 + # ); + # """ + # ) - await db.execute( - """ - CREATE VIEW IF NOT EXISTS balance_used AS - SELECT COALESCE(SUM(s), 0) AS balance FROM ( - SELECT SUM(amount) AS s - FROM proofs_used - WHERE amount > 0 - ); - """ - ) + # await db.execute( + # f""" + # CREATE VIEW {table_with_schema(db, 'balance_used')} AS + # SELECT COALESCE(SUM(s), 0) AS balance FROM ( + # SELECT SUM(amount) AS s + # FROM {table_with_schema(db, 'proofs_used')} + # WHERE amount > 0 + # ); + # """ + # ) - await db.execute( - """ - CREATE VIEW IF NOT EXISTS balance AS - SELECT s_issued - s_used AS balance FROM ( - SELECT bi.balance AS s_issued, bu.balance AS s_used - FROM balance_issued bi - CROSS JOIN balance_used bu - ); - """ - ) + # await db.execute( + # f""" + # CREATE VIEW {table_with_schema(db, 'balance')} AS + # SELECT s_issued - s_used AS balance FROM ( + # SELECT bi.balance AS s_issued, bu.balance AS s_used + # FROM {table_with_schema(db, 'balance_issued')} bi + # CROSS JOIN {table_with_schema(db, 'balance_used')} bu + # ); + # """ + # ) async def m003_mint_keysets(db: Database): @@ -93,12 +94,12 @@ async def m003_mint_keysets(db: Database): """ await db.execute( f""" - CREATE TABLE IF NOT EXISTS keysets ( + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} ( id TEXT NOT NULL, derivation_path TEXT, - valid_from TIMESTAMP DEFAULT {db.timestamp_now}, - valid_to TIMESTAMP DEFAULT {db.timestamp_now}, - first_seen TIMESTAMP DEFAULT {db.timestamp_now}, + valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, active BOOL DEFAULT TRUE, UNIQUE (derivation_path) @@ -108,7 +109,7 @@ async def m003_mint_keysets(db: Database): ) await db.execute( f""" - CREATE TABLE IF NOT EXISTS mint_pubkeys ( + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_pubkeys')} ( id TEXT NOT NULL, amount INTEGER NOT NULL, pubkey TEXT NOT NULL, @@ -124,4 +125,6 @@ async def m004_keysets_add_version(db: Database): """ Column that remembers with which version """ - await db.execute("ALTER TABLE keysets ADD COLUMN version TEXT") + await db.execute( + f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN version TEXT" + )