lnbits migrations work

This commit is contained in:
callebtc
2022-10-12 23:16:08 +02:00
parent 8721694785
commit fcc2c5c3e0
4 changed files with 144 additions and 88 deletions

View File

@@ -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."""

View File

@@ -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),

View File

@@ -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):

View File

@@ -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"
)