1
0

submarine swaps: backport changes from batch_payment_manager

- add 'is_funded' method to SwapData
 - sign claim transactions using the 'make_witness' method
This commit is contained in:
ThomasV
2025-02-14 14:12:12 +01:00
parent 2d8c26f211
commit 18baf003d4
3 changed files with 40 additions and 55 deletions

View File

@@ -161,22 +161,31 @@ class SwapData(StoredObject):
def payment_hash(self) -> bytes: def payment_hash(self) -> bytes:
return self._payment_hash return self._payment_hash
def is_funded(self) -> bool:
return self.funding_txid is not None
def create_claim_tx( def create_claim_tx(
*, *,
txin: PartialTxInput, txin: PartialTxInput,
witness_script: bytes, swap: SwapData,
address: str, config: 'SimpleConfig',
amount_sat: int,
locktime: int,
) -> PartialTransaction: ) -> PartialTransaction:
"""Create tx to either claim successful reverse-swap, """Create tx to either claim successful reverse-swap,
or to get refunded for timed-out forward-swap. or to get refunded for timed-out forward-swap.
""" """
txin.nsequence = 0xffffffff - 2 # FIXME the mining fee should depend on swap.is_reverse.
txin.script_sig = b'' # the txs are not the same size...
txin.witness_script = witness_script amount_sat = txin.value_sats() - SwapManager._get_fee(size=CLAIM_FEE_SIZE, config=config)
txout = PartialTxOutput.from_address_and_value(address, amount_sat) if amount_sat < dust_threshold():
raise BelowDustLimit()
txin, locktime = SwapManager.create_claim_txin(txin=txin, swap=swap, config=config)
txout = PartialTxOutput.from_address_and_value(swap.receive_address, amount_sat)
tx = PartialTransaction.from_io([txin], [txout], version=2, locktime=locktime) tx = PartialTransaction.from_io([txin], [txout], version=2, locktime=locktime)
sig = tx.sign_txin(0, txin.privkey)
txin.script_sig = b''
txin.witness = txin.make_witness(sig)
assert tx.is_complete()
return tx return tx
@@ -316,7 +325,7 @@ class SwapManager(Logger):
""" we must not have broadcast the funding tx """ """ we must not have broadcast the funding tx """
if swap is None: if swap is None:
return return
if swap.funding_txid is not None: if swap.is_funded():
self.logger.info(f'cannot cancel swap {swap.payment_hash.hex()}: already funded') self.logger.info(f'cannot cancel swap {swap.payment_hash.hex()}: already funded')
return return
self._fail_swap(swap, 'user cancelled') self._fail_swap(swap, 'user cancelled')
@@ -330,7 +339,7 @@ class SwapManager(Logger):
e = OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'') e = OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
self.lnworker.save_forwarding_failure(payment_key.hex(), failure_message=e) self.lnworker.save_forwarding_failure(payment_key.hex(), failure_message=e)
self.lnwatcher.remove_callback(swap.lockup_address) self.lnwatcher.remove_callback(swap.lockup_address)
if swap.funding_txid is None: if not swap.is_funded():
self.swaps.pop(swap.payment_hash.hex()) self.swaps.pop(swap.payment_hash.hex())
@log_exceptions @log_exceptions
@@ -443,7 +452,7 @@ class SwapManager(Logger):
if spent_height is not None and not should_bump_fee: if spent_height is not None and not should_bump_fee:
return return
try: try:
tx = self._create_and_sign_claim_tx(txin=txin, swap=swap, config=self.wallet.config) tx = create_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')
return return
@@ -485,7 +494,7 @@ class SwapManager(Logger):
key = payment_hash.hex() key = payment_hash.hex()
if key in self.swaps: if key in self.swaps:
swap = self.swaps[key] swap = self.swaps[key]
if swap.funding_txid is None: if not swap.is_funded():
password = self.wallet.get_unlocked_password() password = self.wallet.get_unlocked_password()
for batch_rbf in [False]: for batch_rbf in [False]:
# FIXME: tx batching is disabled, because extra logic is needed to handle # FIXME: tx batching is disabled, because extra logic is needed to handle
@@ -795,6 +804,9 @@ class SwapManager(Logger):
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
return swap.funding_txid return swap.funding_txid
def create_funding_output(self, swap):
return PartialTxOutput.from_address_and_value(swap.lockup_address, swap.onchain_amount)
def create_funding_tx( def create_funding_tx(
self, self,
swap: SwapData, swap: SwapData,
@@ -806,7 +818,7 @@ class SwapManager(Logger):
# note: rbf must not decrease payment # note: rbf must not decrease payment
# this is taken care of in wallet._is_rbf_allowed_to_touch_tx_output # this is taken care of in wallet._is_rbf_allowed_to_touch_tx_output
if tx is None: if tx is None:
funding_output = PartialTxOutput.from_address_and_value(swap.lockup_address, swap.onchain_amount) funding_output = self.create_funding_output(swap)
tx = self.wallet.create_transaction( tx = self.wallet.create_transaction(
outputs=[funding_output], outputs=[funding_output],
rbf=True, rbf=True,
@@ -1089,13 +1101,11 @@ class SwapManager(Logger):
def is_lockup_address_for_a_swap(self, addr: str) -> bool: def is_lockup_address_for_a_swap(self, addr: str) -> bool:
return bool(self._swaps_by_lockup_address.get(addr)) return bool(self._swaps_by_lockup_address.get(addr))
def add_txin_info(self, txin: PartialTxInput) -> None: @classmethod
def add_txin_info(cls, swap, txin: PartialTxInput) -> None:
"""Add some info to a claim txin. """Add some info to a claim txin.
note: even without signing, this is useful for tx size estimation. note: even without signing, this is useful for tx size estimation.
""" """
swap = self.get_swap_by_claim_txin(txin)
if not swap:
return
preimage = swap.preimage if swap.is_reverse else 0 preimage = swap.preimage if swap.is_reverse else 0
witness_script = swap.redeem_script witness_script = swap.redeem_script
txin.script_sig = b'' txin.script_sig = b''
@@ -1106,45 +1116,27 @@ class SwapManager(Logger):
txin.nsequence = 0xffffffff - 2 txin.nsequence = 0xffffffff - 2
@classmethod @classmethod
def sign_tx(cls, tx: PartialTransaction, swap: SwapData) -> None: def create_claim_txin(
preimage = swap.preimage if swap.is_reverse else 0
witness_script = swap.redeem_script
txin = tx.inputs()[0]
assert len(tx.inputs()) == 1, f"expected 1 input for swap claim tx. found {len(tx.inputs())}"
assert txin.prevout.txid.hex() == swap.funding_txid
txin.script_sig = b''
txin.witness_script = witness_script
sig = tx.sign_txin(0, swap.privkey)
witness = [sig, preimage, witness_script]
txin.witness = construct_witness(witness)
@classmethod
def _create_and_sign_claim_tx(
cls, cls,
*, *,
txin: PartialTxInput, txin: PartialTxInput,
swap: SwapData, swap: SwapData,
config: 'SimpleConfig', config: 'SimpleConfig',
) -> PartialTransaction: ) -> PartialTransaction:
# FIXME the mining fee should depend on swap.is_reverse.
# the txs are not the same size...
amount_sat = txin.value_sats() - cls._get_fee(size=CLAIM_FEE_SIZE, config=config)
if amount_sat < dust_threshold():
raise BelowDustLimit()
if swap.is_reverse: # successful reverse swap if swap.is_reverse: # successful reverse swap
locktime = 0 locktime = 0
# preimage will be set in sign_tx # preimage will be set in sign_tx
else: # timing out forward swap else: # timing out forward swap
locktime = swap.locktime locktime = swap.locktime
tx = create_claim_tx( cls.add_txin_info(swap, txin)
txin=txin, txin.privkey = swap.privkey
witness_script=swap.redeem_script, def make_witness(sig):
address=swap.receive_address, # preimae not known yet
amount_sat=amount_sat, preimage = swap.preimage if swap.is_reverse else 0
locktime=locktime, witness_script = swap.redeem_script
) return construct_witness([sig, preimage, witness_script])
cls.sign_tx(tx, swap) txin.make_witness = make_witness
return tx return txin, locktime
def max_amount_forward_swap(self) -> Optional[int]: def max_amount_forward_swap(self) -> Optional[int]:
""" returns None if we cannot swap """ """ returns None if we cannot swap """

View File

@@ -2509,8 +2509,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
if not is_mine: if not is_mine:
is_mine = self._learn_derivation_path_for_address_from_txinout(txin, address) is_mine = self._learn_derivation_path_for_address_from_txinout(txin, address)
if not is_mine: if not is_mine:
if self.lnworker:
self.lnworker.swap_manager.add_txin_info(txin)
return return
txin.script_descriptor = self.get_script_descriptor_for_address(address) txin.script_descriptor = self.get_script_descriptor_for_address(address)
txin.is_mine = True txin.is_mine = True
@@ -2596,11 +2594,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
return return
if any(DummyAddress.is_dummy_address(txout.address) for txout in tx.outputs()): if any(DummyAddress.is_dummy_address(txout.address) for txout in tx.outputs()):
raise DummyAddressUsedInTxException("tried to sign tx with dummy address!") raise DummyAddressUsedInTxException("tried to sign tx with dummy address!")
# note: swap signing does not require the password
swap = self.get_swap_by_claim_tx(tx)
if swap:
self.lnworker.swap_manager.sign_tx(tx, swap)
return tx
# check if signing is dangerous # check if signing is dangerous
sh_danger = self.check_sighash(tx) sh_danger = self.check_sighash(tx)

View File

@@ -1,7 +1,7 @@
from electrum import SimpleConfig from electrum import SimpleConfig
from electrum.util import bfh from electrum.util import bfh
from electrum.transaction import PartialTxInput, TxOutpoint from electrum.transaction import PartialTxInput, TxOutpoint
from electrum.submarine_swaps import SwapManager, SwapData from electrum.submarine_swaps import SwapData, create_claim_tx
from . import ElectrumTestCase from . import ElectrumTestCase
@@ -35,7 +35,7 @@ class TestSwapTxs(ElectrumTestCase):
prevout=TxOutpoint(txid=bfh(swap_data.funding_txid), out_idx=0), prevout=TxOutpoint(txid=bfh(swap_data.funding_txid), out_idx=0),
) )
txin._trusted_value_sats = swap_data.onchain_amount txin._trusted_value_sats = swap_data.onchain_amount
tx = SwapManager._create_and_sign_claim_tx( tx = create_claim_tx(
txin=txin, txin=txin,
swap=swap_data, swap=swap_data,
config=self.config, config=self.config,
@@ -65,7 +65,7 @@ class TestSwapTxs(ElectrumTestCase):
prevout=TxOutpoint(txid=bfh(swap_data.funding_txid), out_idx=0), prevout=TxOutpoint(txid=bfh(swap_data.funding_txid), out_idx=0),
) )
txin._trusted_value_sats = swap_data.onchain_amount txin._trusted_value_sats = swap_data.onchain_amount
tx = SwapManager._create_and_sign_claim_tx( tx = create_claim_tx(
txin=txin, txin=txin,
swap=swap_data, swap=swap_data,
config=self.config, config=self.config,