Files
nutshell/cashu/mint/crud.py
callebtc 0490f20932 Wallet: Lightning interface (#318)
* mint does not start yet

* fix import

* revert mint db migrations

* handle zero fee case

* cli: adjust fee message

* wallet: replace requests with httpx

* clean up

* rename http client decorator

* fix pending check in main, todo: TEST PROXIES WITH HTTPX

* fix up

* use httpx for nostr as well

* update packages to same versions as https://github.com/lnbits/lnbits/pull/1609/files

* fix proof deserialization

* check for string

* tests passing

* adjust wallet api tests

* lockfile

* add correct responses to Lightning interface and delete melt_id for proofs for which the payent has failed

* fix create_invoice checking_id response

* migrations atomic

* proofs are stored automatically when created

* make format

* use bolt11 lib

* stricter type checking

* add fee response to payments

* assert fees in test_melt

* test that mint_id and melt_id is stored correctly in proofs and proofs_used

* remove traces

* refactor: Lightning interface into own file and LedgerCrud with typing

* fix tests

* fix payment response

* rename variable
2023-10-21 14:38:16 +02:00

384 lines
8.5 KiB
Python

from typing import Any, List, Optional
from ..core.base import BlindedSignature, Invoice, MintKeyset, Proof
from ..core.db import Connection, Database, table_with_schema
class LedgerCrud:
"""
Database interface for Cashu mint.
This class needs to be overloaded by any app that imports the Cashu mint and wants
to use their own database.
"""
async def get_keyset(
self,
db: Database,
id: str = "",
derivation_path: str = "",
conn: Optional[Connection] = None,
):
return await get_keyset(
db=db,
id=id,
derivation_path=derivation_path,
conn=conn,
)
async def get_lightning_invoice(
self,
db: Database,
id: str,
conn: Optional[Connection] = None,
):
return await get_lightning_invoice(
db=db,
id=id,
conn=conn,
)
async def get_secrets_used(
self,
db: Database,
conn: Optional[Connection] = None,
):
return await get_secrets_used(db=db, conn=conn)
async def invalidate_proof(
self,
db: Database,
proof: Proof,
conn: Optional[Connection] = None,
):
return await invalidate_proof(
db=db,
proof=proof,
conn=conn,
)
async def get_proofs_pending(
self,
db: Database,
conn: Optional[Connection] = None,
):
return await get_proofs_pending(db=db, conn=conn)
async def set_proof_pending(
self,
db: Database,
proof: Proof,
conn: Optional[Connection] = None,
):
return await set_proof_pending(
db=db,
proof=proof,
conn=conn,
)
async def unset_proof_pending(
self, proof: Proof, db: Database, conn: Optional[Connection] = None
):
return await unset_proof_pending(
proof=proof,
db=db,
conn=conn,
)
async def store_keyset(
self,
db: Database,
keyset: MintKeyset,
conn: Optional[Connection] = None,
):
return await store_keyset(
db=db,
keyset=keyset,
conn=conn,
)
async def store_lightning_invoice(
self,
db: Database,
invoice: Invoice,
conn: Optional[Connection] = None,
):
return await store_lightning_invoice(
db=db,
invoice=invoice,
conn=conn,
)
async def store_promise(
self,
*,
db: Database,
amount: int,
B_: str,
C_: str,
id: str,
e: str = "",
s: str = "",
conn: Optional[Connection] = None,
):
return await store_promise(
db=db,
amount=amount,
B_=B_,
C_=C_,
id=id,
e=e,
s=s,
conn=conn,
)
async def get_promise(
self,
db: Database,
B_: str,
conn: Optional[Connection] = None,
):
return await get_promise(
db=db,
B_=B_,
conn=conn,
)
async def update_lightning_invoice(
self,
db: Database,
id: str,
issued: bool,
conn: Optional[Connection] = None,
):
return await update_lightning_invoice(
db=db,
id=id,
issued=issued,
conn=conn,
)
async def store_promise(
*,
db: Database,
amount: int,
B_: str,
C_: str,
id: str,
e: str = "",
s: str = "",
conn: Optional[Connection] = None,
):
await (conn or db).execute(
f"""
INSERT INTO {table_with_schema(db, 'promises')}
(amount, B_b, C_b, e, s, id)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
amount,
B_,
C_,
e,
s,
id,
),
)
async def get_promise(
db: Database,
B_: str,
conn: Optional[Connection] = None,
):
row = await (conn or db).fetchone(
f"""
SELECT * from {table_with_schema(db, 'promises')}
WHERE B_b = ?
""",
(str(B_),),
)
return BlindedSignature(amount=row[0], C_=row[2], id=row[3]) if row else None
async def get_secrets_used(
db: Database,
conn: Optional[Connection] = None,
):
rows = await (conn or db).fetchall(f"""
SELECT secret from {table_with_schema(db, 'proofs_used')}
""")
return [row[0] for row in rows]
async def invalidate_proof(
db: Database,
proof: Proof,
conn: Optional[Connection] = None,
):
# we add the proof and secret to the used list
await (conn or db).execute(
f"""
INSERT INTO {table_with_schema(db, 'proofs_used')}
(amount, C, secret, id)
VALUES (?, ?, ?, ?)
""",
(
proof.amount,
str(proof.C),
str(proof.secret),
str(proof.id),
),
)
async def get_proofs_pending(
db: Database,
conn: Optional[Connection] = None,
):
rows = await (conn or db).fetchall(f"""
SELECT * from {table_with_schema(db, 'proofs_pending')}
""")
return [Proof(**r) for r in rows]
async def set_proof_pending(
db: Database,
proof: Proof,
conn: Optional[Connection] = None,
):
# we add the proof and secret to the used list
await (conn or db).execute(
f"""
INSERT INTO {table_with_schema(db, 'proofs_pending')}
(amount, C, secret)
VALUES (?, ?, ?)
""",
(
proof.amount,
str(proof.C),
str(proof.secret),
),
)
async def unset_proof_pending(
proof: Proof,
db: Database,
conn: Optional[Connection] = None,
):
await (conn or db).execute(
f"""
DELETE FROM {table_with_schema(db, 'proofs_pending')}
WHERE secret = ?
""",
(str(proof["secret"]),),
)
async def store_lightning_invoice(
db: Database,
invoice: Invoice,
conn: Optional[Connection] = None,
):
await (conn or db).execute(
f"""
INSERT INTO {table_with_schema(db, 'invoices')}
(amount, bolt11, id, issued, payment_hash, out)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
invoice.amount,
invoice.bolt11,
invoice.id,
invoice.issued,
invoice.payment_hash,
invoice.out,
),
)
async def get_lightning_invoice(
db: Database,
id: str,
conn: Optional[Connection] = None,
):
row = await (conn or db).fetchone(
f"""
SELECT * from {table_with_schema(db, 'invoices')}
WHERE id = ?
""",
(id,),
)
row_dict = dict(row)
return Invoice(**row_dict) if row_dict else None
async def update_lightning_invoice(
db: Database,
id: str,
issued: bool,
conn: Optional[Connection] = None,
):
await (conn or db).execute(
f"UPDATE {table_with_schema(db, 'invoices')} SET issued = ? WHERE id = ?",
(
issued,
id,
),
)
async def store_keyset(
db: Database,
keyset: MintKeyset,
conn: Optional[Connection] = None,
):
await (conn or db).execute( # type: ignore
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 or db.timestamp_now,
keyset.valid_to or db.timestamp_now,
keyset.first_seen or db.timestamp_now,
True,
keyset.version,
),
)
async def get_keyset(
db: Database,
id: str = "",
derivation_path: str = "",
conn: Optional[Connection] = None,
):
clauses = []
values: List[Any] = []
clauses.append("active = ?")
values.append(True)
if id:
clauses.append("id = ?")
values.append(id)
if derivation_path:
clauses.append("derivation_path = ?")
values.append(derivation_path)
where = ""
if clauses:
where = f"WHERE {' AND '.join(clauses)}"
rows = await (conn or db).fetchall( # type: ignore
f"""
SELECT * from {table_with_schema(db, 'keysets')}
{where}
""",
tuple(values),
)
return [MintKeyset(**row) for row in rows]