Browse Source

rework on_channel_closed in LNWorker:

- use detect_who_closed; this allows us to redeem to_remote of breach ctx
 - do not redeem to_local of breach ctx, because it is redundant with lnwatcher
 - rename a few methods
regtest_lnd
ThomasV 6 years ago
parent
commit
6e1aefdc94
  1. 10
      electrum/lnchannel.py
  2. 37
      electrum/lnsweep.py
  3. 37
      electrum/lnworker.py

10
electrum/lnchannel.py

@ -46,8 +46,7 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey
HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc, HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc,
funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs, funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs,
ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script) ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script)
from .lnsweep import create_sweeptxs_for_their_just_revoked_ctx from .lnsweep import create_sweeptxs_for_their_revoked_ctx
from .lnsweep import create_sweeptxs_for_our_latest_ctx, create_sweeptxs_for_their_latest_ctx
from .lnhtlc import HTLCManager from .lnhtlc import HTLCManager
@ -168,6 +167,7 @@ class Channel(Logger):
self.local_commitment = None self.local_commitment = None
self.remote_commitment = None self.remote_commitment = None
self.sweep_info = None
def get_payments(self): def get_payments(self):
out = {} out = {}
@ -186,13 +186,9 @@ class Channel(Logger):
ctn = extract_ctn_from_tx_and_chan(ctx, self) ctn = extract_ctn_from_tx_and_chan(ctx, self)
assert self.signature_fits(ctx), (self.hm.log[LOCAL]) assert self.signature_fits(ctx), (self.hm.log[LOCAL])
self.local_commitment = ctx self.local_commitment = ctx
if self.sweep_address is not None:
self.local_sweeptxs = create_sweeptxs_for_our_latest_ctx(self, self.local_commitment, self.sweep_address)
def set_remote_commitment(self): def set_remote_commitment(self):
self.remote_commitment = self.current_commitment(REMOTE) self.remote_commitment = self.current_commitment(REMOTE)
if self.sweep_address is not None:
self.remote_sweeptxs = create_sweeptxs_for_their_latest_ctx(self, self.remote_commitment, self.sweep_address)
def open_with_first_pcp(self, remote_pcp, remote_sig): def open_with_first_pcp(self, remote_pcp, remote_sig):
self.remote_commitment_to_be_revoked = self.pending_commitment(REMOTE) self.remote_commitment_to_be_revoked = self.pending_commitment(REMOTE)
@ -460,7 +456,7 @@ class Channel(Logger):
return return
outpoint = self.funding_outpoint.to_str() outpoint = self.funding_outpoint.to_str()
ctx = self.remote_commitment_to_be_revoked # FIXME can't we just reconstruct it? ctx = self.remote_commitment_to_be_revoked # FIXME can't we just reconstruct it?
sweeptxs = create_sweeptxs_for_their_just_revoked_ctx(self, ctx, per_commitment_secret, self.sweep_address) sweeptxs = create_sweeptxs_for_their_revoked_ctx(self, ctx, per_commitment_secret, self.sweep_address)
for prev_txid, tx in sweeptxs.items(): for prev_txid, tx in sweeptxs.items():
if tx is not None: if tx is not None:
self.lnwatcher.add_sweep_tx(outpoint, prev_txid, str(tx)) self.lnwatcher.add_sweep_tx(outpoint, prev_txid, str(tx))

37
electrum/lnsweep.py

