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] 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)