From 3265a08a2ea96d1db8922c59b593e7005b030792 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:15:25 -0300 Subject: [PATCH] remove LIGHTNING=True and pass quote id for melt --- README.md | 4 ++-- cashu/core/base.py | 2 +- cashu/mint/crud.py | 2 ++ cashu/mint/ledger.py | 4 ++++ cashu/mint/router.py | 18 +++++++++--------- cashu/wallet/api/router.py | 8 +++----- cashu/wallet/cli/cli.py | 16 ++++++++-------- cashu/wallet/wallet.py | 33 +++++++++++++++++---------------- tests/conftest.py | 4 +++- 9 files changed, 49 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 190c25b0..da558423 100644 --- a/README.md +++ b/README.md @@ -200,9 +200,9 @@ To run the tests in this repository, first install the dev dependencies with poetry install --with dev ``` -Then, make sure to set up your `.env` file to use your local mint and disable Lightning and Tor: +Then, make sure to set up your mint's `.env` file to use a fake Lightning backend and disable Tor: ```bash -LIGHTNING=FALSE +MINT_LIGHTNING_BACKEND=FakeWallet TOR=FALSE ``` You can run the tests with diff --git a/cashu/core/base.py b/cashu/core/base.py index b1a75770..3634c0d5 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -323,7 +323,7 @@ class PostMeltQuoteResponse(BaseModel): quote: str # quote id symbol: str # input symbol amount: int # input amount - fee_reserve: Optional[int] = None # input fee reserve + fee_reserve: int # input fee reserve # ------- API: MELT ------- diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 79c857ed..24b57c13 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -433,6 +433,8 @@ async def get_melt_quote( """, (quote_id,), ) + if row is None: + return None row_dict = dict(row) return MintQuote(**row_dict) if row_dict else None diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index afe281c3..34bb0ee5 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -402,6 +402,7 @@ async def melt( # TODO: needs logic to look up the lightning invoice from the quote ID melt_quote = await self.crud.get_melt_quote(quote_id=quote, db=self.db) assert melt_quote, "quote not found" + assert not melt_quote.issued, "quote already issued" bolt11_request = melt_quote.request logger.trace("melt called") @@ -448,6 +449,9 @@ async def melt( # melt successful, invalidate proofs await self._invalidate_proofs(proofs) + # set quote as issued + await self.crud.update_melt_quote(quote_id=quote, issued=True, db=self.db) + # prepare change to compensate wallet for overpaid fees return_promises: List[BlindedSignature] = [] if outputs and payment.fee_msat is not None: diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 7f9fd5ac..9819e684 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -144,7 +144,7 @@ async def mint_quote(payload: PostMintQuoteRequest) -> PostMintQuoteResponse: Call `POST /mint` after paying the invoice. """ - logger.trace(f"> POST /v1/mint: payload={payload}") + logger.trace(f"> POST /v1/mint/quote: payload={payload}") amount = payload.amount if amount > 21_000_000 * 100_000_000 or amount <= 0: raise CashuError(code=0, detail="Amount must be a valid amount of sat.") @@ -159,7 +159,7 @@ async def mint_quote(payload: PostMintQuoteRequest) -> PostMintQuoteResponse: symbol="sat", amount=amount, ) - logger.trace(f"< GET /mint: {resp}") + logger.trace(f"< POST /v1/mint/quote: {resp}") return resp @@ -178,13 +178,13 @@ async def mint( """ Requests the minting of tokens belonging to a paid payment request. - Call this endpoint after `GET /mint`. + Call this endpoint after `POST /v1/mint/quote`. """ - logger.trace(f"> POST /mint: {payload}") + logger.trace(f"> POST /v1/mint: {payload}") promises = await ledger.mint(outputs=payload.outputs, quote_id=payload.quote) blinded_signatures = PostMintResponse(quote=payload.quote, signatures=promises) - logger.trace(f"< POST /mint: {blinded_signatures}") + logger.trace(f"< POST /v1/mint: {blinded_signatures}") return blinded_signatures @@ -198,9 +198,9 @@ async def melt_quote(payload: PostMeltQuoteRequest) -> PostMeltQuoteResponse: """ Request a quote for melting tokens. """ - logger.trace(f"> POST /melt/quote: {payload}") + logger.trace(f"> POST /v1/melt/quote: {payload}") quote = await ledger.melt_quote(payload) # TODO - logger.trace(f"< POST /melt/quote: {quote}") + logger.trace(f"< POST /v1/melt/quote: {quote}") return quote @@ -221,14 +221,14 @@ async def melt(payload: PostMeltRequest) -> PostMeltResponse: """ Requests tokens to be destroyed and sent out via Lightning. """ - logger.trace(f"> POST /melt: {payload}") + logger.trace(f"> POST /v1/melt: {payload}") ok, preimage, change_promises = await ledger.melt( payload.inputs, payload.quote, payload.outputs ) resp = PostMeltResponse( quote="to_be_replaced", paid=ok, proof=preimage, change=change_promises ) - logger.trace(f"< POST /melt: {resp}") + logger.trace(f"< POST /v1/melt: {resp}") return resp diff --git a/cashu/wallet/api/router.py b/cashu/wallet/api/router.py index d1347492..802a7c80 100644 --- a/cashu/wallet/api/router.py +++ b/cashu/wallet/api/router.py @@ -189,17 +189,15 @@ async def swap( # pay invoice from outgoing mint await outgoing_wallet.load_proofs(reload=True) - total_amount, fee_reserve_sat = await outgoing_wallet.get_pay_amount_with_fees( - invoice.bolt11 - ) - assert total_amount > 0, "amount must be positive" + quote = await outgoing_wallet.get_pay_amount_with_fees(invoice.bolt11) + total_amount = quote.amount + quote.fee_reserve if outgoing_wallet.available_balance < total_amount: raise Exception("balance too low") _, send_proofs = await outgoing_wallet.split_to_send( outgoing_wallet.proofs, total_amount, set_reserved=True ) - await outgoing_wallet.pay_lightning(send_proofs, invoice.bolt11, fee_reserve_sat) + await outgoing_wallet.pay_lightning(send_proofs, invoice.bolt11, quote.fee_reserve, quote.quote) # mint token in incoming mint await incoming_wallet.mint(amount, id=invoice.id) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 7cac94b4..13ee4ab7 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -166,12 +166,13 @@ async def pay(ctx: Context, invoice: str, yes: bool): wallet: Wallet = ctx.obj["WALLET"] await wallet.load_mint() wallet.status() - total_amount, fee_reserve_sat = await wallet.get_pay_amount_with_fees(invoice) + quote = await wallet.get_pay_amount_with_fees(invoice) + total_amount = quote.amount + quote.fee_reserve if not yes: potential = ( - f" ({total_amount} sat with potential fees)" if fee_reserve_sat else "" + f" ({total_amount} sat with potential fees)" if quote.fee_reserve else "" ) - message = f"Pay {total_amount - fee_reserve_sat} sat{potential}?" + message = f"Pay {quote.amount} sat{potential}?" click.confirm( message, abort=True, @@ -184,7 +185,7 @@ async def pay(ctx: Context, invoice: str, yes: bool): print("Error: Balance too low.") return _, send_proofs = await wallet.split_to_send(wallet.proofs, total_amount) - await wallet.pay_lightning(send_proofs, invoice, fee_reserve_sat) + await wallet.pay_lightning(send_proofs, invoice, quote.fee_reserve, quote.quote) wallet.status() @@ -283,15 +284,14 @@ async def swap(ctx: Context): invoice = await incoming_wallet.request_mint(amount) # pay invoice from outgoing mint - total_amount, fee_reserve_sat = await outgoing_wallet.get_pay_amount_with_fees( - invoice.bolt11 - ) + quote = await outgoing_wallet.get_pay_amount_with_fees(invoice.bolt11) + total_amount = quote.amount + quote.fee_reserve if outgoing_wallet.available_balance < total_amount: raise Exception("balance too low") _, send_proofs = await outgoing_wallet.split_to_send( outgoing_wallet.proofs, total_amount, set_reserved=True ) - await outgoing_wallet.pay_lightning(send_proofs, invoice.bolt11, fee_reserve_sat) + await outgoing_wallet.pay_lightning(send_proofs, invoice.bolt11, quote.fee_reserve, quote.quote) # mint token in incoming mint await incoming_wallet.mint(amount, id=invoice.id) diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 8eb71203..a858456e 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -1,6 +1,5 @@ import base64 import json -import math import time import uuid from itertools import groupby @@ -33,6 +32,7 @@ PostMintResponse, PostRestoreResponse, PostSplitRequest, + PostSplitResponse, Proof, TokenV2, TokenV2Mint, @@ -405,7 +405,7 @@ async def mint_quote(self, amount) -> Invoice: logger.trace("Requesting mint: GET /v1/mint") payload = PostMintQuoteRequest(method="bolt11", symbol="sat", amount=amount) resp = await self.httpx.post( - join(self.url, "/v1/mint/quote"), json=payload.json() + join(self.url, "/v1/mint/quote"), json=payload.dict() ) # BEGIN backwards compatibility < 0.14.0 # assume the mint has not upgraded yet if we get a 404 @@ -488,9 +488,7 @@ async def melt( Accepts proofs and a lightning invoice to pay in exchange. """ - payload = PostMeltRequest( - quote="to_be_replaced", inputs=proofs, outputs=outputs - ) + payload = PostMeltRequest(quote=quote, inputs=proofs, outputs=outputs) def _meltrequest_include_fields(proofs: List[Proof]): """strips away fields from the model that aren't necessary for the /melt""" @@ -533,7 +531,7 @@ def _splitrequest_include_fields(proofs: List[Proof]): } return { "outputs": ..., - "proofs": {i: proofs_include for i in range(len(proofs))}, + "inputs": {i: proofs_include for i in range(len(proofs))}, } resp = await self.httpx.post( @@ -542,7 +540,8 @@ def _splitrequest_include_fields(proofs: List[Proof]): ) self.raise_on_error_request(resp) promises_dict = resp.json() - mint_response = PostMintResponse.parse_obj(promises_dict) + print(promises_dict) + mint_response = PostSplitResponse.parse_obj(promises_dict) promises = [BlindedSignature(**p.dict()) for p in mint_response.signatures] if len(promises) == 0: @@ -691,7 +690,7 @@ async def mint( Args: amount (int): Total amount of tokens to be minted - id (str): Id for looking up the paid Lightning invoice. Defaults to None (for testing with LIGHTNING=False). + id (str): Id for looking up the paid Lightning invoice. split (Optional[List[str]], optional): List of desired amount splits to be minted. Total must sum to `amount`. Raises: @@ -843,7 +842,7 @@ async def split( return keep_proofs, send_proofs async def pay_lightning( - self, proofs: List[Proof], invoice: str, fee_reserve_sat: int + self, proofs: List[Proof], invoice: str, fee_reserve_sat: int, quote_id: str ) -> PostMeltResponse: """Pays a lightning invoice and returns the status of the payment. @@ -889,7 +888,7 @@ async def pay_lightning( # store invoice in db as not paid yet await store_lightning_invoice(db=self.db, invoice=invoice_obj) - status = await super().melt("to_be_replaced", proofs, change_outputs) + status = await super().melt(quote_id, proofs, change_outputs) # if payment fails if not status.paid: @@ -1323,14 +1322,16 @@ async def get_pay_amount_with_fees(self, invoice: str): Decodes the amount from a Lightning invoice and returns the total amount (amount+fees) to be paid. """ - decoded_invoice = bolt11.decode(invoice) - assert decoded_invoice.amount_msat, "invoices has no amount." + # decoded_invoice = bolt11.decode(invoice) + # assert decoded_invoice.amount_msat, "invoices has no amount." # check if it's an internal payment melt_quote = await self.melt_quote(invoice) - fees = melt_quote.fee_reserve or 0 - logger.debug(f"Mint wants {fees} sat as fee reserve.") - amount = math.ceil((decoded_invoice.amount_msat + fees * 1000) / 1000) # 1% fee - return amount, fees + # fees = melt_quote.fee_reserve or 0 + logger.debug( + f"Mint wants {melt_quote.fee_reserve} {melt_quote.symbol} as fee reserve." + ) + # amount = math.ceil((decoded_invoice.amount_msat + fees * 1000) / 1000) # 1% fee + return melt_quote async def split_to_send( self, diff --git a/tests/conftest.py b/tests/conftest.py index 1d71ce09..e7bfcbc7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,9 +26,11 @@ settings.mint_host = "0.0.0.0" settings.mint_listen_port = SERVER_PORT settings.mint_url = SERVER_ENDPOINT -settings.lightning = True settings.tor = False settings.mint_lightning_backend = "FakeWallet" +settings.fakewallet_brr = True +settings.fakewallet_delay_payment = False +settings.fakewallet_stochastic_invoice = False settings.mint_database = "./test_data/test_mint" settings.mint_derivation_path = "0/0/0/0" settings.mint_private_key = "TEST_PRIVATE_KEY"