mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-01-06 02:14:21 +01:00
lnbits migrations work
This commit is contained in:
@@ -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."""
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user