From fe9de4cbca40fc1b2459519628dbf09764f36e61 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:08:05 +0200 Subject: [PATCH 01/16] restart imports --- cashu/mint/__init__.py | 4 ---- cashu/mint/app.py | 6 +++--- cashu/mint/ledger.py | 20 +++++++++++--------- cashu/mint/router.py | 10 +++++----- cashu/mint/startup.py | 12 +++++++++--- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/cashu/mint/__init__.py b/cashu/mint/__init__.py index 50d1a38..e69de29 100644 --- a/cashu/mint/__init__.py +++ b/cashu/mint/__init__.py @@ -1,4 +0,0 @@ -from cashu.core.settings import MINT_PRIVATE_KEY -from cashu.mint.ledger import Ledger - -ledger = Ledger(MINT_PRIVATE_KEY, "data/mint", derivation_path="0/0/0/0") diff --git a/cashu/mint/app.py b/cashu/mint/app.py index 002d03e..ccd5b13 100644 --- a/cashu/mint/app.py +++ b/cashu/mint/app.py @@ -11,7 +11,7 @@ from starlette_context.middleware import RawContextMiddleware from cashu.core.settings import DEBUG, VERSION from .router import router -from .startup import load_ledger +from .startup import start_mint_init class CustomHeaderMiddleware(BaseHTTPMiddleware): @@ -86,5 +86,5 @@ app.include_router(router=router) @app.on_event("startup") -async def startup_load_ledger(): - await load_ledger() +async def startup_mint(): + await start_mint_init() diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 83b6b31..f6efe5d 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -40,11 +40,11 @@ from cashu.mint.crud import ( class Ledger: - def __init__(self, secret_key: str, db: str, derivation_path=""): + def __init__(self, db: Database, seed: str, derivation_path=""): self.proofs_used: Set[str] = set() - self.master_key = secret_key + self.master_key = seed self.derivation_path = derivation_path - self.db: Database = Database("mint", db) + self.db = db async def load_used_proofs(self): """Load all used proofs from database.""" @@ -86,8 +86,8 @@ class Ledger: async def _generate_promise(self, amount: int, B_: PublicKey): """Generates a promise for given amount and returns a pair (amount, C').""" - secret_key = self.keyset.private_keys[amount] # Get the correct key - C_ = b_dhke.step2_bob(B_, secret_key) + private_key_amount = self.keyset.private_keys[amount] # Get the correct key + C_ = b_dhke.step2_bob(B_, private_key_amount) await store_promise( amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db ) @@ -109,22 +109,24 @@ class Ledger: raise Exception(f"tokens already spent. Secret: {proof.secret}") # if no keyset id is given in proof, assume the current one if not proof.id: - secret_key = self.keyset.private_keys[proof.amount] + private_key_amount = self.keyset.private_keys[proof.amount] else: # use the appropriate active keyset for this proof.id - secret_key = self.keysets.keysets[proof.id].private_keys[proof.amount] + private_key_amount = self.keysets.keysets[proof.id].private_keys[ + proof.amount + ] C = PublicKey(bytes.fromhex(proof.C), raw=True) # backwards compatibility with old hash_to_curve < 0.3.3 try: - ret = legacy.verify_pre_0_3_3(secret_key, C, proof.secret) + ret = legacy.verify_pre_0_3_3(private_key_amount, C, proof.secret) if ret: return ret except: pass - return b_dhke.verify(secret_key, C, proof.secret) + return b_dhke.verify(private_key_amount, C, proof.secret) def _verify_script(self, idx: int, proof: Proof): """ diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 2845850..d8f28f8 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -16,21 +16,21 @@ from cashu.core.base import ( SplitRequest, ) from cashu.core.errors import CashuError -from cashu.mint import ledger +from cashu.mint.__main__ import ledger router: APIRouter = APIRouter() @router.get("/keys") -def keys() -> dict[int, str]: +async def keys() -> dict[int, str]: """Get the public keys of the mint""" - return ledger.get_keyset() + return await ledger.get_keyset() @router.get("/keysets") -def keysets() -> dict[str, list[str]]: +async def keysets() -> dict[str, list[str]]: """Get all active keysets of the mint""" - return {"keysets": ledger.keysets.get_ids()} + return {"keysets": await ledger.keysets.get_ids()} @router.get("/mint") diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index f148e2c..852c521 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -7,12 +7,18 @@ from cashu.core.settings import CASHU_DIR, LIGHTNING from cashu.lightning import WALLET from cashu.mint import migrations -from . import ledger +from cashu.core.settings import MINT_PRIVATE_KEY +from cashu.mint.ledger import Ledger +from cashu.core.db import Database -async def load_ledger(): +async def start_mint_init(): + ledger = Ledger( + db=Database("mint", "data/mint"), + seed=MINT_PRIVATE_KEY, + derivation_path="0/0/0/0", + ) await migrate_databases(ledger.db, migrations) - # await asyncio.wait([m001_initial(ledger.db)]) await ledger.load_used_proofs() await ledger.init_keysets() From 65cba2aa84924d6a3d5e8580aedc94e1f9a0f1fb Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:47:08 +0200 Subject: [PATCH 02/16] idk --- cashu/mint/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cashu/mint/router.py b/cashu/mint/router.py index d8f28f8..2dfe345 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -16,7 +16,7 @@ from cashu.core.base import ( SplitRequest, ) from cashu.core.errors import CashuError -from cashu.mint.__main__ import ledger +from cashu.mint import ledger router: APIRouter = APIRouter() From 872169478518f350a7d712356825d70f7defc6e3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 12 Oct 2022 20:52:35 +0200 Subject: [PATCH 03/16] can import, migration dont work yet --- cashu/mint/__init__.py | 1 + cashu/mint/main.py | 3 ++- cashu/mint/router.py | 5 +++-- cashu/mint/startup.py | 18 ++++++++++++------ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/cashu/mint/__init__.py b/cashu/mint/__init__.py index e69de29..8b13789 100644 --- a/cashu/mint/__init__.py +++ b/cashu/mint/__init__.py @@ -0,0 +1 @@ + diff --git a/cashu/mint/main.py b/cashu/mint/main.py index 50c4a6a..5cce3a1 100644 --- a/cashu/mint/main.py +++ b/cashu/mint/main.py @@ -22,7 +22,8 @@ def main( ssl_keyfile: str = None, ssl_certfile: str = None, ): - """Launched with `poetry run mint` at root level""" + """This routine starts the uvicorn server if the Cashu mint is + launched with `poetry run mint` at root level""" # this beautiful beast parses all command line arguments and passes them to the uvicorn server d = dict() for a in ctx.args: diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 2dfe345..ced5c74 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -16,7 +16,8 @@ from cashu.core.base import ( SplitRequest, ) from cashu.core.errors import CashuError -from cashu.mint import ledger + +from cashu.mint.startup import ledger router: APIRouter = APIRouter() @@ -24,7 +25,7 @@ router: APIRouter = APIRouter() @router.get("/keys") async def keys() -> dict[int, str]: """Get the public keys of the mint""" - return await ledger.get_keyset() + return ledger.get_keyset() @router.get("/keysets") diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 852c521..8321417 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -1,3 +1,6 @@ +# startup routine of the standalone app. These are the steps that need +# to be taken by external apps importing the cashu mint. + import asyncio from loguru import logger @@ -7,17 +10,20 @@ from cashu.core.settings import CASHU_DIR, LIGHTNING from cashu.lightning import WALLET from cashu.mint import migrations -from cashu.core.settings import MINT_PRIVATE_KEY from cashu.mint.ledger import Ledger +from cashu.core.settings import MINT_PRIVATE_KEY from cashu.core.db import Database +ledger = Ledger( + db=Database("mint", "data/mint"), + seed=MINT_PRIVATE_KEY, + # seed="asd", + derivation_path="0/0/0/0", +) + async def start_mint_init(): - ledger = Ledger( - db=Database("mint", "data/mint"), - seed=MINT_PRIVATE_KEY, - derivation_path="0/0/0/0", - ) + await migrate_databases(ledger.db, migrations) await ledger.load_used_proofs() await ledger.init_keysets() 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 04/16] 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" + ) From 22854f4fb2855158cc09c764204e0cd3f156abae Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 13 Oct 2022 00:23:19 +0200 Subject: [PATCH 05/16] allow for internal and external migrations --- cashu/core/migrations.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cashu/core/migrations.py b/cashu/core/migrations.py index 29cf8c3..2388187 100644 --- a/cashu/core/migrations.py +++ b/cashu/core/migrations.py @@ -12,8 +12,8 @@ async def migrate_databases(db: Database, migrations_module): async def set_migration_version(conn, db_name, version): await conn.execute( - """ - INSERT INTO dbversions (db, version) VALUES (?, ?) + f""" + INSERT INTO {table_with_schema(db, 'dbversions')} (db, version) VALUES (?, ?) ON CONFLICT (db) DO UPDATE SET version = ? """, (db_name, version, version), @@ -22,7 +22,7 @@ async def migrate_databases(db: Database, migrations_module): async def run_migration(db, migrations_module): db_name = migrations_module.__name__.split(".")[-2] for key, migrate in migrations_module.__dict__.items(): - match = match = matcher.match(key) + match = matcher.match(key) if match: version = int(match.group(1)) if version > current_versions.get(db_name, 0): @@ -37,17 +37,19 @@ async def migrate_databases(db: Database, migrations_module): async with db.connect() as conn: if conn.type == SQLITE: exists = await conn.fetchone( - "SELECT * FROM sqlite_master WHERE type='table' AND name='dbversions'" + f"SELECT * FROM sqlite_master WHERE type='table' AND name='{table_with_schema(db, 'dbversions')}'" ) elif conn.type in {POSTGRES, COCKROACH}: exists = await conn.fetchone( - "SELECT * FROM information_schema.tables WHERE table_name = 'dbversions'" + f"SELECT * FROM information_schema.tables WHERE table_name = '{table_with_schema(db, 'dbversions')}'" ) if not exists: await migrations_module.m000_create_migrations_table(conn) - rows = await (await conn.execute("SELECT * FROM dbversions")).fetchall() + rows = await ( + await conn.execute(f"SELECT * FROM {table_with_schema(db, 'dbversions')}") + ).fetchall() current_versions = {row["db"]: row["version"] for row in rows} matcher = re.compile(r"^m(\d\d\d)_") - await run_migration(conn, migrations_module) + await run_migration(db, migrations_module) From 3437af041e1059f836aa859879737d7aff95bbec Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 13 Oct 2022 20:07:04 +0200 Subject: [PATCH 06/16] ability to specify the keyset for generating promises mint --- cashu/lightning/__init__.py | 4 +- cashu/mint/ledger.py | 104 ++++++++++++++++++++++-------------- cashu/mint/router.py | 16 +++--- cashu/mint/startup.py | 19 +++---- cashu/wallet/wallet.py | 2 +- tests/test_wallet.py | 18 +++++++ 6 files changed, 103 insertions(+), 60 deletions(-) diff --git a/cashu/lightning/__init__.py b/cashu/lightning/__init__.py index bc14007..54af20c 100644 --- a/cashu/lightning/__init__.py +++ b/cashu/lightning/__init__.py @@ -1,3 +1,3 @@ -from cashu.lightning.lnbits import LNbitsWallet +# from cashu.lightning.lnbits import LNbitsWallet -WALLET = LNbitsWallet() +# WALLET = LNbitsWallet() diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 086e37d..5259786 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -26,28 +26,27 @@ from cashu.core.script import verify_script 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 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, -# ) + +from cashu.lightning.lnbits import LNbitsWallet class Ledger: - def __init__(self, db: Database, seed: str, derivation_path="", crud=LedgerCrud): + def __init__( + self, + db: Database, + seed: str, + derivation_path="", + crud=LedgerCrud, + lightning=LNbitsWallet(), + ): self.proofs_used: Set[str] = set() self.master_key = seed self.derivation_path = derivation_path self.db = db self.crud = crud + self.lightning = lightning async def load_used_proofs(self): """Load all used proofs from database.""" @@ -81,19 +80,28 @@ class Ledger: if len(self.keysets.keysets): logger.debug(f"Loaded {len(self.keysets.keysets)} keysets from db.") - async def _generate_promises(self, amounts: List[int], B_s: List[str]): + async def _generate_promises( + self, B_s: List[BlindedMessage], keyset: MintKeyset = None + ): """Generates promises that sum to the given amount.""" return [ - await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True)) - for (amount, B_) in zip(amounts, B_s) + await self._generate_promise( + b.amount, PublicKey(bytes.fromhex(b.B_), raw=True), keyset + ) + for b in B_s ] - async def _generate_promise(self, amount: int, B_: PublicKey): + async def _generate_promise( + self, amount: int, B_: PublicKey, keyset: MintKeyset = None + ): """Generates a promise for given amount and returns a pair (amount, C').""" - private_key_amount = self.keyset.private_keys[amount] # Get the correct key + if keyset: + private_key_amount = keyset.private_keys[amount] + else: + private_key_amount = self.keyset.private_keys[amount] # Get the correct key C_ = b_dhke.step2_bob(B_, private_key_amount) await self.crud.store_promise( - amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db + amount=amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db ) return BlindedSignature(amount=amount, C_=C_.serialize().hex()) @@ -215,18 +223,21 @@ class Ledger: async def _request_lightning_invoice(self, amount: int): """Returns an invoice from the Lightning backend.""" - error, balance = await WALLET.status() + error, balance = await self.lightning.status() if error: raise Exception(f"Lightning wallet not responding: {error}") - ok, checking_id, payment_request, error_message = await WALLET.create_invoice( - amount, "cashu deposit" - ) + ( + ok, + checking_id, + payment_request, + error_message, + ) = await self.lightning.create_invoice(amount, "cashu deposit") return payment_request, checking_id 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 self.crud.get_lightning_invoice( - payment_hash, db=self.db + hash=payment_hash, db=self.db ) if invoice.issued: raise Exception("tokens already issued for this invoice.") @@ -235,21 +246,25 @@ class Ledger: raise Exception( f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}" ) - status = await WALLET.get_invoice_status(payment_hash) + status = await self.lightning.get_invoice_status(payment_hash) if status.paid: await self.crud.update_lightning_invoice( - payment_hash, issued=True, db=self.db + hash=payment_hash, issued=True, db=self.db ) return status.paid async def _pay_lightning_invoice(self, invoice: str, fees_msat: int): """Returns an invoice from the Lightning backend.""" - error, _ = await WALLET.status() + error, _ = await self.lightning.status() if error: raise Exception(f"Lightning wallet not responding: {error}") - ok, checking_id, fee_msat, preimage, error_message = await WALLET.pay_invoice( - invoice, fee_limit_msat=fees_msat - ) + ( + ok, + checking_id, + fee_msat, + preimage, + error_message, + ) = await self.lightning.pay_invoice(invoice, fee_limit_msat=fees_msat) return ok, preimage async def _invalidate_proofs(self, proofs: List[Proof]): @@ -259,7 +274,7 @@ class Ledger: self.proofs_used |= proof_msgs # store in db for p in proofs: - await self.crud.invalidate_proof(p, db=self.db) + await self.crud.invalidate_proof(proof=p, db=self.db) def _serialize_pubkeys(self): """Returns public keys for possible amounts.""" @@ -277,11 +292,17 @@ class Ledger: ) if not payment_request or not checking_id: raise Exception(f"Could not create Lightning invoice.") - await self.crud.store_lightning_invoice(invoice, db=self.db) + await self.crud.store_lightning_invoice(invoice=invoice, db=self.db) return payment_request, checking_id - async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None): + async def mint( + self, + B_s: List[BlindedMessage], + payment_hash=None, + keyset: MintKeyset = None, + ): """Mints a promise for coins for B_.""" + amounts = [b.amount for b in B_s] # check if lightning invoice was paid if LIGHTNING: try: @@ -295,9 +316,10 @@ class Ledger: if amount not in [2**i for i in range(MAX_ORDER)]: raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.") - promises = [ - await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts) - ] + # promises = [ + # await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts) + # ] + promises = await self._generate_promises(B_s, keyset) return promises async def melt(self, proofs: List[Proof], invoice: str): @@ -329,13 +351,17 @@ class Ledger: amount = math.ceil(decoded_invoice.amount_msat / 1000) # hack: check if it's internal, if it exists, it will return paid = False, # if id does not exist (not internal), it returns paid = None - paid = await WALLET.get_invoice_status(decoded_invoice.payment_hash) + paid = await self.lightning.get_invoice_status(decoded_invoice.payment_hash) internal = paid.paid == False fees_msat = fee_reserve(amount * 1000, internal) return fees_msat async def split( - self, proofs: List[Proof], amount: int, outputs: List[BlindedMessage] + self, + proofs: List[Proof], + amount: int, + outputs: List[BlindedMessage], + keyset: MintKeyset = None, ): """Consumes proofs and prepares new promises based on the amount split.""" total = sum_proofs(proofs) @@ -370,8 +396,8 @@ class Ledger: B_fst = [od.B_ for od in outputs[: len(outs_fst)]] B_snd = [od.B_ for od in outputs[len(outs_fst) :]] prom_fst, prom_snd = await self._generate_promises( - outs_fst, B_fst - ), await self._generate_promises(outs_snd, B_snd) + outs_fst, B_fst, keyset + ), await self._generate_promises(outs_snd, B_snd, keyset) # verify amounts in produced proofs self._verify_equation_balanced(proofs, prom_fst + prom_snd) return prom_fst, prom_snd diff --git a/cashu/mint/router.py b/cashu/mint/router.py index ced5c74..b846ae4 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -25,13 +25,14 @@ router: APIRouter = APIRouter() @router.get("/keys") async def keys() -> dict[int, str]: """Get the public keys of the mint""" - return ledger.get_keyset() + keyset = ledger.get_keyset() + return keyset @router.get("/keysets") async def keysets() -> dict[str, list[str]]: """Get all active keysets of the mint""" - return {"keysets": await ledger.keysets.get_ids()} + return {"keysets": ledger.keysets.get_ids()} @router.get("/mint") @@ -50,7 +51,7 @@ async def request_mint(amount: int = 0) -> GetMintResponse: @router.post("/mint") async def mint( - payloads: MintRequest, + mint_request: MintRequest, payment_hash: Union[str, None] = None, ) -> Union[List[BlindedSignature], CashuError]: """ @@ -58,13 +59,10 @@ async def mint( Call this endpoint after `GET /mint`. """ - amounts = [] - B_s = [] - for payload in payloads.blinded_messages: - amounts.append(payload.amount) - B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True)) try: - promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash) + promises = await ledger.mint( + mint_request.blinded_messages, payment_hash=payment_hash + ) return promises except Exception as exc: return CashuError(error=str(exc)) diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 8321417..36a189e 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -7,7 +7,8 @@ from loguru import logger from cashu.core.migrations import migrate_databases from cashu.core.settings import CASHU_DIR, LIGHTNING -from cashu.lightning import WALLET + +# from cashu.lightning import WALLET from cashu.mint import migrations from cashu.mint.ledger import Ledger @@ -28,14 +29,14 @@ async def start_mint_init(): await ledger.load_used_proofs() await ledger.init_keysets() - if LIGHTNING: - error_message, balance = await WALLET.status() - if error_message: - logger.warning( - f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'", - RuntimeWarning, - ) - logger.info(f"Lightning balance: {balance} sat") + # if LIGHTNING: + # error_message, balance = await WALLET.status() + # if error_message: + # logger.warning( + # f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'", + # RuntimeWarning, + # ) + # logger.info(f"Lightning balance: {balance} sat") logger.info(f"Data dir: {CASHU_DIR}") logger.info("Mint started.") diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 02ca6eb..18362ae 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -173,7 +173,7 @@ class LedgerAPI: async def _get_keysets(self, url): resp = self.s.get( url + "/keysets", - ).json() + ) resp.raise_for_status() keysets = resp.json() assert len(keysets), Exception("did not receive any keysets") diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 087ca9f..7f5706b 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -11,6 +11,7 @@ from cashu.wallet import migrations from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet as Wallet1 from cashu.wallet.wallet import Wallet as Wallet2 +from cashu.core.settings import MAX_ORDER SERVER_ENDPOINT = "http://localhost:3338" @@ -48,6 +49,23 @@ async def wallet2(): yield wallet2 +@pytest.mark.asyncio +async def test_get_keys(wallet1: Wallet): + assert len(wallet1.keys) == MAX_ORDER + keyset = await wallet1._get_keys(wallet1.url) + assert type(keyset.id) == str + assert len(keyset.id) > 0 + + +@pytest.mark.asyncio +async def test_get_keysets(wallet1: Wallet): + keyset = await wallet1._get_keysets(wallet1.url) + assert type(keyset) == dict + assert type(keyset["keysets"]) == list + assert len(keyset["keysets"]) > 0 + assert keyset["keysets"][0] == wallet1.keyset_id + + @pytest.mark.asyncio async def test_mint(wallet1: Wallet): await wallet1.mint(64) From a84b04605ebf70737059eeb110336e1fc42c7fd2 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 13 Oct 2022 22:11:00 +0200 Subject: [PATCH 07/16] refactor --- cashu/core/base.py | 1 + cashu/core/crypto.py | 9 +++++++ cashu/mint/ledger.py | 59 +++++++++++++++++++++---------------------- cashu/mint/startup.py | 18 +++++++------ 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 99ec2a4..2eaa808 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -262,6 +262,7 @@ class MintKeyset: self.generate_keys(seed) def generate_keys(self, seed): + """Generates keys of a keyset from a seed.""" self.private_keys = derive_keys(seed, self.derivation_path) self.public_keys = derive_pubkeys(self.private_keys) self.id = derive_keyset_id(self.public_keys) diff --git a/cashu/core/crypto.py b/cashu/core/crypto.py index 1563d94..3c8fb0e 100644 --- a/cashu/core/crypto.py +++ b/cashu/core/crypto.py @@ -6,6 +6,15 @@ from cashu.core.secp import PrivateKey, PublicKey from cashu.core.settings import MAX_ORDER +# entropy = bytes([random.getrandbits(8) for i in range(16)]) +# mnemonic = bip39.mnemonic_from_bytes(entropy) +# seed = bip39.mnemonic_to_seed(mnemonic) +# root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"]) + +# bip44_xprv = root.derive("m/44h/1h/0h") +# bip44_xpub = bip44_xprv.to_public() + + def derive_keys(master_key: str, derivation_path: str = ""): """ Deterministic derivation of keys for 2^n values. diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 5259786..0ff1827 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -29,9 +29,6 @@ from cashu.core.split import amount_split from cashu.mint.crud import LedgerCrud -from cashu.lightning.lnbits import LNbitsWallet - - class Ledger: def __init__( self, @@ -39,11 +36,12 @@ class Ledger: seed: str, derivation_path="", crud=LedgerCrud, - lightning=LNbitsWallet(), + lightning=None, ): self.proofs_used: Set[str] = set() self.master_key = seed self.derivation_path = derivation_path + self.db = db self.crud = crud self.lightning = lightning @@ -53,33 +51,34 @@ class Ledger: 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""" - # generate current keyset from seed and current derivation path - self.keyset = MintKeyset( - seed=self.master_key, derivation_path=self.derivation_path, version=VERSION + async def load_keyset(self, derivation_path): + """Load current keyset keyset or generate new one.""" + keyset = MintKeyset( + seed=self.master_key, derivation_path=derivation_path, version=VERSION ) # 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 self.crud.get_keyset( - id=self.keyset.id, db=self.db + logger.debug(f"Loading keyset {keyset.id} from db.") + tmp_keyset_local: List[MintKeyset] = await self.crud.get_keyset( + id=keyset.id, db=self.db ) - if not len(current_keyset_local): - logger.debug(f"Storing keyset {self.keyset.id}.") - await self.crud.store_keyset(keyset=self.keyset, db=self.db) + if not len(tmp_keyset_local): + logger.debug(f"Storing keyset {keyset.id}.") + await self.crud.store_keyset(keyset=keyset, db=self.db) + return keyset + async def init_keysets(self): + """Loads all keysets from db.""" + self.keyset = await self.load_keyset(self.derivation_path) # 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 self.crud.get_keyset(db=self.db) self.keysets = MintKeysets(tmp_keysets) - logger.debug(f"Keysets {self.keysets.keysets}") + logger.debug(f"Loading {len(self.keysets.keysets)} keysets form db.") # generate all derived keys from stored derivation paths of past keysets for _, v in self.keysets.keysets.items(): + logger.debug(f"Generating keys for keyset {v.id}") v.generate_keys(self.master_key) - if len(self.keysets.keysets): - logger.debug(f"Loaded {len(self.keysets.keysets)} keysets from db.") - async def _generate_promises( self, B_s: List[BlindedMessage], keyset: MintKeyset = None ): @@ -276,13 +275,10 @@ class Ledger: for p in proofs: await self.crud.invalidate_proof(proof=p, db=self.db) - def _serialize_pubkeys(self): - """Returns public keys for possible amounts.""" - return {a: p.serialize().hex() for a, p in self.keyset.public_keys.items()} - # Public methods - def get_keyset(self): - return self._serialize_pubkeys() + def get_keyset(self, keyset_id: str = None): + keyset = self.keysets[keyset_id] if keyset_id else self.keyset + return {a: p.serialize().hex() for a, p in keyset.public_keys.items()} async def request_mint(self, amount): """Returns Lightning invoice and stores it in the db.""" @@ -391,13 +387,16 @@ class Ledger: # Mark proofs as used and prepare new promises await self._invalidate_proofs(proofs) + # split outputs according to amount outs_fst = amount_split(total - amount) - outs_snd = amount_split(amount) - B_fst = [od.B_ for od in outputs[: len(outs_fst)]] - B_snd = [od.B_ for od in outputs[len(outs_fst) :]] + B_fst = [od for od in outputs[: len(outs_fst)]] + B_snd = [od for od in outputs[len(outs_fst) :]] + + # generate promises prom_fst, prom_snd = await self._generate_promises( - outs_fst, B_fst, keyset - ), await self._generate_promises(outs_snd, B_snd, keyset) + B_fst, keyset + ), await self._generate_promises(B_snd, keyset) + # verify amounts in produced proofs self._verify_equation_balanced(proofs, prom_fst + prom_snd) return prom_fst, prom_snd diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 36a189e..4cf2151 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -14,12 +14,14 @@ from cashu.mint import migrations from cashu.mint.ledger import Ledger from cashu.core.settings import MINT_PRIVATE_KEY from cashu.core.db import Database +from cashu.lightning.lnbits import LNbitsWallet ledger = Ledger( db=Database("mint", "data/mint"), seed=MINT_PRIVATE_KEY, # seed="asd", derivation_path="0/0/0/0", + lightning=LNbitsWallet() if LIGHTNING else None, ) @@ -29,14 +31,14 @@ async def start_mint_init(): await ledger.load_used_proofs() await ledger.init_keysets() - # if LIGHTNING: - # error_message, balance = await WALLET.status() - # if error_message: - # logger.warning( - # f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'", - # RuntimeWarning, - # ) - # logger.info(f"Lightning balance: {balance} sat") + if LIGHTNING: + error_message, balance = await ledger.lightning.status() + if error_message: + logger.warning( + f"The backend for {ledger.lightning.__class__.__name__} isn't working properly: '{error_message}'", + RuntimeWarning, + ) + logger.info(f"Lightning balance: {balance} sat") logger.info(f"Data dir: {CASHU_DIR}") logger.info("Mint started.") From 5022d4e47fe017f7a899d375b5b4b0719ad08b90 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 14 Oct 2022 00:05:47 +0200 Subject: [PATCH 08/16] test melt without lightning --- cashu/core/base.py | 1 - cashu/mint/ledger.py | 22 +++++++++++++--------- cashu/mint/router.py | 4 ++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 2eaa808..4cb12e6 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -95,7 +95,6 @@ class Invoice(BaseModel): class BlindedMessage(BaseModel): - id: str = "" amount: int B_: str diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 0ff1827..c9c62cc 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -94,15 +94,13 @@ class Ledger: self, amount: int, B_: PublicKey, keyset: MintKeyset = None ): """Generates a promise for given amount and returns a pair (amount, C').""" - if keyset: - private_key_amount = keyset.private_keys[amount] - else: - private_key_amount = self.keyset.private_keys[amount] # Get the correct key + keyset = keyset if keyset else self.keyset + private_key_amount = keyset.private_keys[amount] C_ = b_dhke.step2_bob(B_, private_key_amount) await self.crud.store_promise( amount=amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db ) - return BlindedSignature(amount=amount, C_=C_.serialize().hex()) + return BlindedSignature(id=keyset.id, amount=amount, C_=C_.serialize().hex()) def _check_spendable(self, proof: Proof): """Checks whether the proof was already spent.""" @@ -277,7 +275,7 @@ class Ledger: # Public methods def get_keyset(self, keyset_id: str = None): - keyset = self.keysets[keyset_id] if keyset_id else self.keyset + keyset = self.keysets.keysets[keyset_id] if keyset_id else self.keyset return {a: p.serialize().hex() for a, p in keyset.public_keys.items()} async def request_mint(self, amount): @@ -332,7 +330,10 @@ class Ledger: "provided proofs not enough for Lightning payment." ) - status, preimage = await self._pay_lightning_invoice(invoice, fees_msat) + if LIGHTNING: + status, preimage = await self._pay_lightning_invoice(invoice, fees_msat) + else: + status, preimage = True, "preimage" if status == True: await self._invalidate_proofs(proofs) return status, preimage @@ -347,8 +348,11 @@ class Ledger: amount = math.ceil(decoded_invoice.amount_msat / 1000) # hack: check if it's internal, if it exists, it will return paid = False, # if id does not exist (not internal), it returns paid = None - paid = await self.lightning.get_invoice_status(decoded_invoice.payment_hash) - internal = paid.paid == False + if LIGHTNING: + paid = await self.lightning.get_invoice_status(decoded_invoice.payment_hash) + internal = paid.paid == False + else: + internal = True fees_msat = fee_reserve(amount * 1000, internal) return fees_msat diff --git a/cashu/mint/router.py b/cashu/mint/router.py index b846ae4..79b3ca7 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -59,6 +59,7 @@ async def mint( Call this endpoint after `GET /mint`. """ + print(mint_request.dict()) try: promises = await ledger.mint( mint_request.blinded_messages, payment_hash=payment_hash @@ -73,6 +74,7 @@ async def melt(payload: MeltRequest) -> GetMeltResponse: """ Requests tokens to be destroyed and sent out via Lightning. """ + print(payload) ok, preimage = await ledger.melt(payload.proofs, payload.invoice) resp = GetMeltResponse(paid=ok, preimage=preimage) return resp @@ -81,6 +83,7 @@ async def melt(payload: MeltRequest) -> GetMeltResponse: @router.post("/check") async def check_spendable(payload: CheckRequest) -> Dict[int, bool]: """Check whether a secret has been spent already or not.""" + print(payload) return await ledger.check_spendable(payload.proofs) @@ -91,6 +94,7 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse: Used by wallets for figuring out the fees they need to supply. This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu). """ + print(payload) fees_msat = await ledger.check_fees(payload.pr) return CheckFeesResponse(fee=fees_msat / 1000) From d2363f8f2a154618e8f746062c012c3f4ee91425 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 14 Oct 2022 00:30:24 +0200 Subject: [PATCH 09/16] wallet sends only relevant fields for /check endpoint --- cashu/core/base.py | 4 ++-- cashu/mint/router.py | 4 ---- cashu/wallet/wallet.py | 9 ++++++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 4cb12e6..7db5eb2 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -24,9 +24,9 @@ class P2SHScript(BaseModel): class Proof(BaseModel): id: str = "" - amount: int + amount: int = 0 secret: str = "" - C: str + C: str = "" script: Union[P2SHScript, None] = None reserved: bool = False # whether this proof is reserved for sending send_id: str = "" # unique ID of send attempt diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 79b3ca7..b846ae4 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -59,7 +59,6 @@ async def mint( Call this endpoint after `GET /mint`. """ - print(mint_request.dict()) try: promises = await ledger.mint( mint_request.blinded_messages, payment_hash=payment_hash @@ -74,7 +73,6 @@ async def melt(payload: MeltRequest) -> GetMeltResponse: """ Requests tokens to be destroyed and sent out via Lightning. """ - print(payload) ok, preimage = await ledger.melt(payload.proofs, payload.invoice) resp = GetMeltResponse(paid=ok, preimage=preimage) return resp @@ -83,7 +81,6 @@ async def melt(payload: MeltRequest) -> GetMeltResponse: @router.post("/check") async def check_spendable(payload: CheckRequest) -> Dict[int, bool]: """Check whether a secret has been spent already or not.""" - print(payload) return await ledger.check_spendable(payload.proofs) @@ -94,7 +91,6 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse: Used by wallets for figuring out the fees they need to supply. This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu). """ - print(payload) fees_msat = await ledger.check_fees(payload.pr) return CheckFeesResponse(fee=fees_msat / 1000) diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 18362ae..6f17cfa 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -273,9 +273,16 @@ class LedgerAPI: Cheks whether the secrets in proofs are already spent or not and returns a list of booleans. """ payload = CheckRequest(proofs=proofs) + + def _check_spendable_include_fields(proofs): + """strips away fields from the model that aren't necessary for the /split""" + return { + "proofs": {i: {"secret"} for i in range(len(proofs))}, + } + resp = self.s.post( self.url + "/check", - json=payload.dict(), + json=payload.dict(include=_check_spendable_include_fields(proofs)), ) resp.raise_for_status() return_dict = resp.json() From 167eaf89f2754bcfc99a947014fdfb1a66ae867b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 14 Oct 2022 01:02:19 +0200 Subject: [PATCH 10/16] remove comment --- cashu/mint/ledger.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index c9c62cc..1d26716 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -310,9 +310,6 @@ class Ledger: if amount not in [2**i for i in range(MAX_ORDER)]: raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.") - # promises = [ - # await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts) - # ] promises = await self._generate_promises(B_s, keyset) return promises From ee16b3e0f96670b9525b3edac19a01487470f5c2 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:19:44 +0200 Subject: [PATCH 11/16] disable starlette_context --- cashu/mint/app.py | 35 ++++++++++++++++++----------------- cashu/mint/ledger.py | 5 ++--- poetry.lock | 19 ++----------------- pyproject.toml | 1 - 4 files changed, 22 insertions(+), 38 deletions(-) diff --git a/cashu/mint/app.py b/cashu/mint/app.py index ccd5b13..675ba97 100644 --- a/cashu/mint/app.py +++ b/cashu/mint/app.py @@ -5,8 +5,9 @@ from fastapi import FastAPI from loguru import logger from starlette.middleware import Middleware from starlette.middleware.base import BaseHTTPMiddleware -from starlette_context import context -from starlette_context.middleware import RawContextMiddleware + +# from starlette_context import context +# from starlette_context.middleware import RawContextMiddleware from cashu.core.settings import DEBUG, VERSION @@ -14,15 +15,15 @@ from .router import router from .startup import start_mint_init -class CustomHeaderMiddleware(BaseHTTPMiddleware): - """ - Middleware for starlette that can set the context from request headers - """ +# class CustomHeaderMiddleware(BaseHTTPMiddleware): +# """ +# Middleware for starlette that can set the context from request headers +# """ - async def dispatch(self, request, call_next): - context["client-version"] = request.headers.get("Client-version") - response = await call_next(request) - return response +# async def dispatch(self, request, call_next): +# context["client-version"] = request.headers.get("Client-version") +# response = await call_next(request) +# return response def create_app(config_object="core.settings") -> FastAPI: @@ -60,12 +61,12 @@ def create_app(config_object="core.settings") -> FastAPI: configure_logger() - middleware = [ - Middleware( - RawContextMiddleware, - ), - Middleware(CustomHeaderMiddleware), - ] + # middleware = [ + # Middleware( + # RawContextMiddleware, + # ), + # Middleware(CustomHeaderMiddleware), + # ] app = FastAPI( title="Cashu Mint", @@ -75,7 +76,7 @@ def create_app(config_object="core.settings") -> FastAPI: "name": "MIT License", "url": "https://raw.githubusercontent.com/callebtc/cashu/main/LICENSE", }, - middleware=middleware, + # middleware=middleware, ) return app diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 1d26716..38a6ba2 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -6,7 +6,8 @@ import math from typing import Dict, List, Set from loguru import logger -from starlette_context import context + +# from starlette_context import context import cashu.core.b_dhke as b_dhke import cashu.core.bolt11 as bolt11 @@ -19,7 +20,6 @@ from cashu.core.base import ( MintKeysets, Proof, ) -from cashu.core.crypto import derive_keys, derive_keyset_id, derive_pubkeys from cashu.core.db import Database from cashu.core.helpers import fee_reserve, sum_proofs from cashu.core.script import verify_script @@ -70,7 +70,6 @@ class Ledger: """Loads all keysets from db.""" self.keyset = await self.load_keyset(self.derivation_path) # 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 self.crud.get_keyset(db=self.db) self.keysets = MintKeysets(tmp_keysets) logger.debug(f"Loading {len(self.keysets.keysets)} keysets form db.") diff --git a/poetry.lock b/poetry.lock index c7eb74e..a0dffb8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -168,7 +168,7 @@ starlette = "0.19.1" all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"] -test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.3,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] +test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] [[package]] name = "h11" @@ -553,17 +553,6 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] -[[package]] -name = "starlette-context" -version = "0.3.4" -description = "Access context in Starlette" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -starlette = "*" - [[package]] name = "tomli" version = "2.0.1" @@ -643,7 +632,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "14ff9c57ca971c645f1a075b5c6fa0a84a38eaf6399d14afa724136728a3da03" +content-hash = "b4e980ee90226bab07750b1becc8c69df7752f6d168d200a79c782aa1efe61da" [metadata.files] anyio = [ @@ -1010,10 +999,6 @@ starlette = [ {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"}, {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"}, ] -starlette-context = [ - {file = "starlette_context-0.3.4-py37-none-any.whl", hash = "sha256:b16bf17bd3ead7ded2f458aebf7f913744b9cf28305e16c69b435a6c6ddf1135"}, - {file = "starlette_context-0.3.4.tar.gz", hash = "sha256:2d28e1838302fb5d5adacadc10fb73fb2d5cca1f0aa1e279698701cc96f1567c"}, -] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, diff --git a/pyproject.toml b/pyproject.toml index b8622c3..73d3777 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ bitstring = "^3.1.9" secp256k1 = "^0.14.0" sqlalchemy-aio = "^0.17.0" python-bitcoinlib = "^0.11.2" -starlette-context = "^0.3.4" [tool.poetry.dev-dependencies] black = {version = "^22.8.0", allow-prereleases = true} From 189fb7c5db038c131d0f8240dd759136db32aa3b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:58:49 +0200 Subject: [PATCH 12/16] force sorted dictionary for keyset calculation --- cashu/core/crypto.py | 4 +++- cashu/mint/startup.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cashu/core/crypto.py b/cashu/core/crypto.py index 3c8fb0e..3524281 100644 --- a/cashu/core/crypto.py +++ b/cashu/core/crypto.py @@ -38,7 +38,9 @@ def derive_pubkeys(keys: Dict[int, PrivateKey]): def derive_keyset_id(keys: Dict[int, PublicKey]): """Deterministic derivation keyset_id from set of public keys.""" - pubkeys_concat = "".join([p.serialize().hex() for _, p in keys.items()]) + # sort public keys by amount + sorted_keys = dict(sorted(keys.items())) + pubkeys_concat = "".join([p.serialize().hex() for _, p in sorted_keys.items()]) return base64.b64encode( hashlib.sha256((pubkeys_concat).encode("utf-8")).digest() ).decode()[:12] diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 4cf2151..762533c 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -8,7 +8,6 @@ from loguru import logger from cashu.core.migrations import migrate_databases from cashu.core.settings import CASHU_DIR, LIGHTNING -# from cashu.lightning import WALLET from cashu.mint import migrations from cashu.mint.ledger import Ledger From 7fb2c81ede0b4345f0588d7722a161eb9b0c6074 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:59:01 +0200 Subject: [PATCH 13/16] test mint --- tests/test_mint.py | 119 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/test_mint.py diff --git a/tests/test_mint.py b/tests/test_mint.py new file mode 100644 index 0000000..620bade --- /dev/null +++ b/tests/test_mint.py @@ -0,0 +1,119 @@ +import time +from typing import List + +import pytest +import pytest_asyncio + +from cashu.core.base import BlindedMessage, Proof +from cashu.core.helpers import async_unwrap, sum_proofs +from cashu.core.migrations import migrate_databases + +SERVER_ENDPOINT = "http://localhost:3338" + +from cashu.mint import migrations + +from cashu.mint.ledger import Ledger +from cashu.core.settings import MINT_PRIVATE_KEY, MAX_ORDER +from cashu.core.db import Database +from cashu.lightning.lnbits import LNbitsWallet +import os + + +async def assert_err(f, msg): + """Compute f() and expect an error message 'msg'.""" + try: + await f + except Exception as exc: + assert exc.args[0] == msg, Exception( + f"Expected error: {msg}, got: {exc.args[0]}" + ) + + +def assert_amt(proofs: List[Proof], expected: int): + """Assert amounts the proofs contain.""" + assert [p.amount for p in proofs] == expected + + +async def start_mint_init(ledger): + await migrate_databases(ledger.db, migrations) + await ledger.load_used_proofs() + await ledger.init_keysets() + + +@pytest_asyncio.fixture(scope="function") +async def ledger(): + db_file = "data/mint/test.sqlite3" + if os.path.exists(db_file): + os.remove(db_file) + ledger = Ledger( + db=Database("test", "data/mint"), + seed="TEST_PRIVATE_KEY", + derivation_path="0/0/0/0", + lightning=None, + ) + await start_mint_init(ledger) + yield ledger + + +@pytest.mark.asyncio +async def test_keysets(ledger: Ledger): + assert len(ledger.keysets.keysets) + assert len(ledger.keysets.get_ids()) + assert ledger.keyset.id == "XQM1wwtQbOXE" + + +@pytest.mark.asyncio +async def test_get_keyset(ledger: Ledger): + keyset = ledger.get_keyset() + assert type(keyset) == dict + assert len(keyset) == MAX_ORDER + + +@pytest.mark.asyncio +async def test_mint(ledger: Ledger): + blinded_messages_mock = [ + BlindedMessage( + amount=8, + B_="02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239", + ) + ] + promises = await ledger.mint(blinded_messages_mock) + assert len(promises) + assert promises[0].amount == 8 + assert ( + promises[0].C_ + == "032dfadd74bb3abba8170ecbae5401507e384eafd312defda94148fa37314c0ef0" + ) + + +@pytest.mark.asyncio +async def test_mint_invalid_blinded_message(ledger: Ledger): + blinded_messages_mock_invalid_key = [ + BlindedMessage( + amount=8, + B_="02634a2c2b34bec9e8a4aba4361f6bff02d7fa2365379b0840afe249a7a9d71237", + ) + ] + await assert_err( + ledger.mint(blinded_messages_mock_invalid_key), "invalid public key" + ) + + +@pytest.mark.asyncio +async def test_generate_promises(ledger: Ledger): + blinded_messages_mock = [ + BlindedMessage( + amount=8, + B_="02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239", + ) + ] + promises = await ledger._generate_promises(blinded_messages_mock) + assert ( + promises[0].C_ + == "032dfadd74bb3abba8170ecbae5401507e384eafd312defda94148fa37314c0ef0" + ) + + +@pytest.mark.asyncio +async def test_get_output_split(ledger: Ledger): + assert ledger._get_output_split(13) == [1, 4, 8] From 6f913e3cc92367a2ad7ded7e058bbef79b99d17d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:59:21 +0200 Subject: [PATCH 14/16] make format --- cashu/core/crypto.py | 1 - cashu/mint/app.py | 6 +++--- cashu/mint/ledger.py | 4 ++-- cashu/mint/router.py | 1 - cashu/mint/startup.py | 11 ++++------- tests/test_mint.py | 12 ++++++------ tests/test_wallet.py | 2 +- 7 files changed, 16 insertions(+), 21 deletions(-) diff --git a/cashu/core/crypto.py b/cashu/core/crypto.py index 3524281..a3e0500 100644 --- a/cashu/core/crypto.py +++ b/cashu/core/crypto.py @@ -5,7 +5,6 @@ from typing import Dict, List from cashu.core.secp import PrivateKey, PublicKey from cashu.core.settings import MAX_ORDER - # entropy = bytes([random.getrandbits(8) for i in range(16)]) # mnemonic = bip39.mnemonic_from_bytes(entropy) # seed = bip39.mnemonic_to_seed(mnemonic) diff --git a/cashu/mint/app.py b/cashu/mint/app.py index 675ba97..a0c572e 100644 --- a/cashu/mint/app.py +++ b/cashu/mint/app.py @@ -6,14 +6,14 @@ from loguru import logger from starlette.middleware import Middleware from starlette.middleware.base import BaseHTTPMiddleware -# from starlette_context import context -# from starlette_context.middleware import RawContextMiddleware - from cashu.core.settings import DEBUG, VERSION from .router import router from .startup import start_mint_init +# from starlette_context import context +# from starlette_context.middleware import RawContextMiddleware + # class CustomHeaderMiddleware(BaseHTTPMiddleware): # """ diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 38a6ba2..794d193 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -7,8 +7,6 @@ from typing import Dict, List, Set from loguru import logger -# from starlette_context import context - import cashu.core.b_dhke as b_dhke import cashu.core.bolt11 as bolt11 import cashu.core.legacy as legacy @@ -28,6 +26,8 @@ from cashu.core.settings import LIGHTNING, MAX_ORDER, VERSION from cashu.core.split import amount_split from cashu.mint.crud import LedgerCrud +# from starlette_context import context + class Ledger: def __init__( diff --git a/cashu/mint/router.py b/cashu/mint/router.py index b846ae4..db18cee 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -16,7 +16,6 @@ from cashu.core.base import ( SplitRequest, ) from cashu.core.errors import CashuError - from cashu.mint.startup import ledger router: APIRouter = APIRouter() diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 762533c..a006e07 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -5,15 +5,12 @@ import asyncio from loguru import logger -from cashu.core.migrations import migrate_databases -from cashu.core.settings import CASHU_DIR, LIGHTNING - -from cashu.mint import migrations - -from cashu.mint.ledger import Ledger -from cashu.core.settings import MINT_PRIVATE_KEY from cashu.core.db import Database +from cashu.core.migrations import migrate_databases +from cashu.core.settings import CASHU_DIR, LIGHTNING, MINT_PRIVATE_KEY from cashu.lightning.lnbits import LNbitsWallet +from cashu.mint import migrations +from cashu.mint.ledger import Ledger ledger = Ledger( db=Database("mint", "data/mint"), diff --git a/tests/test_mint.py b/tests/test_mint.py index 620bade..ad5713b 100644 --- a/tests/test_mint.py +++ b/tests/test_mint.py @@ -10,14 +10,14 @@ from cashu.core.migrations import migrate_databases SERVER_ENDPOINT = "http://localhost:3338" -from cashu.mint import migrations - -from cashu.mint.ledger import Ledger -from cashu.core.settings import MINT_PRIVATE_KEY, MAX_ORDER -from cashu.core.db import Database -from cashu.lightning.lnbits import LNbitsWallet import os +from cashu.core.db import Database +from cashu.core.settings import MAX_ORDER, MINT_PRIVATE_KEY +from cashu.lightning.lnbits import LNbitsWallet +from cashu.mint import migrations +from cashu.mint.ledger import Ledger + async def assert_err(f, msg): """Compute f() and expect an error message 'msg'.""" diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 7f5706b..bcccebe 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -7,11 +7,11 @@ import pytest_asyncio from cashu.core.base import Proof from cashu.core.helpers import async_unwrap, sum_proofs from cashu.core.migrations import migrate_databases +from cashu.core.settings import MAX_ORDER from cashu.wallet import migrations from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet as Wallet1 from cashu.wallet.wallet import Wallet as Wallet2 -from cashu.core.settings import MAX_ORDER SERVER_ENDPOINT = "http://localhost:3338" From 223a40e9bd929b726d837bcffcc74ac3d9da2f0d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 15 Oct 2022 01:08:35 +0200 Subject: [PATCH 15/16] bump version to 0.4.0 --- cashu/core/settings.py | 2 +- cashu/mint/ledger.py | 2 +- pyproject.toml | 2 +- requirements.txt | 1 - setup.py | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 7c92601..15f9a1d 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -48,4 +48,4 @@ LNBITS_ENDPOINT = env.str("LNBITS_ENDPOINT", default=None) LNBITS_KEY = env.str("LNBITS_KEY", default=None) MAX_ORDER = 64 -VERSION = "0.3.3" +VERSION = "0.4.0" diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 794d193..0621f92 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -126,7 +126,7 @@ class Ledger: C = PublicKey(bytes.fromhex(proof.C), raw=True) - # backwards compatibility with old hash_to_curve < 0.3.3 + # backwards compatibility with old hash_to_curve < 0.4.0 try: ret = legacy.verify_pre_0_3_3(private_key_amount, C, proof.secret) if ret: diff --git a/pyproject.toml b/pyproject.toml index 73d3777..add76ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cashu" -version = "0.3.3" +version = "0.4.0" description = "Ecash wallet and mint." authors = ["calle "] license = "MIT" diff --git a/requirements.txt b/requirements.txt index 0065b77..475f862 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,6 @@ six==1.16.0 ; python_version >= "3.7" and python_version < "4.0" sniffio==1.3.0 ; python_version >= "3.7" and python_version < "4.0" sqlalchemy-aio==0.17.0 ; python_version >= "3.7" and python_version < "4.0" sqlalchemy==1.3.24 ; python_version >= "3.7" and python_version < "4.0" -starlette-context==0.3.4 ; python_version >= "3.7" and python_version < "4.0" starlette==0.19.1 ; python_version >= "3.7" and python_version < "4.0" tomli==2.0.1 ; python_version >= "3.7" and python_version < "4.0" typing-extensions==4.3.0 ; python_version >= "3.7" and python_version < "4.0" diff --git a/setup.py b/setup.py index ac45661..1769ca8 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ entry_points = {"console_scripts": ["cashu = cashu.wallet.cli:cli"]} setuptools.setup( name="cashu", - version="0.3.3", + version="0.4.0", description="Ecash wallet and mint with Bitcoin Lightning support", long_description=long_description, long_description_content_type="text/markdown", From d8fbdf4b8f7c810856510a238dceb6c00cbe909b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 15 Oct 2022 01:12:52 +0200 Subject: [PATCH 16/16] client test that mint returns tokens with correct keyset id --- tests/test_wallet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_wallet.py b/tests/test_wallet.py index bcccebe..08ca8aa 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -81,6 +81,8 @@ async def test_split(wallet1: Wallet): assert [p.amount for p in p1] == [4, 8, 32] assert sum_proofs(p2) == 20 assert [p.amount for p in p2] == [4, 16] + assert all([p.id == wallet1.keyset_id for p in p1]) + assert all([p.id == wallet1.keyset_id for p in p2]) @pytest.mark.asyncio