mirror of
https://github.com/lnbits/lnbits.git
synced 2025-07-12 06:02:16 +02:00
test: add regtest testcase for no route failure (#3189)
This commit is contained in:
@ -20,7 +20,7 @@ from lnbits.exceptions import InvoiceError, PaymentError
|
|||||||
from lnbits.fiat import get_fiat_provider
|
from lnbits.fiat import get_fiat_provider
|
||||||
from lnbits.helpers import check_callback_url
|
from lnbits.helpers import check_callback_url
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.tasks import internal_invoice_queue_put
|
from lnbits.tasks import create_task, internal_invoice_queue_put
|
||||||
from lnbits.utils.crypto import fake_privkey, random_secret_and_hash
|
from lnbits.utils.crypto import fake_privkey, random_secret_and_hash
|
||||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
||||||
from lnbits.wallets import fake_wallet, get_funding_source
|
from lnbits.wallets import fake_wallet, get_funding_source
|
||||||
@ -711,8 +711,6 @@ async def _pay_external_invoice(
|
|||||||
fee_reserve_msat = fee_reserve(amount_msat, internal=False)
|
fee_reserve_msat = fee_reserve(amount_msat, internal=False)
|
||||||
service_fee_msat = service_fee(amount_msat, internal=False)
|
service_fee_msat = service_fee(amount_msat, internal=False)
|
||||||
|
|
||||||
from lnbits.tasks import create_task
|
|
||||||
|
|
||||||
task = create_task(
|
task = create_task(
|
||||||
_fundingsource_pay_invoice(checking_id, payment.bolt11, fee_reserve_msat)
|
_fundingsource_pay_invoice(checking_id, payment.bolt11, fee_reserve_msat)
|
||||||
)
|
)
|
||||||
@ -726,18 +724,20 @@ async def _pay_external_invoice(
|
|||||||
logger.debug(f"payment timeout, {checking_id} is still pending")
|
logger.debug(f"payment timeout, {checking_id} is still pending")
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
if payment_response.checking_id and payment_response.checking_id != checking_id:
|
# payment failed
|
||||||
logger.warning(
|
if (
|
||||||
f"backend sent unexpected checking_id (expected: {checking_id} got:"
|
payment_response.checking_id is None
|
||||||
f" {payment_response.checking_id})"
|
or payment_response.ok is False
|
||||||
)
|
or payment_response.checking_id != checking_id
|
||||||
if payment_response.checking_id and payment_response.ok is not False:
|
):
|
||||||
|
payment.status = PaymentState.FAILED
|
||||||
|
await update_payment(payment, conn=conn)
|
||||||
|
message = payment_response.error_message or "without an error message."
|
||||||
|
raise PaymentError(f"Payment failed: {message}", status="failed")
|
||||||
|
|
||||||
# payment.ok can be True (paid) or None (pending)!
|
# payment.ok can be True (paid) or None (pending)!
|
||||||
logger.debug(f"updating payment {checking_id}")
|
|
||||||
payment.status = (
|
payment.status = (
|
||||||
PaymentState.SUCCESS
|
PaymentState.SUCCESS if payment_response.ok is True else PaymentState.PENDING
|
||||||
if payment_response.ok is True
|
|
||||||
else PaymentState.PENDING
|
|
||||||
)
|
)
|
||||||
payment.fee = -(abs(payment_response.fee_msat or 0) + abs(service_fee_msat))
|
payment.fee = -(abs(payment_response.fee_msat or 0) + abs(service_fee_msat))
|
||||||
payment.preimage = payment_response.preimage
|
payment.preimage = payment_response.preimage
|
||||||
@ -746,21 +746,6 @@ async def _pay_external_invoice(
|
|||||||
if payment.success:
|
if payment.success:
|
||||||
await send_payment_notification(wallet, payment)
|
await send_payment_notification(wallet, payment)
|
||||||
logger.success(f"payment successful {payment_response.checking_id}")
|
logger.success(f"payment successful {payment_response.checking_id}")
|
||||||
elif payment_response.checking_id is None and payment_response.ok is False:
|
|
||||||
# payment failed
|
|
||||||
logger.debug(f"payment failed {checking_id}, {payment_response.error_message}")
|
|
||||||
payment.status = PaymentState.FAILED
|
|
||||||
await update_payment(payment, conn=conn)
|
|
||||||
raise PaymentError(
|
|
||||||
f"Payment failed: {payment_response.error_message}"
|
|
||||||
or "Payment failed, but backend didn't give us an error message.",
|
|
||||||
status="failed",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
"didn't receive checking_id from backend, payment may be stuck in"
|
|
||||||
f" database: {checking_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
|
@ -378,6 +378,9 @@ async def api_payment(payment_hash, x_api_key: Optional[str] = Header(None)):
|
|||||||
return {"paid": True, "preimage": payment.preimage, "details": payment}
|
return {"paid": True, "preimage": payment.preimage, "details": payment}
|
||||||
return {"paid": True, "preimage": payment.preimage}
|
return {"paid": True, "preimage": payment.preimage}
|
||||||
|
|
||||||
|
if payment.failed:
|
||||||
|
return {"paid": False, "status": "failed", "details": payment}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
status = await payment.check_status()
|
status = await payment.check_status()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -218,7 +218,7 @@ class CoreLightningRestWallet(Wallet):
|
|||||||
data = exc.response.json()
|
data = exc.response.json()
|
||||||
error_code = int(data["error"]["code"])
|
error_code = int(data["error"]["code"])
|
||||||
if error_code in self.pay_failure_error_codes:
|
if error_code in self.pay_failure_error_codes:
|
||||||
error_message = f"Payment failed: {data['error']['message']}"
|
error_message = data["error"]["message"]
|
||||||
return PaymentResponse(ok=False, error_message=error_message)
|
return PaymentResponse(ok=False, error_message=error_message)
|
||||||
error_message = f"REST failed with {data['error']['message']}."
|
error_message = f"REST failed with {data['error']['message']}."
|
||||||
return PaymentResponse(error_message=error_message)
|
return PaymentResponse(error_message=error_message)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .helpers import get_hold_invoice, get_real_invoice
|
from .helpers import get_hold_invoice, get_real_invoice, get_real_invoice_noroute
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
@ -22,3 +22,13 @@ async def real_amountless_invoice():
|
|||||||
invoice = get_real_invoice(0)
|
invoice = get_real_invoice(0)
|
||||||
yield invoice["payment_request"]
|
yield invoice["payment_request"]
|
||||||
del invoice
|
del invoice
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
async def real_invoice_noroute():
|
||||||
|
invoice = get_real_invoice_noroute(100)
|
||||||
|
yield {
|
||||||
|
"bolt11": invoice["payment_request"],
|
||||||
|
"payment_hash": invoice["r_hash"],
|
||||||
|
}
|
||||||
|
del invoice
|
||||||
|
@ -39,6 +39,17 @@ docker_lightning_unconnected_cli = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
docker_lightning_noroute_cli = [
|
||||||
|
"docker",
|
||||||
|
"exec",
|
||||||
|
"lnbits-lnd-4-1",
|
||||||
|
"lncli",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--rpcserver=lnd-4",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def run_cmd(cmd: list) -> str:
|
def run_cmd(cmd: list) -> str:
|
||||||
timeout = 10
|
timeout = 10
|
||||||
process = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
process = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||||
@ -100,6 +111,12 @@ def get_real_invoice(sats: int) -> dict:
|
|||||||
return run_cmd_json(cmd)
|
return run_cmd_json(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def get_real_invoice_noroute(sats: int) -> dict:
|
||||||
|
cmd = docker_lightning_noroute_cli.copy()
|
||||||
|
cmd.extend(["addinvoice", str(sats)])
|
||||||
|
return run_cmd_json(cmd)
|
||||||
|
|
||||||
|
|
||||||
def pay_real_invoice(invoice: str) -> str:
|
def pay_real_invoice(invoice: str) -> str:
|
||||||
cmd = docker_lightning_cli.copy()
|
cmd = docker_lightning_cli.copy()
|
||||||
cmd.extend(["payinvoice", "--force", invoice])
|
cmd.extend(["payinvoice", "--force", invoice])
|
||||||
|
@ -69,6 +69,68 @@ async def test_pay_real_invoice(
|
|||||||
assert prev_balance - balance == 100
|
assert prev_balance - balance == 100
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
||||||
|
async def test_pay_real_invoice_noroute(
|
||||||
|
client,
|
||||||
|
real_invoice_noroute,
|
||||||
|
adminkey_headers_from,
|
||||||
|
inkey_headers_from,
|
||||||
|
):
|
||||||
|
response = await client.post(
|
||||||
|
"/api/v1/payments", json=real_invoice_noroute, headers=adminkey_headers_from
|
||||||
|
)
|
||||||
|
assert response.status_code == 520
|
||||||
|
invoice = response.json()
|
||||||
|
|
||||||
|
assert invoice["status"] == "failed"
|
||||||
|
|
||||||
|
# check the payment status
|
||||||
|
response = await client.get(
|
||||||
|
f'/api/v1/payments/{real_invoice_noroute["payment_hash"]}',
|
||||||
|
headers=inkey_headers_from,
|
||||||
|
)
|
||||||
|
assert response.status_code < 300
|
||||||
|
payment = response.json()
|
||||||
|
assert payment["paid"] is False
|
||||||
|
assert payment["status"] == "failed"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
||||||
|
async def test_pay_real_invoice_mainnet(
|
||||||
|
client,
|
||||||
|
adminkey_headers_from,
|
||||||
|
inkey_headers_from,
|
||||||
|
):
|
||||||
|
"""regtest should fail paying a mainnet invoice"""
|
||||||
|
inv = (
|
||||||
|
"lnbc100n1p59ujlrpp5mn5g5tu7fz0up6asnz0gcceru4hwz0w42g7fuz8gxw67jl7kjjeqcqzyssp5qq"
|
||||||
|
"5y92fwazqdtnu8u9p9qf333hqnkuvtuu5csdze5ak4q86hyrhq9q7sqqqqqqqqqqqqqqqqqqqsqqqqqys"
|
||||||
|
"gqdq2f38xy6t5wvmqz9gxqrrssrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glc"
|
||||||
|
"lll4ttz7sp6kpvqqqqlgqqqqqeqqjqm9fkydmtwsxcxx3j44x9fckjqttg54zlxzw92yeaz9nzn8w7hgv"
|
||||||
|
"87ph5ug4wmgxqpk929k7l6dsnc2y9532daaqlpg9tfjglshuh48cpjh0dua"
|
||||||
|
)
|
||||||
|
payment_hash = "dce88a2f9e489fc0ebb0989e8c6323e56ee13dd5523c9e08e833b5e97fd694b2"
|
||||||
|
|
||||||
|
response = await client.post(
|
||||||
|
"/api/v1/payments", json={"bolt11": inv}, headers=adminkey_headers_from
|
||||||
|
)
|
||||||
|
assert response.status_code == 520
|
||||||
|
invoice = response.json()
|
||||||
|
assert invoice["status"] == "failed"
|
||||||
|
|
||||||
|
# check the payment status
|
||||||
|
response = await client.get(
|
||||||
|
f"/api/v1/payments/{payment_hash}",
|
||||||
|
headers=inkey_headers_from,
|
||||||
|
)
|
||||||
|
assert response.status_code < 300
|
||||||
|
payment = response.json()
|
||||||
|
assert payment["paid"] is False
|
||||||
|
assert payment["status"] == "failed"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
||||||
async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_from):
|
async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_from):
|
||||||
|
@ -558,18 +558,14 @@ async def test_pay_external_invoice_success_bad_checking_id(
|
|||||||
AsyncMock(return_value=payment_reponse_success),
|
AsyncMock(return_value=payment_reponse_success),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with pytest.raises(PaymentError):
|
||||||
await pay_invoice(
|
await pay_invoice(
|
||||||
wallet_id=from_wallet.id,
|
wallet_id=from_wallet.id,
|
||||||
payment_request=external_invoice.payment_request,
|
payment_request=external_invoice.payment_request,
|
||||||
)
|
)
|
||||||
|
|
||||||
payment = await get_standalone_payment(bad_checking_id)
|
payment = await get_standalone_payment(bad_checking_id)
|
||||||
assert payment
|
assert payment is None, "Payment should not be created with bad checking_id"
|
||||||
assert payment.checking_id == bad_checking_id, "checking_id updated"
|
|
||||||
assert payment.payment_hash == external_invoice.checking_id
|
|
||||||
assert payment.amount == -2108_000
|
|
||||||
assert payment.preimage == preimage
|
|
||||||
assert payment.status == PaymentState.SUCCESS.value
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
@ -590,6 +586,7 @@ async def test_no_checking_id(
|
|||||||
AsyncMock(return_value=payment_reponse_pending),
|
AsyncMock(return_value=payment_reponse_pending),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with pytest.raises(PaymentError):
|
||||||
await pay_invoice(
|
await pay_invoice(
|
||||||
wallet_id=from_wallet.id,
|
wallet_id=from_wallet.id,
|
||||||
payment_request=external_invoice.payment_request,
|
payment_request=external_invoice.payment_request,
|
||||||
@ -598,11 +595,12 @@ async def test_no_checking_id(
|
|||||||
payment = await get_standalone_payment(external_invoice.checking_id)
|
payment = await get_standalone_payment(external_invoice.checking_id)
|
||||||
|
|
||||||
assert payment
|
assert payment
|
||||||
|
assert payment.status == PaymentState.FAILED.value
|
||||||
|
|
||||||
assert payment.checking_id == external_invoice.checking_id
|
assert payment.checking_id == external_invoice.checking_id
|
||||||
assert payment.payment_hash == external_invoice.checking_id
|
assert payment.payment_hash == external_invoice.checking_id
|
||||||
assert payment.amount == -2110_000
|
assert payment.amount == -2110_000
|
||||||
assert payment.preimage is None
|
assert payment.preimage is None
|
||||||
assert payment.status == PaymentState.PENDING.value
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
|
Reference in New Issue
Block a user