@ -56,8 +56,8 @@ def maybe_create_sweeptx_for_their_ctx_to_local(ctx: Transaction, revocation_pri
return sweep_tx return sweep_tx
def create_sweeptxs_for_their_just_revoked_ctx(chan: 'Channel', ctx: Transaction, per_commitment_secret: bytes, def create_sweeptxs_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per_commitment_secret: bytes,
sweep_address: str) -> Dict[str,Transaction]: sweep_address: str) -> Dict[str,Transaction]:
"""Presign sweeping transactions using the just received revoked pcs. """Presign sweeping transactions using the just received revoked pcs.
These will only be utilised if the remote breaches. These will only be utilised if the remote breaches.
Sweep 'to_local', and all the HTLCs (two cases: directly from ctx, or from HTLC tx). Sweep 'to_local', and all the HTLCs (two cases: directly from ctx, or from HTLC tx).
@ -135,12 +135,8 @@ class ChannelClosedBy(Enum):
UNKNOWN = auto() UNKNOWN = auto()
class ChannelCloseSituationReport(NamedTuple):
closed_by: ChannelClosedBy
is_breach: Optional[bool]
def detect_who_closed(chan: 'Channel', ctx: Transaction) -> ChannelClosedBy:
def detect_how_channel_was_closed(chan: 'Channel', ctx: Transaction) -> ChannelCloseSituationReport:
ctn = extract_ctn_from_tx_and_chan(ctx, chan) ctn = extract_ctn_from_tx_and_chan(ctx, chan)
our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True) our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
@ -193,19 +189,19 @@ def detect_how_channel_was_closed(chan: 'Channel', ctx: Transaction) -> ChannelC
to_local_address, to_remote_address, is_breach = get_to_local_and_to_remote_addresses_for_our_ctx() to_local_address, to_remote_address, is_breach = get_to_local_and_to_remote_addresses_for_our_ctx()
if (to_local_address and ctx.get_output_idx_from_address(to_local_address) is not None if (to_local_address and ctx.get_output_idx_from_address(to_local_address) is not None
or to_remote_address and ctx.get_output_idx_from_address(to_remote_address) is not None): or to_remote_address and ctx.get_output_idx_from_address(to_remote_address) is not None):
return ChannelCloseSituationReport(closed_by=ChannelClosedBy.US, is_breach=is_breach) return ChannelClosedBy.US
# their ctx? # their ctx?
to_local_address, to_remote_address, is_breach = get_to_local_and_to_remote_addresses_for_their_ctx() to_local_address, to_remote_address, is_breach = get_to_local_and_to_remote_addresses_for_their_ctx()
if (to_local_address and ctx.get_output_idx_from_address(to_local_address) is not None if (to_local_address and ctx.get_output_idx_from_address(to_local_address) is not None
or to_remote_address and ctx.get_output_idx_from_address(to_remote_address) is not None): or to_remote_address and ctx.get_output_idx_from_address(to_remote_address) is not None):
return ChannelCloseSituationReport(closed_by=ChannelClosedBy.THEM, is_breach=is_breach) return ChannelClosedBy.THEM
return ChannelCloseSituationReport(closed_by=ChannelClosedBy.UNKNOWN, is_breach=None) return ChannelClosedBy.UNKNOWN
def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction, def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction,
sweep_address: str) -> Dict[str,Transaction]: sweep_address: str) -> Dict[str,Transaction]:
"""Handle the case where we force close unilaterally with our latest ctx. """Handle the case where we force close unilaterally with our latest ctx.
Construct sweep txns for 'to_local', and for all HTLCs (2 txns each). Construct sweep txns for 'to_local', and for all HTLCs (2 txns each).
'to_local' can be swept even if this is a breach (by us), 'to_local' can be swept even if this is a breach (by us),
@ -277,8 +273,8 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
return txs return txs
def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction, def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction,
sweep_address: str) -> Dict[str,Transaction]: sweep_address: str) -> Dict[str,Transaction]:
"""Handle the case when the remote force-closes with their ctx. """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'. 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'. If it is a breach, also construct sweep tx for 'to_local'.
@ -313,18 +309,7 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
other_payment_privkey = ecc.ECPrivkey.from_secret_scalar(other_payment_privkey) other_payment_privkey = ecc.ECPrivkey.from_secret_scalar(other_payment_privkey)
txs = {} txs = {}
if per_commitment_secret: # breach # to_local is handled by lnwatcher
# to_local
other_revocation_privkey = derive_blinded_privkey(other_conf.revocation_basepoint.privkey,
per_commitment_secret)
this_delayed_pubkey = derive_pubkey(this_conf.delayed_basepoint.pubkey, their_pcp)
sweep_tx = maybe_create_sweeptx_for_their_ctx_to_local(ctx=ctx,
revocation_privkey=other_revocation_privkey,
to_self_delay=other_conf.to_self_delay,
delayed_pubkey=this_delayed_pubkey,
sweep_address=sweep_address)
if sweep_tx:
txs[sweep_tx.prevout(0)] = sweep_tx
# to_remote # to_remote
sweep_tx = maybe_create_sweeptx_for_their_ctx_to_remote(ctx=ctx, sweep_tx = maybe_create_sweeptx_for_their_ctx_to_remote(ctx=ctx,
sweep_address=sweep_address, sweep_address=sweep_address,

37
electrum/lnworker.py

@ -46,6 +46,7 @@ from .lnrouter import RouteEdge, is_route_sane_to_use
from .address_synchronizer import TX_HEIGHT_LOCAL from .address_synchronizer import TX_HEIGHT_LOCAL
from . import lnsweep from . import lnsweep
from .lnsweep import ChannelClosedBy from .lnsweep import ChannelClosedBy
from .lnsweep import create_sweeptxs_for_their_ctx, create_sweeptxs_for_our_ctx
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
@ -511,24 +512,24 @@ class LNWallet(LNWorker):
# remove from channel_db # remove from channel_db
if chan.short_channel_id is not None: if chan.short_channel_id is not None:
self.channel_db.remove_channel(chan.short_channel_id) self.channel_db.remove_channel(chan.short_channel_id)
# detect who closed
assert closing_tx, f"no closing tx... {repr(closing_tx)}" # detect who closed and set sweep_info
sitrep = lnsweep.detect_how_channel_was_closed(chan, closing_tx) if chan.sweep_info is None:
if sitrep.closed_by == ChannelClosedBy.US: closed_by = lnsweep.detect_who_closed(chan, closing_tx)
self.logger.info(f'we force closed {funding_outpoint}. sitrep: {repr(sitrep)}') if closed_by == ChannelClosedBy.US:
encumbered_sweeptxs = chan.local_sweeptxs self.logger.info(f'we force closed {funding_outpoint}.')
elif sitrep.closed_by == ChannelClosedBy.THEM and sitrep.is_breach is False: chan.sweep_info = create_sweeptxs_for_our_ctx(chan, closing_tx, chan.sweep_address)
self.logger.info(f'they force closed {funding_outpoint}. sitrep: {repr(sitrep)}') elif closed_by == ChannelClosedBy.THEM:
encumbered_sweeptxs = chan.remote_sweeptxs self.logger.info(f'they force closed {funding_outpoint}.')
else: chan.sweep_info = create_sweeptxs_for_their_ctx(chan, closing_tx, chan.sweep_address)
self.logger.info(f'not sure who closed {funding_outpoint} {closing_txid}. sitrep: {repr(sitrep)}') else:
return self.logger.info(f'not sure who closed {funding_outpoint} {closing_txid}.')
# sweep chan.sweep_info = {}
for prevout, spender in spenders.items(): self.logger.info(f'{repr(chan.sweep_info)}')
e_tx = encumbered_sweeptxs.get(prevout)
if e_tx is None: # create and broadcast transaction
continue for prevout, e_tx in chan.sweep_info.items():
if spender is not None: if spenders.get(prevout) is not None:
self.logger.info(f'outpoint already spent {prevout}') self.logger.info(f'outpoint already spent {prevout}')
continue continue
prev_txid, prev_index = prevout.split(':') prev_txid, prev_index = prevout.split(':')

Loading…
Cancel
Save