diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index 30a07ec29..dbe2fc99d 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -11,7 +11,7 @@ from .crypto import sha256, hash_160 from .ecc import ECPrivkey from .bitcoin import (script_to_p2wsh, opcodes, p2wsh_nested_script, push_script, is_segwit_address, construct_witness) -from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction +from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction, Transaction, TxInput from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey from .util import log_exceptions from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY, ln_dummy_address @@ -246,7 +246,7 @@ class SwapManager(Logger): assert self.lnwatcher privkey = os.urandom(32) pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True) - lnaddr, invoice = await self.lnworker.create_invoice( + lnaddr, invoice = self.lnworker.create_invoice( amount_msat=lightning_amount_sat * 1000, message='swap', expiry=3600 * 24, @@ -535,9 +535,12 @@ class SwapManager(Logger): send_amount += self.get_claim_fee() return send_amount - def get_swap_by_tx(self, tx): + def get_swap_by_tx(self, tx: Transaction) -> Optional[SwapData]: # determine if tx is spending from a swap txin = tx.inputs()[0] + return self.get_swap_by_claim_txin(txin) + + def get_swap_by_claim_txin(self, txin: TxInput) -> Optional[SwapData]: for key, swap in self.swaps.items(): if txin.prevout.txid.hex() == swap.funding_txid: return swap @@ -549,10 +552,29 @@ class SwapManager(Logger): return True return False - def sign_tx(self, tx, swap): + def add_txin_info(self, txin: PartialTxInput) -> None: + """Add some info to a claim txin. + 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 + witness_script = swap.redeem_script + txin.script_type = 'p2wsh' + txin.num_sig = 1 # hack so that txin not considered "is_complete" + txin.script_sig = b'' + txin.witness_script = witness_script + sig_dummy = b'\x00' * 71 # DER-encoded ECDSA sig, with low S and low R + witness = [sig_dummy, preimage, witness_script] + txin.witness_sizehint = len(bytes.fromhex(construct_witness(witness))) + + def sign_tx(self, tx: PartialTransaction, swap: SwapData) -> None: 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_type = 'p2wsh' txin.script_sig = b'' txin.witness_script = witness_script diff --git a/electrum/transaction.py b/electrum/transaction.py index 57cb89995..7e4d206ce 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -712,6 +712,8 @@ class Transaction: if not txin.is_segwit(): return construct_witness([]) + if estimate_size and txin.witness_sizehint is not None: + return '00' * txin.witness_sizehint if _type in ('address', 'unknown') and estimate_size: _type = cls.guess_txintype_from_address(txin.address) pubkeys, sig_list = cls.get_siglist(txin, estimate_size=estimate_size) @@ -1213,6 +1215,7 @@ class PartialTxInput(TxInput, PSBTSection): self.spent_txid = None # type: Optional[str] # txid of the spender self._is_p2sh_segwit = None # type: Optional[bool] # None means unknown self._is_native_segwit = None # type: Optional[bool] # None means unknown + self.witness_sizehint = None # type: Optional[int] # byte size of serialized complete witness, for tx size est @property def utxo(self): diff --git a/electrum/wallet.py b/electrum/wallet.py index 5c7278e17..88f4eb676 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -1929,10 +1929,13 @@ class Abstract_Wallet(AddressSynchronizer, ABC): address = self.get_txin_address(txin) # note: we add input utxos regardless of is_mine self._add_input_utxo_info(txin, ignore_network_issues=ignore_network_issues, address=address) - if not self.is_mine(address): + is_mine = self.is_mine(address) + if not is_mine: is_mine = self._learn_derivation_path_for_address_from_txinout(txin, address) - if not is_mine: - return + if not is_mine: + if self.lnworker: + self.lnworker.swap_manager.add_txin_info(txin) + return # set script_type first, as later checks might rely on it: txin.script_type = self.get_txin_type(address) txin.num_sig = self.m if isinstance(self, Multisig_Wallet) else 1