mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 12:28:22 +00:00
add detailed error message
This commit is contained in:
+12
-5
@@ -2647,11 +2647,18 @@ async def periodic_routstr_fee_payout() -> None:
|
|||||||
logger.warning("Routstr fee payout was already claimed")
|
logger.warning("Routstr fee payout was already claimed")
|
||||||
continue
|
continue
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
logger.critical(
|
if attempt_quote_id is None:
|
||||||
"Routstr fee payout outcome is unknown; awaiting quote reconciliation",
|
logger.error(
|
||||||
extra={"payout_in_progress_msats": paid_msats},
|
"Routstr fee payout failed before melt dispatch",
|
||||||
exc_info=isinstance(e, Exception),
|
extra={"payout_msats": paid_msats},
|
||||||
)
|
exc_info=isinstance(e, Exception),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.critical(
|
||||||
|
"Routstr fee payout outcome is unknown; awaiting quote reconciliation",
|
||||||
|
extra={"payout_in_progress_msats": paid_msats},
|
||||||
|
exc_info=isinstance(e, Exception),
|
||||||
|
)
|
||||||
if not isinstance(e, Exception):
|
if not isinstance(e, Exception):
|
||||||
raise
|
raise
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
|||||||
|
|
||||||
from routstr import wallet
|
from routstr import wallet
|
||||||
from routstr.core import db
|
from routstr.core import db
|
||||||
|
from routstr.payment.lnurl import LNURLError
|
||||||
|
|
||||||
|
|
||||||
class _SessionContext:
|
class _SessionContext:
|
||||||
@@ -525,6 +526,47 @@ async def test_fee_payout_keeps_legacy_checkpoint_without_quote_locked() -> None
|
|||||||
critical.assert_called_once()
|
critical.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_fee_payout_failure_before_quote_is_not_reported_as_unknown() -> None:
|
||||||
|
session = Mock()
|
||||||
|
fee = SimpleNamespace(
|
||||||
|
accumulated_msats=1_061_000,
|
||||||
|
payout_in_progress_msats=0,
|
||||||
|
payout_started_at=None,
|
||||||
|
)
|
||||||
|
reset = AsyncMock()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("routstr.auth.ROUTSTR_FEE_DEFAULT_PAYOUT", 1),
|
||||||
|
patch("routstr.auth.ROUTSTR_FEE_PAYOUT_INTERVAL_SECONDS", 1),
|
||||||
|
patch("routstr.auth.ROUTSTR_LN_ADDRESS", "fees@example.com"),
|
||||||
|
patch(
|
||||||
|
"routstr.wallet.asyncio.sleep",
|
||||||
|
AsyncMock(side_effect=[None, asyncio.CancelledError()]),
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"routstr.wallet.db.create_session", return_value=_session_context(session)
|
||||||
|
),
|
||||||
|
patch("routstr.wallet.db.get_routstr_fee", AsyncMock(return_value=fee)),
|
||||||
|
patch("routstr.wallet.db.reset_routstr_fee", reset),
|
||||||
|
patch("routstr.wallet.get_wallet", AsyncMock(return_value=Mock())),
|
||||||
|
patch("routstr.wallet.get_proofs_per_mint_and_unit", return_value=[]),
|
||||||
|
patch(
|
||||||
|
"routstr.wallet.raw_send_to_lnurl",
|
||||||
|
side_effect=LNURLError("Cashu melt fees leave no payable LNURL amount"),
|
||||||
|
),
|
||||||
|
patch("routstr.wallet.logger.error") as error,
|
||||||
|
patch("routstr.wallet.logger.critical") as critical,
|
||||||
|
):
|
||||||
|
with pytest.raises(asyncio.CancelledError):
|
||||||
|
await wallet.periodic_routstr_fee_payout()
|
||||||
|
|
||||||
|
reset.assert_not_awaited()
|
||||||
|
critical.assert_not_called()
|
||||||
|
error.assert_called_once()
|
||||||
|
assert error.call_args.args[0] == "Routstr fee payout failed before melt dispatch"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_fee_payout_keeps_checkpoint_when_send_outcome_is_unknown() -> None:
|
async def test_fee_payout_keeps_checkpoint_when_send_outcome_is_unknown() -> None:
|
||||||
session = Mock()
|
session = Mock()
|
||||||
|
|||||||
@@ -347,21 +347,21 @@ async def test_send_to_lnurl_does_not_reserve_before_lnurl_validation() -> None:
|
|||||||
assert raw_send.await_args.kwargs["amount"] == 1000
|
assert raw_send.await_args.kwargs["amount"] == 1000
|
||||||
|
|
||||||
|
|
||||||
def test_select_melt_proofs_stops_at_minimal_cover_when_over_budget() -> None:
|
def test_select_melt_proofs_ignores_fees_for_unneeded_wallet_proofs() -> None:
|
||||||
from routstr.payment.lnurl import _select_melt_proofs
|
from routstr.payment.lnurl import _select_melt_proofs
|
||||||
|
|
||||||
wallet = MagicMock()
|
wallet = MagicMock()
|
||||||
wallet.get_fees_for_proofs = MagicMock(side_effect=lambda selected: len(selected))
|
wallet.get_fees_for_proofs = MagicMock(side_effect=lambda selected: len(selected))
|
||||||
proofs = [MagicMock(amount=600, reserved=False) for _ in range(3)]
|
proofs = [MagicMock(amount=2048, reserved=False) for _ in range(1100)]
|
||||||
|
|
||||||
selected, shortfall = _select_melt_proofs(
|
selected, shortfall = _select_melt_proofs(
|
||||||
wallet,
|
wallet,
|
||||||
proofs,
|
proofs,
|
||||||
quote_amount=1000,
|
quote_amount=1061,
|
||||||
fee_reserve=0,
|
fee_reserve=1,
|
||||||
gross_budget=1000,
|
gross_budget=1061,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert selected is None
|
assert selected is None
|
||||||
assert shortfall == 2
|
assert shortfall == 2
|
||||||
assert wallet.get_fees_for_proofs.call_count == 2
|
assert wallet.get_fees_for_proofs.call_count == 1
|
||||||
|
|||||||
Reference in New Issue
Block a user