1
0

Merge pull request #10157 from SomberNight/202508_swap_sanity_check_costs

swaps: add sanity-check for total swap costs
This commit is contained in:
ghost43
2025-08-20 18:05:13 +00:00
committed by GitHub
2 changed files with 30 additions and 3 deletions

View File

@@ -13,7 +13,10 @@ from electrum.i18n import _
from electrum.logging import Logger
from electrum.bitcoin import DummyAddress
from electrum.plugin import run_hook
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, parse_max_spend, UserCancelled, ChoiceItem
from electrum.util import (
NotEnoughFunds, NoDynamicFeeEstimates, parse_max_spend, UserCancelled, ChoiceItem,
UserFacingException,
)
from electrum.invoices import PR_PAID, Invoice, PR_BROADCASTING, PR_BROADCAST
from electrum.transaction import Transaction, PartialTxInput, PartialTxOutput
from electrum.network import TxBroadcastError, BestEffortRequestFailed
@@ -338,7 +341,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
coro = sm.request_swap_for_amount(transport=transport, onchain_amount=swap_dummy_output.value)
try:
swap, swap_invoice = self.window.run_coroutine_dialog(coro, _('Requesting swap invoice...'))
except SwapServerError as e:
except (SwapServerError, UserFacingException) as e:
self.show_error(str(e))
return
except UserCancelled:

View File

@@ -33,7 +33,7 @@ from .transaction import (
from .util import (
log_exceptions, ignore_exceptions, BelowDustLimit, OldTaskGroup, ca_path, gen_nostr_ann_pow,
get_nostr_ann_pow_amount, make_aiohttp_proxy_connector, get_running_loop, get_asyncio_loop, wait_for2,
run_sync_function_on_asyncio_thread, trigger_callback, NoDynamicFeeEstimates
run_sync_function_on_asyncio_thread, trigger_callback, NoDynamicFeeEstimates, UserFacingException,
)
from . import lnutil
from .lnutil import hex_to_bytes, REDEEM_AFTER_DOUBLE_SPENT_DELAY, Keypair
@@ -507,6 +507,26 @@ class SwapManager(Logger):
fee_policy = FeePolicy(policy_descriptor)
return fee_policy.estimate_fee(SWAP_TX_SIZE, network=self.network, allow_fallback_to_static_rates=True)
def _sanity_check_swap_costs(
self,
*,
incoming_sat: int,
outgoing_sat: int,
) -> None:
"""The user should have already seen the swap amounts, and hence the cost.
These are just some last-minute sanity checks that the cost of the swap is not insane.
"""
costs_abs = outgoing_sat - incoming_sat
costs_ratio = 1 - incoming_sat / outgoing_sat
if costs_abs < 10_000: # "small" amounts are exempt from checks
return
exc = UserFacingException(_("Total swap costs are insane.") + f"\n({costs_ratio=}, {costs_abs=} sat)")
if costs_ratio > 0.25:
raise exc
if costs_abs > 1_000_000:
if costs_ratio > 0.15:
raise exc
def get_swap(self, payment_hash: bytes) -> Optional[SwapData]:
# for history
swap = self._swaps.get(payment_hash.hex())
@@ -755,6 +775,8 @@ class SwapManager(Logger):
expected_onchain_amount_sat: int,
channels: Optional[Sequence['Channel']] = None,
) -> Tuple[SwapData, str]:
self._sanity_check_swap_costs(
incoming_sat=lightning_amount_sat, outgoing_sat=expected_onchain_amount_sat)
await self.is_initialized.wait() # add timeout
refund_privkey = os.urandom(32)
refund_pubkey = ECPrivkey(refund_privkey).get_public_key_bytes(compressed=True)
@@ -927,6 +949,8 @@ class SwapManager(Logger):
"""
assert self.network
assert self.lnwatcher
self._sanity_check_swap_costs(
incoming_sat=expected_onchain_amount_sat, outgoing_sat=lightning_amount_sat)
privkey = os.urandom(32)
our_pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True)
preimage = os.urandom(32)