Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REFACTOR: paid flag -> PaymentResult object #619

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 50 additions & 6 deletions cashu/lightning/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from typing import AsyncGenerator, Coroutine, Optional, Union
from enum import Enum

from pydantic import BaseModel

Expand Down Expand Up @@ -33,36 +34,79 @@ class InvoiceResponse(BaseModel):
payment_request: Optional[str] = None
error_message: Optional[str] = None

class PaymentResult(Enum):
FAILED = 0
SETTLED = 1
PENDING = 2

UNKNOWN = 3

def __str__(self):
return self.name

# We assume `None` is `FAILED`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that None should be PENDING, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you are right. But we also used None to signal what now would be UNKNOWN

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't matter too much, though. One shouldn't be using that. I only ever created it to use it in FakeWallet

Copy link
Contributor

@prusnak prusnak Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I suggest to change this code to assume that None is PENDING.

That way it is consistent with rest of the old and new code.

@classmethod
def from_paid_flag(cls, paid: Optional[bool]):
if paid is None or paid == False:
return cls.FAILED
elif paid == True:
return cls.SETTLED

class PaymentResponse(BaseModel):
result: PaymentResult
ok: Optional[bool] = None # True: paid, False: failed, None: pending or unknown
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ok: Optional[bool] = None # True: paid, False: failed, None: pending or unknown

I suggest we remove all legacy flags

checking_id: Optional[str] = None
fee: Optional[Amount] = None
preimage: Optional[str] = None
error_message: Optional[str] = None

@property
def pending(self) -> bool:
return self.result == PaymentResult.PENDING

@property
def settled(self) -> bool:
return self.result == PaymentResult.SETTLED

@property
def failed(self) -> bool:
return self.result == PaymentResult.FAILED

@property
def unknown(self) -> bool:
return self.result == PaymentResult.UNKNOWN

class PaymentStatus(BaseModel):
result: PaymentResult
paid: Optional[bool] = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
paid: Optional[bool] = None

fee: Optional[Amount] = None
preimage: Optional[str] = None
error_message: Optional[str] = None

@property
def pending(self) -> bool:
return self.paid is not True
return self.result == PaymentResult.PENDING

@property
def settled(self) -> bool:
return self.result == PaymentResult.SETTLED

@property
def failed(self) -> bool:
return self.paid is False
return self.result == PaymentResult.FAILED

@property
def unknown(self) -> bool:
return self.result == PaymentResult.UNKNOWN

def __str__(self) -> str:
if self.paid is True:
if self.result == PaymentResult.SETTLED:
return "settled"
elif self.paid is False:
elif self.result == PaymentResult.FAILED:
return "failed"
elif self.paid is None:
elif self.result == PaymentResult.PENDING:
return "still pending"
else:
else: # self.result == PaymentResult.UNKNOWN:
return "unknown (should never happen)"


Expand Down
53 changes: 39 additions & 14 deletions cashu/lightning/clnrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,33 @@
PaymentStatus,
StatusResponse,
Unsupported,
PaymentResult,
)

# https://docs.corelightning.org/reference/lightning-pay
PAYMENT_STATUSES = {
"complete": True,
"pending": None,
"failed": False,
}
PAYMENT_RESULT_MAP = {
"complete": PaymentResult.SETTLED,
"pending": PaymentResult.PENDING,
"failed": PaymentResult.FAILED,
}

# https://docs.corelightning.org/reference/lightning-listinvoices
INVOICE_STATUSES = {
"paid": True,
"unpaid": None,
"expired": False,
}
INVOICE_RESULT_MAP = {
"paid": PaymentResult.SETTLED,
"unpaid": PaymentResult.PENDING,
"expired": PaymentResult.FAILED,
}


