refactor:

- private methods to the bottom
- do not raise but return status
This commit is contained in:
Vlad Stan
2026-02-13 16:59:05 +02:00
parent 3b040ee22c
commit e990901548
+106 -99
View File
@@ -1,6 +1,7 @@
import asyncio import asyncio
import hashlib import hashlib
import json import json
import os
import shutil import shutil
import subprocess import subprocess
import uuid import uuid
@@ -80,34 +81,6 @@ class LightsparkSparkWallet(Wallet):
except RuntimeError as e: except RuntimeError as e:
logger.warning(f"Error closing wallet connection: {e}") logger.warning(f"Error closing wallet connection: {e}")
async def _request(
self, method: str, path: str, json_data: dict[str, Any] | None = None
) -> dict[str, Any]:
error_message = None
try:
r = await self.client.request(method, path, json=json_data)
r.raise_for_status()
j = r.json()
except (httpx.RequestError, httpx.HTTPStatusError, json.JSONDecodeError) as exc:
if isinstance(exc, httpx.HTTPStatusError) and exc.response is not None:
try:
error_json = exc.response.json()
if "error" in error_json:
error_message = error_json["error"]
except Exception as json_exc:
logger.error(
f"Failed to parse Spark error response as JSON: {json_exc}"
)
raise SparkSidecarError(
error_message or f"Spark sidecar request error: '{exc}'"
) from exc
if error_message or j.get("error"):
raise SparkSidecarError(
error_message or f"Spark sidecar error: {j['error']}"
)
return j
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
try: try:
res = await self._request("POST", "/v1/balance") res = await self._request("POST", "/v1/balance")
@@ -119,6 +92,7 @@ class LightsparkSparkWallet(Wallet):
return StatusResponse("Spark sidecar: missing balance.", 0) return StatusResponse("Spark sidecar: missing balance.", 0)
return StatusResponse(None, int(balance_sats) * 1000) return StatusResponse(None, int(balance_sats) * 1000)
except Exception as e: except Exception as e:
logger.warning(e)
return StatusResponse(f"Spark sidecar status error: {e}", 0) return StatusResponse(f"Spark sidecar status error: {e}", 0)
async def create_invoice( async def create_invoice(
@@ -149,8 +123,9 @@ class LightsparkSparkWallet(Wallet):
bolt11 = res.get("payment_request") bolt11 = res.get("payment_request")
checking_id = res.get("checking_id") checking_id = res.get("checking_id")
if not bolt11 or not checking_id: if not bolt11 or not checking_id:
raise SparkSidecarError( return InvoiceResponse(
"Spark sidecar invoice response missing fields." ok=False,
error_message="Spark sidecar invoice response missing fields.",
) )
self.pending_invoices.append(checking_id) self.pending_invoices.append(checking_id)
@@ -158,7 +133,7 @@ class LightsparkSparkWallet(Wallet):
ok=True, ok=True,
payment_request=bolt11, payment_request=bolt11,
checking_id=checking_id, checking_id=checking_id,
preimage=res.get("preimage"), preimage=res.get("preimage", None),
) )
except Exception as e: except Exception as e:
return InvoiceResponse(ok=False, error_message=str(e)) return InvoiceResponse(ok=False, error_message=str(e))
@@ -170,7 +145,8 @@ class LightsparkSparkWallet(Wallet):
payment_hash = None payment_hash = None
try: try:
payment_hash = bolt11_decode(bolt11).payment_hash payment_hash = bolt11_decode(bolt11).payment_hash
except Exception: except Exception as exc:
logger.warning(exc)
payment_hash = None payment_hash = None
payload = { payload = {
"bolt11": bolt11, "bolt11": bolt11,
@@ -178,10 +154,11 @@ class LightsparkSparkWallet(Wallet):
"payment_hash": payment_hash, "payment_hash": payment_hash,
} }
res = await self._request("POST", "/v1/payments", payload) res = await self._request("POST", "/v1/payments", payload)
checking_id = payment_hash or res.get("checking_id") checking_id = payment_hash or res.get("checking_id") # todo: preimage
if not checking_id: if not checking_id:
raise SparkSidecarError( return PaymentResponse(
"Spark sidecar payment response missing checking_id." ok=False,
error_message="Spark sidecar payment response missing checking_id.",
) )
status = res.get("status") status = res.get("status")
fee_msat = res.get("fee_msat") fee_msat = res.get("fee_msat")
@@ -205,7 +182,8 @@ class LightsparkSparkWallet(Wallet):
if not status: if not status:
return PaymentPendingStatus() return PaymentPendingStatus()
return self._map_invoice_status(status) return self._map_invoice_status(status)
except Exception: except Exception as exc:
logger.warning(exc)
return PaymentPendingStatus() return PaymentPendingStatus()
async def get_payment_status(self, checking_id: str) -> PaymentStatus: async def get_payment_status(self, checking_id: str) -> PaymentStatus:
@@ -225,69 +203,10 @@ class LightsparkSparkWallet(Wallet):
if mapped.failed: if mapped.failed:
return PaymentFailedStatus() return PaymentFailedStatus()
return PaymentPendingStatus() return PaymentPendingStatus()
except Exception: except Exception as exc:
logger.warning(exc)
return PaymentPendingStatus() return PaymentPendingStatus()
def _map_invoice_status(self, status: str) -> PaymentStatus:
success = {
"LIGHTNING_PAYMENT_RECEIVED",
"TRANSFER_COMPLETED",
"PAYMENT_PREIMAGE_RECOVERED",
}
failed = {
"TRANSFER_FAILED",
"PAYMENT_PREIMAGE_RECOVERING_FAILED",
"REFUND_SIGNING_FAILED",
"REFUND_SIGNING_COMMITMENTS_QUERYING_FAILED",
"TRANSFER_CREATION_FAILED",
}
if status in success:
return PaymentSuccessStatus()
if status in failed:
return PaymentFailedStatus()
return PaymentPendingStatus()
def _map_payment_status(self, status: str) -> PaymentStatus:
success = {
"LIGHTNING_PAYMENT_SUCCEEDED",
"TRANSFER_COMPLETED",
"PREIMAGE_PROVIDED",
}
failed = {
"LIGHTNING_PAYMENT_FAILED",
"TRANSFER_FAILED",
"PREIMAGE_PROVIDING_FAILED",
"USER_TRANSFER_VALIDATION_FAILED",
"USER_SWAP_RETURN_FAILED",
}
if status in success:
return PaymentSuccessStatus()
if status in failed:
return PaymentFailedStatus()
return PaymentPendingStatus()
def _map_payment_ok(self, status: str) -> bool | None:
mapped = self._map_payment_status(status)
if mapped.success:
return True
if mapped.failed:
return False
return None
async def _poll_pending_invoices(self) -> AsyncGenerator[str, None]:
while settings.lnbits_running:
for invoice in list(self.pending_invoices):
try:
status = await self.get_invoice_status(invoice)
if status.paid:
yield invoice
self.pending_invoices.remove(invoice)
elif status.failed:
self.pending_invoices.remove(invoice)
except Exception as exc:
logger.error(f"could not get status of invoice {invoice}: '{exc}' ")
await asyncio.sleep(5)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
stream_path = "/v1/invoices/stream" stream_path = "/v1/invoices/stream"
while settings.lnbits_running: while settings.lnbits_running:
@@ -318,6 +237,48 @@ class LightsparkSparkWallet(Wallet):
) )
await asyncio.sleep(5) await asyncio.sleep(5)
async def _request(
self, method: str, path: str, json_data: dict[str, Any] | None = None
) -> dict[str, Any]:
error_message = None
try:
r = await self.client.request(method, path, json=json_data)
r.raise_for_status()
j = r.json()
except (httpx.RequestError, httpx.HTTPStatusError, json.JSONDecodeError) as exc:
if isinstance(exc, httpx.HTTPStatusError) and exc.response is not None:
try:
error_json = exc.response.json()
if "error" in error_json:
error_message = error_json["error"]
except Exception as json_exc:
logger.error(
f"Failed to parse Spark error response as JSON: {json_exc}"
)
raise SparkSidecarError(
error_message or f"Spark sidecar request error: '{exc}'"
) from exc
if error_message or j.get("error"):
raise SparkSidecarError(
error_message or f"Spark sidecar error: {j['error']}"
)
return j
async def _poll_pending_invoices(self) -> AsyncGenerator[str, None]:
while settings.lnbits_running:
for invoice in list(self.pending_invoices):
try:
status = await self.get_invoice_status(invoice)
if status.paid:
yield invoice
self.pending_invoices.remove(invoice)
elif status.failed:
self.pending_invoices.remove(invoice)
except Exception as exc:
logger.error(f"could not get status of invoice {invoice}: '{exc}' ")
await asyncio.sleep(5)
async def _start_sidecar(self): async def _start_sidecar(self):
logger.info("Starting Spark sidecar") logger.info("Starting Spark sidecar")
node_path = shutil.which("node") node_path = shutil.which("node")
@@ -391,6 +352,8 @@ class LightsparkSparkWallet(Wallet):
zip_path = Path(self._sidecar_path, f"{repo}.zip") zip_path = Path(self._sidecar_path, f"{repo}.zip")
logger.info(f"⏳ Downloading Spark sidecar to {zip_path}") logger.info(f"⏳ Downloading Spark sidecar to {zip_path}")
Path(zip_path).parent.mkdir(parents=True, exist_ok=True) Path(zip_path).parent.mkdir(parents=True, exist_ok=True)
if zip_path.is_file():
os.remove(zip_path)
await asyncio.to_thread( await asyncio.to_thread(
download_url, download_url,
@@ -406,8 +369,6 @@ class LightsparkSparkWallet(Wallet):
self._sidecar_path, self._sidecar_path,
) )
logger.info("✅ Extracted Spark sidecar.") logger.info("✅ Extracted Spark sidecar.")
shutil.rmtree(zip_path, ignore_errors=True)
# todo: remove zip
def _log_process_output(self, process: subprocess.Popen): def _log_process_output(self, process: subprocess.Popen):
if process.stdout: if process.stdout:
@@ -415,3 +376,49 @@ class LightsparkSparkWallet(Wallet):
logger.warning(f"[Lightspark]: {line}", end="") logger.warning(f"[Lightspark]: {line}", end="")
else: else:
logger.error(" No output captured for Spark sidecar.") logger.error(" No output captured for Spark sidecar.")
def _map_invoice_status(self, status: str) -> PaymentStatus:
success = {
"LIGHTNING_PAYMENT_RECEIVED",
"TRANSFER_COMPLETED",
"PAYMENT_PREIMAGE_RECOVERED",
}
failed = {
"TRANSFER_FAILED",
"PAYMENT_PREIMAGE_RECOVERING_FAILED",
"REFUND_SIGNING_FAILED",
"REFUND_SIGNING_COMMITMENTS_QUERYING_FAILED",
"TRANSFER_CREATION_FAILED",
}
if status in success:
return PaymentSuccessStatus()
if status in failed:
return PaymentFailedStatus()
return PaymentPendingStatus()
def _map_payment_status(self, status: str) -> PaymentStatus:
success = {
"LIGHTNING_PAYMENT_SUCCEEDED",
"TRANSFER_COMPLETED",
"PREIMAGE_PROVIDED",
}
failed = {
"LIGHTNING_PAYMENT_FAILED",
"TRANSFER_FAILED",
"PREIMAGE_PROVIDING_FAILED",
"USER_TRANSFER_VALIDATION_FAILED",
"USER_SWAP_RETURN_FAILED",
}
if status in success:
return PaymentSuccessStatus()
if status in failed:
return PaymentFailedStatus()
return PaymentPendingStatus()
def _map_payment_ok(self, status: str) -> bool | None:
mapped = self._map_payment_status(status)
if mapped.success:
return True
if mapped.failed:
return False
return None