Files
nutshell/cashu/mint/crud.py
callebtc 0b2468914d Determinstic secrets / ecash restore (#131)
* first working version but some sats go missing

* back at it

* make format

* restore to main

* move mint database

* fix some tests

* make format

* remove old _construct_outputs we reintroduced in merge with main

* add type annotations

* add wallet private key to tests

* wallet: load proofs

* fix tests

* _generate_secrets with deterministic generation (temporary)

* allow wallet initialization with custom private key

* add pk to wallet api test

* mint scope=module

* remove private_key from test_wallet.py to see if it helps with the github tests

* readd private keys to tests

* workflow without env

* add more private key!

* readd env

* ledger scope session

* add default private key for testing

* generate private keys if not available

* testing

* its working!!!

* first iteration of bip32 working

* get mint info and add many type annotations

* tests

* fix tests with bip32

* restore from multiple mints

* disable profiler

* make format

* failed POST /mint do not increment secret counter

* store derivation path in each token

* fix tests

* refactor migrations so private keys can be generated by the wallet with .with_db() classmethod

* start fixing tests

* all tests passing except those that need to set a specific private key

* bip39 mnemonic to seed - with db but restore doesnt work yet with custom seed

* mnemonic restore works

* enter mnemonic in cli

* fix tests to use different mnemonic

* properly ask user for seed input

* tests: dont ask for inputs

* try to fix tests

* fix cashu -d

* fixing

* bump version and add more text to mnemonic enter

* add more comments

* add many more comments and type annotations in the wallet

* dont print generated mnemonic and dont wait for input

* fix test

* does this fix tests?

* sigh....

* make format

* do not restore from an initialized wallet

* fix mnemonics

* fix nitpicks

* print wallet name if nonstandard wallet

* fix merge error and remove comments

* poetry lock and requirements

* remove unused code

* fix tests

* mnemonic.lower() and add keyset id if not present for backwards compat

* edit comment
2023-07-24 13:42:56 +02:00

272 lines
6.6 KiB
Python

import time
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.
"""
async def get_keyset(*args, **kwags):
return await get_keyset(*args, **kwags) # type: ignore
async def get_lightning_invoice(*args, **kwags):
return await get_lightning_invoice(*args, **kwags) # type: ignore
async def get_proofs_used(*args, **kwags):
return await get_proofs_used(*args, **kwags) # type: ignore
async def invalidate_proof(*args, **kwags):
return await invalidate_proof(*args, **kwags) # type: ignore
async def get_proofs_pending(*args, **kwags):
return await get_proofs_pending(*args, **kwags) # type: ignore
async def set_proof_pending(*args, **kwags):
return await set_proof_pending(*args, **kwags) # type: ignore
async def unset_proof_pending(*args, **kwags):
return await unset_proof_pending(*args, **kwags) # type: ignore
async def store_keyset(*args, **kwags):
return await store_keyset(*args, **kwags) # type: ignore
async def store_lightning_invoice(*args, **kwags):
return await store_lightning_invoice(*args, **kwags) # type: ignore
async def store_promise(*args, **kwags):
return await store_promise(*args, **kwags) # type: ignore
async def get_promise(*args, **kwags):
return await get_promise(*args, **kwags) # type: ignore
async def update_lightning_invoice(*args, **kwags):
return await update_lightning_invoice(*args, **kwags) # type: ignore
async def store_promise(
db: Database,
amount: int,
B_: str,
C_: str,
id: str,
conn: Optional[Connection] = None,
):
await (conn or db).execute(
f"""
INSERT INTO {table_with_schema(db, 'promises')}
(amount, B_b, C_b, id)
VALUES (?, ?, ?, ?)
""",
(
amount,
str(B_),
str(C_),
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_proofs_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, pr, hash, issued, payment_hash)
VALUES (?, ?, ?, ?, ?)
""",
(
invoice.amount,
invoice.pr,
invoice.hash,
invoice.issued,
invoice.payment_hash,
),
)
async def get_lightning_invoice(
db: Database,
hash: str,
conn: Optional[Connection] = None,
):
row = await (conn or db).fetchone(
f"""
SELECT * from {table_with_schema(db, 'invoices')}
WHERE hash = ?
""",
(hash,),
)
return Invoice(**row) if row else None
async def update_lightning_invoice(
db: Database,
hash: str,
issued: bool,
conn: Optional[Connection] = None,
):
await (conn or db).execute(
f"UPDATE {table_with_schema(db, 'invoices')} SET issued = ? WHERE hash = ?",
(
issued,
hash,
),
)
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]