mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-02-03 07:44:21 +01:00
Refactor: melt quote unit validation (#489)
* refactor: mint quote validation * convert amount in Lightning backend for later validation * fix blink amount return tests * retry tests * fix conftest fakewallet * fix start
This commit is contained in:
@@ -58,6 +58,8 @@ MINT_DATABASE=data/mint
|
||||
# Funding source backends
|
||||
# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, BlinkWallet, LNbitsWallet, StrikeUSDWallet
|
||||
MINT_BACKEND_BOLT11_SAT=FakeWallet
|
||||
# Only works if a usd derivation path is set
|
||||
# MINT_BACKEND_BOLT11_SAT=FakeWallet
|
||||
|
||||
# for use with LNbitsWallet
|
||||
MINT_LNBITS_ENDPOINT=https://legend.lnbits.com
|
||||
|
||||
2
.github/workflows/regtest.yml
vendored
2
.github/workflows/regtest.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
MINT_PORT: 3337
|
||||
MINT_TEST_DATABASE: ${{ inputs.mint-database }}
|
||||
TOR: false
|
||||
MINT_LIGHTNING_BACKEND: ${{ inputs.backend-wallet-class }}
|
||||
MINT_BACKEND_BOLT11_SAT: ${{ inputs.backend-wallet-class }}
|
||||
MINT_LNBITS_ENDPOINT: http://localhost:5001
|
||||
MINT_LNBITS_KEY: d08a3313322a4514af75d488bcc27eee
|
||||
MINT_LND_REST_ENDPOINT: https://localhost:8081/
|
||||
|
||||
@@ -444,7 +444,11 @@ class BlinkWallet(LightningBackend):
|
||||
|
||||
fees = Amount(unit=Unit.msat, amount=fees_msat)
|
||||
amount = Amount(unit=Unit.msat, amount=amount_msat)
|
||||
return PaymentQuoteResponse(checking_id=bolt11, fee=fees, amount=amount)
|
||||
return PaymentQuoteResponse(
|
||||
checking_id=bolt11,
|
||||
fee=fees.to(self.unit, round="up"),
|
||||
amount=amount.to(self.unit, round="up"),
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
|
||||
@@ -320,5 +320,7 @@ class CoreLightningRestWallet(LightningBackend):
|
||||
fees = Amount(unit=Unit.msat, amount=fees_msat)
|
||||
amount = Amount(unit=Unit.msat, amount=amount_msat)
|
||||
return PaymentQuoteResponse(
|
||||
checking_id=invoice_obj.payment_hash, fee=fees, amount=amount
|
||||
checking_id=invoice_obj.payment_hash,
|
||||
fee=fees.to(self.unit, round="up"),
|
||||
amount=amount.to(self.unit, round="up"),
|
||||
)
|
||||
|
||||
@@ -169,5 +169,7 @@ class FakeWallet(LightningBackend):
|
||||
raise NotImplementedError()
|
||||
|
||||
return PaymentQuoteResponse(
|
||||
checking_id=invoice_obj.payment_hash, fee=fees, amount=amount
|
||||
checking_id=invoice_obj.payment_hash,
|
||||
fee=fees.to(self.unit, round="up"),
|
||||
amount=amount.to(self.unit, round="up"),
|
||||
)
|
||||
|
||||
@@ -165,5 +165,7 @@ class LNbitsWallet(LightningBackend):
|
||||
fees = Amount(unit=Unit.msat, amount=fees_msat)
|
||||
amount = Amount(unit=Unit.msat, amount=amount_msat)
|
||||
return PaymentQuoteResponse(
|
||||
checking_id=invoice_obj.payment_hash, fee=fees, amount=amount
|
||||
checking_id=invoice_obj.payment_hash,
|
||||
fee=fees.to(self.unit, round="up"),
|
||||
amount=amount.to(self.unit, round="up"),
|
||||
)
|
||||
|
||||
@@ -272,5 +272,7 @@ class LndRestWallet(LightningBackend):
|
||||
fees = Amount(unit=Unit.msat, amount=fees_msat)
|
||||
amount = Amount(unit=Unit.msat, amount=amount_msat)
|
||||
return PaymentQuoteResponse(
|
||||
checking_id=invoice_obj.payment_hash, fee=fees, amount=amount
|
||||
checking_id=invoice_obj.payment_hash,
|
||||
fee=fees.to(self.unit, round="up"),
|
||||
amount=amount.to(self.unit, round="up"),
|
||||
)
|
||||
|
||||
@@ -195,27 +195,3 @@ class StrikeUSDWallet(LightningBackend):
|
||||
fee_msat=data["details"]["fee"],
|
||||
preimage=data["preimage"],
|
||||
)
|
||||
|
||||
# async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
# url = f"{self.endpoint}/api/v1/payments/sse"
|
||||
|
||||
# while True:
|
||||
# try:
|
||||
# async with requests.stream("GET", url) as r:
|
||||
# async for line in r.aiter_lines():
|
||||
# if line.startswith("data:"):
|
||||
# try:
|
||||
# data = json.loads(line[5:])
|
||||
# except json.decoder.JSONDecodeError:
|
||||
# continue
|
||||
|
||||
# if type(data) is not dict:
|
||||
# continue
|
||||
|
||||
# yield data["payment_hash"] # payment_hash
|
||||
|
||||
# except:
|
||||
# pass
|
||||
|
||||
# print("lost connection to lnbits /payments/sse, retrying in 5 seconds")
|
||||
# await asyncio.sleep(5)
|
||||
|
||||
@@ -480,22 +480,14 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
# NOTE: we normalize the request to lowercase to avoid case sensitivity
|
||||
# This works with Lightning but might not work with other methods
|
||||
request = melt_quote.request.lower()
|
||||
invoice_obj = bolt11.decode(melt_quote.request)
|
||||
assert invoice_obj.amount_msat, "invoice has no amount."
|
||||
|
||||
# check if there is a mint quote with the same payment request
|
||||
# so that we can handle the transaction internally without lightning
|
||||
# and respond with zero fees
|
||||
# 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_by_request(
|
||||
request=request, db=self.db
|
||||
)
|
||||
if mint_quote:
|
||||
# internal transaction, validate and return amount from
|
||||
# associated mint quote and demand zero fees
|
||||
# assert (
|
||||
# Amount(unit, mint_quote.amount).to(Unit.msat).amount
|
||||
# == invoice_obj.amount_msat
|
||||
# ), "amounts do not match"
|
||||
assert request == mint_quote.request, "bolt11 requests do not match"
|
||||
assert mint_quote.unit == melt_quote.unit, "units do not match"
|
||||
assert mint_quote.method == method.name, "methods do not match"
|
||||
@@ -512,10 +504,23 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
f" {mint_quote.quote} ({mint_quote.amount} {mint_quote.unit})"
|
||||
)
|
||||
else:
|
||||
# not internal, get quote by backend
|
||||
# not internal, get payment quote by backend
|
||||
payment_quote = await self.backends[method][unit].get_payment_quote(request)
|
||||
assert payment_quote.checking_id, "quote has no checking id"
|
||||
# make sure the backend returned the amount with a correct unit
|
||||
assert (
|
||||
payment_quote.amount.unit == unit
|
||||
), "payment quote amount units do not match"
|
||||
# fee from the backend must be in the same unit as the amount
|
||||
assert (
|
||||
payment_quote.fee.unit == unit
|
||||
), "payment quote fee units do not match"
|
||||
|
||||
# We assume that the request is a bolt11 invoice, this works since we
|
||||
# support only the bol11 method for now.
|
||||
invoice_obj = bolt11.decode(melt_quote.request)
|
||||
assert invoice_obj.amount_msat, "invoice has no amount."
|
||||
# we set the expiry of this quote to the expiry of the bolt11 invoice
|
||||
expiry = None
|
||||
if invoice_obj.expiry is not None:
|
||||
expiry = invoice_obj.date + invoice_obj.expiry
|
||||
|
||||
@@ -31,7 +31,7 @@ settings.mint_listen_port = SERVER_PORT
|
||||
settings.mint_url = SERVER_ENDPOINT
|
||||
settings.tor = False
|
||||
settings.wallet_unit = "sat"
|
||||
settings.mint_lightning_backend = settings.mint_lightning_backend or "FakeWallet"
|
||||
settings.mint_backend_bolt11_sat = settings.mint_backend_bolt11_sat or "FakeWallet"
|
||||
settings.fakewallet_brr = True
|
||||
settings.fakewallet_delay_payment = False
|
||||
settings.fakewallet_stochastic_invoice = False
|
||||
@@ -111,7 +111,7 @@ async def ledger():
|
||||
await conn.execute("CREATE SCHEMA public;")
|
||||
|
||||
wallets_module = importlib.import_module("cashu.lightning")
|
||||
lightning_backend = getattr(wallets_module, settings.mint_lightning_backend)()
|
||||
lightning_backend = getattr(wallets_module, settings.mint_backend_bolt11_sat)()
|
||||
backends = {
|
||||
Method.bolt11: {Unit.sat: lightning_backend},
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ from cashu.core.settings import settings
|
||||
from cashu.lightning.blink import MINIMUM_FEE_MSAT, BlinkWallet
|
||||
|
||||
settings.mint_blink_key = "123"
|
||||
blink = BlinkWallet()
|
||||
blink = BlinkWallet(unit=Unit.sat)
|
||||
payment_request = (
|
||||
"lnbc10u1pjap7phpp50s9lzr3477j0tvacpfy2ucrs4q0q6cvn232ex7nt2zqxxxj8gxrsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzzsxqrrsss"
|
||||
"p575z0n39w2j7zgnpqtdlrgz9rycner4eptjm3lz363dzylnrm3h4s9qyyssqfz8jglcshnlcf0zkw4qu8fyr564lg59x5al724kms3h6gpuhx9xrfv27tgx3l3u3cyf6"
|
||||
@@ -194,32 +194,32 @@ async def test_blink_get_payment_quote():
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
quote = await blink.get_payment_quote(payment_request)
|
||||
assert quote.checking_id == payment_request
|
||||
assert quote.amount == Amount(Unit.msat, 1000000) # msat
|
||||
assert quote.fee == Amount(Unit.msat, 5000) # msat
|
||||
assert quote.amount == Amount(Unit.sat, 1000) # sat
|
||||
assert quote.fee == Amount(Unit.sat, 5) # sat
|
||||
|
||||
# response says 10 sat fees but invoice (1000 sat) * 0.5% is 5 sat so we expect 10 sat
|
||||
mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 10}}}
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
quote = await blink.get_payment_quote(payment_request)
|
||||
assert quote.checking_id == payment_request
|
||||
assert quote.amount == Amount(Unit.msat, 1000000) # msat
|
||||
assert quote.fee == Amount(Unit.msat, 10000) # msat
|
||||
assert quote.amount == Amount(Unit.sat, 1000) # sat
|
||||
assert quote.fee == Amount(Unit.sat, 10) # sat
|
||||
|
||||
# response says 10 sat fees but invoice (4973 sat) * 0.5% is 24.865 sat so we expect 25 sat
|
||||
mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 10}}}
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
quote = await blink.get_payment_quote(payment_request_4973)
|
||||
assert quote.checking_id == payment_request_4973
|
||||
assert quote.amount == Amount(Unit.msat, 4973000) # msat
|
||||
assert quote.fee == Amount(Unit.msat, 25000) # msat
|
||||
assert quote.amount == Amount(Unit.sat, 4973) # sat
|
||||
assert quote.fee == Amount(Unit.sat, 25) # sat
|
||||
|
||||
# response says 0 sat fees but invoice (1 sat) * 0.5% is 0.005 sat so we expect MINIMUM_FEE_MSAT/1000 sat
|
||||
mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 0}}}
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
quote = await blink.get_payment_quote(payment_request_1)
|
||||
assert quote.checking_id == payment_request_1
|
||||
assert quote.amount == Amount(Unit.msat, 1000) # msat
|
||||
assert quote.fee == Amount(Unit.msat, MINIMUM_FEE_MSAT) # msat
|
||||
assert quote.amount == Amount(Unit.sat, 1) # sat
|
||||
assert quote.fee == Amount(Unit.sat, MINIMUM_FEE_MSAT // 1000) # msat
|
||||
|
||||
|
||||
@respx.mock
|
||||
@@ -230,5 +230,5 @@ async def test_blink_get_payment_quote_backend_error():
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
quote = await blink.get_payment_quote(payment_request)
|
||||
assert quote.checking_id == payment_request
|
||||
assert quote.amount == Amount(Unit.msat, 1000000) # msat
|
||||
assert quote.fee == Amount(Unit.msat, 5000) # msat
|
||||
assert quote.amount == Amount(Unit.sat, 1000) # sat
|
||||
assert quote.fee == Amount(Unit.sat, 5) # sat
|
||||
|
||||
@@ -118,7 +118,7 @@ async def test_mint_external(wallet1: Wallet, ledger: Ledger):
|
||||
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=128, unit="sat"))
|
||||
|
||||
mint_quote = await ledger.get_mint_quote(quote.quote)
|
||||
assert not mint_quote.paid, "mint quote not should be paid"
|
||||
assert not mint_quote.paid, "mint quote already paid"
|
||||
|
||||
await assert_err(
|
||||
wallet1.mint(128, id=quote.quote),
|
||||
|
||||
Reference in New Issue
Block a user