Mega-merge 3: CLN update client lib with descriptionhash support (WIP) (#792)

* CoreLightningWallet
This commit is contained in:
calle 2022-08-01 16:41:50 +02:00 committed by GitHub
parent f1ec7e33f0
commit 9c19b61e4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 63 additions and 48 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)