Mint: fix Blink multiple failed attempts (#440)

* fix: blink multiple failed attempts

* fix line length again
This commit is contained in:
callebtc
2024-02-18 19:26:10 +01:00
committed by GitHub
parent ad906df788
commit 9c3949d197
3 changed files with 92 additions and 28 deletions

View File

@@ -24,6 +24,8 @@ from .base import (
# according to https://github.com/GaloyMoney/galoy/blob/7e79cc27304de9b9c2e7d7f4fdd3bac09df23aac/core/api/src/domain/bitcoin/index.ts#L59
BLINK_MAX_FEE_PERCENT = 0.5
DIRECTION_SEND = "SEND"
DIRECTION_RECEIVE = "RECEIVE"
class BlinkWallet(LightningBackend):
@@ -35,7 +37,11 @@ class BlinkWallet(LightningBackend):
wallet_ids: Dict[Unit, str] = {}
endpoint = "https://api.blink.sv/graphql"
invoice_statuses = {"PENDING": None, "PAID": True, "EXPIRED": False}
payment_execution_statuses = {"SUCCESS": True, "ALREADY_PAID": None}
payment_execution_statuses = {
"SUCCESS": True,
"ALREADY_PAID": None,
"FAILURE": False,
}
payment_statuses = {"SUCCESS": True, "PENDING": None, "FAILURE": False}
def __init__(self):
@@ -78,10 +84,12 @@ class BlinkWallet(LightningBackend):
)
balance = 0
for wallet_dict in resp["data"]["me"]["defaultAccount"]["wallets"]:
if wallet_dict["walletCurrency"] == "USD":
for wallet_dict in (
resp.get("data", {}).get("me", {}).get("defaultAccount", {}).get("wallets")
):
if wallet_dict.get("walletCurrency") == "USD":
self.wallet_ids[Unit.usd] = wallet_dict["id"]
elif wallet_dict["walletCurrency"] == "BTC":
elif wallet_dict.get("walletCurrency") == "BTC":
self.wallet_ids[Unit.sat] = wallet_dict["id"]
balance = wallet_dict["balance"]
@@ -137,9 +145,13 @@ class BlinkWallet(LightningBackend):
resp = r.json()
assert resp, "invalid response"
payment_request = resp["data"]["lnInvoiceCreateOnBehalfOfRecipient"]["invoice"][
"paymentRequest"
]
payment_request = (
resp.get("data", {})
.get("lnInvoiceCreateOnBehalfOfRecipient", {})
.get("invoice", {})
.get("paymentRequest")
)
assert payment_request, "payment request not found"
checking_id = payment_request
return InvoiceResponse(
@@ -178,6 +190,7 @@ class BlinkWallet(LightningBackend):
r = await self.client.post(
url=self.endpoint,
data=json.dumps(data),
timeout=None,
)
r.raise_for_status()
except Exception as e:
@@ -186,9 +199,14 @@ class BlinkWallet(LightningBackend):
resp: dict = r.json()
paid = self.payment_execution_statuses[
resp["data"]["lnInvoicePaymentSend"]["status"]
resp.get("data", {}).get("lnInvoicePaymentSend", {}).get("status")
]
fee = resp["data"]["lnInvoicePaymentSend"]["transaction"]["settlementFee"]
fee = (
resp.get("data", {})
.get("lnInvoicePaymentSend", {})
.get("transaction", {})
.get("settlementFee")
)
checking_id = quote.request
return PaymentResponse(
@@ -221,12 +239,15 @@ class BlinkWallet(LightningBackend):
logger.error(f"Blink API error: {str(e)}")
return PaymentStatus(paid=None)
resp: dict = r.json()
if resp["data"]["lnInvoicePaymentStatus"]["errors"]:
if resp.get("data", {}).get("lnInvoicePaymentStatus", {}).get("errors"):
logger.error(
"Blink Error", resp["data"]["lnInvoicePaymentStatus"]["errors"]
"Blink Error",
resp.get("data", {}).get("lnInvoicePaymentStatus", {}).get("errors"),
)
return PaymentStatus(paid=None)
paid = self.invoice_statuses[resp["data"]["lnInvoicePaymentStatus"]["status"]]
paid = self.invoice_statuses[
resp.get("data", {}).get("lnInvoicePaymentStatus", {}).get("status")
]
return PaymentStatus(paid=paid)
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
@@ -267,19 +288,49 @@ class BlinkWallet(LightningBackend):
resp: dict = r.json()
# no result found
if not resp["data"]["me"]["defaultAccount"]["walletById"][
"transactionsByPaymentHash"
]:
if (
not resp.get("data", {})
.get("me", {})
.get("defaultAccount", {})
.get("walletById", {})
.get("transactionsByPaymentHash")
):
return PaymentStatus(paid=None)
paid = self.payment_statuses[
resp["data"]["me"]["defaultAccount"]["walletById"][
"transactionsByPaymentHash"
][0]["status"]
]
fee = resp["data"]["me"]["defaultAccount"]["walletById"][
"transactionsByPaymentHash"
][0]["settlementFee"]
all_payments_with_this_hash = (
resp.get("data", {})
.get("me", {})
.get("defaultAccount", {})
.get("walletById", {})
.get("transactionsByPaymentHash")
)
# Blink API edge case: for a failed payment attempt, it returns two payments with the same hash
# if there are two payments with the same hash with "direction" == "SEND" and "RECEIVE"
# it means that the payment previously failed and we can ignore the attempt and return
# PaymentStatus(paid=None)
if len(all_payments_with_this_hash) == 2 and all(
p["direction"] in [DIRECTION_SEND, DIRECTION_RECEIVE]
for p in all_payments_with_this_hash
):
return PaymentStatus(paid=None)
# if there is only one payment with the same hash, it means that the payment might have succeeded
# we only care about the payment with "direction" == "SEND"
payment = next(
(
p
for p in all_payments_with_this_hash
if p.get("direction") == DIRECTION_SEND
),
None,
)
if not payment:
return PaymentStatus(paid=None)
# we read the status of the payment
paid = self.payment_statuses[payment["status"]]
fee = payment["settlementFee"]
return PaymentStatus(
paid=paid,
@@ -312,22 +363,30 @@ class BlinkWallet(LightningBackend):
r = await self.client.post(
url=self.endpoint,
data=json.dumps(data),
timeout=None,
)
r.raise_for_status()
except Exception as e:
logger.error(f"Blink API error: {str(e)}")
return PaymentResponse(ok=False, error_message=str(e))
raise e
resp: dict = r.json()
if resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("errors"):
raise Exception(
resp["data"]["lnInvoiceFeeProbe"]["errors"][0].get("message")
or "Unknown error"
)
invoice_obj = decode(bolt11)
assert invoice_obj.amount_msat, "invoice has no amount."
amount_msat = int(invoice_obj.amount_msat)
fees_response_msat = int(resp["data"]["lnInvoiceFeeProbe"]["amount"]) * 1000
fees_response_msat = (
int(resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("amount")) * 1000
)
# we either take fee_msat_response or the BLINK_MAX_FEE_PERCENT, whichever is higher
fees_msat = max(
fees_response_msat, math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT)
fees_response_msat,
max(math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT), 1000),
)
fees = Amount(unit=Unit.msat, amount=fees_msat)

View File

@@ -498,6 +498,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
payment_quote = await self.backends[method][unit].get_payment_quote(
melt_quote.request
)
assert payment_quote.checking_id, "quote has no checking id"
quote = MeltQuote(
quote=random_hash(),

View File

@@ -110,7 +110,11 @@ async def test_blink_get_payment_status():
"defaultAccount": {
"walletById": {
"transactionsByPaymentHash": [
{"status": "SUCCESS", "settlementFee": 10}
{
"status": "SUCCESS",
"settlementFee": 10,
"direction": "SEND",
}
]
}
}