From 40039bb295082eb8c64f7de691f9197078f668a7 Mon Sep 17 00:00:00 2001 From: gudnuf Date: Mon, 28 Oct 2024 06:32:49 -0700 Subject: [PATCH 1/4] WIP settle different units externally --- cashu/mint/ledger.py | 8 ++++--- tests/test_mint_melt.py | 46 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index c08531e2..d3a74c9e 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -660,7 +660,7 @@ async def melt_quote( # so that we would be able to handle the transaction internally # and therefore respond with internal transaction fees (0 for now) mint_quote = await self.crud.get_mint_quote(request=request, db=self.db) - if mint_quote: + if mint_quote and mint_quote.unit == melt_quote.unit: payment_quote = self.create_internal_melt_quote(mint_quote, melt_quote) else: # not internal @@ -811,6 +811,10 @@ async def melt_mint_settle_internally( if not mint_quote: return melt_quote + # try to settle externally if units are different + if not mint_quote.unit == melt_quote.unit: + return melt_quote + # we settle the transaction internally if melt_quote.paid: raise TransactionError("melt quote already paid") @@ -825,8 +829,6 @@ async def melt_mint_settle_internally( raise TransactionError("amounts do not match") if not bolt11_request == mint_quote.request: raise TransactionError("bolt11 requests do not match") - if not mint_quote.unit == melt_quote.unit: - raise TransactionError("units do not match") if not mint_quote.method == melt_quote.method: raise TransactionError("methods do not match") diff --git a/tests/test_mint_melt.py b/tests/test_mint_melt.py index 8d2d2489..22bc0b50 100644 --- a/tests/test_mint_melt.py +++ b/tests/test_mint_melt.py @@ -5,7 +5,7 @@ from cashu.core.base import MeltQuote, MeltQuoteState, Proof from cashu.core.errors import LightningError -from cashu.core.models import PostMeltQuoteRequest +from cashu.core.models import PostMeltQuoteRequest, PostMintQuoteRequest from cashu.core.settings import settings from cashu.lightning.base import PaymentResult from cashu.mint.ledger import Ledger @@ -331,3 +331,47 @@ async def test_melt_lightning_pay_invoice_exception_exception( ledger.melt(proofs=wallet.proofs, quote=quote_id), "Melt is disabled. Please contact the operator.", ) + + +@pytest.mark.asyncio +@pytest.mark.skipif(is_regtest, reason="only fake wallet") +async def test_mint_melt_different_units(ledger: Ledger, wallet: Wallet): + """Mint and melt different units.""" + # load the wallet + invoice = await wallet.request_mint(64) + await wallet.mint(64, id=invoice.id) + + amount = 32 + + # create a mint quote that is unpaid + sat_mint_quote = await ledger.mint_quote( + quote_request=PostMintQuoteRequest(amount=amount, unit="sat") + ) + sat_invoice = sat_mint_quote.request + assert sat_mint_quote.paid is False + + # create a melt quote with above invoice in different unit + usd_melt_quote = await ledger.melt_quote( + PostMeltQuoteRequest(unit="usd", request=sat_invoice) + ) + assert usd_melt_quote.paid is False + + melt_resp = await ledger.melt(proofs=wallet.proofs, quote=usd_melt_quote.quote) + + assert_err( + melt_resp.state == MeltQuoteState.unpaid.value, + f"Expected state to be paid, got {melt_resp.state}", + ) + + output_amounts = [32] + + secrets, rs, derivation_paths = await wallet.generate_n_secrets(len(output_amounts)) + outputs, rs = wallet._construct_outputs(output_amounts, secrets, rs) + mint_resp = await ledger.mint(outputs=outputs, quote_id=invoice.id) + + assert_err(len(mint_resp) > 0, f"Expected promises, got {len(mint_resp)} promises") + + await assert_err( + ledger.mint(outputs=outputs, quote_id=invoice.id), + "outputs have already been signed before.", + ) From 83cbdd4e9597e61977599ec89bb4e43216fd31da Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:41:27 +0100 Subject: [PATCH 2/4] mint melt externally different units --- cashu/lightning/fake.py | 2 +- tests/conftest.py | 22 ++++++++++++---------- tests/test_mint_api.py | 6 ++++++ tests/test_mint_init.py | 2 +- tests/test_mint_melt.py | 21 +++++++-------------- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/cashu/lightning/fake.py b/cashu/lightning/fake.py index 59073fd1..1d3cbab1 100644 --- a/cashu/lightning/fake.py +++ b/cashu/lightning/fake.py @@ -30,6 +30,7 @@ class FakeWallet(LightningBackend): + unit: Unit fake_btc_price = 1e8 / 1337 paid_invoices_queue: asyncio.Queue[Bolt11] = asyncio.Queue(0) payment_secrets: Dict[str, str] = dict() @@ -46,7 +47,6 @@ class FakeWallet(LightningBackend): ).hex() supported_units = {Unit.sat, Unit.msat, Unit.usd, Unit.eur} - unit = Unit.sat supports_incoming_payment_stream: bool = True supports_description: bool = True diff --git a/tests/conftest.py b/tests/conftest.py index 024f02b7..4ccebc61 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,6 +33,7 @@ settings.tor = False settings.wallet_unit = "sat" settings.mint_backend_bolt11_sat = settings.mint_backend_bolt11_sat or "FakeWallet" +settings.mint_backend_bolt11_usd = settings.mint_backend_bolt11_usd or "FakeWallet" settings.fakewallet_brr = True settings.fakewallet_delay_outgoing_payment = 0 settings.fakewallet_delay_incoming_payment = 1 @@ -42,7 +43,7 @@ ), "Test database is the same as the main database" settings.mint_database = settings.mint_test_database settings.mint_derivation_path = "m/0'/0'/0'" -settings.mint_derivation_path_list = [] +settings.mint_derivation_path_list = ["m/0'/2'/0'"] # USD settings.mint_private_key = "TEST_PRIVATE_KEY" settings.mint_seed_decryption_key = "" settings.mint_max_balance = 0 @@ -85,13 +86,6 @@ def run(self, *args, **kwargs): async def ledger(): async def start_mint_init(ledger: Ledger) -> Ledger: await migrate_databases(ledger.db, migrations_mint) - ledger = Ledger( - db=Database("mint", settings.mint_database), - seed=settings.mint_private_key, - derivation_path=settings.mint_derivation_path, - backends=backends, - crud=LedgerCrudSqlite(), - ) await ledger.startup_ledger() return ledger @@ -110,9 +104,17 @@ async def start_mint_init(ledger: Ledger) -> Ledger: await db.engine.dispose() wallets_module = importlib.import_module("cashu.lightning") - lightning_backend = getattr(wallets_module, settings.mint_backend_bolt11_sat)() + lightning_backend_sat = getattr(wallets_module, settings.mint_backend_bolt11_sat)( + unit=Unit.sat + ) + lightning_backend_usd = getattr(wallets_module, settings.mint_backend_bolt11_usd)( + unit=Unit.usd + ) backends = { - Method.bolt11: {Unit.sat: lightning_backend}, + Method.bolt11: { + Unit.sat: lightning_backend_sat, + Unit.usd: lightning_backend_usd, + }, } ledger = Ledger( db=Database("mint", settings.mint_database), diff --git a/tests/test_mint_api.py b/tests/test_mint_api.py index aa5b402f..fe38bc70 100644 --- a/tests/test_mint_api.py +++ b/tests/test_mint_api.py @@ -94,6 +94,12 @@ async def test_api_keysets(ledger: Ledger): "active": True, "input_fee_ppk": 0, }, + { + "id": "00c074b96c7e2b0e", + "unit": "usd", + "active": True, + "input_fee_ppk": 0, + }, ] } assert response.json() == expected diff --git a/tests/test_mint_init.py b/tests/test_mint_init.py index 71aa7021..60817fe3 100644 --- a/tests/test_mint_init.py +++ b/tests/test_mint_init.py @@ -60,7 +60,7 @@ async def wallet(ledger: Ledger): async def test_init_keysets(ledger: Ledger): ledger.keysets = {} await ledger.init_keysets() - assert len(ledger.keysets) == 1 + assert len(ledger.keysets) == 2 @pytest.mark.asyncio diff --git a/tests/test_mint_melt.py b/tests/test_mint_melt.py index 22bc0b50..c135992f 100644 --- a/tests/test_mint_melt.py +++ b/tests/test_mint_melt.py @@ -343,35 +343,28 @@ async def test_mint_melt_different_units(ledger: Ledger, wallet: Wallet): amount = 32 - # create a mint quote that is unpaid + # mint quote in sat sat_mint_quote = await ledger.mint_quote( quote_request=PostMintQuoteRequest(amount=amount, unit="sat") ) sat_invoice = sat_mint_quote.request assert sat_mint_quote.paid is False - # create a melt quote with above invoice in different unit + # melt quote in usd usd_melt_quote = await ledger.melt_quote( PostMeltQuoteRequest(unit="usd", request=sat_invoice) ) assert usd_melt_quote.paid is False - melt_resp = await ledger.melt(proofs=wallet.proofs, quote=usd_melt_quote.quote) - - assert_err( - melt_resp.state == MeltQuoteState.unpaid.value, - f"Expected state to be paid, got {melt_resp.state}", - ) + # pay melt quote with usd + await ledger.melt(proofs=wallet.proofs, quote=usd_melt_quote.quote) output_amounts = [32] secrets, rs, derivation_paths = await wallet.generate_n_secrets(len(output_amounts)) outputs, rs = wallet._construct_outputs(output_amounts, secrets, rs) - mint_resp = await ledger.mint(outputs=outputs, quote_id=invoice.id) - assert_err(len(mint_resp) > 0, f"Expected promises, got {len(mint_resp)} promises") + # mint in sat + mint_resp = await ledger.mint(outputs=outputs, quote_id=sat_mint_quote.quote) - await assert_err( - ledger.mint(outputs=outputs, quote_id=invoice.id), - "outputs have already been signed before.", - ) + assert len(mint_resp) == len(outputs) From 7d32a82cd1efa2694f4d59541918d22d862a9de3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:23:17 +0100 Subject: [PATCH 3/4] deprecated route return only sat --- cashu/mint/router_deprecated.py | 5 +++-- tests/test_mint_api_deprecated.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cashu/mint/router_deprecated.py b/cashu/mint/router_deprecated.py index d09c2f72..73f39d91 100644 --- a/cashu/mint/router_deprecated.py +++ b/cashu/mint/router_deprecated.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, Request from loguru import logger -from ..core.base import BlindedMessage, BlindedSignature +from ..core.base import BlindedMessage, BlindedSignature, Unit from ..core.errors import CashuError from ..core.models import ( CheckFeesRequest_deprecated, @@ -114,7 +114,8 @@ async def keyset_deprecated(idBase64Urlsafe: str) -> Dict[str, str]: async def keysets_deprecated() -> KeysetsResponse_deprecated: """This endpoint returns a list of keysets that the mint currently supports and will accept tokens from.""" logger.trace("> GET /keysets") - keysets = KeysetsResponse_deprecated(keysets=list(ledger.keysets.keys())) + sat_keysets = {k: v for k, v in ledger.keysets.items() if v.unit == Unit.sat} + keysets = KeysetsResponse_deprecated(keysets=list(sat_keysets.keys())) return keysets diff --git a/tests/test_mint_api_deprecated.py b/tests/test_mint_api_deprecated.py index b9aac81a..226af28a 100644 --- a/tests/test_mint_api_deprecated.py +++ b/tests/test_mint_api_deprecated.py @@ -2,7 +2,7 @@ import pytest import pytest_asyncio -from cashu.core.base import Proof +from cashu.core.base import Proof, Unit from cashu.core.models import ( CheckSpendableRequest_deprecated, CheckSpendableResponse_deprecated, @@ -51,7 +51,8 @@ async def test_api_keysets(ledger: Ledger): response = httpx.get(f"{BASE_URL}/keysets") assert response.status_code == 200, f"{response.url} {response.status_code}" assert ledger.keyset.public_keys - assert response.json()["keysets"] == list(ledger.keysets.keys()) + sat_keysets = {k: v for k, v in ledger.keysets.items() if v.unit == Unit.sat} + assert response.json()["keysets"] == list(sat_keysets.keys()) @pytest.mark.asyncio From 0a50328c42b34331d5e4573ff529dbb2516faf8c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:39:12 +0100 Subject: [PATCH 4/4] comment --- cashu/mint/ledger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index d3a74c9e..8dae66c7 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -811,8 +811,8 @@ async def melt_mint_settle_internally( if not mint_quote: return melt_quote - # try to settle externally if units are different - if not mint_quote.unit == melt_quote.unit: + # settle externally if units are different + if mint_quote.unit != melt_quote.unit: return melt_quote # we settle the transaction internally