refactor:
- private methods to the bottom - do not raise but return status
This commit is contained in:
+106
-99
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user