submarine swaps: fail received HTLCs of normal swap htlcs if
the swap is still unfunded and the refund delay has expired.
This commit is contained in:
@@ -2070,9 +2070,12 @@ class LNWallet(LNWorker):
|
|||||||
info = PaymentInfo(payment_hash, lightning_amount_sat * 1000, RECEIVED, PR_UNPAID)
|
info = PaymentInfo(payment_hash, lightning_amount_sat * 1000, RECEIVED, PR_UNPAID)
|
||||||
self.save_payment_info(info, write_to_disk=False)
|
self.save_payment_info(info, write_to_disk=False)
|
||||||
|
|
||||||
def register_callback_for_hold_invoice(self, payment_hash: bytes, cb: Callable[[bytes], Awaitable[None]]):
|
def register_hold_invoice(self, payment_hash: bytes, cb: Callable[[bytes], Awaitable[None]]):
|
||||||
self.hold_invoice_callbacks[payment_hash] = cb
|
self.hold_invoice_callbacks[payment_hash] = cb
|
||||||
|
|
||||||
|
def unregister_hold_invoice(self, payment_hash: bytes):
|
||||||
|
self.hold_invoice_callbacks.pop(payment_hash)
|
||||||
|
|
||||||
def save_payment_info(self, info: PaymentInfo, *, write_to_disk: bool = True) -> None:
|
def save_payment_info(self, info: PaymentInfo, *, write_to_disk: bool = True) -> None:
|
||||||
key = info.payment_hash.hex()
|
key = info.payment_hash.hex()
|
||||||
assert info.status in SAVED_PR_STATUS
|
assert info.status in SAVED_PR_STATUS
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ class SwapManager(Logger):
|
|||||||
swap._payment_hash = payment_hash
|
swap._payment_hash = payment_hash
|
||||||
self._add_or_reindex_swap(swap)
|
self._add_or_reindex_swap(swap)
|
||||||
if not swap.is_reverse and not swap.is_redeemed:
|
if not swap.is_reverse and not swap.is_redeemed:
|
||||||
self.lnworker.register_callback_for_hold_invoice(payment_hash, self.hold_invoice_callback)
|
self.lnworker.register_hold_invoice(payment_hash, self.hold_invoice_callback)
|
||||||
|
|
||||||
self.prepayments = {} # type: Dict[bytes, bytes] # fee_rhash -> rhash
|
self.prepayments = {} # type: Dict[bytes, bytes] # fee_rhash -> rhash
|
||||||
for k, swap in self.swaps.items():
|
for k, swap in self.swaps.items():
|
||||||
@@ -251,6 +251,15 @@ class SwapManager(Logger):
|
|||||||
continue
|
continue
|
||||||
await self.taskgroup.spawn(self.pay_invoice(key))
|
await self.taskgroup.spawn(self.pay_invoice(key))
|
||||||
|
|
||||||
|
def fail_normal_swap(self, swap):
|
||||||
|
if swap.payment_hash in self.lnworker.hold_invoice_callbacks:
|
||||||
|
self.logger.info(f'failing normal swap {swap.payment_hash.hex()}')
|
||||||
|
self.lnworker.unregister_hold_invoice(swap.payment_hash)
|
||||||
|
payment_secret = self.lnworker.get_payment_secret(swap.payment_hash)
|
||||||
|
payment_key = swap.payment_hash + payment_secret
|
||||||
|
self.lnworker.fail_final_onion_forwarding(payment_key)
|
||||||
|
self.lnwatcher.remove_callback(swap.lockup_address)
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def _claim_swap(self, swap: SwapData) -> None:
|
async def _claim_swap(self, swap: SwapData) -> None:
|
||||||
assert self.network
|
assert self.network
|
||||||
@@ -260,10 +269,23 @@ class SwapManager(Logger):
|
|||||||
current_height = self.network.get_local_height()
|
current_height = self.network.get_local_height()
|
||||||
delta = current_height - swap.locktime
|
delta = current_height - swap.locktime
|
||||||
txos = self.lnwatcher.adb.get_addr_outputs(swap.lockup_address)
|
txos = self.lnwatcher.adb.get_addr_outputs(swap.lockup_address)
|
||||||
|
|
||||||
for txin in txos.values():
|
for txin in txos.values():
|
||||||
if swap.is_reverse and txin.value_sats() < swap.onchain_amount:
|
if swap.is_reverse and txin.value_sats() < swap.onchain_amount:
|
||||||
self.logger.info('amount too low, we should not reveal the preimage')
|
# amount too low, we must not reveal the preimage
|
||||||
continue
|
continue
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# swap not funded.
|
||||||
|
if delta >= 0:
|
||||||
|
if not swap.is_reverse:
|
||||||
|
# we might have received HTLCs and double spent the funding tx
|
||||||
|
# in that case we need to fail the HTLCs
|
||||||
|
self.fail_normal_swap(swap)
|
||||||
|
txin = None
|
||||||
|
|
||||||
|
if txin:
|
||||||
|
# the swap is funded
|
||||||
swap.funding_txid = txin.prevout.txid.hex()
|
swap.funding_txid = txin.prevout.txid.hex()
|
||||||
swap._funding_prevout = txin.prevout
|
swap._funding_prevout = txin.prevout
|
||||||
self._add_or_reindex_swap(swap) # to update _swaps_by_funding_outpoint
|
self._add_or_reindex_swap(swap) # to update _swaps_by_funding_outpoint
|
||||||
@@ -298,42 +320,39 @@ class SwapManager(Logger):
|
|||||||
else:
|
else:
|
||||||
# refund tx
|
# refund tx
|
||||||
if spent_height > 0:
|
if spent_height > 0:
|
||||||
self.logger.info(f'found confirmed refund')
|
self.fail_normal_swap(swap)
|
||||||
payment_secret = self.lnworker.get_payment_secret(swap.payment_hash)
|
return
|
||||||
payment_key = swap.payment_hash + payment_secret
|
|
||||||
self.lnworker.fail_final_onion_forwarding(payment_key)
|
|
||||||
|
|
||||||
if delta < 0:
|
if delta < 0:
|
||||||
# too early for refund
|
# too early for refund
|
||||||
continue
|
return
|
||||||
else:
|
else:
|
||||||
if swap.preimage is None:
|
if swap.preimage is None:
|
||||||
swap.preimage = self.lnworker.get_preimage(swap.payment_hash)
|
swap.preimage = self.lnworker.get_preimage(swap.payment_hash)
|
||||||
if swap.preimage is None:
|
if swap.preimage is None:
|
||||||
if funding_conf <= 0:
|
if funding_conf <= 0:
|
||||||
continue
|
return
|
||||||
key = swap.payment_hash.hex()
|
key = swap.payment_hash.hex()
|
||||||
if -delta <= MIN_LOCKTIME_DELTA:
|
if -delta <= MIN_LOCKTIME_DELTA:
|
||||||
if key in self.invoices_to_pay:
|
if key in self.invoices_to_pay:
|
||||||
# fixme: should consider cltv of ln payment
|
# fixme: should consider cltv of ln payment
|
||||||
self.logger.info(f'locktime too close {key} {delta}')
|
self.logger.info(f'locktime too close {key} {delta}')
|
||||||
self.invoices_to_pay.pop(key, None)
|
self.invoices_to_pay.pop(key, None)
|
||||||
continue
|
return
|
||||||
if key not in self.invoices_to_pay:
|
if key not in self.invoices_to_pay:
|
||||||
self.invoices_to_pay[key] = 0
|
self.invoices_to_pay[key] = 0
|
||||||
continue
|
return
|
||||||
|
|
||||||
if self.network.config.TEST_SWAPSERVER_REFUND:
|
if self.network.config.TEST_SWAPSERVER_REFUND:
|
||||||
# for testing: do not create claim tx
|
# for testing: do not create claim tx
|
||||||
continue
|
return
|
||||||
|
|
||||||
if spent_height is not None:
|
if spent_height is not None:
|
||||||
continue
|
return
|
||||||
try:
|
try:
|
||||||
tx = self._create_and_sign_claim_tx(txin=txin, swap=swap, config=self.wallet.config)
|
tx = self._create_and_sign_claim_tx(txin=txin, swap=swap, config=self.wallet.config)
|
||||||
except BelowDustLimit:
|
except BelowDustLimit:
|
||||||
self.logger.info('utxo value below dust threshold')
|
self.logger.info('utxo value below dust threshold')
|
||||||
continue
|
return
|
||||||
self.logger.info(f'adding claim tx {tx.txid()}')
|
self.logger.info(f'adding claim tx {tx.txid()}')
|
||||||
self.wallet.adb.add_transaction(tx)
|
self.wallet.adb.add_transaction(tx)
|
||||||
swap.spending_txid = tx.txid()
|
swap.spending_txid = tx.txid()
|
||||||
@@ -391,7 +410,7 @@ class SwapManager(Logger):
|
|||||||
invoice=None,
|
invoice=None,
|
||||||
prepay=True,
|
prepay=True,
|
||||||
)
|
)
|
||||||
self.lnworker.register_callback_for_hold_invoice(payment_hash, self.hold_invoice_callback)
|
self.lnworker.register_hold_invoice(payment_hash, self.hold_invoice_callback)
|
||||||
return swap, invoice, prepay_invoice
|
return swap, invoice, prepay_invoice
|
||||||
|
|
||||||
def add_normal_swap(
|
def add_normal_swap(
|
||||||
@@ -652,7 +671,7 @@ class SwapManager(Logger):
|
|||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
async def callback(payment_hash):
|
async def callback(payment_hash):
|
||||||
await self.broadcast_funding_tx(swap, tx)
|
await self.broadcast_funding_tx(swap, tx)
|
||||||
self.lnworker.register_callback_for_hold_invoice(payment_hash, callback)
|
self.lnworker.register_hold_invoice(payment_hash, callback)
|
||||||
# wait for funding tx
|
# wait for funding tx
|
||||||
lnaddr = lndecode(invoice)
|
lnaddr = lndecode(invoice)
|
||||||
while swap.funding_txid is None and not lnaddr.is_expired():
|
while swap.funding_txid is None and not lnaddr.is_expired():
|
||||||
|
|||||||
@@ -288,7 +288,8 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
|||||||
_on_maybe_forwarded_htlc_resolved = LNWallet._on_maybe_forwarded_htlc_resolved
|
_on_maybe_forwarded_htlc_resolved = LNWallet._on_maybe_forwarded_htlc_resolved
|
||||||
_force_close_channel = LNWallet._force_close_channel
|
_force_close_channel = LNWallet._force_close_channel
|
||||||
suggest_splits = LNWallet.suggest_splits
|
suggest_splits = LNWallet.suggest_splits
|
||||||
register_callback_for_hold_invoice = LNWallet.register_callback_for_hold_invoice
|
register_hold_invoice = LNWallet.register_hold_invoice
|
||||||
|
unregister_hold_invoice = LNWallet.unregister_hold_invoice
|
||||||
add_payment_info_for_hold_invoice = LNWallet.add_payment_info_for_hold_invoice
|
add_payment_info_for_hold_invoice = LNWallet.add_payment_info_for_hold_invoice
|
||||||
|
|
||||||
update_mpp_with_received_htlc = LNWallet.update_mpp_with_received_htlc
|
update_mpp_with_received_htlc = LNWallet.update_mpp_with_received_htlc
|
||||||
@@ -784,7 +785,7 @@ class TestPeer(ElectrumTestCase):
|
|||||||
w2.save_preimage(payment_hash, preimage)
|
w2.save_preimage(payment_hash, preimage)
|
||||||
else:
|
else:
|
||||||
raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
|
raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
|
||||||
w2.register_callback_for_hold_invoice(payment_hash, cb)
|
w2.register_hold_invoice(payment_hash, cb)
|
||||||
|
|
||||||
if test_bundle:
|
if test_bundle:
|
||||||
lnaddr2, pay_req2 = self.prepare_invoice(w2)
|
lnaddr2, pay_req2 = self.prepare_invoice(w2)
|
||||||
|
|||||||
Reference in New Issue
Block a user