mirror of
https://github.com/lnbits/lnbits.git
synced 2025-05-23 18:00:05 +02:00
Mega-merge 4: Reenable LndWallet gRPC and use TrackPaymentV2 (#745)
* readd lndgrpc * debug logging * Use TrackPaymentV2 * /v2/router/track * lnd_router_grpc * flag for blocking check * error handling * fix name * regtest lndgrpc * new test pipeline * fix env * check for description hash * remove unnecessary asserts for clarity * assume that description_hash is a hash already * no lock * description hashing in backend * restore bolt11.py * /api/v1/payments with hex of description * comment * refactor wallets * forgot eclair * fix lnpay * bytes directly * make format * mypy check * make format * remove old code * WIP status check * LND GRPC docs * restore cln to main * fix regtest * import * remove unused import * format * do not expect ok * check ok * delete comments
This commit is contained in:
parent
1f139884fe
commit
4fc0a25d41
42
.github/workflows/regtest.yml
vendored
42
.github/workflows/regtest.yml
vendored
@ -40,7 +40,47 @@ jobs:
|
|||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
file: ./coverage.xml
|
file: ./coverage.xml
|
||||||
|
LndWallet:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: [3.8]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- uses: abatilo/actions-poetry@v2.1.3
|
||||||
|
- name: Setup Regtest
|
||||||
|
run: |
|
||||||
|
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
||||||
|
cd docker
|
||||||
|
chmod +x ./tests
|
||||||
|
./tests
|
||||||
|
sudo chmod -R a+rwx .
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
poetry install
|
||||||
|
poetry add grpcio protobuf
|
||||||
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
PYTHONUNBUFFERED: 1
|
||||||
|
PORT: 5123
|
||||||
|
LNBITS_DATA_FOLDER: ./data
|
||||||
|
LNBITS_BACKEND_WALLET_CLASS: LndWallet
|
||||||
|
LND_GRPC_ENDPOINT: localhost
|
||||||
|
LND_GRPC_PORT: 10009
|
||||||
|
LND_GRPC_CERT: docker/data/lnd-1/tls.cert
|
||||||
|
LND_GRPC_MACAROON: docker/data/lnd-1/data/chain/bitcoin/regtest/admin.macaroon
|
||||||
|
run: |
|
||||||
|
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
|
||||||
|
make test-real-wallet
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
file: ./coverage.xml
|
||||||
CoreLightningWallet:
|
CoreLightningWallet:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -37,6 +37,22 @@ or
|
|||||||
|
|
||||||
- `LND_REST_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
|
- `LND_REST_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
|
||||||
|
|
||||||
|
### LND (gRPC)
|
||||||
|
|
||||||
|
Using this wallet requires the installation of the `grpcio` and `protobuf` Python packages.
|
||||||
|
|
||||||
|
- `LNBITS_BACKEND_WALLET_CLASS`: **LndWallet**
|
||||||
|
- `LND_GRPC_ENDPOINT`: ip_address
|
||||||
|
- `LND_GRPC_PORT`: port
|
||||||
|
- `LND_GRPC_CERT`: /file/path/tls.cert
|
||||||
|
- `LND_GRPC_MACAROON`: /file/path/admin.macaroon or Bech64/Hex
|
||||||
|
|
||||||
|
You can also use an AES-encrypted macaroon (more info) instead by using
|
||||||
|
|
||||||
|
- `LND_GRPC_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
|
||||||
|
|
||||||
|
To encrypt your macaroon, run `./venv/bin/python lnbits/wallets/macaroon/macaroon.py`.
|
||||||
|
|
||||||
### LNbits
|
### LNbits
|
||||||
|
|
||||||
- `LNBITS_BACKEND_WALLET_CLASS`: **LNbitsWallet**
|
- `LNBITS_BACKEND_WALLET_CLASS`: **LNbitsWallet**
|
||||||
|
@ -4,6 +4,8 @@ from typing import Any, Dict, List, Optional
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits import bolt11
|
from lnbits import bolt11
|
||||||
from lnbits.db import COCKROACH, POSTGRES, Connection
|
from lnbits.db import COCKROACH, POSTGRES, Connection
|
||||||
from lnbits.settings import DEFAULT_WALLET_NAME, LNBITS_ADMIN_USERS
|
from lnbits.settings import DEFAULT_WALLET_NAME, LNBITS_ADMIN_USERS
|
||||||
@ -334,7 +336,7 @@ async def delete_expired_invoices(
|
|||||||
expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
|
expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
|
||||||
if expiration_date > datetime.datetime.utcnow():
|
if expiration_date > datetime.datetime.utcnow():
|
||||||
continue
|
continue
|
||||||
|
logger.debug(f"Deleting expired invoice: {invoice.payment_hash}")
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
DELETE FROM apipayments
|
DELETE FROM apipayments
|
||||||
|
@ -141,19 +141,25 @@ class Payment(BaseModel):
|
|||||||
if self.is_uncheckable:
|
if self.is_uncheckable:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Checking {'outgoing' if self.is_out else 'incoming'} pending payment {self.checking_id}"
|
||||||
|
)
|
||||||
|
|
||||||
if self.is_out:
|
if self.is_out:
|
||||||
status = await WALLET.get_payment_status(self.checking_id)
|
status = await WALLET.get_payment_status(self.checking_id)
|
||||||
else:
|
else:
|
||||||
status = await WALLET.get_invoice_status(self.checking_id)
|
status = await WALLET.get_invoice_status(self.checking_id)
|
||||||
|
|
||||||
|
logger.debug(f"Status: {status}")
|
||||||
|
|
||||||
if self.is_out and status.failed:
|
if self.is_out and status.failed:
|
||||||
logger.info(
|
logger.info(
|
||||||
f" - deleting outgoing failed payment {self.checking_id}: {status}"
|
f"Deleting outgoing failed payment {self.checking_id}: {status}"
|
||||||
)
|
)
|
||||||
await self.delete()
|
await self.delete()
|
||||||
elif not status.pending:
|
elif not status.pending:
|
||||||
logger.info(
|
logger.info(
|
||||||
f" - marking '{'in' if self.is_in else 'out'}' {self.checking_id} as not pending anymore: {status}"
|
f"Marking '{'in' if self.is_in else 'out'}' {self.checking_id} as not pending anymore: {status}"
|
||||||
)
|
)
|
||||||
await self.set_pending(status.pending)
|
await self.set_pending(status.pending)
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ async def pay_invoice(
|
|||||||
payment_request, fee_reserve_msat
|
payment_request, fee_reserve_msat
|
||||||
)
|
)
|
||||||
logger.debug(f"backend: pay_invoice finished {temp_id}")
|
logger.debug(f"backend: pay_invoice finished {temp_id}")
|
||||||
if payment.checking_id:
|
if payment.ok and payment.checking_id:
|
||||||
logger.debug(f"creating final payment {payment.checking_id}")
|
logger.debug(f"creating final payment {payment.checking_id}")
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await create_payment(
|
await create_payment(
|
||||||
@ -196,7 +196,7 @@ async def pay_invoice(
|
|||||||
logger.debug(f"deleting temporary payment {temp_id}")
|
logger.debug(f"deleting temporary payment {temp_id}")
|
||||||
await delete_payment(temp_id, conn=conn)
|
await delete_payment(temp_id, conn=conn)
|
||||||
else:
|
else:
|
||||||
logger.debug(f"backend payment failed, no checking_id {temp_id}")
|
logger.debug(f"backend payment failed")
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
logger.debug(f"deleting temporary payment {temp_id}")
|
logger.debug(f"deleting temporary payment {temp_id}")
|
||||||
await delete_payment(temp_id, conn=conn)
|
await delete_payment(temp_id, conn=conn)
|
||||||
@ -337,13 +337,16 @@ async def perform_lnurlauth(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def check_invoice_status(
|
async def check_transaction_status(
|
||||||
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
|
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
|
||||||
) -> PaymentStatus:
|
) -> PaymentStatus:
|
||||||
payment = await get_wallet_payment(wallet_id, payment_hash, conn=conn)
|
payment = await get_wallet_payment(wallet_id, payment_hash, conn=conn)
|
||||||
if not payment:
|
if not payment:
|
||||||
return PaymentStatus(None)
|
return PaymentStatus(None)
|
||||||
status = await WALLET.get_invoice_status(payment.checking_id)
|
if payment.is_out:
|
||||||
|
status = await WALLET.get_payment_status(payment.checking_id)
|
||||||
|
else:
|
||||||
|
status = await WALLET.get_invoice_status(payment.checking_id)
|
||||||
if not payment.pending:
|
if not payment.pending:
|
||||||
return status
|
return status
|
||||||
if payment.is_out and status.failed:
|
if payment.is_out and status.failed:
|
||||||
|
@ -48,7 +48,7 @@ from ..crud import (
|
|||||||
from ..services import (
|
from ..services import (
|
||||||
InvoiceFailure,
|
InvoiceFailure,
|
||||||
PaymentFailure,
|
PaymentFailure,
|
||||||
check_invoice_status,
|
check_transaction_status,
|
||||||
create_invoice,
|
create_invoice,
|
||||||
pay_invoice,
|
pay_invoice,
|
||||||
perform_lnurlauth,
|
perform_lnurlauth,
|
||||||
@ -123,7 +123,7 @@ async def api_payments(
|
|||||||
offset=offset,
|
offset=offset,
|
||||||
)
|
)
|
||||||
for payment in pendingPayments:
|
for payment in pendingPayments:
|
||||||
await check_invoice_status(
|
await check_transaction_status(
|
||||||
wallet_id=payment.wallet_id, payment_hash=payment.payment_hash
|
wallet_id=payment.wallet_id, payment_hash=payment.payment_hash
|
||||||
)
|
)
|
||||||
return await get_payments(
|
return await get_payments(
|
||||||
@ -407,7 +407,7 @@ async def api_payment(payment_hash, X_Api_Key: Optional[str] = Header(None)):
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist."
|
||||||
)
|
)
|
||||||
await check_invoice_status(payment.wallet_id, payment_hash)
|
await check_transaction_status(payment.wallet_id, payment_hash)
|
||||||
payment = await get_standalone_payment(
|
payment = await get_standalone_payment(
|
||||||
payment_hash, wallet_id=wallet.id if wallet else None
|
payment_hash, wallet_id=wallet.id if wallet else None
|
||||||
)
|
)
|
||||||
|
@ -148,7 +148,9 @@ async def wallet(
|
|||||||
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
|
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"Access wallet {wallet_name}{'of user '+ user.id if user else ''}")
|
logger.debug(
|
||||||
|
f"Access {'user '+ user.id + ' ' if user else ''} {'wallet ' + wallet_name if wallet_name else ''}"
|
||||||
|
)
|
||||||
userwallet = user.get_wallet(wallet_id) # type: ignore
|
userwallet = user.get_wallet(wallet_id) # type: ignore
|
||||||
if not userwallet:
|
if not userwallet:
|
||||||
return template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
|
@ -6,7 +6,7 @@ from fastapi.params import Depends, Query
|
|||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.core.services import check_invoice_status, create_invoice
|
from lnbits.core.services import check_transaction_status, create_invoice
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||||
from lnbits.extensions.lnaddress.models import CreateAddress, CreateDomain
|
from lnbits.extensions.lnaddress.models import CreateAddress, CreateDomain
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ async def api_address_send_address(payment_hash):
|
|||||||
address = await get_address(payment_hash)
|
address = await get_address(payment_hash)
|
||||||
domain = await get_domain(address.domain)
|
domain = await get_domain(address.domain)
|
||||||
try:
|
try:
|
||||||
status = await check_invoice_status(domain.wallet, payment_hash)
|
status = await check_transaction_status(domain.wallet, payment_hash)
|
||||||
is_paid = not status.pending
|
is_paid = not status.pending
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"paid": False, "error": str(e)}
|
return {"paid": False, "error": str(e)}
|
||||||
|
@ -4,7 +4,7 @@ from fastapi import Depends, Query
|
|||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core.crud import get_user, get_wallet
|
from lnbits.core.crud import get_user, get_wallet
|
||||||
from lnbits.core.services import check_invoice_status, create_invoice
|
from lnbits.core.services import check_transaction_status, create_invoice
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||||
|
|
||||||
from . import paywall_ext
|
from . import paywall_ext
|
||||||
@ -87,7 +87,7 @@ async def api_paywal_check_invoice(
|
|||||||
status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist."
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
status = await check_invoice_status(paywall.wallet, payment_hash)
|
status = await check_transaction_status(paywall.wallet, payment_hash)
|
||||||
is_paid = not status.pending
|
is_paid = not status.pending
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"paid": False}
|
return {"paid": False}
|
||||||
|
@ -5,7 +5,7 @@ from fastapi.params import Depends
|
|||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.core.services import check_invoice_status, create_invoice
|
from lnbits.core.services import check_transaction_status, create_invoice
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||||
from lnbits.extensions.subdomains.models import CreateDomain, CreateSubdomain
|
from lnbits.extensions.subdomains.models import CreateDomain, CreateSubdomain
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ async def api_subdomain_make_subdomain(domain_id, data: CreateSubdomain):
|
|||||||
async def api_subdomain_send_subdomain(payment_hash):
|
async def api_subdomain_send_subdomain(payment_hash):
|
||||||
subdomain = await get_subdomain(payment_hash)
|
subdomain = await get_subdomain(payment_hash)
|
||||||
try:
|
try:
|
||||||
status = await check_invoice_status(subdomain.wallet, payment_hash)
|
status = await check_transaction_status(subdomain.wallet, payment_hash)
|
||||||
is_paid = not status.pending
|
is_paid = not status.pending
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"paid": False}
|
return {"paid": False}
|
||||||
|
@ -6,6 +6,7 @@ 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
|
||||||
|
from .lndgrpc import LndWallet
|
||||||
from .lndrest import LndRestWallet
|
from .lndrest import LndRestWallet
|
||||||
from .lnpay import LNPayWallet
|
from .lnpay import LNPayWallet
|
||||||
from .lntxbot import LntxbotWallet
|
from .lntxbot import LntxbotWallet
|
||||||
|
File diff suppressed because one or more lines are too long
665
lnbits/wallets/lnd_grpc_files/router_pb2.py
Normal file
665
lnbits/wallets/lnd_grpc_files/router_pb2.py
Normal file
File diff suppressed because one or more lines are too long
871
lnbits/wallets/lnd_grpc_files/router_pb2_grpc.py
Normal file
871
lnbits/wallets/lnd_grpc_files/router_pb2_grpc.py
Normal file
@ -0,0 +1,871 @@
|
|||||||
|
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||||
|
"""Client and server classes corresponding to protobuf-defined services."""
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as lightning__pb2
|
||||||
|
import lnbits.wallets.lnd_grpc_files.router_pb2 as router__pb2
|
||||||
|
|
||||||
|
|
||||||
|
class RouterStub(object):
|
||||||
|
"""Router is a service that offers advanced interaction with the router
|
||||||
|
subsystem of the daemon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, channel):
|
||||||
|
"""Constructor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel: A grpc.Channel.
|
||||||
|
"""
|
||||||
|
self.SendPaymentV2 = channel.unary_stream(
|
||||||
|
"/routerrpc.Router/SendPaymentV2",
|
||||||
|
request_serializer=router__pb2.SendPaymentRequest.SerializeToString,
|
||||||
|
response_deserializer=lightning__pb2.Payment.FromString,
|
||||||
|
)
|
||||||
|
self.TrackPaymentV2 = channel.unary_stream(
|
||||||
|
"/routerrpc.Router/TrackPaymentV2",
|
||||||
|
request_serializer=router__pb2.TrackPaymentRequest.SerializeToString,
|
||||||
|
response_deserializer=lightning__pb2.Payment.FromString,
|
||||||
|
)
|
||||||
|
self.EstimateRouteFee = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/EstimateRouteFee",
|
||||||
|
request_serializer=router__pb2.RouteFeeRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.RouteFeeResponse.FromString,
|
||||||
|
)
|
||||||
|
self.SendToRoute = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/SendToRoute",
|
||||||
|
request_serializer=router__pb2.SendToRouteRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.SendToRouteResponse.FromString,
|
||||||
|
)
|
||||||
|
self.SendToRouteV2 = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/SendToRouteV2",
|
||||||
|
request_serializer=router__pb2.SendToRouteRequest.SerializeToString,
|
||||||
|
response_deserializer=lightning__pb2.HTLCAttempt.FromString,
|
||||||
|
)
|
||||||
|
self.ResetMissionControl = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/ResetMissionControl",
|
||||||
|
request_serializer=router__pb2.ResetMissionControlRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.ResetMissionControlResponse.FromString,
|
||||||
|
)
|
||||||
|
self.QueryMissionControl = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/QueryMissionControl",
|
||||||
|
request_serializer=router__pb2.QueryMissionControlRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.QueryMissionControlResponse.FromString,
|
||||||
|
)
|
||||||
|
self.XImportMissionControl = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/XImportMissionControl",
|
||||||
|
request_serializer=router__pb2.XImportMissionControlRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.XImportMissionControlResponse.FromString,
|
||||||
|
)
|
||||||
|
self.GetMissionControlConfig = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/GetMissionControlConfig",
|
||||||
|
request_serializer=router__pb2.GetMissionControlConfigRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.GetMissionControlConfigResponse.FromString,
|
||||||
|
)
|
||||||
|
self.SetMissionControlConfig = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/SetMissionControlConfig",
|
||||||
|
request_serializer=router__pb2.SetMissionControlConfigRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.SetMissionControlConfigResponse.FromString,
|
||||||
|
)
|
||||||
|
self.QueryProbability = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/QueryProbability",
|
||||||
|
request_serializer=router__pb2.QueryProbabilityRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.QueryProbabilityResponse.FromString,
|
||||||
|
)
|
||||||
|
self.BuildRoute = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/BuildRoute",
|
||||||
|
request_serializer=router__pb2.BuildRouteRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.BuildRouteResponse.FromString,
|
||||||
|
)
|
||||||
|
self.SubscribeHtlcEvents = channel.unary_stream(
|
||||||
|
"/routerrpc.Router/SubscribeHtlcEvents",
|
||||||
|
request_serializer=router__pb2.SubscribeHtlcEventsRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.HtlcEvent.FromString,
|
||||||
|
)
|
||||||
|
self.SendPayment = channel.unary_stream(
|
||||||
|
"/routerrpc.Router/SendPayment",
|
||||||
|
request_serializer=router__pb2.SendPaymentRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.PaymentStatus.FromString,
|
||||||
|
)
|
||||||
|
self.TrackPayment = channel.unary_stream(
|
||||||
|
"/routerrpc.Router/TrackPayment",
|
||||||
|
request_serializer=router__pb2.TrackPaymentRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.PaymentStatus.FromString,
|
||||||
|
)
|
||||||
|
self.HtlcInterceptor = channel.stream_stream(
|
||||||
|
"/routerrpc.Router/HtlcInterceptor",
|
||||||
|
request_serializer=router__pb2.ForwardHtlcInterceptResponse.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.ForwardHtlcInterceptRequest.FromString,
|
||||||
|
)
|
||||||
|
self.UpdateChanStatus = channel.unary_unary(
|
||||||
|
"/routerrpc.Router/UpdateChanStatus",
|
||||||
|
request_serializer=router__pb2.UpdateChanStatusRequest.SerializeToString,
|
||||||
|
response_deserializer=router__pb2.UpdateChanStatusResponse.FromString,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RouterServicer(object):
|
||||||
|
"""Router is a service that offers advanced interaction with the router
|
||||||
|
subsystem of the daemon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def SendPaymentV2(self, request, context):
|
||||||
|
"""
|
||||||
|
SendPaymentV2 attempts to route a payment described by the passed
|
||||||
|
PaymentRequest to the final destination. The call returns a stream of
|
||||||
|
payment updates.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def TrackPaymentV2(self, request, context):
|
||||||
|
"""
|
||||||
|
TrackPaymentV2 returns an update stream for the payment identified by the
|
||||||
|
payment hash.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def EstimateRouteFee(self, request, context):
|
||||||
|
"""
|
||||||
|
EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||||
|
may cost to send an HTLC to the target end destination.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def SendToRoute(self, request, context):
|
||||||
|
"""
|
||||||
|
Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via
|
||||||
|
the specified route. This method differs from SendPayment in that it
|
||||||
|
allows users to specify a full route manually. This can be used for
|
||||||
|
things like rebalancing, and atomic swaps. It differs from the newer
|
||||||
|
SendToRouteV2 in that it doesn't return the full HTLC information.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def SendToRouteV2(self, request, context):
|
||||||
|
"""
|
||||||
|
SendToRouteV2 attempts to make a payment via the specified route. This
|
||||||
|
method differs from SendPayment in that it allows users to specify a full
|
||||||
|
route manually. This can be used for things like rebalancing, and atomic
|
||||||
|
swaps.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def ResetMissionControl(self, request, context):
|
||||||
|
"""
|
||||||
|
ResetMissionControl clears all mission control state and starts with a clean
|
||||||
|
slate.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def QueryMissionControl(self, request, context):
|
||||||
|
"""
|
||||||
|
QueryMissionControl exposes the internal mission control state to callers.
|
||||||
|
It is a development feature.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def XImportMissionControl(self, request, context):
|
||||||
|
"""
|
||||||
|
XImportMissionControl is an experimental API that imports the state provided
|
||||||
|
to the internal mission control's state, using all results which are more
|
||||||
|
recent than our existing values. These values will only be imported
|
||||||
|
in-memory, and will not be persisted across restarts.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def GetMissionControlConfig(self, request, context):
|
||||||
|
"""
|
||||||
|
GetMissionControlConfig returns mission control's current config.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def SetMissionControlConfig(self, request, context):
|
||||||
|
"""
|
||||||
|
SetMissionControlConfig will set mission control's config, if the config
|
||||||
|
provided is valid.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def QueryProbability(self, request, context):
|
||||||
|
"""
|
||||||
|
QueryProbability returns the current success probability estimate for a
|
||||||
|
given node pair and amount.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def BuildRoute(self, request, context):
|
||||||
|
"""
|
||||||
|
BuildRoute builds a fully specified route based on a list of hop public
|
||||||
|
keys. It retrieves the relevant channel policies from the graph in order to
|
||||||
|
calculate the correct fees and time locks.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def SubscribeHtlcEvents(self, request, context):
|
||||||
|
"""
|
||||||
|
SubscribeHtlcEvents creates a uni-directional stream from the server to
|
||||||
|
the client which delivers a stream of htlc events.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def SendPayment(self, request, context):
|
||||||
|
"""
|
||||||
|
Deprecated, use SendPaymentV2. SendPayment attempts to route a payment
|
||||||
|
described by the passed PaymentRequest to the final destination. The call
|
||||||
|
returns a stream of payment status updates.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def TrackPayment(self, request, context):
|
||||||
|
"""
|
||||||
|
Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
|
||||||
|
the payment identified by the payment hash.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def HtlcInterceptor(self, request_iterator, context):
|
||||||
|
"""*
|
||||||
|
HtlcInterceptor dispatches a bi-directional streaming RPC in which
|
||||||
|
Forwarded HTLC requests are sent to the client and the client responds with
|
||||||
|
a boolean that tells LND if this htlc should be intercepted.
|
||||||
|
In case of interception, the htlc can be either settled, cancelled or
|
||||||
|
resumed later by using the ResolveHoldForward endpoint.
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
def UpdateChanStatus(self, request, context):
|
||||||
|
"""
|
||||||
|
UpdateChanStatus attempts to manually set the state of a channel
|
||||||
|
(enabled, disabled, or auto). A manual "disable" request will cause the
|
||||||
|
channel to stay disabled until a subsequent manual request of either
|
||||||
|
"enable" or "auto".
|
||||||
|
"""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details("Method not implemented!")
|
||||||
|
raise NotImplementedError("Method not implemented!")
|
||||||
|
|
||||||
|
|
||||||
|
def add_RouterServicer_to_server(servicer, server):
|
||||||
|
rpc_method_handlers = {
|
||||||
|
"SendPaymentV2": grpc.unary_stream_rpc_method_handler(
|
||||||
|
servicer.SendPaymentV2,
|
||||||
|
request_deserializer=router__pb2.SendPaymentRequest.FromString,
|
||||||
|
response_serializer=lightning__pb2.Payment.SerializeToString,
|
||||||
|
),
|
||||||
|
"TrackPaymentV2": grpc.unary_stream_rpc_method_handler(
|
||||||
|
servicer.TrackPaymentV2,
|
||||||
|
request_deserializer=router__pb2.TrackPaymentRequest.FromString,
|
||||||
|
response_serializer=lightning__pb2.Payment.SerializeToString,
|
||||||
|
),
|
||||||
|
"EstimateRouteFee": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.EstimateRouteFee,
|
||||||
|
request_deserializer=router__pb2.RouteFeeRequest.FromString,
|
||||||
|
response_serializer=router__pb2.RouteFeeResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"SendToRoute": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.SendToRoute,
|
||||||
|
request_deserializer=router__pb2.SendToRouteRequest.FromString,
|
||||||
|
response_serializer=router__pb2.SendToRouteResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"SendToRouteV2": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.SendToRouteV2,
|
||||||
|
request_deserializer=router__pb2.SendToRouteRequest.FromString,
|
||||||
|
response_serializer=lightning__pb2.HTLCAttempt.SerializeToString,
|
||||||
|
),
|
||||||
|
"ResetMissionControl": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.ResetMissionControl,
|
||||||
|
request_deserializer=router__pb2.ResetMissionControlRequest.FromString,
|
||||||
|
response_serializer=router__pb2.ResetMissionControlResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"QueryMissionControl": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.QueryMissionControl,
|
||||||
|
request_deserializer=router__pb2.QueryMissionControlRequest.FromString,
|
||||||
|
response_serializer=router__pb2.QueryMissionControlResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"XImportMissionControl": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.XImportMissionControl,
|
||||||
|
request_deserializer=router__pb2.XImportMissionControlRequest.FromString,
|
||||||
|
response_serializer=router__pb2.XImportMissionControlResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"GetMissionControlConfig": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.GetMissionControlConfig,
|
||||||
|
request_deserializer=router__pb2.GetMissionControlConfigRequest.FromString,
|
||||||
|
response_serializer=router__pb2.GetMissionControlConfigResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"SetMissionControlConfig": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.SetMissionControlConfig,
|
||||||
|
request_deserializer=router__pb2.SetMissionControlConfigRequest.FromString,
|
||||||
|
response_serializer=router__pb2.SetMissionControlConfigResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"QueryProbability": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.QueryProbability,
|
||||||
|
request_deserializer=router__pb2.QueryProbabilityRequest.FromString,
|
||||||
|
response_serializer=router__pb2.QueryProbabilityResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"BuildRoute": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.BuildRoute,
|
||||||
|
request_deserializer=router__pb2.BuildRouteRequest.FromString,
|
||||||
|
response_serializer=router__pb2.BuildRouteResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
"SubscribeHtlcEvents": grpc.unary_stream_rpc_method_handler(
|
||||||
|
servicer.SubscribeHtlcEvents,
|
||||||
|
request_deserializer=router__pb2.SubscribeHtlcEventsRequest.FromString,
|
||||||
|
response_serializer=router__pb2.HtlcEvent.SerializeToString,
|
||||||
|
),
|
||||||
|
"SendPayment": grpc.unary_stream_rpc_method_handler(
|
||||||
|
servicer.SendPayment,
|
||||||
|
request_deserializer=router__pb2.SendPaymentRequest.FromString,
|
||||||
|
response_serializer=router__pb2.PaymentStatus.SerializeToString,
|
||||||
|
),
|
||||||
|
"TrackPayment": grpc.unary_stream_rpc_method_handler(
|
||||||
|
servicer.TrackPayment,
|
||||||
|
request_deserializer=router__pb2.TrackPaymentRequest.FromString,
|
||||||
|
response_serializer=router__pb2.PaymentStatus.SerializeToString,
|
||||||
|
),
|
||||||
|
"HtlcInterceptor": grpc.stream_stream_rpc_method_handler(
|
||||||
|
servicer.HtlcInterceptor,
|
||||||
|
request_deserializer=router__pb2.ForwardHtlcInterceptResponse.FromString,
|
||||||
|
response_serializer=router__pb2.ForwardHtlcInterceptRequest.SerializeToString,
|
||||||
|
),
|
||||||
|
"UpdateChanStatus": grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.UpdateChanStatus,
|
||||||
|
request_deserializer=router__pb2.UpdateChanStatusRequest.FromString,
|
||||||
|
response_serializer=router__pb2.UpdateChanStatusResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
generic_handler = grpc.method_handlers_generic_handler(
|
||||||
|
"routerrpc.Router", rpc_method_handlers
|
||||||
|
)
|
||||||
|
server.add_generic_rpc_handlers((generic_handler,))
|
||||||
|
|
||||||
|
|
||||||
|
# This class is part of an EXPERIMENTAL API.
|
||||||
|
class Router(object):
|
||||||
|
"""Router is a service that offers advanced interaction with the router
|
||||||
|
subsystem of the daemon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def SendPaymentV2(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_stream(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/SendPaymentV2",
|
||||||
|
router__pb2.SendPaymentRequest.SerializeToString,
|
||||||
|
lightning__pb2.Payment.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def TrackPaymentV2(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_stream(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/TrackPaymentV2",
|
||||||
|
router__pb2.TrackPaymentRequest.SerializeToString,
|
||||||
|
lightning__pb2.Payment.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def EstimateRouteFee(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/EstimateRouteFee",
|
||||||
|
router__pb2.RouteFeeRequest.SerializeToString,
|
||||||
|
router__pb2.RouteFeeResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def SendToRoute(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/SendToRoute",
|
||||||
|
router__pb2.SendToRouteRequest.SerializeToString,
|
||||||
|
router__pb2.SendToRouteResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def SendToRouteV2(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/SendToRouteV2",
|
||||||
|
router__pb2.SendToRouteRequest.SerializeToString,
|
||||||
|
lightning__pb2.HTLCAttempt.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ResetMissionControl(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/ResetMissionControl",
|
||||||
|
router__pb2.ResetMissionControlRequest.SerializeToString,
|
||||||
|
router__pb2.ResetMissionControlResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def QueryMissionControl(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/QueryMissionControl",
|
||||||
|
router__pb2.QueryMissionControlRequest.SerializeToString,
|
||||||
|
router__pb2.QueryMissionControlResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def XImportMissionControl(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/XImportMissionControl",
|
||||||
|
router__pb2.XImportMissionControlRequest.SerializeToString,
|
||||||
|
router__pb2.XImportMissionControlResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def GetMissionControlConfig(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/GetMissionControlConfig",
|
||||||
|
router__pb2.GetMissionControlConfigRequest.SerializeToString,
|
||||||
|
router__pb2.GetMissionControlConfigResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def SetMissionControlConfig(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/SetMissionControlConfig",
|
||||||
|
router__pb2.SetMissionControlConfigRequest.SerializeToString,
|
||||||
|
router__pb2.SetMissionControlConfigResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def QueryProbability(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/QueryProbability",
|
||||||
|
router__pb2.QueryProbabilityRequest.SerializeToString,
|
||||||
|
router__pb2.QueryProbabilityResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def BuildRoute(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/BuildRoute",
|
||||||
|
router__pb2.BuildRouteRequest.SerializeToString,
|
||||||
|
router__pb2.BuildRouteResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def SubscribeHtlcEvents(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_stream(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/SubscribeHtlcEvents",
|
||||||
|
router__pb2.SubscribeHtlcEventsRequest.SerializeToString,
|
||||||
|
router__pb2.HtlcEvent.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def SendPayment(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_stream(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/SendPayment",
|
||||||
|
router__pb2.SendPaymentRequest.SerializeToString,
|
||||||
|
router__pb2.PaymentStatus.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def TrackPayment(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_stream(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/TrackPayment",
|
||||||
|
router__pb2.TrackPaymentRequest.SerializeToString,
|
||||||
|
router__pb2.PaymentStatus.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def HtlcInterceptor(
|
||||||
|
request_iterator,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.stream_stream(
|
||||||
|
request_iterator,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/HtlcInterceptor",
|
||||||
|
router__pb2.ForwardHtlcInterceptResponse.SerializeToString,
|
||||||
|
router__pb2.ForwardHtlcInterceptRequest.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def UpdateChanStatus(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None,
|
||||||
|
):
|
||||||
|
return grpc.experimental.unary_unary(
|
||||||
|
request,
|
||||||
|
target,
|
||||||
|
"/routerrpc.Router/UpdateChanStatus",
|
||||||
|
router__pb2.UpdateChanStatusRequest.SerializeToString,
|
||||||
|
router__pb2.UpdateChanStatusResponse.FromString,
|
||||||
|
options,
|
||||||
|
channel_credentials,
|
||||||
|
insecure,
|
||||||
|
call_credentials,
|
||||||
|
compression,
|
||||||
|
wait_for_ready,
|
||||||
|
timeout,
|
||||||
|
metadata,
|
||||||
|
)
|
@ -2,10 +2,11 @@ imports_ok = True
|
|||||||
try:
|
try:
|
||||||
import grpc
|
import grpc
|
||||||
from google import protobuf
|
from google import protobuf
|
||||||
|
from grpc import RpcError
|
||||||
except ImportError: # pragma: nocover
|
except ImportError: # pragma: nocover
|
||||||
imports_ok = False
|
imports_ok = False
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
import hashlib
|
import hashlib
|
||||||
@ -19,6 +20,8 @@ from .macaroon import AESCipher, load_macaroon
|
|||||||
if imports_ok:
|
if imports_ok:
|
||||||
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as ln
|
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as ln
|
||||||
import lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc as lnrpc
|
import lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc as lnrpc
|
||||||
|
import lnbits.wallets.lnd_grpc_files.router_pb2 as router
|
||||||
|
import lnbits.wallets.lnd_grpc_files.router_pb2_grpc as routerrpc
|
||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
InvoiceResponse,
|
InvoiceResponse,
|
||||||
@ -111,6 +114,7 @@ class LndWallet(Wallet):
|
|||||||
f"{self.endpoint}:{self.port}", composite_creds
|
f"{self.endpoint}:{self.port}", composite_creds
|
||||||
)
|
)
|
||||||
self.rpc = lnrpc.LightningStub(channel)
|
self.rpc = lnrpc.LightningStub(channel)
|
||||||
|
self.routerpc = routerrpc.RouterStub(channel)
|
||||||
|
|
||||||
def metadata_callback(self, _, callback):
|
def metadata_callback(self, _, callback):
|
||||||
callback([("macaroon", self.macaroon)], None)
|
callback([("macaroon", self.macaroon)], None)
|
||||||
@ -118,6 +122,8 @@ class LndWallet(Wallet):
|
|||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
try:
|
try:
|
||||||
resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest())
|
resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest())
|
||||||
|
except RpcError as exc:
|
||||||
|
return StatusResponse(str(exc._details), 0)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return StatusResponse(str(exc), 0)
|
return StatusResponse(str(exc), 0)
|
||||||
|
|
||||||
@ -132,9 +138,10 @@ class LndWallet(Wallet):
|
|||||||
params: Dict = {"value": amount, "expiry": 600, "private": True}
|
params: Dict = {"value": amount, "expiry": 600, "private": True}
|
||||||
|
|
||||||
if description_hash:
|
if description_hash:
|
||||||
params["description_hash"] = base64.b64encode(
|
params["description_hash"] = hashlib.sha256(
|
||||||
hashlib.sha256(description_hash).digest()
|
description_hash
|
||||||
) # as bytes directly
|
).digest() # as bytes directly
|
||||||
|
|
||||||
else:
|
else:
|
||||||
params["memo"] = memo or ""
|
params["memo"] = memo or ""
|
||||||
|
|
||||||
@ -150,18 +157,39 @@ 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, fee_limit_msat: int) -> PaymentResponse:
|
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||||
fee_limit_fixed = ln.FeeLimit(fixed=fee_limit_msat // 1000)
|
# fee_limit_fixed = ln.FeeLimit(fixed=fee_limit_msat // 1000)
|
||||||
req = ln.SendRequest(payment_request=bolt11, fee_limit=fee_limit_fixed)
|
req = router.SendPaymentRequest(
|
||||||
resp = await self.rpc.SendPaymentSync(req)
|
payment_request=bolt11,
|
||||||
|
fee_limit_msat=fee_limit_msat,
|
||||||
|
timeout_seconds=30,
|
||||||
|
no_inflight_updates=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
resp = await self.routerpc.SendPaymentV2(req).read()
|
||||||
|
except RpcError as exc:
|
||||||
|
return PaymentResponse(False, "", 0, None, exc._details)
|
||||||
|
except Exception as exc:
|
||||||
|
return PaymentResponse(False, "", 0, None, str(exc))
|
||||||
|
|
||||||
if resp.payment_error:
|
# PaymentStatus from https://github.com/lightningnetwork/lnd/blob/master/channeldb/payments.go#L178
|
||||||
return PaymentResponse(False, "", 0, None, resp.payment_error)
|
statuses = {
|
||||||
|
0: None, # NON_EXISTENT
|
||||||
|
1: None, # IN_FLIGHT
|
||||||
|
2: True, # SUCCEEDED
|
||||||
|
3: False, # FAILED
|
||||||
|
}
|
||||||
|
|
||||||
r_hash = hashlib.sha256(resp.payment_preimage).digest()
|
if resp.status in [0, 1, 3]:
|
||||||
checking_id = stringify_checking_id(r_hash)
|
fee_msat = 0
|
||||||
fee_msat = resp.payment_route.total_fees_msat
|
preimage = ""
|
||||||
preimage = resp.payment_preimage.hex()
|
checking_id = ""
|
||||||
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
|
elif resp.status == 2: # SUCCEEDED
|
||||||
|
fee_msat = resp.htlcs[-1].route.total_fees_msat
|
||||||
|
preimage = resp.payment_preimage
|
||||||
|
checking_id = resp.payment_hash
|
||||||
|
return PaymentResponse(
|
||||||
|
statuses[resp.status], checking_id, fee_msat, preimage, None
|
||||||
|
)
|
||||||
|
|
||||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||||
try:
|
try:
|
||||||
@ -180,20 +208,55 @@ class LndWallet(Wallet):
|
|||||||
return PaymentStatus(None)
|
return PaymentStatus(None)
|
||||||
|
|
||||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||||
return PaymentStatus(True)
|
"""
|
||||||
|
This routine checks the payment status using routerpc.TrackPaymentV2.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
r_hash = parse_checking_id(checking_id)
|
||||||
|
if len(r_hash) != 32:
|
||||||
|
raise binascii.Error
|
||||||
|
except binascii.Error:
|
||||||
|
# this may happen if we switch between backend wallets
|
||||||
|
# that use different checking_id formats
|
||||||
|
return PaymentStatus(None)
|
||||||
|
|
||||||
|
# for some reason our checking_ids are in base64 but the payment hashes
|
||||||
|
# returned here are in hex, lnd is weird
|
||||||
|
checking_id = checking_id.replace("_", "/")
|
||||||
|
checking_id = base64.b64decode(checking_id).hex()
|
||||||
|
|
||||||
|
resp = self.routerpc.TrackPaymentV2(
|
||||||
|
router.TrackPaymentRequest(payment_hash=r_hash)
|
||||||
|
)
|
||||||
|
|
||||||
|
# HTLCAttempt.HTLCStatus:
|
||||||
|
# https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto#L3641
|
||||||
|
statuses = {
|
||||||
|
0: None, # IN_FLIGHT
|
||||||
|
1: True, # "SUCCEEDED"
|
||||||
|
2: False, # "FAILED"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
async for payment in resp:
|
||||||
|
return PaymentStatus(statuses[payment.htlcs[-1].status])
|
||||||
|
except: # most likely the payment wasn't found
|
||||||
|
return PaymentStatus(None)
|
||||||
|
|
||||||
|
return PaymentStatus(None)
|
||||||
|
|
||||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
request = ln.InvoiceSubscription()
|
while True:
|
||||||
try:
|
request = ln.InvoiceSubscription()
|
||||||
async for i in self.rpc.SubscribeInvoices(request):
|
try:
|
||||||
if not i.settled:
|
async for i in self.rpc.SubscribeInvoices(request):
|
||||||
continue
|
if not i.settled:
|
||||||
|
continue
|
||||||
|
|
||||||
checking_id = stringify_checking_id(i.r_hash)
|
checking_id = stringify_checking_id(i.r_hash)
|
||||||
yield checking_id
|
yield checking_id
|
||||||
except error:
|
except Exception as exc:
|
||||||
logger.error(error)
|
logger.error(
|
||||||
|
f"lost connection to lnd invoices stream: '{exc}', retrying in 5 seconds"
|
||||||
logger.error(
|
)
|
||||||
"lost connection to lnd InvoiceSubscription, please restart lnbits."
|
await asyncio.sleep(5)
|
||||||
)
|
|
||||||
|
@ -142,15 +142,10 @@ class LndRestWallet(Wallet):
|
|||||||
return PaymentStatus(True)
|
return PaymentStatus(True)
|
||||||
|
|
||||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||||
async with httpx.AsyncClient(verify=self.cert) as client:
|
"""
|
||||||
r = await client.get(
|
This routine checks the payment status using routerpc.TrackPaymentV2.
|
||||||
url=f"{self.endpoint}/v1/payments",
|
"""
|
||||||
headers=self.auth,
|
url = f"{self.endpoint}/v2/router/track/{checking_id}"
|
||||||
params={"max_payments": "20", "reversed": True},
|
|
||||||
)
|
|
||||||
|
|
||||||
if r.is_error:
|
|
||||||
return PaymentStatus(None)
|
|
||||||
|
|
||||||
# check payment.status:
|
# check payment.status:
|
||||||
# https://api.lightning.community/rest/index.html?python#peersynctype
|
# https://api.lightning.community/rest/index.html?python#peersynctype
|
||||||
@ -161,14 +156,27 @@ class LndRestWallet(Wallet):
|
|||||||
"FAILED": False,
|
"FAILED": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# for some reason our checking_ids are in base64 but the payment hashes
|
async with httpx.AsyncClient(
|
||||||
# returned here are in hex, lnd is weird
|
timeout=None, headers=self.auth, verify=self.cert
|
||||||
checking_id = checking_id.replace("_", "/")
|
) as client:
|
||||||
checking_id = base64.b64decode(checking_id).hex()
|
async with client.stream("GET", url) as r:
|
||||||
|
async for l in r.aiter_lines():
|
||||||
for p in r.json()["payments"]:
|
try:
|
||||||
if p["payment_hash"] == checking_id:
|
line = json.loads(l)
|
||||||
return PaymentStatus(statuses[p["status"]])
|
if line.get("error"):
|
||||||
|
logger.error(
|
||||||
|
line["error"]["message"]
|
||||||
|
if "message" in line["error"]
|
||||||
|
else line["error"]
|
||||||
|
)
|
||||||
|
return PaymentStatus(None)
|
||||||
|
payment = line.get("result")
|
||||||
|
if payment is not None and payment.get("status"):
|
||||||
|
return PaymentStatus(statuses[payment["status"]])
|
||||||
|
else:
|
||||||
|
return PaymentStatus(None)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
return PaymentStatus(None)
|
return PaymentStatus(None)
|
||||||
|
|
||||||
@ -191,10 +199,8 @@ class LndRestWallet(Wallet):
|
|||||||
|
|
||||||
payment_hash = base64.b64decode(inv["r_hash"]).hex()
|
payment_hash = base64.b64decode(inv["r_hash"]).hex()
|
||||||
yield payment_hash
|
yield payment_hash
|
||||||
except (OSError, httpx.ConnectError, httpx.ReadError):
|
except Exception as exc:
|
||||||
pass
|
logger.error(
|
||||||
|
f"lost connection to lnd invoices stream: '{exc}', retrying in 5 seconds"
|
||||||
logger.error(
|
)
|
||||||
"lost connection to lnd invoices stream, retrying in 5 seconds"
|
await asyncio.sleep(5)
|
||||||
)
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user