Backend: Unstuck outgoing payments (#857)

* first attempts

* lndrest works

* fix details

* optional fee update

* use base64.urlsafe_b64encode

* return paymentstatus

* CLN: return pending for pending invoices

* grpc wip

* lndgrpc works

* cln: return pending for pending invoices really this time

* retry wallet out of exception

* wip eclair

* take all routines into try except

* cliche: return error

* rename payment.check_pending() to payment.check_status()

* rename payment.check_pending() to payment.check_status()

* eclair: works

* eclair: better error check

* opennode: works

* check payment.checking_id istead of payment.ok

* payment.ok check as well

* cln: works?

* cln: works

* lntxbot: works

* lnbits/wallets/lnpay.py

* cln: error handling

* make format

* lndhub full detail update

* spark: wip

* error to False

* wallets: return clean PaymentResponse

* opennode: strict error

* cliche: works

* lnbits: works

* cln: dont throw error

* preimage not error

* fix cln

* do not add duplicate payments

* revert cln

* extra safety for cln

* undo crud changes until tests work

* tasks: better logging and 0.5s sleep for regular status check

* 0.1 s

* check if wallet exists

* lnbits unhashed description

* remove sleep

* revert app.py

* cleanup

* add check

* clean error

* readd app.py

* fix eclaid
This commit is contained in:
calle
2022-08-30 13:28:58 +02:00
committed by GitHub
parent 78a98ca97d
commit 2ee10e28c5
18 changed files with 384 additions and 194 deletions
+45 -24
View File
@@ -65,14 +65,32 @@ def get_ssl_context(cert_path: str):
return context
def parse_checking_id(checking_id: str) -> bytes:
def b64_to_bytes(checking_id: str) -> bytes:
return base64.b64decode(checking_id.replace("_", "/"))
def stringify_checking_id(r_hash: bytes) -> str:
def bytes_to_b64(r_hash: bytes) -> str:
return base64.b64encode(r_hash).decode("utf-8").replace("/", "_")
def hex_to_b64(hex_str: str) -> str:
try:
return base64.b64encode(bytes.fromhex(hex_str)).decode()
except ValueError:
return ""
def hex_to_bytes(hex_str: str) -> bytes:
try:
return bytes.fromhex(hex_str)
except:
return b""
def bytes_to_hex(b: bytes) -> str:
return b.hex()
# 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.
@@ -153,7 +171,7 @@ class LndWallet(Wallet):
error_message = str(exc)
return InvoiceResponse(False, None, None, error_message)
checking_id = stringify_checking_id(resp.r_hash)
checking_id = bytes_to_hex(resp.r_hash)
payment_request = str(resp.payment_request)
return InvoiceResponse(True, checking_id, payment_request, None)
@@ -168,9 +186,9 @@ class LndWallet(Wallet):
try:
resp = await self.routerpc.SendPaymentV2(req).read()
except RpcError as exc:
return PaymentResponse(False, "", 0, None, exc._details)
return PaymentResponse(False, None, None, None, exc._details)
except Exception as exc:
return PaymentResponse(False, "", 0, None, str(exc))
return PaymentResponse(False, None, None, None, str(exc))
# PaymentStatus from https://github.com/lightningnetwork/lnd/blob/master/channeldb/payments.go#L178
statuses = {
@@ -180,29 +198,31 @@ class LndWallet(Wallet):
3: False, # FAILED
}
if resp.status in [0, 1, 3]:
fee_msat = 0
preimage = ""
checking_id = ""
elif resp.status == 2: # SUCCEEDED
fee_msat = resp.htlcs[-1].route.total_fees_msat
preimage = resp.payment_preimage
checking_id = resp.payment_hash
fee_msat = None
preimage = None
checking_id = resp.payment_hash
if resp.status: # SUCCEEDED
fee_msat = -resp.htlcs[-1].route.total_fees_msat
preimage = bytes_to_hex(resp.payment_preimage)
return PaymentResponse(
statuses[resp.status], checking_id, fee_msat, preimage, None
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try:
r_hash = parse_checking_id(checking_id)
r_hash = hex_to_bytes(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)
resp = await self.rpc.LookupInvoice(ln.PaymentHash(r_hash=r_hash))
try:
resp = await self.rpc.LookupInvoice(ln.PaymentHash(r_hash=r_hash))
except RpcError as exc:
return PaymentStatus(None)
if resp.settled:
return PaymentStatus(True)
@@ -213,7 +233,7 @@ class LndWallet(Wallet):
This routine checks the payment status using routerpc.TrackPaymentV2.
"""
try:
r_hash = parse_checking_id(checking_id)
r_hash = hex_to_bytes(checking_id)
if len(r_hash) != 32:
raise binascii.Error
except binascii.Error:
@@ -221,11 +241,6 @@ class LndWallet(Wallet):
# 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)
)
@@ -240,6 +255,12 @@ class LndWallet(Wallet):
try:
async for payment in resp:
if statuses[payment.htlcs[-1].status]:
return PaymentStatus(
True,
-payment.htlcs[-1].route.total_fees_msat,
bytes_to_hex(payment.htlcs[-1].preimage),
)
return PaymentStatus(statuses[payment.htlcs[-1].status])
except: # most likely the payment wasn't found
return PaymentStatus(None)
@@ -248,13 +269,13 @@ class LndWallet(Wallet):
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
while True:
request = ln.InvoiceSubscription()
try:
request = ln.InvoiceSubscription()
async for i in self.rpc.SubscribeInvoices(request):
if not i.settled:
continue
checking_id = stringify_checking_id(i.r_hash)
checking_id = bytes_to_hex(i.r_hash)
yield checking_id
except Exception as exc:
logger.error(