class CLNRestWallet(LightningBackend):
supported_units = set([Unit.sat, Unit.msat])
Expand Down Expand Up @@ -169,6 +194,7 @@ async def pay_invoice(
invoice = decode(quote.request)
except Bolt11Exception as exc:
return PaymentResponse(
result=PaymentResult.FAILED,
ok=False,
checking_id=None,
fee=None,
Expand All @@ -179,6 +205,7 @@ async def pay_invoice(
if not invoice.amount_msat or invoice.amount_msat <= 0:
error_message = "0 amount invoices are not allowed"
return PaymentResponse(
result=PaymentResult.FAILED,
ok=False,
checking_id=None,
fee=None,
Expand All @@ -205,6 +232,7 @@ async def pay_invoice(
error_message = "mint does not support MPP"
logger.error(error_message)
return PaymentResponse(
result=PaymentResult.FAILED,
ok=False,
checking_id=None,
fee=None,
Expand All @@ -220,6 +248,7 @@ async def pay_invoice(
except Exception:
error_message = r.text
return PaymentResponse(
result=PaymentResult.FAILED,
ok=False,
checking_id=None,
fee=None,
Expand All @@ -229,21 +258,13 @@ async def pay_invoice(

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"]],
ok=PAYMENT_STATUSES[data["status"]],
checking_id=checking_id,
fee=Amount(unit=Unit.msat, amount=fee_msat) if fee_msat else None,
preimage=preimage,
Expand All @@ -261,10 +282,13 @@ async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

if r.is_error or "message" 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"]],
paid=INVOICE_STATUSES[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, paid=None)

async def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.post(
Expand Down Expand Up @@ -293,13 +317,14 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
preimage = pay["preimage"]

return PaymentStatus(
paid=self.statuses.get(pay["status"]),
result=PAYMENT_RESULT_MAP[pay["status"]],
paid=PAYMENT_STATUSES[pay["status"]],
fee=Amount(unit=Unit.msat, amount=fee_msat) if fee_msat else None,
preimage=preimage,
)
except Exception as e:
logger.error(f"Error getting payment status: {e}")
return PaymentStatus(paid=None)
return PaymentStatus(result=PaymentResult.UNKNOWN, paid=None)

async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
# call listinvoices to determine the last pay_index
Expand Down
51 changes: 37 additions & 14 deletions cashu/lightning/corelightningrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,33 @@
PaymentStatus,
StatusResponse,
Unsupported,
PaymentResult,
)
from .macaroon import load_macaroon

# https://docs.corelightning.org/reference/lightning-pay
PAYMENT_STATUSES = {
"complete": True,
"pending": None,
"failed": False,
}
PAYMENT_RESULT_MAP = {
"complete": PaymentResult.SETTLED,
"pending": PaymentResult.PENDING,
"failed": PaymentResult.FAILED,
}

# https://docs.corelightning.org/reference/lightning-listinvoices
INVOICE_STATUSES = {
"paid": True,
"unpaid": None,
"expired": False,
}
INVOICE_RESULT_MAP = {
"paid": PaymentResult.SETTLED,
"unpaid": PaymentResult.PENDING,
"expired": PaymentResult.FAILED,
}

class CoreLightningRestWallet(LightningBackend):
supported_units = set([Unit.sat, Unit.msat])
Expand Down Expand Up @@ -162,6 +186,7 @@ async def pay_invoice(
invoice = decode(quote.request)
except Bolt11Exception as exc:
return PaymentResponse(
result=PaymentResult.FAILED,
ok=False,
checking_id=None,
fee=None,
Expand All @@ -172,6 +197,7 @@ async def pay_invoice(
if not invoice.amount_msat or invoice.amount_msat <= 0:
error_message = "0 amount invoices are not allowed"
return PaymentResponse(
result=PaymentResult.FAILED,
ok=False,
checking_id=None,
fee=None,
Expand All @@ -197,6 +223,7 @@ async def pay_invoice(
except Exception:
error_message = r.text
return PaymentResponse(
result=PaymentResult.FAILED,
ok=False,
checking_id=None,
fee=None,
Expand All @@ -206,21 +233,13 @@ async def pay_invoice(

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"]],
ok=PAYMENT_STATUSES[data["status"]],
checking_id=checking_id,
fee=Amount(unit=Unit.msat, amount=fee_msat) if fee_msat else None,
preimage=preimage,
Expand All @@ -238,10 +257,13 @@ async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

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"]],
paid=INVOICE_STATUSES[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, paid=None)

async def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(
Expand Down Expand Up @@ -270,13 +292,14 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
preimage = pay["preimage"]

return PaymentStatus(
paid=self.statuses.get(pay["status"]),
result=INVOICE_RESULT_MAP[pay["status"]],
paid=INVOICE_STATUSES[pay["status"]],
fee=Amount(unit=Unit.msat, amount=fee_msat) if fee_msat else None,
preimage=preimage,
)
except Exception as e:
logger.error(f"Error getting payment status: {e}")
return PaymentStatus(paid=None)
return PaymentStatus(result=PaymentResult.UNKNOWN, paid=None)

async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
# call listinvoices to determine the last pay_index
Expand Down
16 changes: 13 additions & 3 deletions cashu/lightning/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
PaymentResponse,
PaymentStatus,
StatusResponse,
PaymentResult,
)


Expand Down Expand Up @@ -158,14 +159,17 @@ async def pay_invoice(self, quote: MeltQuote, fee_limit: int) -> PaymentResponse
raise ValueError("Invoice already paid")

return PaymentResponse(
result=PaymentResult.SETTLED,
ok=True,
checking_id=invoice.payment_hash,
fee=Amount(unit=self.unit, amount=1),
preimage=self.payment_secrets.get(invoice.payment_hash) or "0" * 64,
)
else:
return PaymentResponse(
ok=False, error_message="Only internal invoices can be used!"
result=PaymentResult.FAILED,
ok=False,
error_message="Only internal invoices can be used!",
)

async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
Expand All @@ -176,10 +180,16 @@ async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
else:
paid = False

return PaymentStatus(paid=paid)
return PaymentStatus(
result=PaymentResult.from_paid_flag(paid),
paid=paid,
)

async def get_payment_status(self, _: str) -> PaymentStatus:
return PaymentStatus(paid=settings.fakewallet_payment_state)
return PaymentStatus(
result=PaymentResult.from_paid_flag(settings.fakewallet_payment_state),
paid=settings.fakewallet_payment_state,
)

async def get_payment_quote(
self, melt_quote: PostMeltQuoteRequest
Expand Down
Loading
Loading