electrum

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

commit 8d99fe8243d19a85962eff159562d7e7ee17085b
parent 3dacc525e63570b835246ee06e1c97a08c81deee
Author: ThomasV <thomasv@electrum.org>
Date:   Thu, 30 May 2019 10:11:15 +0200

Let lnworker sweep HTLC outputs after breach, instead of lnwatcher

Diffstat:
Melectrum/lnchannel.py | 15---------------
Melectrum/lnsweep.py | 90+++++++++++++++++++++++++------------------------------------------------------
2 files changed, 28 insertions(+), 77 deletions(-)

diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py @@ -806,18 +806,3 @@ class Channel(Logger): tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig)) assert tx.is_complete() return tx - - def included_htlcs_in_their_latest_ctxs(self, htlc_initiator) -> Dict[int, List[UpdateAddHtlc]]: - """ A map from commitment number to list of HTLCs in - their latest two commitment transactions. - The oldest might have been revoked. """ - assert type(htlc_initiator) is HTLCOwner - direction = RECEIVED if htlc_initiator == LOCAL else SENT - old_ctn = self.config[REMOTE].ctn - old_htlcs = self.included_htlcs(REMOTE, direction, ctn=old_ctn) - - new_ctn = self.config[REMOTE].ctn+1 - new_htlcs = self.included_htlcs(REMOTE, direction, ctn=new_ctn) - - return {old_ctn: old_htlcs, - new_ctn: new_htlcs, } diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py @@ -66,18 +66,7 @@ def create_sweeptxs_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per we_receive=not is_received_htlc, commit=ctx, htlc=htlc) - htlc_tx_txin = htlc_tx.inputs()[0] - htlc_output_witness_script = bfh(Transaction.get_preimage_script(htlc_tx_txin)) - # sweep directly from ctx - direct_sweep_tx = maybe_create_sweeptx_for_their_ctx_htlc( - ctx=ctx, - sweep_address=sweep_address, - htlc_output_witness_script=htlc_output_witness_script, - privkey=other_revocation_privkey, - preimage=None, - is_revocation=True) - # sweep from htlc tx - secondstage_sweep_tx = create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx( + return create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx( 'sweep_from_their_ctx_htlc_', to_self_delay=0, htlc_tx=htlc_tx, @@ -85,23 +74,18 @@ def create_sweeptxs_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per sweep_address=sweep_address, privkey=other_revocation_privkey, is_revocation=True) - return direct_sweep_tx, secondstage_sweep_tx, htlc_tx ctn = extract_ctn_from_tx_and_chan(ctx, chan) assert ctn == chan.config[REMOTE].ctn # received HTLCs, in their ctx received_htlcs = chan.included_htlcs(REMOTE, RECEIVED, ctn) for htlc in received_htlcs: - direct_sweep_tx, secondstage_sweep_tx, htlc_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True) - if direct_sweep_tx: - txs.append(direct_sweep_tx) + secondstage_sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True) if secondstage_sweep_tx: txs.append(secondstage_sweep_tx) # offered HTLCs, in their ctx offered_htlcs = chan.included_htlcs(REMOTE, SENT, ctn) for htlc in offered_htlcs: - direct_sweep_tx, secondstage_sweep_tx, htlc_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False) - if direct_sweep_tx: - txs.append(direct_sweep_tx) + secondstage_sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False) if secondstage_sweep_tx: txs.append(secondstage_sweep_tx) return txs @@ -262,10 +246,8 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, sweep_address: str) -> Dict[str,Transaction]: """Handle the case when the remote force-closes with their ctx. - Regardless of it is a breach or not, construct sweep tx for 'to_remote'. - If it is a breach, also construct sweep tx for 'to_local'. - Sweep txns for HTLCs are only constructed if it is NOT a breach, as - lnchannel does not store old HTLCs. + Sweep outputs that do not have a CSV delay ('to_remote' and first-stage HTLCs). + Outputs with CSV delay ('to_local' and second-stage HTLCs) are redeemed by LNWatcher. """ this_conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=False) ctn = extract_ctn_from_tx_and_chan(ctx, chan) @@ -274,20 +256,23 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, per_commitment_secret = None if ctn == this_conf.ctn: their_pcp = this_conf.current_per_commitment_point + is_revocation = False elif ctn == this_conf.ctn + 1: their_pcp = this_conf.next_per_commitment_point + is_revocation = False elif ctn < this_conf.ctn: # breach try: per_commitment_secret = this_conf.revocation_store.retrieve_secret(RevocationStore.START_INDEX - ctn) except UnableToDeriveSecret: return {} their_pcp = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True) + is_revocation = True else: return {} # prep other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, their_pcp) - other_htlc_privkey = derive_privkey(secret=int.from_bytes(other_conf.htlc_basepoint.privkey, 'big'), - per_commitment_point=their_pcp) + other_revocation_privkey = derive_blinded_privkey(other_conf.revocation_basepoint.privkey, per_commitment_secret) + other_htlc_privkey = derive_privkey(secret=int.from_bytes(other_conf.htlc_basepoint.privkey, 'big'), per_commitment_point=their_pcp) other_htlc_privkey = ecc.ECPrivkey.from_secret_scalar(other_htlc_privkey) this_htlc_pubkey = derive_pubkey(this_conf.htlc_basepoint.pubkey, their_pcp) other_payment_bp_privkey = ecc.ECPrivkey(other_conf.payment_basepoint.privkey) @@ -308,13 +293,8 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, our_payment_privkey=other_payment_privkey) txs[sweep_tx.prevout(0)] = sweep_tx # HTLCs - # from their ctx, we can only redeem HTLCs if the ctx was not revoked, - # as old HTLCs are not stored. (if it was revoked, then we should have presigned txns - # to handle the breach already; out of scope here) - if ctn not in (this_conf.ctn, this_conf.ctn + 1): - return txs def create_sweeptx_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Optional[Transaction]: - if not is_received_htlc: + if not is_received_htlc and not is_revocation: try: preimage = chan.lnworker.get_preimage(htlc.payment_hash) except UnknownPaymentHash as e: @@ -329,23 +309,29 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, local_htlc_pubkey=this_htlc_pubkey, payment_hash=htlc.payment_hash, cltv_expiry=htlc.cltv_expiry) - sweep_tx = maybe_create_sweeptx_for_their_ctx_htlc( - ctx=ctx, - sweep_address=sweep_address, - htlc_output_witness_script=htlc_output_witness_script, - privkey=other_htlc_privkey.get_secret_bytes(), - preimage=preimage, - is_revocation=False, - cltv_expiry=htlc.cltv_expiry if is_received_htlc else 0) - return sweep_tx + htlc_address = redeem_script_to_address('p2wsh', bh2u(htlc_output_witness_script)) + # FIXME handle htlc_address collision + # also: https://github.com/lightningnetwork/lightning-rfc/issues/448 + output_idx = ctx.get_output_idx_from_address(htlc_address) + if output_idx is not None: + sweep_tx = create_sweeptx_their_ctx_htlc( + ctx=ctx, + witness_script=htlc_output_witness_script, + sweep_address=sweep_address, + preimage=preimage, + output_idx=output_idx, + privkey=other_revocation_privkey if is_revocation else other_htlc_privkey.get_secret_bytes(), + is_revocation=is_revocation, + cltv_expiry=htlc.cltv_expiry if is_received_htlc and not is_revocation else 0) + return sweep_tx # received HTLCs, in their ctx --> "timeout" - received_htlcs = chan.included_htlcs_in_their_latest_ctxs(LOCAL)[ctn] # type: List[UpdateAddHtlc] + received_htlcs = chan.included_htlcs(REMOTE, RECEIVED, ctn=ctn) # type: List[UpdateAddHtlc] for htlc in received_htlcs: sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True) if sweep_tx: txs[sweep_tx.prevout(0)] = sweep_tx # offered HTLCs, in their ctx --> "success" - offered_htlcs = chan.included_htlcs_in_their_latest_ctxs(REMOTE)[ctn] # type: List[UpdateAddHtlc] + offered_htlcs = chan.included_htlcs(REMOTE, SENT, ctn=ctn) # type: List[UpdateAddHtlc] for htlc in offered_htlcs: sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False) if sweep_tx: @@ -375,26 +361,6 @@ def create_htlctx_that_spends_from_our_ctx(chan: 'Channel', our_pcp: bytes, return witness_script, htlc_tx -def maybe_create_sweeptx_for_their_ctx_htlc(ctx: Transaction, sweep_address: str, - htlc_output_witness_script: bytes, - privkey: bytes, is_revocation: bool, - preimage: Optional[bytes], cltv_expiry: int = 0) -> Optional[Transaction]: - htlc_address = redeem_script_to_address('p2wsh', bh2u(htlc_output_witness_script)) - # FIXME handle htlc_address collision - # also: https://github.com/lightningnetwork/lightning-rfc/issues/448 - output_idx = ctx.get_output_idx_from_address(htlc_address) - if output_idx is None: return None - sweep_tx = create_sweeptx_their_ctx_htlc(ctx=ctx, - witness_script=htlc_output_witness_script, - sweep_address=sweep_address, - preimage=preimage, - output_idx=output_idx, - privkey=privkey, - is_revocation=is_revocation, - cltv_expiry=cltv_expiry) - return sweep_tx - - def create_sweeptx_their_ctx_htlc(ctx: Transaction, witness_script: bytes, sweep_address: str, preimage: Optional[bytes], output_idx: int, privkey: bytes, is_revocation: bool, cltv_expiry: int,