Mega-merge 3: CLN update client lib with descriptionhash support (WIP) (#792)
* CoreLightningWallet
This commit is contained in:
parent
f1ec7e33f0
commit
9c19b61e4a
6 changed files with 63 additions and 48 deletions
|
|
@ -35,7 +35,7 @@ LNBITS_THEME_OPTIONS="classic, bitcoin, freedom, mint, autumn, monochrome, salva
|
||||||
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
|
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
|
||||||
|
|
||||||
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet
|
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet
|
||||||
# LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
|
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
|
||||||
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
|
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
|
||||||
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
|
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
|
||||||
# just so you can see the UI before dealing with this file.
|
# just so you can see the UI before dealing with this file.
|
||||||
|
|
@ -49,8 +49,8 @@ CLICHE_ENDPOINT=ws://127.0.0.1:12000
|
||||||
SPARK_URL=http://localhost:9737/rpc
|
SPARK_URL=http://localhost:9737/rpc
|
||||||
SPARK_TOKEN=myaccesstoken
|
SPARK_TOKEN=myaccesstoken
|
||||||
|
|
||||||
# CLightningWallet
|
# CoreLightningWallet
|
||||||
CLIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"
|
CORELIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"
|
||||||
|
|
||||||
# LnbitsWallet
|
# LnbitsWallet
|
||||||
LNBITS_ENDPOINT=https://legend.lnbits.com
|
LNBITS_ENDPOINT=https://legend.lnbits.com
|
||||||
|
|
|
||||||
10
.github/workflows/regtest.yml
vendored
10
.github/workflows/regtest.yml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
python -m venv ${{ env.VIRTUAL_ENV }}
|
python -m venv ${{ env.VIRTUAL_ENV }}
|
||||||
./venv/bin/python -m pip install --upgrade pip
|
./venv/bin/python -m pip install --upgrade pip
|
||||||
./venv/bin/pip install -r requirements.txt
|
./venv/bin/pip install -r requirements.txt
|
||||||
./venv/bin/pip install pylightning
|
./venv/bin/pip install pyln-client
|
||||||
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
|
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
env:
|
||||||
|
|
@ -47,7 +47,7 @@ jobs:
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
file: ./coverage.xml
|
file: ./coverage.xml
|
||||||
CLightningWallet:
|
CoreLightningWallet:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -73,15 +73,15 @@ jobs:
|
||||||
python -m venv ${{ env.VIRTUAL_ENV }}
|
python -m venv ${{ env.VIRTUAL_ENV }}
|
||||||
./venv/bin/python -m pip install --upgrade pip
|
./venv/bin/python -m pip install --upgrade pip
|
||||||
./venv/bin/pip install -r requirements.txt
|
./venv/bin/pip install -r requirements.txt
|
||||||
./venv/bin/pip install pylightning
|
./venv/bin/pip install pyln-client
|
||||||
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
|
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
env:
|
||||||
PYTHONUNBUFFERED: 1
|
PYTHONUNBUFFERED: 1
|
||||||
PORT: 5123
|
PORT: 5123
|
||||||
LNBITS_DATA_FOLDER: ./data
|
LNBITS_DATA_FOLDER: ./data
|
||||||
LNBITS_BACKEND_WALLET_CLASS: CLightningWallet
|
LNBITS_BACKEND_WALLET_CLASS: CoreLightningWallet
|
||||||
CLIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
|
CORELIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
|
||||||
run: |
|
run: |
|
||||||
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
|
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
|
||||||
make test-real-wallet
|
make test-real-wallet
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ COPY requirements.txt /tmp/requirements.txt
|
||||||
RUN pip install -r /tmp/requirements.txt
|
RUN pip install -r /tmp/requirements.txt
|
||||||
|
|
||||||
# Install c-lightning specific deps
|
# Install c-lightning specific deps
|
||||||
RUN pip install pylightning
|
RUN pip install pyln-client
|
||||||
|
|
||||||
# Install LND specific deps
|
# Install LND specific deps
|
||||||
RUN pip install lndgrpc
|
RUN pip install lndgrpc
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,17 @@ Backend wallets
|
||||||
===============
|
===============
|
||||||
|
|
||||||
LNbits can run on top of many lightning-network funding sources. Currently there is support for
|
LNbits can run on top of many lightning-network funding sources. Currently there is support for
|
||||||
CLightning, LND, LNbits, LNPay, lntxbot and OpenNode, with more being added regularily.
|
CoreLightning, LND, LNbits, LNPay, lntxbot and OpenNode, with more being added regularily.
|
||||||
|
|
||||||
A backend wallet can be configured using the following LNbits environment variables:
|
A backend wallet can be configured using the following LNbits environment variables:
|
||||||
|
|
||||||
|
|
||||||
### CLightning
|
### CoreLightning
|
||||||
|
|
||||||
Using this wallet requires the installation of the `pylightning` Python package.
|
Using this wallet requires the installation of the `pylightning` Python package.
|
||||||
|
|
||||||
- `LNBITS_BACKEND_WALLET_CLASS`: **CLightningWallet**
|
- `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningWallet**
|
||||||
- `CLIGHTNING_RPC`: /file/path/lightning-rpc
|
- `CORELIGHTNING_RPC`: /file/path/lightning-rpc
|
||||||
|
|
||||||
### Spark (c-lightning)
|
### Spark (c-lightning)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
|
||||||
from .cliche import ClicheWallet
|
from .cliche import ClicheWallet
|
||||||
from .clightning import CLightningWallet
|
from .cln import CoreLightningWallet # legacy .env support
|
||||||
|
from .cln import CoreLightningWallet as CLightningWallet
|
||||||
from .eclair import EclairWallet
|
from .eclair import EclairWallet
|
||||||
from .fake import FakeWallet
|
from .fake import FakeWallet
|
||||||
from .lnbits import LNbitsWallet
|
from .lnbits import LNbitsWallet
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
try:
|
try:
|
||||||
from lightning import LightningRpc, RpcError # type: ignore
|
from pyln.client import LightningRpc, RpcError # type: ignore
|
||||||
except ImportError: # pragma: nocover
|
except ImportError: # pragma: nocover
|
||||||
LightningRpc = None
|
LightningRpc = None
|
||||||
|
|
||||||
|
|
@ -11,6 +11,8 @@ from functools import partial, wraps
|
||||||
from os import getenv
|
from os import getenv
|
||||||
from typing import AsyncGenerator, Optional
|
from typing import AsyncGenerator, Optional
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits import bolt11 as lnbits_bolt11
|
from lnbits import bolt11 as lnbits_bolt11
|
||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
|
|
@ -42,26 +44,20 @@ def _paid_invoices_stream(ln, last_pay_index):
|
||||||
return ln.waitanyinvoice(last_pay_index)
|
return ln.waitanyinvoice(last_pay_index)
|
||||||
|
|
||||||
|
|
||||||
class CLightningWallet(Wallet):
|
class CoreLightningWallet(Wallet):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if LightningRpc is None: # pragma: nocover
|
if LightningRpc is None: # pragma: nocover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"The `pylightning` library must be installed to use `CLightningWallet`."
|
"The `pyln-client` library must be installed to use `CoreLightningWallet`."
|
||||||
)
|
)
|
||||||
|
|
||||||
self.rpc = getenv("CLIGHTNING_RPC")
|
self.rpc = getenv("CORELIGHTNING_RPC") or getenv("CLIGHTNING_RPC")
|
||||||
self.ln = LightningRpc(self.rpc)
|
self.ln = LightningRpc(self.rpc)
|
||||||
|
|
||||||
# check description_hash support (could be provided by a plugin)
|
# check if description_hash is supported (from CLN>=v0.11.0)
|
||||||
self.supports_description_hash = False
|
self.supports_description_hash = (
|
||||||
try:
|
"deschashonly" in self.ln.help("invoice")["help"][0]["command"]
|
||||||
answer = self.ln.help("invoicewithdescriptionhash")
|
)
|
||||||
if answer["help"][0]["command"].startswith(
|
|
||||||
"invoicewithdescriptionhash msatoshi label description_hash"
|
|
||||||
):
|
|
||||||
self.supports_description_hash = True
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# check last payindex so we can listen from that point on
|
# check last payindex so we can listen from that point on
|
||||||
self.last_pay_index = 0
|
self.last_pay_index = 0
|
||||||
|
|
@ -89,21 +85,32 @@ class CLightningWallet(Wallet):
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
label = "lbl{}".format(random.random())
|
label = "lbl{}".format(random.random())
|
||||||
msat = amount * 1000
|
msat = amount * 1000
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if description_hash:
|
if description_hash and not self.supports_description_hash:
|
||||||
if not self.supports_description_hash:
|
|
||||||
raise Unsupported("description_hash")
|
raise Unsupported("description_hash")
|
||||||
|
r = self.ln.invoice(
|
||||||
|
msatoshi=msat,
|
||||||
|
label=label,
|
||||||
|
description=description_hash.decode("utf-8")
|
||||||
|
if description_hash
|
||||||
|
else memo,
|
||||||
|
exposeprivatechannels=True,
|
||||||
|
deschashonly=True
|
||||||
|
if description_hash
|
||||||
|
else False, # we can't pass None here
|
||||||
|
)
|
||||||
|
|
||||||
params = [msat, label, hashlib.sha256(description_hash).hexdigest()]
|
if r.get("code") and r.get("code") < 0:
|
||||||
r = self.ln.call("invoicewithdescriptionhash", params)
|
raise Exception(r.get("message"))
|
||||||
return InvoiceResponse(True, label, r["bolt11"], "")
|
|
||||||
else:
|
return InvoiceResponse(True, r["payment_hash"], r["bolt11"], "")
|
||||||
r = self.ln.invoice(msat, label, memo, exposeprivatechannels=True)
|
|
||||||
return InvoiceResponse(True, label, r["bolt11"], "")
|
|
||||||
except RpcError as exc:
|
except RpcError as exc:
|
||||||
error_message = f"lightningd '{exc.method}' failed with '{exc.error}'."
|
error_message = f"lightningd '{exc.method}' failed with '{exc.error}'."
|
||||||
return InvoiceResponse(False, label, None, error_message)
|
logger.error("RPC error:", error_message)
|
||||||
|
return InvoiceResponse(False, None, None, error_message)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("error:", e)
|
||||||
|
return InvoiceResponse(False, None, None, str(e))
|
||||||
|
|
||||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||||
invoice = lnbits_bolt11.decode(bolt11)
|
invoice = lnbits_bolt11.decode(bolt11)
|
||||||
|
|
@ -117,18 +124,19 @@ class CLightningWallet(Wallet):
|
||||||
try:
|
try:
|
||||||
wrapped = async_wrap(_pay_invoice)
|
wrapped = async_wrap(_pay_invoice)
|
||||||
r = await wrapped(self.ln, payload)
|
r = await wrapped(self.ln, payload)
|
||||||
except RpcError as exc:
|
except Exception as exc:
|
||||||
return PaymentResponse(False, None, 0, None, str(exc))
|
return PaymentResponse(False, None, 0, None, str(exc))
|
||||||
|
|
||||||
fee_msat = r["msatoshi_sent"] - r["msatoshi"]
|
fee_msat = r["msatoshi_sent"] - r["msatoshi"]
|
||||||
preimage = r["payment_preimage"]
|
return PaymentResponse(
|
||||||
return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None)
|
True, r["payment_hash"], fee_msat, r["payment_preimage"], None
|
||||||
|
)
|
||||||
|
|
||||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||||
r = self.ln.listinvoices(checking_id)
|
r = self.ln.listinvoices(payment_hash=checking_id)
|
||||||
if not r["invoices"]:
|
if not r["invoices"]:
|
||||||
return PaymentStatus(False)
|
return PaymentStatus(False)
|
||||||
if r["invoices"][0]["label"] == checking_id:
|
if r["invoices"][0]["payment_hash"] == checking_id:
|
||||||
return PaymentStatus(r["invoices"][0]["status"] == "paid")
|
return PaymentStatus(r["invoices"][0]["status"] == "paid")
|
||||||
raise KeyError("supplied an invalid checking_id")
|
raise KeyError("supplied an invalid checking_id")
|
||||||
|
|
||||||
|
|
@ -147,7 +155,13 @@ class CLightningWallet(Wallet):
|
||||||
|
|
||||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
while True:
|
while True:
|
||||||
|
try:
|
||||||
wrapped = async_wrap(_paid_invoices_stream)
|
wrapped = async_wrap(_paid_invoices_stream)
|
||||||
paid = await wrapped(self.ln, self.last_pay_index)
|
paid = await wrapped(self.ln, self.last_pay_index)
|
||||||
self.last_pay_index = paid["pay_index"]
|
self.last_pay_index = paid["pay_index"]
|
||||||
yield paid["label"]
|
yield paid["payment_hash"]
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(
|
||||||
|
f"lost connection to cln invoices stream: '{exc}', retrying in 5 seconds"
|
||||||
|
)
|
||||||
|
await asyncio.sleep(5)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue