Skip to content

Commit

Permalink
Mega-merge 3: CLN update client lib with descriptionhash support (WIP) (
Browse files Browse the repository at this point in the history
#792)

* CoreLightningWallet
  • Loading branch information
callebtc committed Aug 1, 2022
1 parent f1ec7e3 commit 9c19b61
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 49 deletions.
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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"

# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet
# LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
# just so you can see the UI before dealing with this file.
Expand All @@ -49,8 +49,8 @@ CLICHE_ENDPOINT=ws://127.0.0.1:12000
SPARK_URL=http://localhost:9737/rpc
SPARK_TOKEN=myaccesstoken

# CLightningWallet
CLIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"
# CoreLightningWallet
CORELIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"

# LnbitsWallet
LNBITS_ENDPOINT=https://legend.lnbits.com
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/regtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
python -m venv ${{ env.VIRTUAL_ENV }}
./venv/bin/python -m pip install --upgrade pip
./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
- name: Run tests
env:
Expand All @@ -47,7 +47,7 @@ jobs:
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
CLightningWallet:
CoreLightningWallet:
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -73,15 +73,15 @@ jobs:
python -m venv ${{ env.VIRTUAL_ENV }}
./venv/bin/python -m pip install --upgrade pip
./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
- name: Run tests
env:
PYTHONUNBUFFERED: 1
PORT: 5123
LNBITS_DATA_FOLDER: ./data
LNBITS_BACKEND_WALLET_CLASS: CLightningWallet
CLIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
LNBITS_BACKEND_WALLET_CLASS: CoreLightningWallet
CORELIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ COPY requirements.txt /tmp/requirements.txt
RUN pip install -r /tmp/requirements.txt

# Install c-lightning specific deps
RUN pip install pylightning
RUN pip install pyln-client

# Install LND specific deps
RUN pip install lndgrpc
Expand Down
8 changes: 4 additions & 4 deletions docs/guide/wallets.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ Backend wallets
===============

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:


### CLightning
### CoreLightning

Using this wallet requires the installation of the `pylightning` Python package.

- `LNBITS_BACKEND_WALLET_CLASS`: **CLightningWallet**
- `CLIGHTNING_RPC`: /file/path/lightning-rpc
- `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningWallet**
- `CORELIGHTNING_RPC`: /file/path/lightning-rpc

### Spark (c-lightning)

Expand Down
3 changes: 2 additions & 1 deletion lnbits/wallets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# flake8: noqa

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 .fake import FakeWallet
from .lnbits import LNbitsWallet
Expand Down
84 changes: 49 additions & 35 deletions lnbits/wallets/clightning.py → lnbits/wallets/cln.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
try:
from lightning import LightningRpc, RpcError # type: ignore
from pyln.client import LightningRpc, RpcError # type: ignore
except ImportError: # pragma: nocover
LightningRpc = None

Expand All @@ -11,6 +11,8 @@
from os import getenv
from typing import AsyncGenerator, Optional

from loguru import logger

from lnbits import bolt11 as lnbits_bolt11

from .base import (
Expand Down Expand Up @@ -42,26 +44,20 @@ def _paid_invoices_stream(ln, last_pay_index):
return ln.waitanyinvoice(last_pay_index)


class CLightningWallet(Wallet):
class CoreLightningWallet(Wallet):
def __init__(self):
if LightningRpc is None: # pragma: nocover
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)

# check description_hash support (could be provided by a plugin)
self.supports_description_hash = False
try:
answer = self.ln.help("invoicewithdescriptionhash")
if answer["help"][0]["command"].startswith(
"invoicewithdescriptionhash msatoshi label description_hash"
):
self.supports_description_hash = True
except:
pass
# check if description_hash is supported (from CLN>=v0.11.0)
self.supports_description_hash = (
"deschashonly" in self.ln.help("invoice")["help"][0]["command"]
)

# check last payindex so we can listen from that point on
self.last_pay_index = 0
Expand Down Expand Up @@ -89,21 +85,32 @@ async def create_invoice(
) -> InvoiceResponse:
label = "lbl{}".format(random.random())
msat = amount * 1000

try:
if description_hash:
if not self.supports_description_hash:
raise Unsupported("description_hash")

params = [msat, label, hashlib.sha256(description_hash).hexdigest()]
r = self.ln.call("invoicewithdescriptionhash", params)
return InvoiceResponse(True, label, r["bolt11"], "")
else:
r = self.ln.invoice(msat, label, memo, exposeprivatechannels=True)
return InvoiceResponse(True, label, r["bolt11"], "")
if description_hash and not self.supports_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
)

if r.get("code") and r.get("code") < 0:
raise Exception(r.get("message"))

return InvoiceResponse(True, r["payment_hash"], r["bolt11"], "")
except RpcError as exc:
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:
invoice = lnbits_bolt11.decode(bolt11)
Expand All @@ -117,18 +124,19 @@ async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse
try:
wrapped = async_wrap(_pay_invoice)
r = await wrapped(self.ln, payload)
except RpcError as exc:
except Exception as exc:
return PaymentResponse(False, None, 0, None, str(exc))

fee_msat = r["msatoshi_sent"] - r["msatoshi"]
preimage = r["payment_preimage"]
return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None)
return PaymentResponse(
True, r["payment_hash"], fee_msat, r["payment_preimage"], None
)

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"]:
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")
raise KeyError("supplied an invalid checking_id")

Expand All @@ -147,7 +155,13 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:

async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
while True:
wrapped = async_wrap(_paid_invoices_stream)
paid = await wrapped(self.ln, self.last_pay_index)
self.last_pay_index = paid["pay_index"]
yield paid["label"]
try:
wrapped = async_wrap(_paid_invoices_stream)
paid = await wrapped(self.ln, self.last_pay_index)
self.last_pay_index = paid["pay_index"]
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)

0 comments on commit 9c19b61

Please sign in to comment.