mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-21 11:04:19 +01:00
[PATCH] LND use_mission_control + exclude failing channels (#738)
* lnd_grpc multinut patch * lndrest multinut patch * mypy fixes * fixes non escaped double quotes in error messages formats * fix * fix debug log with correct hops number * correctly escape "hops" * remove `ignored_pairs` constraint * Apply suggestions from code review change some error logs to debug * add tests and some cleanup --------- Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
@@ -41,6 +41,9 @@ INVOICE_RESULT_MAP = {
|
||||
"ACCEPTED": PaymentResult.PENDING,
|
||||
}
|
||||
|
||||
MAX_ROUTE_RETRIES = 50
|
||||
TEMPORARY_CHANNEL_FAILURE_ERROR = "TEMPORARY_CHANNEL_FAILURE"
|
||||
|
||||
|
||||
class LndRestWallet(LightningBackend):
|
||||
"""https://api.lightning.community/rest/index.html#lnd-rest-api-reference"""
|
||||
@@ -219,6 +222,8 @@ class LndRestWallet(LightningBackend):
|
||||
async def pay_partial_invoice(
|
||||
self, quote: MeltQuote, amount: Amount, fee_limit_msat: int
|
||||
) -> PaymentResponse:
|
||||
attempts = 0
|
||||
|
||||
# set the fee limit for the payment
|
||||
lnrpcFeeLimit = dict()
|
||||
lnrpcFeeLimit["fixed_msat"] = f"{fee_limit_msat}"
|
||||
@@ -236,56 +241,78 @@ class LndRestWallet(LightningBackend):
|
||||
assert payer_addr_tag
|
||||
payer_addr = str(payer_addr_tag.data)
|
||||
|
||||
# get the route
|
||||
r = await self.client.post(
|
||||
url=f"/v1/graph/routes/{pubkey}/{amount.to(Unit.sat).amount}",
|
||||
json={"fee_limit": lnrpcFeeLimit},
|
||||
timeout=None,
|
||||
)
|
||||
# add the mpp_record to the last hop
|
||||
response: Optional[httpx.Response] = None
|
||||
route: Optional[httpx.Response] = None
|
||||
|
||||
data = r.json()
|
||||
if r.is_error or data.get("message"):
|
||||
error_message = data.get("message") or r.text
|
||||
return PaymentResponse(
|
||||
result=PaymentResult.FAILED, error_message=error_message
|
||||
for attempt in range(MAX_ROUTE_RETRIES):
|
||||
attempts += 1
|
||||
# get the route
|
||||
route = await self.client.post(
|
||||
url=f"/v1/graph/routes/{pubkey}/{amount.to(Unit.sat).amount}",
|
||||
json={
|
||||
"fee_limit": lnrpcFeeLimit,
|
||||
"use_mission_control": True,
|
||||
},
|
||||
timeout=None,
|
||||
)
|
||||
|
||||
# We need to set the mpp_record for a partial payment
|
||||
mpp_record = {
|
||||
"mpp_record": {
|
||||
"payment_addr": base64.b64encode(bytes.fromhex(payer_addr)).decode(),
|
||||
"total_amt_msat": total_amount_msat,
|
||||
}
|
||||
}
|
||||
assert route
|
||||
|
||||
# add the mpp_record to the last hop
|
||||
r = None # type: ignore
|
||||
for route_nr in range(len(data["routes"])):
|
||||
logger.debug(f"Trying to pay partial amount with route number {route_nr+1}")
|
||||
data["routes"][route_nr]["hops"][-1].update(mpp_record)
|
||||
route_data = route.json()
|
||||
if route.is_error or route_data.get("message"):
|
||||
error_message = route_data.get("message") or route.text
|
||||
return PaymentResponse(
|
||||
result=PaymentResult.FAILED, error_message=error_message
|
||||
)
|
||||
|
||||
# We need to set the mpp_record for a partial payment
|
||||
mpp_record = {
|
||||
"mpp_record": {
|
||||
"payment_addr": base64.b64encode(
|
||||
bytes.fromhex(payer_addr)
|
||||
).decode(),
|
||||
"total_amt_msat": total_amount_msat,
|
||||
}
|
||||
}
|
||||
route_data["routes"][0]["hops"][-1].update(mpp_record)
|
||||
|
||||
# send to route
|
||||
r = await self.client.post(
|
||||
response = await self.client.post(
|
||||
url="/v2/router/route/send",
|
||||
json={
|
||||
"payment_hash": base64.b64encode(
|
||||
bytes.fromhex(invoice.payment_hash)
|
||||
).decode(),
|
||||
"route": data["routes"][route_nr],
|
||||
"route": route_data["routes"][0],
|
||||
},
|
||||
timeout=None,
|
||||
)
|
||||
|
||||
data = r.json()
|
||||
failure = data.get("failure")
|
||||
if failure:
|
||||
if failure["code"] == 15:
|
||||
# Try with a different route
|
||||
assert response
|
||||
|
||||
response_data = response.json()
|
||||
if response_data.get("status") == "FAILED":
|
||||
if response_data["failure"]["code"] == TEMPORARY_CHANNEL_FAILURE_ERROR:
|
||||
# Add the channels that failed to the excluded channels
|
||||
failure_index = response_data["failure"]["failure_source_index"]
|
||||
failed_source = route_data["routes"][0]["hops"][failure_index - 1][
|
||||
"pub_key"
|
||||
]
|
||||
failed_dest = route_data["routes"][0]["hops"][failure_index][
|
||||
"pub_key"
|
||||
]
|
||||
logger.debug(
|
||||
f"Partial payment failed from {failed_source} to {failed_dest} at index {failure_index-1} of the route"
|
||||
)
|
||||
continue
|
||||
break
|
||||
|
||||
if r.is_error or data.get("message"):
|
||||
error_message = data.get("message") or r.text
|
||||
assert response and route
|
||||
|
||||
data = response.json()
|
||||
if response.is_error or data.get("message") or data.get("status") == "FAILED":
|
||||
error_message = f"Sending to route failed with code {data.get('failure').get('code')} after {attempts} tries."
|
||||
return PaymentResponse(
|
||||
result=PaymentResult.FAILED, error_message=error_message
|
||||
)
|
||||
@@ -296,6 +323,11 @@ class LndRestWallet(LightningBackend):
|
||||
preimage = (
|
||||
base64.b64decode(data["preimage"]).hex() if data.get("preimage") else None
|
||||
)
|
||||
|
||||
logger.debug(f"Partial payment succeeded after {attempts} different tries!")
|
||||
logger.debug(
|
||||
f"Partial payment route length was {len(route.json().get('routes'))} hops."
|
||||
)
|
||||
return PaymentResponse(
|
||||
result=result,
|
||||
checking_id=checking_id,
|
||||
|
||||
Reference in New Issue
Block a user