fix: switch from purerpc to grpcio for LND Wallet

purerpc is unmaintained and had some old dependencies.
This commit is contained in:
Stefan Stammberger 2021-11-07 17:24:22 +01:00
parent 54f4dfd838
commit 768aa05b11
No known key found for this signature in database
GPG key ID: 645FA807E935D9D5
5 changed files with 17710 additions and 49 deletions

View file

@ -29,7 +29,7 @@ Using this wallet requires the installation of the `pylightning` Python package.
### LND (gRPC) ### LND (gRPC)
Using this wallet requires the installation of the `lndgrpc` and `purerpc` Python packages. Using this wallet requires the installation of the `grpcio` Python packages.
- `LNBITS_BACKEND_WALLET_CLASS`: **LndWallet** - `LNBITS_BACKEND_WALLET_CLASS`: **LndWallet**
- `LND_GRPC_ENDPOINT`: ip_address - `LND_GRPC_ENDPOINT`: ip_address

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,17 @@
try: try:
import lndgrpc # type: ignore import grpc
from lndgrpc.common import ln # type: ignore
except ImportError: # pragma: nocover except ImportError: # pragma: nocover
lndgrpc = None grpc = None
try:
import purerpc # type: ignore
except ImportError: # pragma: nocover
purerpc = None
import binascii import binascii
import base64 import base64
import hashlib import hashlib
from os import getenv from os import environ, error, getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as ln
import lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc as lnrpc
from .base import ( from .base import (
StatusResponse, StatusResponse,
InvoiceResponse, InvoiceResponse,
@ -71,16 +68,17 @@ def stringify_checking_id(r_hash: bytes) -> str:
return base64.b64encode(r_hash).decode("utf-8").replace("/", "_") return base64.b64encode(r_hash).decode("utf-8").replace("/", "_")
# Due to updated ECDSA generated tls.cert we need to let gprc know that
# we need to use that cipher suite otherwise there will be a handhsake
# error when we communicate with the lnd rpc server.
environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
class LndWallet(Wallet): class LndWallet(Wallet):
def __init__(self): def __init__(self):
if lndgrpc is None: # pragma: nocover if grpc is None: # pragma: nocover
raise ImportError( raise ImportError(
"The `lndgrpc` library must be installed to use `LndWallet`." "The `grpcio` library must be installed to use `GRPC LndWallet`. Alternatively try using the LndRESTWallet."
)
if purerpc is None: # pragma: nocover
raise ImportError(
"The `purerpc` library must be installed to use `LndWallet`."
) )
endpoint = getenv("LND_GRPC_ENDPOINT") endpoint = getenv("LND_GRPC_ENDPOINT")
@ -88,25 +86,34 @@ class LndWallet(Wallet):
self.port = int(getenv("LND_GRPC_PORT")) self.port = int(getenv("LND_GRPC_PORT"))
self.cert_path = getenv("LND_GRPC_CERT") or getenv("LND_CERT") self.cert_path = getenv("LND_GRPC_CERT") or getenv("LND_CERT")
self.macaroon_path = ( macaroon_path = (
getenv("LND_GRPC_MACAROON") getenv("LND_GRPC_MACAROON")
or getenv("LND_GRPC_ADMIN_MACAROON") or getenv("LND_GRPC_ADMIN_MACAROON")
or getenv("LND_ADMIN_MACAROON") or getenv("LND_ADMIN_MACAROON")
or getenv("LND_GRPC_INVOICE_MACAROON") or getenv("LND_GRPC_INVOICE_MACAROON")
or getenv("LND_INVOICE_MACAROON") or getenv("LND_INVOICE_MACAROON")
) )
network = getenv("LND_GRPC_NETWORK", "mainnet")
self.rpc = lndgrpc.LNDClient( if macaroon_path.split(".")[-1] == "macaroon":
f"{self.endpoint}:{self.port}", self.macaroon = load_macaroon(macaroon_path)
cert_filepath=self.cert_path, else:
network=network, self.macaroon = macaroon_path
macaroon_filepath=self.macaroon_path,
cert = open(self.cert_path, "rb").read()
creds = grpc.ssl_channel_credentials(cert)
auth_creds = grpc.metadata_call_credentials(self.metadata_callback)
composite_creds = grpc.composite_channel_credentials(creds, auth_creds)
channel = grpc.aio.secure_channel(
f"{self.endpoint}:{self.port}", composite_creds
) )
self.rpc = lnrpc.LightningStub(channel)
def metadata_callback(self, _, callback):
callback([("macaroon", self.macaroon)], None)
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
try: try:
resp = self.rpc._ln_stub.ChannelBalance(ln.ChannelBalanceRequest()) resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest())
except Exception as exc: except Exception as exc:
return StatusResponse(str(exc), 0) return StatusResponse(str(exc), 0)
@ -127,7 +134,7 @@ class LndWallet(Wallet):
try: try:
req = ln.Invoice(**params) req = ln.Invoice(**params)
resp = self.rpc._ln_stub.AddInvoice(req) resp = await self.rpc.AddInvoice(req)
except Exception as exc: except Exception as exc:
error_message = str(exc) error_message = str(exc)
return InvoiceResponse(False, None, None, error_message) return InvoiceResponse(False, None, None, error_message)
@ -137,7 +144,9 @@ class LndWallet(Wallet):
return InvoiceResponse(True, checking_id, payment_request, None) return InvoiceResponse(True, checking_id, payment_request, None)
async def pay_invoice(self, bolt11: str) -> PaymentResponse: async def pay_invoice(self, bolt11: str) -> PaymentResponse:
resp = self.rpc.send_payment(payment_request=bolt11) resp = await self.rpc.SendPayment(
lnrpc.SendPaymentRequest(payment_request=bolt11)
)
if resp.payment_error: if resp.payment_error:
return PaymentResponse(False, "", 0, None, resp.payment_error) return PaymentResponse(False, "", 0, None, resp.payment_error)
@ -158,7 +167,7 @@ class LndWallet(Wallet):
# that use different checking_id formats # that use different checking_id formats
return PaymentStatus(None) return PaymentStatus(None)
resp = self.rpc.lookup_invoice(r_hash.hex()) resp = await self.rpc.LookupInvoice(ln.PaymentHash(r_hash=r_hash))
if resp.settled: if resp.settled:
return PaymentStatus(True) return PaymentStatus(True)
@ -168,29 +177,15 @@ class LndWallet(Wallet):
return PaymentStatus(True) return PaymentStatus(True)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
async with purerpc.secure_channel( request = ln.InvoiceSubscription()
self.endpoint, self.port, get_ssl_context(self.cert_path) try:
) as channel: async for i in self.rpc.SubscribeInvoices(request):
client = purerpc.Client("lnrpc.Lightning", channel) if not i.settled:
subscribe_invoices = client.get_method_stub(
"SubscribeInvoices",
purerpc.RPCSignature(
purerpc.Cardinality.UNARY_STREAM, ln.InvoiceSubscription, ln.Invoice
),
)
if self.macaroon_path.split(".")[-1] == "macaroon":
macaroon = load_macaroon(self.macaroon_path)
else:
macaroon = self.macaroon_path
async for inv in subscribe_invoices(
ln.InvoiceSubscription(), metadata=[("macaroon", macaroon)]
):
if not inv.settled:
continue continue
checking_id = stringify_checking_id(inv.r_hash) checking_id = stringify_checking_id(i.r_hash)
yield checking_id yield checking_id
except error:
print(error)
print("lost connection to lnd InvoiceSubscription, please restart lnbits.") print("lost connection to lnd InvoiceSubscription, please restart lnbits.")