Mint: settle mint-melt on same mint with different units externally (#651)

* WIP settle different units externally

* mint melt externally different units

* deprecated route return only sat

* comment

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
Damian
2024-10-30 13:59:57 -07:00
committed by GitHub
parent 48b0368ad2
commit b8dd43deaa
8 changed files with 69 additions and 20 deletions

View File

@@ -30,6 +30,7 @@ from .base import (
class FakeWallet(LightningBackend): class FakeWallet(LightningBackend):
unit: Unit
fake_btc_price = 1e8 / 1337 fake_btc_price = 1e8 / 1337
paid_invoices_queue: asyncio.Queue[Bolt11] = asyncio.Queue(0) paid_invoices_queue: asyncio.Queue[Bolt11] = asyncio.Queue(0)
payment_secrets: Dict[str, str] = dict() payment_secrets: Dict[str, str] = dict()
@@ -46,7 +47,6 @@ class FakeWallet(LightningBackend):
).hex() ).hex()
supported_units = {Unit.sat, Unit.msat, Unit.usd, Unit.eur} supported_units = {Unit.sat, Unit.msat, Unit.usd, Unit.eur}
unit = Unit.sat
supports_incoming_payment_stream: bool = True supports_incoming_payment_stream: bool = True
supports_description: bool = True supports_description: bool = True

View File

@@ -660,7 +660,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
# so that we would be able to handle the transaction internally # so that we would be able to handle the transaction internally
# and therefore respond with internal transaction fees (0 for now) # and therefore respond with internal transaction fees (0 for now)
mint_quote = await self.crud.get_mint_quote(request=request, db=self.db) 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) payment_quote = self.create_internal_melt_quote(mint_quote, melt_quote)
else: else:
# not internal # not internal
@@ -811,6 +811,10 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
if not mint_quote: if not mint_quote:
return melt_quote return melt_quote
# settle externally if units are different
if mint_quote.unit != melt_quote.unit:
return melt_quote
# we settle the transaction internally # we settle the transaction internally
if melt_quote.paid: if melt_quote.paid:
raise TransactionError("melt quote already paid") raise TransactionError("melt quote already paid")
@@ -825,8 +829,6 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
raise TransactionError("amounts do not match") raise TransactionError("amounts do not match")
if not bolt11_request == mint_quote.request: if not bolt11_request == mint_quote.request:
raise TransactionError("bolt11 requests do not match") 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: if not mint_quote.method == melt_quote.method:
raise TransactionError("methods do not match") raise TransactionError("methods do not match")

View File

