lnbits-legend/lnbits/wallets/corelightning.py

214 lines
8.0 KiB
Python
Raw Normal View History

import asyncio
import random
from typing import Any, AsyncGenerator, Optional
2022-07-16 14:23:03 +02:00
from bolt11.decode import decode as bolt11_decode
from bolt11.exceptions import Bolt11Exception
from loguru import logger
from pyln.client import LightningRpc, RpcError
[FEAT] Node Managment (#1895) * [FEAT] Node Managment feat: node dashboard channels and transactions fix: update channel variables better types refactor ui add onchain balances and backend_name mock values for fake wallet remove app tab start implementing peers and channel management peer and channel management implement channel closing add channel states, better errors seperate payments and invoices on transactions tab display total channel balance feat: optional public page feat: show node address fix: port conversion feat: details dialog on transactions fix: peer info without alias fix: rename channel balances small improvements to channels tab feat: pagination on transactions tab test caching transactions refactor: move WALLET into wallets module fix: backwards compatibility refactor: move get_node_class to nodes modules post merge bundle fundle feat: disconnect peer feat: initial lnd support only use filtered channels for total balance adjust closing logic add basic node tests add setting for disabling transactions tab revert unnecessary changes add tests for invoices and payments improve payment and invoice implementations the previously used invoice fixture has a session scope, but a new invoice is required tests and bug fixes for channels api use query instead of body in channel delete delete requests should generally not use a body take node id through path instead of body for delete endpoint add peer management tests more tests for errors improve error handling rename id and pubkey to peer_id for consistency remove dead code fix http status codes make cache keys safer cache node public info comments for node settings rename node prop in frontend adjust tests to new status codes cln: use amount_msat instead of value for onchain balance turn transactions tab off by default enable transactions in tests only allow super user to create or delete fix prop name in admin navbar --------- Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-09-25 15:04:44 +02:00
from lnbits.nodes.cln import CoreLightningNode
2022-10-05 13:01:41 +02:00
from lnbits.settings import settings
from .base import (
InvoiceResponse,
PaymentResponse,
PaymentStatus,
StatusResponse,
Unsupported,
Wallet,
)
2020-04-03 12:21:10 +02:00
async def run_sync(func) -> Any:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, func)
class CoreLightningWallet(Wallet):
[FEAT] Node Managment (#1895) * [FEAT] Node Managment feat: node dashboard channels and transactions fix: update channel variables better types refactor ui add onchain balances and backend_name mock values for fake wallet remove app tab start implementing peers and channel management peer and channel management implement channel closing add channel states, better errors seperate payments and invoices on transactions tab display total channel balance feat: optional public page feat: show node address fix: port conversion feat: details dialog on transactions fix: peer info without alias fix: rename channel balances small improvements to channels tab feat: pagination on transactions tab test caching transactions refactor: move WALLET into wallets module fix: backwards compatibility refactor: move get_node_class to nodes modules post merge bundle fundle feat: disconnect peer feat: initial lnd support only use filtered channels for total balance adjust closing logic add basic node tests add setting for disabling transactions tab revert unnecessary changes add tests for invoices and payments improve payment and invoice implementations the previously used invoice fixture has a session scope, but a new invoice is required tests and bug fixes for channels api use query instead of body in channel delete delete requests should generally not use a body take node id through path instead of body for delete endpoint add peer management tests more tests for errors improve error handling rename id and pubkey to peer_id for consistency remove dead code fix http status codes make cache keys safer cache node public info comments for node settings rename node prop in frontend adjust tests to new status codes cln: use amount_msat instead of value for onchain balance turn transactions tab off by default enable transactions in tests only allow super user to create or delete fix prop name in admin navbar --------- Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-09-25 15:04:44 +02:00
__node_cls__ = CoreLightningNode
2020-04-03 12:21:10 +02:00
def __init__(self):
2022-10-05 13:01:41 +02:00
self.rpc = settings.corelightning_rpc or settings.clightning_rpc
self.ln = LightningRpc(self.rpc)
Wallets: add cln-rest (#1775) * receive and pay works * fix linter issues * import Paymentstatus from core.models * fix test real payment * fix get_payment_status check in lnbits * fix tests? * simplify * refactor AsyncClient * inline import of get_wallet_class fixes the previous cyclic import * invoice stream working * add notes as a reminder to get rid of labels when cln-rest supports payment_hash * create Payment dummy classmethod * remove unnecessary fields from dummy * fixes tests? * fix model * fix cln bug (#1814) * auth header * rename cln to corelightning * add clnrest to admin_ui * add to clnrest allowed sources * add allowed sources to .env.example * allow macaroon files * add corelightning rest to workflow * proper env names * cleanup routine * log wallet connection errors and fix macaroon clnrest * print error on connection fails * clnrest: handle disconnects faster * fix test use of get_payment_status * make format * clnrest: add unhashed_description * add unhashed_description to test * description_hash test * unhashed_description not supported by clnrest * fix checking_id return in api_payments_create_invoice * refactor test to use client instead of api_payments * formatting, some errorlogging * fix test 1 * fix other tests, paid statuses was missing * error handling * revert unnecessary changes (#1854) * apply review of motorina0 --------- Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com> Co-authored-by: dni ⚡ <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
# check if description_hash is supported (from corelightning>=v0.11.0)
command = self.ln.help("invoice")["help"][0]["command"] # type: ignore
self.supports_description_hash = "deschashonly" in command
# check last payindex so we can listen from that point on
self.last_pay_index = 0
Wallets: add cln-rest (#1775) * receive and pay works * fix linter issues * import Paymentstatus from core.models * fix test real payment * fix get_payment_status check in lnbits * fix tests? * simplify * refactor AsyncClient * inline import of get_wallet_class fixes the previous cyclic import * invoice stream working * add notes as a reminder to get rid of labels when cln-rest supports payment_hash * create Payment dummy classmethod * remove unnecessary fields from dummy * fixes tests? * fix model * fix cln bug (#1814) * auth header * rename cln to corelightning * add clnrest to admin_ui * add to clnrest allowed sources * add allowed sources to .env.example * allow macaroon files * add corelightning rest to workflow * proper env names * cleanup routine * log wallet connection errors and fix macaroon clnrest * print error on connection fails * clnrest: handle disconnects faster * fix test use of get_payment_status * make format * clnrest: add unhashed_description * add unhashed_description to test * description_hash test * unhashed_description not supported by clnrest * fix checking_id return in api_payments_create_invoice * refactor test to use client instead of api_payments * formatting, some errorlogging * fix test 1 * fix other tests, paid statuses was missing * error handling * revert unnecessary changes (#1854) * apply review of motorina0 --------- Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com> Co-authored-by: dni ⚡ <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
invoices: dict = self.ln.listinvoices() # type: ignore
for inv in invoices["invoices"][::-1]:
if "pay_index" in inv:
self.last_pay_index = inv["pay_index"]
break
2020-04-26 13:28:19 +02:00
async def status(self) -> StatusResponse:
try:
Wallets: add cln-rest (#1775) * receive and pay works * fix linter issues * import Paymentstatus from core.models * fix test real payment * fix get_payment_status check in lnbits * fix tests? * simplify * refactor AsyncClient * inline import of get_wallet_class fixes the previous cyclic import * invoice stream working * add notes as a reminder to get rid of labels when cln-rest supports payment_hash * create Payment dummy classmethod * remove unnecessary fields from dummy * fixes tests? * fix model * fix cln bug (#1814) * auth header * rename cln to corelightning * add clnrest to admin_ui * add to clnrest allowed sources * add allowed sources to .env.example * allow macaroon files * add corelightning rest to workflow * proper env names * cleanup routine * log wallet connection errors and fix macaroon clnrest * print error on connection fails * clnrest: handle disconnects faster * fix test use of get_payment_status * make format * clnrest: add unhashed_description * add unhashed_description to test * description_hash test * unhashed_description not supported by clnrest * fix checking_id return in api_payments_create_invoice * refactor test to use client instead of api_payments * formatting, some errorlogging * fix test 1 * fix other tests, paid statuses was missing * error handling * revert unnecessary changes (#1854) * apply review of motorina0 --------- Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com> Co-authored-by: dni ⚡ <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
funds: dict = self.ln.listfunds() # type: ignore
return StatusResponse(
None, sum([int(ch["our_amount_msat"]) for ch in funds["channels"]])
)
except RpcError as exc:
error_message = f"lightningd '{exc.method}' failed with '{exc.error}'."
return StatusResponse(error_message, 0)
async def create_invoice(
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
label = f"lbl{random.random()}"
2022-08-04 17:40:04 +02:00
msat: int = int(amount * 1000)
try:
if description_hash and not unhashed_description:
raise Unsupported(
"'description_hash' unsupported by CoreLightning, provide"
" 'unhashed_description'"
)
if unhashed_description and not self.supports_description_hash:
raise Unsupported("unhashed_description")
Wallets: add cln-rest (#1775) * receive and pay works * fix linter issues * import Paymentstatus from core.models * fix test real payment * fix get_payment_status check in lnbits * fix tests? * simplify * refactor AsyncClient * inline import of get_wallet_class fixes the previous cyclic import * invoice stream working * add notes as a reminder to get rid of labels when cln-rest supports payment_hash * create Payment dummy classmethod * remove unnecessary fields from dummy * fixes tests? * fix model * fix cln bug (#1814) * auth header * rename cln to corelightning * add clnrest to admin_ui * add to clnrest allowed sources * add allowed sources to .env.example * allow macaroon files * add corelightning rest to workflow * proper env names * cleanup routine * log wallet connection errors and fix macaroon clnrest * print error on connection fails * clnrest: handle disconnects faster * fix test use of get_payment_status * make format * clnrest: add unhashed_description * add unhashed_description to test * description_hash test * unhashed_description not supported by clnrest * fix checking_id return in api_payments_create_invoice * refactor test to use client instead of api_payments * formatting, some errorlogging * fix test 1 * fix other tests, paid statuses was missing * error handling * revert unnecessary changes (#1854) * apply review of motorina0 --------- Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com> Co-authored-by: dni ⚡ <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
r: dict = self.ln.invoice( # type: ignore
msatoshi=msat,
label=label,
description=(
unhashed_description.decode() if unhashed_description else memo
),
exposeprivatechannels=True,
deschashonly=(
True if unhashed_description else False
), # we can't pass None here
expiry=kwargs.get("expiry"),
)
Wallets: add cln-rest (#1775) * receive and pay works * fix linter issues * import Paymentstatus from core.models * fix test real payment * fix get_payment_status check in lnbits * fix tests? * simplify * refactor AsyncClient * inline import of get_wallet_class fixes the previous cyclic import * invoice stream working * add notes as a reminder to get rid of labels when cln-rest supports payment_hash * create Payment dummy classmethod * remove unnecessary fields from dummy * fixes tests? * fix model * fix cln bug (#1814) * auth header * rename cln to corelightning * add clnrest to admin_ui * add to clnrest allowed sources * add allowed sources to .env.example * allow macaroon files * add corelightning rest to workflow * proper env names * cleanup routine * log wallet connection errors and fix macaroon clnrest * print error on connection fails * clnrest: handle disconnects faster * fix test use of get_payment_status * make format * clnrest: add unhashed_description * add unhashed_description to test * description_hash test * unhashed_description not supported by clnrest * fix checking_id return in api_payments_create_invoice * refactor test to use client instead of api_payments * formatting, some errorlogging * fix test 1 * fix other tests, paid statuses was missing * error handling * revert unnecessary changes (#1854) * apply review of motorina0 --------- Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com> Co-authored-by: dni ⚡ <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
if r.get("code") and r.get("code") < 0: # type: ignore
raise Exception(r.get("message"))
return InvoiceResponse(True, r["payment_hash"], r["bolt11"], "")
except RpcError as exc:
error_message = (
f"CoreLightning method '{exc.method}' failed with"
f" '{exc.error.get('message') or exc.error}'." # type: ignore
)
return InvoiceResponse(False, None, None, error_message)
except Exception as e:
return InvoiceResponse(False, None, None, str(e))
2020-04-03 12:21:10 +02:00
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
try:
invoice = bolt11_decode(bolt11)
except Bolt11Exception as exc:
return PaymentResponse(False, None, None, None, str(exc))
previous_payment = await self.get_payment_status(invoice.payment_hash)
if previous_payment.paid:
return PaymentResponse(False, None, None, None, "invoice already paid")
if not invoice.amount_msat or invoice.amount_msat <= 0:
return PaymentResponse(
False, None, None, None, "CLN 0 amount invoice not supported"
)
fee_limit_percent = fee_limit_msat / invoice.amount_msat * 100
# so fee_limit_percent is applied even on payments with fee < 5000 millisatoshi
# (which is default value of exemptfee)
payload = {
"bolt11": bolt11,
"maxfeepercent": f"{fee_limit_percent:.11}",
"exemptfee": 0,
[FEAT] Node Managment (#1895) * [FEAT] Node Managment feat: node dashboard channels and transactions fix: update channel variables better types refactor ui add onchain balances and backend_name mock values for fake wallet remove app tab start implementing peers and channel management peer and channel management implement channel closing add channel states, better errors seperate payments and invoices on transactions tab display total channel balance feat: optional public page feat: show node address fix: port conversion feat: details dialog on transactions fix: peer info without alias fix: rename channel balances small improvements to channels tab feat: pagination on transactions tab test caching transactions refactor: move WALLET into wallets module fix: backwards compatibility refactor: move get_node_class to nodes modules post merge bundle fundle feat: disconnect peer feat: initial lnd support only use filtered channels for total balance adjust closing logic add basic node tests add setting for disabling transactions tab revert unnecessary changes add tests for invoices and payments improve payment and invoice implementations the previously used invoice fixture has a session scope, but a new invoice is required tests and bug fixes for channels api use query instead of body in channel delete delete requests should generally not use a body take node id through path instead of body for delete endpoint add peer management tests more tests for errors improve error handling rename id and pubkey to peer_id for consistency remove dead code fix http status codes make cache keys safer cache node public info comments for node settings rename node prop in frontend adjust tests to new status codes cln: use amount_msat instead of value for onchain balance turn transactions tab off by default enable transactions in tests only allow super user to create or delete fix prop name in admin navbar --------- Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-09-25 15:04:44 +02:00
# so fee_limit_percent is applied even on payments with fee < 5000
# millisatoshi (which is default value of exemptfee)
"description": invoice.description,
}
try:
r = await run_sync(lambda: self.ln.call("pay", payload))
except RpcError as exc:
try:
Wallets: add cln-rest (#1775) * receive and pay works * fix linter issues * import Paymentstatus from core.models * fix test real payment * fix get_payment_status check in lnbits * fix tests? * simplify * refactor AsyncClient * inline import of get_wallet_class fixes the previous cyclic import * invoice stream working * add notes as a reminder to get rid of labels when cln-rest supports payment_hash * create Payment dummy classmethod * remove unnecessary fields from dummy * fixes tests? * fix model * fix cln bug (#1814) * auth header * rename cln to corelightning * add clnrest to admin_ui * add to clnrest allowed sources * add allowed sources to .env.example * allow macaroon files * add corelightning rest to workflow * proper env names * cleanup routine * log wallet connection errors and fix macaroon clnrest * print error on connection fails * clnrest: handle disconnects faster * fix test use of get_payment_status * make format * clnrest: add unhashed_description * add unhashed_description to test * description_hash test * unhashed_description not supported by clnrest * fix checking_id return in api_payments_create_invoice * refactor test to use client instead of api_payments * formatting, some errorlogging * fix test 1 * fix other tests, paid statuses was missing * error handling * revert unnecessary changes (#1854) * apply review of motorina0 --------- Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com> Co-authored-by: dni ⚡ <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
error_message = exc.error["attempts"][-1]["fail_reason"] # type: ignore
except Exception:
error_message = (
f"CoreLightning method '{exc.method}' failed with"
f" '{exc.error.get('message') or exc.error}'." # type: ignore
)
return PaymentResponse(False, None, None, None, error_message)
except Exception as exc:
return PaymentResponse(False, None, None, None, str(exc))
fee_msat = -int(r["amount_sent_msat"] - r["amount_msat"])
return PaymentResponse(
True, r["payment_hash"], fee_msat, r["payment_preimage"], None
)
2020-04-03 12:21:10 +02:00
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try:
Wallets: add cln-rest (#1775) * receive and pay works * fix linter issues * import Paymentstatus from core.models * fix test real payment * fix get_payment_status check in lnbits * fix tests? * simplify * refactor AsyncClient * inline import of get_wallet_class fixes the previous cyclic import * invoice stream working * add notes as a reminder to get rid of labels when cln-rest supports payment_hash * create Payment dummy classmethod * remove unnecessary fields from dummy * fixes tests? * fix model * fix cln bug (#1814) * auth header * rename cln to corelightning * add clnrest to admin_ui * add to clnrest allowed sources * add allowed sources to .env.example * allow macaroon files * add corelightning rest to workflow * proper env names * cleanup routine * log wallet connection errors and fix macaroon clnrest * print error on connection fails * clnrest: handle disconnects faster * fix test use of get_payment_status * make format * clnrest: add unhashed_description * add unhashed_description to test * description_hash test * unhashed_description not supported by clnrest * fix checking_id return in api_payments_create_invoice * refactor test to use client instead of api_payments * formatting, some errorlogging * fix test 1 * fix other tests, paid statuses was missing * error handling * revert unnecessary changes (#1854) * apply review of motorina0 --------- Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com> Co-authored-by: dni ⚡ <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
r: dict = self.ln.listinvoices(payment_hash=checking_id) # type: ignore
except RpcError:
return PaymentStatus(None)
2020-08-29 17:23:01 +02:00
if not r["invoices"]:
return PaymentStatus(None)
invoice_resp = r["invoices"][-1]
if invoice_resp["payment_hash"] == checking_id:
if invoice_resp["status"] == "paid":
return PaymentStatus(True)
elif invoice_resp["status"] == "unpaid":
return PaymentStatus(None)
elif invoice_resp["status"] == "expired":
return PaymentStatus(False)
else:
logger.warning(f"supplied an invalid checking_id: {checking_id}")
return PaymentStatus(None)
2020-04-03 12:21:10 +02:00
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
try:
Wallets: add cln-rest (#1775) * receive and pay works * fix linter issues * import Paymentstatus from core.models * fix test real payment * fix get_payment_status check in lnbits * fix tests? * simplify * refactor AsyncClient * inline import of get_wallet_class fixes the previous cyclic import * invoice stream working * add notes as a reminder to get rid of labels when cln-rest supports payment_hash * create Payment dummy classmethod * remove unnecessary fields from dummy * fixes tests? * fix model * fix cln bug (#1814) * auth header * rename cln to corelightning * add clnrest to admin_ui * add to clnrest allowed sources * add allowed sources to .env.example * allow macaroon files * add corelightning rest to workflow * proper env names * cleanup routine * log wallet connection errors and fix macaroon clnrest * print error on connection fails * clnrest: handle disconnects faster * fix test use of get_payment_status * make format * clnrest: add unhashed_description * add unhashed_description to test * description_hash test * unhashed_description not supported by clnrest * fix checking_id return in api_payments_create_invoice * refactor test to use client instead of api_payments * formatting, some errorlogging * fix test 1 * fix other tests, paid statuses was missing * error handling * revert unnecessary changes (#1854) * apply review of motorina0 --------- Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com> Co-authored-by: dni ⚡ <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
r: dict = self.ln.listpays(payment_hash=checking_id) # type: ignore
except Exception:
return PaymentStatus(None)
if "pays" not in r:
return PaymentStatus(None)
if not r["pays"]:
# no payment with this payment_hash is found
return PaymentStatus(False)
payment_resp = r["pays"][-1]
if payment_resp["payment_hash"] == checking_id:
status = payment_resp["status"]
2020-08-29 17:23:01 +02:00
if status == "complete":
fee_msat = -int(
payment_resp["amount_sent_msat"] - payment_resp["amount_msat"]
)
return PaymentStatus(True, fee_msat, payment_resp["preimage"])
2020-08-29 17:23:01 +02:00
elif status == "failed":
return PaymentStatus(False)
else:
return PaymentStatus(None)
else:
logger.warning(f"supplied an invalid checking_id: {checking_id}")
return PaymentStatus(None)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
while True:
try:
paid = await run_sync(
lambda: self.ln.waitanyinvoice(self.last_pay_index, timeout=2)
)
self.last_pay_index = paid["pay_index"]
yield paid["payment_hash"]
except RpcError as exc:
# only raise if not a timeout
if exc.error["code"] != 904: # type: ignore
raise
except Exception as exc:
logger.error(
f"lost connection to corelightning invoices stream: '{exc}', "
"retrying in 5 seconds"
)
await asyncio.sleep(5)