WIP: New melt flow (#622)

* `PaymentResult`

* ledger: rely on PaymentResult instead of paid flag. Double check for payments marked pending.

* `None` is `PENDING`

* make format

* reflected changes API tests where `PaymentStatus` is used + reflected changes in lnbits

* reflect changes in blink backend and tests

* fix lnbits get_payment_status

* remove paid flag

* fix mypy

* remove more paid flags

* fix strike mypy

* green

* shorten all state checks

* fix

* fix some tests

* gimme 

* fix............

* fix lnbits

* fix error

* lightning refactor

* add more regtest tests

* add tests for pending state and failure

* shorten checks

* use match case for startup check - and remember modified checking_id from pay_invoice

* fix strike pending return

* new tests?

* refactor startup routine into get_melt_quote

* test with purge

* refactor blink

* cleanup responses

* blink: return checking_id on failure

* fix lndgrpc try except

* add more testing for melt branches

* speed things up a bit

* remove comments

* remove comments

* block pending melt quotes

* remove comments

---------

Co-authored-by: lollerfirst <lollerfirst@gmail.com>
This commit is contained in:
callebtc
2024-09-24 14:55:35 +02:00
committed by GitHub
parent 25f0763f94
commit d8d3037cc5
39 changed files with 1575 additions and 682 deletions

View File

@@ -19,12 +19,27 @@ from .base import (
LightningBackend,
PaymentQuoteResponse,
PaymentResponse,
PaymentResult,
PaymentStatus,
StatusResponse,
Unsupported,
)
from .macaroon import load_macaroon
# https://docs.corelightning.org/reference/lightning-pay
PAYMENT_RESULT_MAP = {
"complete": PaymentResult.SETTLED,
"pending": PaymentResult.PENDING,
"failed": PaymentResult.FAILED,
}
# https://docs.corelightning.org/reference/lightning-listinvoices
INVOICE_RESULT_MAP = {
"paid": PaymentResult.SETTLED,
"unpaid": PaymentResult.PENDING,
"expired": PaymentResult.FAILED,
}
class CoreLightningRestWallet(LightningBackend):
supported_units = {Unit.sat, Unit.msat}
@@ -61,12 +76,6 @@ class CoreLightningRestWallet(LightningBackend):
base_url=self.url, verify=self.cert, headers=self.auth
)
self.last_pay_index = 0
self.statuses = {
"paid": True,
"complete": True,
"failed": False,
"pending": None,
}
async def cleanup(self):
try:
@@ -140,8 +149,6 @@ class CoreLightningRestWallet(LightningBackend):
return InvoiceResponse(
ok=False,
checking_id=None,
payment_request=None,
error_message=error_message,
)
@@ -152,7 +159,6 @@ class CoreLightningRestWallet(LightningBackend):
ok=True,
checking_id=data["payment_hash"],
payment_request=data["bolt11"],
error_message=None,
)
async def pay_invoice(
@@ -162,20 +168,14 @@ class CoreLightningRestWallet(LightningBackend):
invoice = decode(quote.request)
except Bolt11Exception as exc:
return PaymentResponse(
ok=False,
checking_id=None,
fee=None,
preimage=None,
result=PaymentResult.FAILED,
error_message=str(exc),
)
if not invoice.amount_msat or invoice.amount_msat <= 0:
error_message = "0 amount invoices are not allowed"
return PaymentResponse(
ok=False,
checking_id=None,
fee=None,
preimage=None,
result=PaymentResult.FAILED,
error_message=error_message,
)
fee_limit_percent = fee_limit_msat / invoice.amount_msat * 100
@@ -193,38 +193,25 @@ class CoreLightningRestWallet(LightningBackend):
if r.is_error or "error" in r.json():
try:
data = r.json()
error_message = data["error"]
error_message = data["error"]["message"]
except Exception:
error_message = r.text
return PaymentResponse(
ok=False,
checking_id=None,
fee=None,
preimage=None,
result=PaymentResult.FAILED,
error_message=error_message,
)
data = r.json()
if data["status"] != "complete":
return PaymentResponse(
ok=False,
checking_id=None,
fee=None,
preimage=None,
error_message="payment failed",
)
checking_id = data["payment_hash"]
preimage = data["payment_preimage"]
fee_msat = data["amount_sent_msat"] - data["amount_msat"]
return PaymentResponse(
ok=self.statuses.get(data["status"]),
result=PAYMENT_RESULT_MAP[data["status"]],
checking_id=checking_id,
fee=Amount(unit=Unit.msat, amount=fee_msat) if fee_msat else None,
preimage=preimage,
error_message=None,
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
@@ -238,45 +225,44 @@ class CoreLightningRestWallet(LightningBackend):
if r.is_error or "error" in data or data.get("invoices") is None:
raise Exception("error in cln response")
return PaymentStatus(paid=self.statuses.get(data["invoices"][0]["status"]))
return PaymentStatus(
result=INVOICE_RESULT_MAP[data["invoices"][0]["status"]],
)
except Exception as e:
logger.error(f"Error getting invoice status: {e}")
return PaymentStatus(paid=None)
return PaymentStatus(result=PaymentResult.UNKNOWN, error_message=str(e))
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(
"/v1/pay/listPays",
params={"payment_hash": checking_id},
)
try:
r.raise_for_status()
data = r.json()
if not data.get("pays"):
# payment not found
logger.error(f"payment not found: {data.get('pays')}")
raise Exception("payment not found")
if r.is_error or "error" in data:
message = data.get("error") or data
raise Exception(f"error in corelightning-rest response: {message}")
pay = data["pays"][0]
fee_msat, preimage = None, None
if self.statuses[pay["status"]]:
# cut off "msat" and convert to int
fee_msat = -int(pay["amount_sent_msat"]) - int(pay["amount_msat"])
preimage = pay["preimage"]
r.raise_for_status()
data = r.json()
if not data.get("pays"):
# payment not found
logger.error(f"payment not found: {data.get('pays')}")
return PaymentStatus(
paid=self.statuses.get(pay["status"]),
fee=Amount(unit=Unit.msat, amount=fee_msat) if fee_msat else None,
preimage=preimage,
result=PaymentResult.UNKNOWN, error_message="payment not found"
)
except Exception as e:
logger.error(f"Error getting payment status: {e}")
return PaymentStatus(paid=None)
if r.is_error or "error" in data:
message = data.get("error") or data
raise Exception(f"error in corelightning-rest response: {message}")
pay = data["pays"][0]
fee_msat, preimage = None, None
if PAYMENT_RESULT_MAP.get(pay["status"]) == PaymentResult.SETTLED:
fee_msat = -int(pay["amount_sent_msat"]) - int(pay["amount_msat"])
preimage = pay["preimage"]
return PaymentStatus(
result=PAYMENT_RESULT_MAP[pay["status"]],
fee=Amount(unit=Unit.msat, amount=fee_msat) if fee_msat else None,
preimage=preimage,
)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
# call listinvoices to determine the last pay_index
@@ -285,7 +271,8 @@ class CoreLightningRestWallet(LightningBackend):
data = r.json()
if r.is_error or "error" in data:
raise Exception("error in cln response")
self.last_pay_index = data["invoices"][-1]["pay_index"]
if data.get("invoices"):
self.last_pay_index = data["invoices"][-1]["pay_index"]
while True:
try:
@@ -315,9 +302,11 @@ class CoreLightningRestWallet(LightningBackend):
)
paid_invoce = r.json()
logger.trace(f"paid invoice: {paid_invoce}")
assert self.statuses[
paid_invoce["invoices"][0]["status"]
], "streamed invoice not paid"
if (
INVOICE_RESULT_MAP[paid_invoce["invoices"][0]["status"]]
!= PaymentResult.SETTLED
):
raise Exception("invoice not paid")
assert "invoices" in paid_invoce, "no invoices in response"
assert len(paid_invoce["invoices"]), "no invoices in response"
yield paid_invoce["invoices"][0]["payment_hash"]