electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 11c3ca281c30c7830c705c8d1295e4cd7d9d567f
parent 72eb179c7a0cae032d44333a3098659551900796
Author: ThomasV <thomasv@electrum.org>
Date:   Fri,  5 Oct 2018 19:37:55 +0200

create sweep transaction outside of lnwatcher

Diffstat:
Melectrum/lnbase.py | 8++++----
Melectrum/lnhtlc.py | 179++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Melectrum/lnutil.py | 17+++++++++++++++++
Melectrum/lnwatcher.py | 214+++----------------------------------------------------------------------------
Melectrum/lnworker.py | 8++++----
5 files changed, 201 insertions(+), 225 deletions(-)

diff --git a/electrum/lnbase.py b/electrum/lnbase.py @@ -519,7 +519,7 @@ class Peer(PrintError): return local_config, per_commitment_secret_seed @aiosafe - async def channel_establishment_flow(self, password, funding_sat, push_msat, temp_channel_id, sweep_address): + async def channel_establishment_flow(self, password, funding_sat, push_msat, temp_channel_id): await self.initialized local_config, per_commitment_secret_seed = self.make_local_config(funding_sat, push_msat, LOCAL) # amounts @@ -613,7 +613,7 @@ class Peer(PrintError): } m = HTLCStateMachine(chan) m.lnwatcher = self.lnwatcher - m.sweep_address = sweep_address + m.sweep_address = self.lnworker.sweep_address sig_64, _ = m.sign_next_commitment() self.send_message(gen_msg("funding_created", temporary_channel_id=temp_channel_id, @@ -715,7 +715,7 @@ class Peer(PrintError): } m = HTLCStateMachine(chan) m.lnwatcher = self.lnwatcher - m.sweep_address = self.lnworker.wallet.get_unused_address() + m.sweep_address = self.lnworker.sweep_address remote_sig = funding_created['signature'] m.receive_new_commitment(remote_sig, []) sig_64, _ = m.sign_next_commitment() @@ -728,7 +728,7 @@ class Peer(PrintError): m.remote_state = m.remote_state._replace(ctn=0) m.local_state = m.local_state._replace(ctn=0, current_commitment_signature=remote_sig) self.lnworker.save_channel(m) - self.lnwatcher.watch_channel(m, m.sweep_address, partial(self.lnworker.on_channel_utxos, m)) + self.lnwatcher.watch_channel(m, partial(self.lnworker.on_channel_utxos, m)) self.lnworker.on_channels_updated() while True: try: diff --git a/electrum/lnhtlc.py b/electrum/lnhtlc.py @@ -6,19 +6,21 @@ from enum import Enum, auto from typing import Optional from .util import bfh, PrintError, bh2u -from .bitcoin import Hash, TYPE_SCRIPT +from .bitcoin import Hash, TYPE_SCRIPT, TYPE_ADDRESS from .bitcoin import redeem_script_to_address from .crypto import sha256 from . import ecc -from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore +from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, EncumberedTransaction from .lnutil import get_per_commitment_secret_from_seed -from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey +from .lnutil import make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script +from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey from .lnutil import sign_and_get_sig_string from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closing_tx, make_outputs from .lnutil import ScriptHtlc, SENT, RECEIVED -from .transaction import Transaction +from .transaction import Transaction, TxOutput, construct_witness +from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE FailHtlc = namedtuple("FailHtlc", ["htlc_id"]) @@ -259,8 +261,7 @@ class HTLCStateMachine(PrintError): if self.constraints.is_initiator and pending_fee.had(FUNDEE_ACKED): pending_fee.set(FUNDER_SIGNED) - if self.lnwatcher: - self.lnwatcher.process_new_offchain_ctx(self, pending_remote_commitment, ours=False) + self.process_new_offchain_ctx(pending_remote_commitment, ours=False) htlcsigs.sort() htlcsigs = [x[1] for x in htlcsigs] @@ -314,8 +315,7 @@ class HTLCStateMachine(PrintError): if self.constraints.is_initiator and pending_fee.had(FUNDEE_ACKED): pending_fee.set(FUNDER_SIGNED) - if self.lnwatcher: - self.lnwatcher.process_new_offchain_ctx(self, pending_local_commitment, ours=True) + self.process_new_offchain_ctx(pending_local_commitment, ours=True) def revoke_current_commitment(self): @@ -370,6 +370,30 @@ class HTLCStateMachine(PrintError): next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big')) return last_secret, this_point, next_point + # TODO batch sweeps + # TODO sweep HTLC outputs + def process_new_offchain_ctx(self, ctx, ours: bool): + funding_address = self.get_funding_address() + outpoint = self.funding_outpoint.to_str() + ctn = (self.local_state.ctn if ours else self.remote_state.ctn) + 1 + if ours: + our_per_commitment_secret = get_per_commitment_secret_from_seed( + self.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - ctn) + our_cur_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True) + encumbered_sweeptx = maybe_create_sweeptx_for_our_ctx_to_local(self, ctx, our_cur_pcp, self.sweep_address) + else: + their_cur_pcp = self.remote_state.next_per_commitment_point + encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_remote(self, ctx, their_cur_pcp, self.sweep_address) + self.lnwatcher.add_offchain_ctx(ctn, funding_address, ours, outpoint, ctx.txid(), encumbered_sweeptx) + + def process_new_revocation_secret(self, per_commitment_secret: bytes): + funding_address = self.get_funding_address() + outpoint = self.funding_outpoint.to_str() + ctx = self.remote_commitment_to_be_revoked + ctn = self.remote_state.ctn + encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_local(self, ctx, per_commitment_secret, self.sweep_address) + self.lnwatcher.add_revocation_secret(ctn, funding_address, outpoint, ctx.txid(), encumbered_sweeptx) + def receive_revocation(self, revocation): """ ReceiveRevocation processes a revocation sent by the remote party for the @@ -395,8 +419,7 @@ class HTLCStateMachine(PrintError): prev_remote_commitment = self.pending_remote_commitment self.remote_state.revocation_store.add_next_entry(revocation.per_commitment_secret) - if self.lnwatcher: - self.lnwatcher.process_new_revocation_secret(self, revocation.per_commitment_secret) + self.process_new_revocation_secret(revocation.per_commitment_secret) def mark_settled(subject): """ @@ -741,3 +764,139 @@ class HTLCStateMachine(PrintError): der_sig = bfh(closing_tx.sign_txin(0, self.local_config.multisig_key.privkey)) sig = ecc.sig_string_from_der_sig(der_sig[:-1]) return sig, fee_sat + +def maybe_create_sweeptx_for_their_ctx_to_remote(chan, ctx, their_pcp: bytes, + sweep_address) -> Optional[EncumberedTransaction]: + assert isinstance(their_pcp, bytes) + payment_bp_privkey = ecc.ECPrivkey(chan.local_config.payment_basepoint.privkey) + our_payment_privkey = derive_privkey(payment_bp_privkey.secret_scalar, their_pcp) + our_payment_privkey = ecc.ECPrivkey.from_secret_scalar(our_payment_privkey) + our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True) + to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey) + for output_idx, (type_, addr, val) in enumerate(ctx.outputs()): + if type_ == TYPE_ADDRESS and addr == to_remote_address: + break + else: + return None + sweep_tx = create_sweeptx_their_ctx_to_remote(address=sweep_address, + ctx=ctx, + output_idx=output_idx, + our_payment_privkey=our_payment_privkey) + return EncumberedTransaction(sweep_tx, csv_delay=0) + + +def maybe_create_sweeptx_for_their_ctx_to_local(chan, ctx, per_commitment_secret: bytes, + sweep_address) -> Optional[EncumberedTransaction]: + assert isinstance(per_commitment_secret, bytes) + per_commitment_point = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True) + revocation_privkey = derive_blinded_privkey(chan.local_config.revocation_basepoint.privkey, + per_commitment_secret) + revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True) + to_self_delay = chan.local_config.to_self_delay + delayed_pubkey = derive_pubkey(chan.remote_config.delayed_basepoint.pubkey, + per_commitment_point) + witness_script = bh2u(make_commitment_output_to_local_witness_script( + revocation_pubkey, to_self_delay, delayed_pubkey)) + to_local_address = redeem_script_to_address('p2wsh', witness_script) + for output_idx, o in enumerate(ctx.outputs()): + if o.type == TYPE_ADDRESS and o.address == to_local_address: + break + else: + return None + sweep_tx = create_sweeptx_ctx_to_local(address=sweep_address, + ctx=ctx, + output_idx=output_idx, + witness_script=witness_script, + privkey=revocation_privkey, + is_revocation=True) + return EncumberedTransaction(sweep_tx, csv_delay=0) + + +def maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_pcp: bytes, + sweep_address) -> Optional[EncumberedTransaction]: + assert isinstance(our_pcp, bytes) + delayed_bp_privkey = ecc.ECPrivkey(chan.local_config.delayed_basepoint.privkey) + our_localdelayed_privkey = derive_privkey(delayed_bp_privkey.secret_scalar, our_pcp) + our_localdelayed_privkey = ecc.ECPrivkey.from_secret_scalar(our_localdelayed_privkey) + our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True) + revocation_pubkey = derive_blinded_pubkey(chan.remote_config.revocation_basepoint.pubkey, + our_pcp) + to_self_delay = chan.remote_config.to_self_delay + witness_script = bh2u(make_commitment_output_to_local_witness_script( + revocation_pubkey, to_self_delay, our_localdelayed_pubkey)) + to_local_address = redeem_script_to_address('p2wsh', witness_script) + for output_idx, o in enumerate(ctx.outputs()): + if o.type == TYPE_ADDRESS and o.address == to_local_address: + break + else: + return None + sweep_tx = create_sweeptx_ctx_to_local(address=sweep_address, + ctx=ctx, + output_idx=output_idx, + witness_script=witness_script, + privkey=our_localdelayed_privkey.get_secret_bytes(), + is_revocation=False, + to_self_delay=to_self_delay) + + return EncumberedTransaction(sweep_tx, csv_delay=to_self_delay) + + +def create_sweeptx_their_ctx_to_remote(address, ctx, output_idx: int, our_payment_privkey: ecc.ECPrivkey, + fee_per_kb: int=None) -> Transaction: + our_payment_pubkey = our_payment_privkey.get_public_key_hex(compressed=True) + val = ctx.outputs()[output_idx].value + sweep_inputs = [{ + 'type': 'p2wpkh', + 'x_pubkeys': [our_payment_pubkey], + 'num_sig': 1, + 'prevout_n': output_idx, + 'prevout_hash': ctx.txid(), + 'value': val, + 'coinbase': False, + 'signatures': [None], + }] + tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh + if fee_per_kb is None: fee_per_kb = FEERATE_FALLBACK_STATIC_FEE + fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes) + sweep_outputs = [TxOutput(TYPE_ADDRESS, address, val-fee)] + sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs) + sweep_tx.set_rbf(True) + sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)}) + if not sweep_tx.is_complete(): + raise Exception('channel close sweep tx is not complete') + return sweep_tx + + +def create_sweeptx_ctx_to_local(address, ctx, output_idx: int, witness_script: str, + privkey: bytes, is_revocation: bool, + to_self_delay: int=None, + fee_per_kb: int=None) -> Transaction: + """Create a txn that sweeps the 'to_local' output of a commitment + transaction into our wallet. + + privkey: either revocation_privkey or localdelayed_privkey + is_revocation: tells us which ^ + """ + val = ctx.outputs()[output_idx].value + sweep_inputs = [{ + 'scriptSig': '', + 'type': 'p2wsh', + 'signatures': [], + 'num_sig': 0, + 'prevout_n': output_idx, + 'prevout_hash': ctx.txid(), + 'value': val, + 'coinbase': False, + 'preimage_script': witness_script, + }] + if to_self_delay is not None: + sweep_inputs[0]['sequence'] = to_self_delay + tx_size_bytes = 121 # approx size of to_local -> p2wpkh + if fee_per_kb is None: fee_per_kb = FEERATE_FALLBACK_STATIC_FEE + fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes) + sweep_outputs = [TxOutput(TYPE_ADDRESS, address, val - fee)] + sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2) + sig = sweep_tx.sign_txin(0, privkey) + witness = construct_witness([sig, int(is_revocation), witness_script]) + sweep_tx.inputs()[0]['witness'] = witness + return sweep_tx diff --git a/electrum/lnutil.py b/electrum/lnutil.py @@ -543,3 +543,20 @@ class LnKeyFamily(IntEnum): def generate_keypair(ln_keystore: BIP32_KeyStore, key_family: LnKeyFamily, index: int) -> Keypair: return Keypair(*ln_keystore.get_keypair([key_family, 0, index], None)) + + +from typing import Optional + +class EncumberedTransaction(NamedTuple("EncumberedTransaction", [('tx', Transaction), + ('csv_delay', Optional[int])])): + def to_json(self) -> dict: + return { + 'tx': str(self.tx), + 'csv_delay': self.csv_delay, + } + + @classmethod + def from_json(cls, d: dict): + d2 = dict(d) + d2['tx'] = Transaction(d['tx']) + return EncumberedTransaction(**d2) diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py @@ -1,20 +1,11 @@ import threading -from typing import Optional, NamedTuple, Iterable +from typing import NamedTuple, Iterable import os from collections import defaultdict from .util import PrintError, bh2u, bfh, NoDynamicFeeEstimates, aiosafe -from .lnutil import (extract_ctn_from_tx_and_chan, derive_privkey, - get_per_commitment_secret_from_seed, derive_pubkey, - make_commitment_output_to_remote_address, - RevocationStore, Outpoint) -from . import lnutil -from .bitcoin import redeem_script_to_address, TYPE_ADDRESS -from . import transaction -from .transaction import Transaction, TxOutput -from . import ecc +from .lnutil import EncumberedTransaction, Outpoint from . import wallet -from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE from .storage import WalletStorage from .address_synchronizer import AddressSynchronizer @@ -22,23 +13,9 @@ from .address_synchronizer import AddressSynchronizer TX_MINED_STATUS_DEEP, TX_MINED_STATUS_SHALLOW, TX_MINED_STATUS_MEMPOOL, TX_MINED_STATUS_FREE = range(0, 4) -class EncumberedTransaction(NamedTuple("EncumberedTransaction", [('tx', Transaction), - ('csv_delay', Optional[int])])): - def to_json(self) -> dict: - return { - 'tx': str(self.tx), - 'csv_delay': self.csv_delay, - } - - @classmethod - def from_json(cls, d: dict): - d2 = dict(d) - d2['tx'] = Transaction(d['tx']) - return EncumberedTransaction(**d2) class ChannelWatchInfo(NamedTuple("ChannelWatchInfo", [('outpoint', Outpoint), - ('sweep_address', str), ('local_pubkey', bytes), ('remote_pubkey', bytes), ('last_ctn_our_ctx', int), @@ -47,7 +24,6 @@ class ChannelWatchInfo(NamedTuple("ChannelWatchInfo", [('outpoint', Outpoint), def to_json(self) -> dict: return { 'outpoint': self.outpoint, - 'sweep_address': self.sweep_address, 'local_pubkey': bh2u(self.local_pubkey), 'remote_pubkey': bh2u(self.remote_pubkey), 'last_ctn_our_ctx': self.last_ctn_our_ctx, @@ -112,13 +88,12 @@ class LNWatcher(PrintError): storage.put('sweepstore', sweepstore) storage.write() - def watch_channel(self, chan, sweep_address, callback_funding_txo_spent): + def watch_channel(self, chan, callback_funding_txo_spent): address = chan.get_funding_address() self.watch_address(address) with self.lock: if address not in self.channel_info: self.channel_info[address] = ChannelWatchInfo(outpoint=chan.funding_outpoint, - sweep_address=sweep_address, local_pubkey=chan.local_config.payment_basepoint.pubkey, remote_pubkey=chan.remote_config.payment_basepoint.pubkey, last_ctn_our_ctx=0, @@ -212,17 +187,6 @@ class LNWatcher(PrintError): .format(num_conf, e_tx.csv_delay, funding_outpoint, ctx.txid())) return keep_watching_this - def _get_sweep_address_for_chan(self, chan) -> str: - funding_address = chan.get_funding_address() - try: - channel_info = self.channel_info[funding_address] - except KeyError: - # this is used during channel opening, as we only start watching - # the channel once it gets into the "opening" state, but we need to - # process the first ctx before that. - return chan.sweep_address - return channel_info.sweep_address - def _get_last_ctn_for_processed_ctx(self, funding_address: str, ours: bool) -> int: try: ci = self.channel_info[funding_address] @@ -259,45 +223,19 @@ class LNWatcher(PrintError): ci = ci._replace(last_ctn_revoked_pcs=ci.last_ctn_revoked_pcs + 1) self.channel_info[funding_address] = ci - # TODO batch sweeps - # TODO sweep HTLC outputs - def process_new_offchain_ctx(self, chan, ctx, ours: bool): - funding_address = chan.get_funding_address() - ctn = extract_ctn_from_tx_and_chan(ctx, chan) - latest_ctn_on_channel = chan.local_state.ctn if ours else chan.remote_state.ctn + def add_offchain_ctx(self, ctn, funding_address, ours, outpoint, ctx_id, encumbered_sweeptx): last_ctn_watcher_saw = self._get_last_ctn_for_processed_ctx(funding_address, ours) - if latest_ctn_on_channel + 1 != ctn: - raise Exception('unexpected ctn {}. latest is {}. our ctx: {}'.format(ctn, latest_ctn_on_channel, ours)) if last_ctn_watcher_saw + 1 != ctn: raise Exception('watcher skipping ctns!! ctn {}. last seen {}. our ctx: {}'.format(ctn, last_ctn_watcher_saw, ours)) - #self.print_error("process_new_offchain_ctx. funding {}, ours {}, ctn {}, ctx {}" - # .format(chan.funding_outpoint.to_str(), ours, ctn, ctx.txid())) - sweep_address = self._get_sweep_address_for_chan(chan) - if ours: - our_per_commitment_secret = get_per_commitment_secret_from_seed( - chan.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - ctn) - our_cur_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True) - encumbered_sweeptx = maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_cur_pcp, sweep_address) - else: - their_cur_pcp = chan.remote_state.next_per_commitment_point - encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_remote(chan, ctx, their_cur_pcp, sweep_address) - self.add_to_sweepstore(chan.funding_outpoint.to_str(), ctx.txid(), encumbered_sweeptx) + self.add_to_sweepstore(outpoint, ctx_id, encumbered_sweeptx) self._inc_last_ctn_for_processed_ctx(funding_address, ours) self.write_to_disk() - def process_new_revocation_secret(self, chan, per_commitment_secret: bytes): - funding_address = chan.get_funding_address() - ctx = chan.remote_commitment_to_be_revoked - ctn = extract_ctn_from_tx_and_chan(ctx, chan) - latest_ctn_on_channel = chan.remote_state.ctn + def add_revocation_secret(self, ctn, funding_address, outpoint, ctx_id, encumbered_sweeptx): last_ctn_watcher_saw = self._get_last_ctn_for_revoked_secret(funding_address) - if latest_ctn_on_channel != ctn: - raise Exception('unexpected ctn {}. latest is {}'.format(ctn, latest_ctn_on_channel)) if last_ctn_watcher_saw + 1 != ctn: raise Exception('watcher skipping ctns!! ctn {}. last seen {}'.format(ctn, last_ctn_watcher_saw)) - sweep_address = self._get_sweep_address_for_chan(chan) - encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_local(chan, ctx, per_commitment_secret, sweep_address) - self.add_to_sweepstore(chan.funding_outpoint.to_str(), ctx.txid(), encumbered_sweeptx) + self.add_to_sweepstore(outpoint, ctx_id, encumbered_sweeptx) self._inc_last_ctn_for_revoked_secret(funding_address) self.write_to_disk() @@ -336,141 +274,3 @@ class LNWatcher(PrintError): def print_tx_broadcast_result(self, res): success, msg = res self.print_error('broadcast: {}, {}'.format('success' if success else 'failure', msg)) - - - -def maybe_create_sweeptx_for_their_ctx_to_remote(chan, ctx, their_pcp: bytes, - sweep_address) -> Optional[EncumberedTransaction]: - assert isinstance(their_pcp, bytes) - payment_bp_privkey = ecc.ECPrivkey(chan.local_config.payment_basepoint.privkey) - our_payment_privkey = derive_privkey(payment_bp_privkey.secret_scalar, their_pcp) - our_payment_privkey = ecc.ECPrivkey.from_secret_scalar(our_payment_privkey) - our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True) - to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey) - for output_idx, (type_, addr, val) in enumerate(ctx.outputs()): - if type_ == TYPE_ADDRESS and addr == to_remote_address: - break - else: - return None - sweep_tx = create_sweeptx_their_ctx_to_remote(address=sweep_address, - ctx=ctx, - output_idx=output_idx, - our_payment_privkey=our_payment_privkey) - return EncumberedTransaction(sweep_tx, csv_delay=0) - - -def maybe_create_sweeptx_for_their_ctx_to_local(chan, ctx, per_commitment_secret: bytes, - sweep_address) -> Optional[EncumberedTransaction]: - assert isinstance(per_commitment_secret, bytes) - per_commitment_point = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True) - revocation_privkey = lnutil.derive_blinded_privkey(chan.local_config.revocation_basepoint.privkey, - per_commitment_secret) - revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True) - to_self_delay = chan.local_config.to_self_delay - delayed_pubkey = derive_pubkey(chan.remote_config.delayed_basepoint.pubkey, - per_commitment_point) - witness_script = bh2u(lnutil.make_commitment_output_to_local_witness_script( - revocation_pubkey, to_self_delay, delayed_pubkey)) - to_local_address = redeem_script_to_address('p2wsh', witness_script) - for output_idx, o in enumerate(ctx.outputs()): - if o.type == TYPE_ADDRESS and o.address == to_local_address: - break - else: - return None - sweep_tx = create_sweeptx_ctx_to_local(address=sweep_address, - ctx=ctx, - output_idx=output_idx, - witness_script=witness_script, - privkey=revocation_privkey, - is_revocation=True) - return EncumberedTransaction(sweep_tx, csv_delay=0) - - -def maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_pcp: bytes, - sweep_address) -> Optional[EncumberedTransaction]: - assert isinstance(our_pcp, bytes) - delayed_bp_privkey = ecc.ECPrivkey(chan.local_config.delayed_basepoint.privkey) - our_localdelayed_privkey = derive_privkey(delayed_bp_privkey.secret_scalar, our_pcp) - our_localdelayed_privkey = ecc.ECPrivkey.from_secret_scalar(our_localdelayed_privkey) - our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True) - revocation_pubkey = lnutil.derive_blinded_pubkey(chan.remote_config.revocation_basepoint.pubkey, - our_pcp) - to_self_delay = chan.remote_config.to_self_delay - witness_script = bh2u(lnutil.make_commitment_output_to_local_witness_script( - revocation_pubkey, to_self_delay, our_localdelayed_pubkey)) - to_local_address = redeem_script_to_address('p2wsh', witness_script) - for output_idx, o in enumerate(ctx.outputs()): - if o.type == TYPE_ADDRESS and o.address == to_local_address: - break - else: - return None - sweep_tx = create_sweeptx_ctx_to_local(address=sweep_address, - ctx=ctx, - output_idx=output_idx, - witness_script=witness_script, - privkey=our_localdelayed_privkey.get_secret_bytes(), - is_revocation=False, - to_self_delay=to_self_delay) - - return EncumberedTransaction(sweep_tx, csv_delay=to_self_delay) - - -def create_sweeptx_their_ctx_to_remote(address, ctx, output_idx: int, our_payment_privkey: ecc.ECPrivkey, - fee_per_kb: int=None) -> Transaction: - our_payment_pubkey = our_payment_privkey.get_public_key_hex(compressed=True) - val = ctx.outputs()[output_idx].value - sweep_inputs = [{ - 'type': 'p2wpkh', - 'x_pubkeys': [our_payment_pubkey], - 'num_sig': 1, - 'prevout_n': output_idx, - 'prevout_hash': ctx.txid(), - 'value': val, - 'coinbase': False, - 'signatures': [None], - }] - tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh - if fee_per_kb is None: fee_per_kb = FEERATE_FALLBACK_STATIC_FEE - fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes) - sweep_outputs = [TxOutput(TYPE_ADDRESS, address, val-fee)] - sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs) - sweep_tx.set_rbf(True) - sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)}) - if not sweep_tx.is_complete(): - raise Exception('channel close sweep tx is not complete') - return sweep_tx - - -def create_sweeptx_ctx_to_local(address, ctx, output_idx: int, witness_script: str, - privkey: bytes, is_revocation: bool, - to_self_delay: int=None, - fee_per_kb: int=None) -> Transaction: - """Create a txn that sweeps the 'to_local' output of a commitment - transaction into our wallet. - - privkey: either revocation_privkey or localdelayed_privkey - is_revocation: tells us which ^ - """ - val = ctx.outputs()[output_idx].value - sweep_inputs = [{ - 'scriptSig': '', - 'type': 'p2wsh', - 'signatures': [], - 'num_sig': 0, - 'prevout_n': output_idx, - 'prevout_hash': ctx.txid(), - 'value': val, - 'coinbase': False, - 'preimage_script': witness_script, - }] - if to_self_delay is not None: - sweep_inputs[0]['sequence'] = to_self_delay - tx_size_bytes = 121 # approx size of to_local -> p2wpkh - if fee_per_kb is None: fee_per_kb = FEERATE_FALLBACK_STATIC_FEE - fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes) - sweep_outputs = [TxOutput(TYPE_ADDRESS, address, val - fee)] - sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2) - sig = sweep_tx.sign_txin(0, privkey) - witness = transaction.construct_witness([sig, int(is_revocation), witness_script]) - sweep_tx.inputs()[0]['witness'] = witness - return sweep_tx diff --git a/electrum/lnworker.py b/electrum/lnworker.py @@ -52,9 +52,10 @@ class LNWorker(PrintError): self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))} for c in self.channels.values(): c.lnwatcher = network.lnwatcher + c.sweep_address = self.sweep_address self.invoices = wallet.storage.get('lightning_invoices', {}) for chan_id, chan in self.channels.items(): - self.network.lnwatcher.watch_channel(chan, self.sweep_address, partial(self.on_channel_utxos, chan)) + self.network.lnwatcher.watch_channel(chan, partial(self.on_channel_utxos, chan)) self._last_tried_peer = {} # LNPeerAddr -> unix timestamp self._add_peers_from_config() # wait until we see confirmations @@ -184,13 +185,12 @@ class LNWorker(PrintError): openingchannel = await peer.channel_establishment_flow(password, funding_sat=local_amount_sat + push_sat, push_msat=push_sat * 1000, - temp_channel_id=os.urandom(32), - sweep_address=self.sweep_address) + temp_channel_id=os.urandom(32)) if not openingchannel: self.print_error("Channel_establishment_flow returned None") return self.save_channel(openingchannel) - self.network.lnwatcher.watch_channel(openingchannel, self.sweep_address, partial(self.on_channel_utxos, openingchannel)) + self.network.lnwatcher.watch_channel(openingchannel, partial(self.on_channel_utxos, openingchannel)) self.on_channels_updated() def on_channels_updated(self):