From 5cb7afe18acf372665bbffb4c5d4d205156ef7fa Mon Sep 17 00:00:00 2001 From: Arc Date: Fri, 31 Jul 2026 11:23:40 +0100 Subject: [PATCH] Vlads comments --- lnbits/wallets/boltz.py | 39 +++++++++++---------- lnbits/wallets/clnrest.py | 5 +-- lnbits/wallets/lndgrpc.py | 37 +++++++++---------- lnbits/wallets/strike.py | 13 +++---- tests/unit/test_wallet_payment_ambiguity.py | 28 +++++++++++++++ 5 files changed, 77 insertions(+), 45 deletions(-) diff --git a/lnbits/wallets/boltz.py b/lnbits/wallets/boltz.py index d8ae3b439..050021a6c 100644 --- a/lnbits/wallets/boltz.py +++ b/lnbits/wallets/boltz.py @@ -24,25 +24,6 @@ from .base import ( Wallet, ) -_PRE_DISPATCH_CREATE_SWAP_ERROR_CODES = { - StatusCode.INVALID_ARGUMENT, - StatusCode.PERMISSION_DENIED, - StatusCode.UNAUTHENTICATED, -} -_PRE_DISPATCH_CREATE_SWAP_ERROR_MESSAGES = ( - "boltz error: could not find route to pay invoice", -) - - -def _is_pre_dispatch_create_swap_error(exc: AioRpcError) -> bool: - if exc.code() in _PRE_DISPATCH_CREATE_SWAP_ERROR_CODES: - return True - - details = (exc.details() or "").lower() - return any( - message in details for message in _PRE_DISPATCH_CREATE_SWAP_ERROR_MESSAGES - ) - class BoltzWallet(Wallet): """ @@ -445,3 +426,23 @@ class BoltzWallet(Wallet): except Exception as e: logger.error(f"❌ Failed to create Boltz wallet: {e}") + + +_PRE_DISPATCH_CREATE_SWAP_ERROR_CODES = { + StatusCode.INVALID_ARGUMENT, + StatusCode.PERMISSION_DENIED, + StatusCode.UNAUTHENTICATED, +} +_PRE_DISPATCH_CREATE_SWAP_ERROR_MESSAGES = ( + "boltz error: could not find route to pay invoice", +) + + +def _is_pre_dispatch_create_swap_error(exc: AioRpcError) -> bool: + if exc.code() in _PRE_DISPATCH_CREATE_SWAP_ERROR_CODES: + return True + + details = (exc.details() or "").lower() + return any( + message in details for message in _PRE_DISPATCH_CREATE_SWAP_ERROR_MESSAGES + ) diff --git a/lnbits/wallets/clnrest.py b/lnbits/wallets/clnrest.py index c9aceeb34..d9b86c370 100644 --- a/lnbits/wallets/clnrest.py +++ b/lnbits/wallets/clnrest.py @@ -380,10 +380,11 @@ class CLNRestWallet(Wallet): pay = pays_list[-1] - if pay["status"] == "complete": + status = pay.get("status") + if status == "complete": fee_msat = pay["amount_sent_msat"] - pay["amount_msat"] return PaymentSuccessStatus(fee_msat=fee_msat, preimage=pay["preimage"]) - if pay["status"] == "failed": + if status == "failed": return PaymentFailedStatus() except Exception as exc: diff --git a/lnbits/wallets/lndgrpc.py b/lnbits/wallets/lndgrpc.py index 48c42d152..2641de233 100644 --- a/lnbits/wallets/lndgrpc.py +++ b/lnbits/wallets/lndgrpc.py @@ -82,24 +82,6 @@ def bytes_to_hex(b: bytes) -> str: # error when we communicate with the lnd rpc server. environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA" -_PRE_DISPATCH_PAYMENT_ERROR_CODES = { - grpc.StatusCode.INVALID_ARGUMENT, - grpc.StatusCode.PERMISSION_DENIED, - grpc.StatusCode.UNAUTHENTICATED, -} -_PRE_DISPATCH_PAYMENT_ERROR_MESSAGES = ( - "invoice not for current active network", - "invoice expired", -) - - -def _is_pre_dispatch_payment_error(exc: grpc.aio.AioRpcError) -> bool: - if exc.code() in _PRE_DISPATCH_PAYMENT_ERROR_CODES: - return True - - details = (exc.details() or "").lower() - return any(message in details for message in _PRE_DISPATCH_PAYMENT_ERROR_MESSAGES) - class LndWallet(Wallet): rpc: LightningStub @@ -402,3 +384,22 @@ class LndWallet(Wallet): ) # If we reach here, the invoice was successfully canceled and payment failed return InvoiceResponse(True, checking_id=payment_hash) + + +_PRE_DISPATCH_PAYMENT_ERROR_CODES = { + grpc.StatusCode.INVALID_ARGUMENT, + grpc.StatusCode.PERMISSION_DENIED, + grpc.StatusCode.UNAUTHENTICATED, +} +_PRE_DISPATCH_PAYMENT_ERROR_MESSAGES = ( + "invoice not for current active network", + "invoice expired", +) + + +def _is_pre_dispatch_payment_error(exc: grpc.aio.AioRpcError) -> bool: + if exc.code() in _PRE_DISPATCH_PAYMENT_ERROR_CODES: + return True + + details = (exc.details() or "").lower() + return any(message in details for message in _PRE_DISPATCH_PAYMENT_ERROR_MESSAGES) diff --git a/lnbits/wallets/strike.py b/lnbits/wallets/strike.py index ad8776c2d..aa2b0170d 100644 --- a/lnbits/wallets/strike.py +++ b/lnbits/wallets/strike.py @@ -278,8 +278,7 @@ class StrikeWallet(Wallet): ) # Handle failed payment - failed_states = {"CANCELED", "FAILED", "TIMED_OUT"} - if state in failed_states: + if state == "FAILED": logger.warning( f"Strike payment {payment_id} failed with state: {state}" ) @@ -633,7 +632,7 @@ class StrikeWallet(Wallet): if state in {"SUCCEEDED", "COMPLETED"}: self.pending_payments.pop(checking_id, None) return PaymentSuccessStatus(fee_msat=fee_msat, preimage=preimage) - if state in {"CANCELED", "FAILED", "TIMED_OUT"}: + if state == "FAILED": self.pending_payments.pop(checking_id, None) return PaymentFailedStatus() @@ -675,7 +674,7 @@ class StrikeWallet(Wallet): if state in {"SUCCEEDED", "COMPLETED"}: self.pending_payments.pop(checking_id, None) return PaymentSuccessStatus(fee_msat=fee_msat, preimage=preimage) - if state in {"CANCELED", "FAILED", "TIMED_OUT"}: + if state == "FAILED": self.pending_payments.pop(checking_id, None) return PaymentFailedStatus() @@ -719,8 +718,10 @@ class StrikeWallet(Wallet): "be a legacy invoice payment hash. Keeping pending." ) return PaymentPendingStatus() - except ValueError: - pass + except ValueError as exc: + logger.warning( + f"Payment identifier '{checking_id}' is not valid hex: {exc}" + ) logger.warning(f"Payment {checking_id} not found. Marking as failed.") self.pending_payments.pop(checking_id, None) return PaymentFailedStatus() diff --git a/tests/unit/test_wallet_payment_ambiguity.py b/tests/unit/test_wallet_payment_ambiguity.py index 34d97926c..d4c4bc5e9 100644 --- a/tests/unit/test_wallet_payment_ambiguity.py +++ b/tests/unit/test_wallet_payment_ambiguity.py @@ -845,6 +845,34 @@ async def test_strike_terminal_state_does_not_require_payment_id( assert response.checking_id == "payment-hash" +@pytest.mark.anyio +@pytest.mark.parametrize("state", ["CANCELED", "TIMED_OUT", "UNKNOWN"]) +async def test_strike_undocumented_payment_state_is_pending( + mocker: MockerFixture, state: str +): + wallet = object.__new__(StrikeWallet) + wallet.pending_payments = {} + mocker.patch( + "lnbits.wallets.strike.bolt11_decode", + return_value=SimpleNamespace(payment_hash="payment-hash"), + ) + mocker.patch.object( + wallet, + "_create_payment_quote", + return_value=("quote-id", None), + ) + mocker.patch.object( + wallet, + "_execute_payment_quote", + return_value=({"state": state, "paymentId": "payment-id"}, None), + ) + + response = await wallet.pay_invoice("bolt11", 1_000) + + assert response.ok is None + assert response.checking_id == "payment-id" + + @pytest.mark.anyio async def test_strike_persisted_payment_hash_not_found_stays_pending( mocker: MockerFixture,