@@ -3,7 +3,7 @@ from typing import Dict, List, Optional
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from loguru import logger from loguru import logger
from ..core.base import BlindedMessage, BlindedSignature from ..core.base import BlindedMessage, BlindedSignature, Unit
from ..core.errors import CashuError from ..core.errors import CashuError
from ..core.models import ( from ..core.models import (
CheckFeesRequest_deprecated, CheckFeesRequest_deprecated,
@@ -114,7 +114,8 @@ async def keyset_deprecated(idBase64Urlsafe: str) -> Dict[str, str]:
async def keysets_deprecated() -> KeysetsResponse_deprecated: async def keysets_deprecated() -> KeysetsResponse_deprecated:
"""This endpoint returns a list of keysets that the mint currently supports and will accept tokens from.""" """This endpoint returns a list of keysets that the mint currently supports and will accept tokens from."""
logger.trace("> GET /keysets") 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 return keysets

View File

@@ -33,6 +33,7 @@ settings.mint_url = SERVER_ENDPOINT
settings.tor = False settings.tor = False
settings.wallet_unit = "sat" settings.wallet_unit = "sat"
settings.mint_backend_bolt11_sat = settings.mint_backend_bolt11_sat or "FakeWallet" 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_brr = True
settings.fakewallet_delay_outgoing_payment = 0 settings.fakewallet_delay_outgoing_payment = 0
settings.fakewallet_delay_incoming_payment = 1 settings.fakewallet_delay_incoming_payment = 1
@@ -42,7 +43,7 @@ assert (
), "Test database is the same as the main database" ), "Test database is the same as the main database"
settings.mint_database = settings.mint_test_database settings.mint_database = settings.mint_test_database
settings.mint_derivation_path = "m/0'/0'/0'" 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_private_key = "TEST_PRIVATE_KEY"
settings.mint_seed_decryption_key = "" settings.mint_seed_decryption_key = ""
settings.mint_max_balance = 0 settings.mint_max_balance = 0
@@ -85,13 +86,6 @@ class UvicornServer(multiprocessing.Process):
async def ledger(): async def ledger():
async def start_mint_init(ledger: Ledger) -> Ledger: async def start_mint_init(ledger: Ledger) -> Ledger:
await migrate_databases(ledger.db, migrations_mint) 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() await ledger.startup_ledger()
return ledger return ledger
@@ -110,9 +104,17 @@ async def ledger():
await db.engine.dispose() await db.engine.dispose()
wallets_module = importlib.import_module("cashu.lightning") 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 = { backends = {
Method.bolt11: {Unit.sat: lightning_backend}, Method.bolt11: {
Unit.sat: lightning_backend_sat,
Unit.usd: lightning_backend_usd,
},
} }
ledger = Ledger( ledger = Ledger(
db=Database("mint", settings.mint_database), db=Database("mint", settings.mint_database),

View File

@@ -94,6 +94,12 @@ async def test_api_keysets(ledger: Ledger):
"active": True, "active": True,
"input_fee_ppk": 0, "input_fee_ppk": 0,
}, },
{
"id": "00c074b96c7e2b0e",
"unit": "usd",
"active": True,
"input_fee_ppk": 0,
},
] ]
} }
assert response.json() == expected assert response.json() == expected

View File

@@ -2,7 +2,7 @@ import httpx
import pytest import pytest
import pytest_asyncio import pytest_asyncio
from cashu.core.base import Proof from cashu.core.base import Proof, Unit
from cashu.core.models import ( from cashu.core.models import (
CheckSpendableRequest_deprecated, CheckSpendableRequest_deprecated,
CheckSpendableResponse_deprecated, CheckSpendableResponse_deprecated,
@@ -51,7 +51,8 @@ async def test_api_keysets(ledger: Ledger):
response = httpx.get(f"{BASE_URL}/keysets") response = httpx.get(f"{BASE_URL}/keysets")
assert response.status_code == 200, f"{response.url} {response.status_code}" assert response.status_code == 200, f"{response.url} {response.status_code}"
assert ledger.keyset.public_keys 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 @pytest.mark.asyncio

View File

@@ -60,7 +60,7 @@ async def wallet(ledger: Ledger):
async def test_init_keysets(ledger: Ledger): async def test_init_keysets(ledger: Ledger):
ledger.keysets = {} ledger.keysets = {}
await ledger.init_keysets() await ledger.init_keysets()
assert len(ledger.keysets) == 1 assert len(ledger.keysets) == 2
@pytest.mark.asyncio @pytest.mark.asyncio

View File

@@ -5,7 +5,7 @@ import pytest_asyncio
from cashu.core.base import MeltQuote, MeltQuoteState, Proof from cashu.core.base import MeltQuote, MeltQuoteState, Proof
from cashu.core.errors import LightningError 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.core.settings import settings
from cashu.lightning.base import PaymentResult from cashu.lightning.base import PaymentResult
from cashu.mint.ledger import Ledger from cashu.mint.ledger import Ledger
@@ -331,3 +331,40 @@ async def test_melt_lightning_pay_invoice_exception_exception(
ledger.melt(proofs=wallet.proofs, quote=quote_id), ledger.melt(proofs=wallet.proofs, quote=quote_id),
"Melt is disabled. Please contact the operator.", "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
# 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
# melt quote in usd
usd_melt_quote = await ledger.melt_quote(
PostMeltQuoteRequest(unit="usd", request=sat_invoice)
)
assert usd_melt_quote.paid is False
# 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 in sat
mint_resp = await ledger.mint(outputs=outputs, quote_id=sat_mint_quote.quote)
assert len(mint_resp) == len(outputs)