mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-01-16 23:24:20 +01:00
Fix false mpp payment handling of unsupported backends (#547)
* fix mpp spec and backend support check * refactor validation in ledger * remove weird error * fix mpp melt model
This commit is contained in:
@@ -131,12 +131,34 @@ class PostMintResponse_deprecated(BaseModel):
|
|||||||
# ------- API: MELT QUOTE -------
|
# ------- API: MELT QUOTE -------
|
||||||
|
|
||||||
|
|
||||||
|
class PostMeltRequestOptionMpp(BaseModel):
|
||||||
|
amount: int = Field(gt=0) # input amount
|
||||||
|
|
||||||
|
|
||||||
|
class PostMeltRequestOptions(BaseModel):
|
||||||
|
mpp: Optional[PostMeltRequestOptionMpp]
|
||||||
|
|
||||||
|
|
||||||
class PostMeltQuoteRequest(BaseModel):
|
class PostMeltQuoteRequest(BaseModel):
|
||||||
unit: str = Field(..., max_length=settings.mint_max_request_length) # input unit
|
unit: str = Field(..., max_length=settings.mint_max_request_length) # input unit
|
||||||
request: str = Field(
|
request: str = Field(
|
||||||
..., max_length=settings.mint_max_request_length
|
..., max_length=settings.mint_max_request_length
|
||||||
) # output payment request
|
) # output payment request
|
||||||
amount: Optional[int] = Field(default=None, gt=0) # input amount
|
options: Optional[PostMeltRequestOptions] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_mpp(self) -> bool:
|
||||||
|
if self.options and self.options.mpp:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mpp_amount(self) -> int:
|
||||||
|
if self.is_mpp and self.options and self.options.mpp:
|
||||||
|
return self.options.mpp.amount
|
||||||
|
else:
|
||||||
|
raise Exception("quote request is not mpp.")
|
||||||
|
|
||||||
|
|
||||||
class PostMeltQuoteResponse(BaseModel):
|
class PostMeltQuoteResponse(BaseModel):
|
||||||
|
|||||||
@@ -376,8 +376,8 @@ class LndRestWallet(LightningBackend):
|
|||||||
) -> PaymentQuoteResponse:
|
) -> PaymentQuoteResponse:
|
||||||
# get amount from melt_quote or from bolt11
|
# get amount from melt_quote or from bolt11
|
||||||
amount = (
|
amount = (
|
||||||
Amount(Unit[melt_quote.unit], melt_quote.amount)
|
Amount(Unit[melt_quote.unit], melt_quote.mpp_amount)
|
||||||
if melt_quote.amount
|
if melt_quote.is_mpp
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -519,6 +519,65 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
|||||||
del self.locks[quote_id]
|
del self.locks[quote_id]
|
||||||
return promises
|
return promises
|
||||||
|
|
||||||
|
def create_internal_melt_quote(
|
||||||
|
self, mint_quote: MintQuote, melt_quote: PostMeltQuoteRequest
|
||||||
|
) -> PaymentQuoteResponse:
|
||||||
|
unit, method = self._verify_and_get_unit_method(
|
||||||
|
melt_quote.unit, Method.bolt11.name
|
||||||
|
)
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
if not 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 == method.name:
|
||||||
|
raise TransactionError("methods do not match")
|
||||||
|
if mint_quote.paid:
|
||||||
|
raise TransactionError("mint quote already paid")
|
||||||
|
if mint_quote.issued:
|
||||||
|
raise TransactionError("mint quote already issued")
|
||||||
|
if not mint_quote.checking_id:
|
||||||
|
raise TransactionError("mint quote has no checking id")
|
||||||
|
if melt_quote.is_mpp:
|
||||||
|
raise TransactionError("internal payments do not support mpp")
|
||||||
|
|
||||||
|
internal_fee = Amount(unit, 0) # no internal fees
|
||||||
|
amount = Amount(unit, mint_quote.amount)
|
||||||
|
|
||||||
|
payment_quote = PaymentQuoteResponse(
|
||||||
|
checking_id=mint_quote.checking_id,
|
||||||
|
amount=amount,
|
||||||
|
fee=internal_fee,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Issuing internal melt quote: {request} ->"
|
||||||
|
f" {mint_quote.quote} ({amount.str()} + {internal_fee.str()} fees)"
|
||||||
|
)
|
||||||
|
|
||||||
|
return payment_quote
|
||||||
|
|
||||||
|
def validate_payment_quote(
|
||||||
|
self, melt_quote: PostMeltQuoteRequest, payment_quote: PaymentQuoteResponse
|
||||||
|
):
|
||||||
|
# payment quote validation
|
||||||
|
unit, method = self._verify_and_get_unit_method(
|
||||||
|
melt_quote.unit, Method.bolt11.name
|
||||||
|
)
|
||||||
|
if not payment_quote.checking_id:
|
||||||
|
raise Exception("quote has no checking id")
|
||||||
|
# verify that payment quote amount is as expected
|
||||||
|
if melt_quote.is_mpp and melt_quote.mpp_amount != payment_quote.amount.amount:
|
||||||
|
raise TransactionError("quote amount not as requested")
|
||||||
|
# make sure the backend returned the amount with a correct unit
|
||||||
|
if not payment_quote.amount.unit == unit:
|
||||||
|
raise TransactionError("payment quote amount units do not match")
|
||||||
|
# fee from the backend must be in the same unit as the amount
|
||||||
|
if not payment_quote.fee.unit == unit:
|
||||||
|
raise TransactionError("payment quote fee units do not match")
|
||||||
|
|
||||||
async def melt_quote(
|
async def melt_quote(
|
||||||
self, melt_quote: PostMeltQuoteRequest
|
self, melt_quote: PostMeltQuoteRequest
|
||||||
) -> PostMeltQuoteResponse:
|
) -> PostMeltQuoteResponse:
|
||||||
@@ -550,43 +609,19 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
|||||||
request=request, db=self.db
|
request=request, db=self.db
|
||||||
)
|
)
|
||||||
if mint_quote:
|
if mint_quote:
|
||||||
if not request == mint_quote.request:
|
payment_quote = self.create_internal_melt_quote(mint_quote, melt_quote)
|
||||||
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 == method.name:
|
|
||||||
raise TransactionError("methods do not match")
|
|
||||||
if mint_quote.paid:
|
|
||||||
raise TransactionError("mint quote already paid")
|
|
||||||
if mint_quote.issued:
|
|
||||||
raise TransactionError("mint quote already issued")
|
|
||||||
if not mint_quote.checking_id:
|
|
||||||
raise TransactionError("mint quote has no checking id")
|
|
||||||
|
|
||||||
internal_fee = Amount(unit, 0) # no internal fees
|
|
||||||
amount = Amount(unit, mint_quote.amount)
|
|
||||||
|
|
||||||
payment_quote = PaymentQuoteResponse(
|
|
||||||
checking_id=mint_quote.checking_id,
|
|
||||||
amount=amount,
|
|
||||||
fee=internal_fee,
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"Issuing internal melt quote: {request} ->"
|
|
||||||
f" {mint_quote.quote} ({amount.str()} + {internal_fee.str()} fees)"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# not internal, get payment quote by backend
|
# not internal
|
||||||
|
# verify that the backend supports mpp if the quote request has an amount
|
||||||
|
if melt_quote.is_mpp and not self.backends[method][unit].supports_mpp:
|
||||||
|
raise TransactionError("backend does not support mpp")
|
||||||
|
# get payment quote by backend
|
||||||
payment_quote = await self.backends[method][unit].get_payment_quote(
|
payment_quote = await self.backends[method][unit].get_payment_quote(
|
||||||
melt_quote=melt_quote
|
melt_quote=melt_quote
|
||||||
)
|
)
|
||||||
assert payment_quote.checking_id, "quote has no checking id"
|
|
||||||
# make sure the backend returned the amount with a correct unit
|
self.validate_payment_quote(melt_quote, payment_quote)
|
||||||
if not payment_quote.amount.unit == unit:
|
|
||||||
raise TransactionError("payment quote amount units do not match")
|
|
||||||
# fee from the backend must be in the same unit as the amount
|
|
||||||
if not payment_quote.fee.unit == unit:
|
|
||||||
raise TransactionError("payment quote fee units do not match")
|
|
||||||
|
|
||||||
# verify that the amount of the proofs is not larger than the maximum allowed
|
# verify that the amount of the proofs is not larger than the maximum allowed
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ from ..core.models import (
|
|||||||
PostMeltQuoteRequest,
|
PostMeltQuoteRequest,
|
||||||
PostMeltQuoteResponse,
|
PostMeltQuoteResponse,
|
||||||
PostMeltRequest,
|
PostMeltRequest,
|
||||||
|
PostMeltRequestOptionMpp,
|
||||||
|
PostMeltRequestOptions,
|
||||||
PostMeltResponse,
|
PostMeltResponse,
|
||||||
PostMeltResponse_deprecated,
|
PostMeltResponse_deprecated,
|
||||||
PostMintQuoteRequest,
|
PostMintQuoteRequest,
|
||||||
@@ -361,9 +363,17 @@ class LedgerAPI(LedgerAPIDeprecated, object):
|
|||||||
"""Checks whether the Lightning payment is internal."""
|
"""Checks whether the Lightning payment is internal."""
|
||||||
invoice_obj = bolt11.decode(payment_request)
|
invoice_obj = bolt11.decode(payment_request)
|
||||||
assert invoice_obj.amount_msat, "invoice must have amount"
|
assert invoice_obj.amount_msat, "invoice must have amount"
|
||||||
|
# add mpp amount for partial melts
|
||||||
|
melt_options = None
|
||||||
|
if amount:
|
||||||
|
melt_options = PostMeltRequestOptions(
|
||||||
|
mpp=PostMeltRequestOptionMpp(amount=amount)
|
||||||
|
)
|
||||||
|
|
||||||
payload = PostMeltQuoteRequest(
|
payload = PostMeltQuoteRequest(
|
||||||
unit=unit.name, request=payment_request, amount=amount
|
unit=unit.name, request=payment_request, options=melt_options
|
||||||
)
|
)
|
||||||
|
|
||||||
resp = await self.httpx.post(
|
resp = await self.httpx.post(
|
||||||
join(self.url, "/v1/melt/quote/bolt11"),
|
join(self.url, "/v1/melt/quote/bolt11"),
|
||||||
json=payload.dict(),
|
json=payload.dict(),
|
||||||
|
|||||||
Reference in New Issue
Block a user