make lndgrpc work using the purerpc library and a manually-declared method.

This commit is contained in:
fiatjaf 2020-10-04 00:22:37 -03:00
parent 9994e61615
commit f5b8ed8fc6
6 changed files with 94 additions and 27 deletions

View file

@ -34,7 +34,7 @@ You will need to copy `.env.example` to `.env`, then set variables there.
![Files](https://i.imgur.com/ri2zOe8.png)
You might also need to install additional packages, depending on the [backend wallet](../guide/wallets.md) you use.
E.g. when you want to use LND you have to `pipenv run pip install lndgrpc`.
E.g. when you want to use LND you have to `pipenv run pip install lndgrpc` and `pipenv run pip install pureprc`.
Take a look at [Polar][polar] for an excellent way of spinning up a Lightning Network dev environment.

View file

@ -32,4 +32,5 @@ E.g. when you want to use LND you have to run:
```sh
./venv/bin/pip install lndgrpc
./venv/bin/pip install purerpc
```

View file

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

View file

@ -4,6 +4,12 @@ try:
except ImportError: # pragma: nocover
lndgrpc = None
try:
import purerpc # type: ignore
except ImportError: # pragma: nocover
purerpc = None
import trio # type: ignore
import binascii
import base64
import hashlib
@ -34,33 +40,31 @@ class LndWallet(Wallet):
if lndgrpc is None: # pragma: nocover
raise ImportError("The `lndgrpc` library must be installed to use `LndWallet`.")
if purerpc is None:
import warnings
warnings.warn("To enable invoices subscription on `LndWallet` the `purerpc` library must be nistalled.")
endpoint = getenv("LND_GRPC_ENDPOINT")
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
port = getenv("LND_GRPC_PORT")
cert = getenv("LND_GRPC_CERT") or getenv("LND_CERT")
auth_admin = getenv("LND_GRPC_ADMIN_MACAROON") or getenv("LND_ADMIN_MACAROON")
auth_invoices = getenv("LND_GRPC_INVOICE_MACAROON") or getenv("LND_INVOICE_MACAROON")
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
self.port = int(getenv("LND_GRPC_PORT"))
self.cert_path = getenv("LND_GRPC_CERT") or getenv("LND_CERT")
self.auth_admin = getenv("LND_GRPC_ADMIN_MACAROON") or getenv("LND_ADMIN_MACAROON")
self.auth_invoices = getenv("LND_GRPC_INVOICE_MACAROON") or getenv("LND_INVOICE_MACAROON")
network = getenv("LND_GRPC_NETWORK", "mainnet")
self.admin_rpc = lndgrpc.LNDClient(
endpoint + ":" + port,
cert_filepath=cert,
f"{self.endpoint}:{self.port}",
cert_filepath=self.cert_path,
network=network,
macaroon_filepath=auth_admin,
macaroon_filepath=self.auth_admin,
)
self.invoices_rpc = lndgrpc.LNDClient(
endpoint + ":" + port,
cert_filepath=cert,
f"{self.endpoint}:{self.port}",
cert_filepath=self.cert_path,
network=network,
macaroon_filepath=auth_invoices,
)
self.async_rpc = lndgrpc.AsyncLNDClient(
endpoint + ":" + port,
cert_filepath=cert,
network=network,
macaroon_filepath=auth_invoices,
macaroon_filepath=self.auth_invoices,
)
def create_invoice(
@ -114,9 +118,71 @@ class LndWallet(Wallet):
return PaymentStatus(True)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
async for inv in self.async_rpc._ln_stub.SubscribeInvoices(ln.InvoiceSubscription()):
if not purerpc:
trio.sleep(5)
yield ""
async with purerpc.secure_channel(
self.endpoint,
self.port,
get_ssl_context(self.cert_path),
) as channel:
client = purerpc.Client("lnrpc.Lightning", channel)
subscribe_invoices = client.get_method_stub(
"SubscribeInvoices",
purerpc.RPCSignature(
purerpc.Cardinality.UNARY_STREAM,
ln.InvoiceSubscription,
ln.Invoice,
),
)
macaroon = load_macaroon(self.auth_admin)
async for inv in subscribe_invoices(
ln.InvoiceSubscription(),
metadata=[("macaroon", macaroon)],
):
if not inv.settled:
continue
checking_id = stringify_checking_id(inv.r_hash)
yield checking_id
def get_ssl_context(cert_path: str):
import ssl
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
context.options |= ssl.OP_NO_TLSv1
context.options |= ssl.OP_NO_TLSv1_1
context.options |= ssl.OP_NO_COMPRESSION
context.set_ciphers(
":".join(
[
"ECDHE+AESGCM",
"ECDHE+CHACHA20",
"DHE+AESGCM",
"DHE+CHACHA20",
"ECDH+AESGCM",
"DH+AESGCM",
"ECDH+AES",
"DH+AES",
"RSA+AESGCM",
"RSA+AES",
"!aNULL",
"!eNULL",
"!MD5",
"!DSS",
]
)
)
context.load_verify_locations(capath=cert_path)
return context
def load_macaroon(macaroon_path: str):
with open(macaroon_path, "rb") as f:
macaroon_bytes = f.read()
return macaroon_bytes.hex()

View file

@ -95,6 +95,6 @@ class LNPayWallet(Wallet):
)
data = r.json()
if data["settled"]:
self.send.send(lntx_id)
await self.send.send(lntx_id)
return "", HTTPStatus.NO_CONTENT

View file

@ -97,5 +97,5 @@ class OpenNodeWallet(Wallet):
print("invalid webhook, not from opennode")
return "", HTTPStatus.NO_CONTENT
self.send.send(charge_id)
await self.send.send(charge_id)
return "", HTTPStatus.NO_CONTENT