1
0

lnsweep: update sweeps to_remote and htlcs

* sweep to_remote output, as this is now a p2wsh (previously internal
  wallet address)
* sweep htlc outputs with new scripts
This commit is contained in:
bitromortac
2021-09-13 14:31:01 +02:00
committed by ThomasV
parent b6e224c864
commit 9bfeffcdd1

View File

@@ -18,7 +18,7 @@ from .lnutil import (make_commitment_output_to_remote_address, make_commitment_o
LOCAL, REMOTE, make_htlc_output_witness_script,
get_ordered_channel_configs, get_per_commitment_secret_from_seed,
RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret, SENT, RECEIVED,
map_htlcs_to_ctx_output_idxs, Direction)
map_htlcs_to_ctx_output_idxs, Direction, make_commitment_output_to_remote_witness_script)
from .transaction import (Transaction, TxOutput, PartialTransaction, PartialTxInput,
PartialTxOutput, TxOutpoint)
from .simple_config import SimpleConfig
@@ -147,7 +147,7 @@ def create_sweeptx_for_their_revoked_htlc(
htlc_tx: Transaction,
sweep_address: str) -> Optional[SweepInfo]:
x = analyze_ctx(chan, ctx)
x = extract_ctx_secrets(chan, ctx)
if not x:
return
ctn, their_pcp, is_revocation, per_commitment_secret = x
@@ -187,10 +187,18 @@ def create_sweeptxs_for_our_ctx(
*, chan: 'AbstractChannel',
ctx: Transaction,
sweep_address: str) -> Optional[Dict[str, SweepInfo]]:
"""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).
"""Handle the case where we force-close unilaterally with our latest ctx.
We sweep:
to_local: CSV delayed
htlc success: CSV delay with anchors, no delay otherwise
htlc timeout: CSV delay with anchors, CLTV locktime
second-stage htlc transactions: CSV delay
'to_local' can be swept even if this is a breach (by us),
but HTLCs cannot (old HTLCs are no longer stored).
Outputs with CSV/CLTV are redeemed by LNWatcher.
"""
ctn = extract_ctn_from_tx_and_chan(ctx, chan)
our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
@@ -315,7 +323,7 @@ def create_sweeptxs_for_our_ctx(
return txs
def analyze_ctx(chan: 'Channel', ctx: Transaction):
def extract_ctx_secrets(chan: 'Channel', ctx: Transaction):
# note: the remote sometimes has two valid non-revoked commitment transactions,
# either of which could be broadcast
our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
@@ -351,14 +359,23 @@ def create_sweeptxs_for_their_ctx(
"""Handle the case when the remote force-closes with their ctx.
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.
We sweep:
to_local: if revoked
to_remote: CSV delay with anchors, otherwise sweeping not needed
htlc success: CSV delay with anchors, no delay otherwise, or revoked
htlc timeout: CSV delay with anchors, CLTV locktime, or revoked
second-stage htlc transactions: CSV delay
Outputs with CSV/CLTV are redeemed by LNWatcher.
"""
txs = {} # type: Dict[str, SweepInfo]
our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
x = analyze_ctx(chan, ctx)
x = extract_ctx_secrets(chan, ctx)
if not x:
return
ctn, their_pcp, is_revocation, per_commitment_secret = x
# to_local and to_remote addresses
# to_local
our_revocation_pubkey = derive_blinded_pubkey(our_conf.revocation_basepoint.pubkey, their_pcp)
their_delayed_pubkey = derive_pubkey(their_conf.delayed_basepoint.pubkey, their_pcp)
witness_script = make_commitment_output_to_local_witness_script(
@@ -377,6 +394,7 @@ def create_sweeptxs_for_their_ctx(
if not found_to_local and not found_to_remote:
return
chan.logger.debug(f'(lnsweep) found their ctx: {to_local_address} {to_remote_address}')
# to_local is handled by lnwatcher
if is_revocation:
our_revocation_privkey = derive_blinded_privkey(our_conf.revocation_basepoint.privkey, per_commitment_secret)
gen_tx = create_sweeptx_for_their_revoked_ctx(chan, ctx, per_commitment_secret, sweep_address)
@@ -387,12 +405,42 @@ def create_sweeptxs_for_their_ctx(
csv_delay=0,
cltv_abs=0,
gen_tx=gen_tx)
# prep
# to_remote
if chan.has_anchors():
csv_delay = 1
sweep_to_remote = True
our_payment_privkey = ecc.ECPrivkey(our_conf.payment_basepoint.privkey)
else:
assert chan.is_static_remotekey_enabled()
csv_delay = 0
sweep_to_remote = False
our_payment_privkey = None
if sweep_to_remote:
assert our_payment_pubkey == our_payment_privkey.get_public_key_bytes(compressed=True)
output_idxs = ctx.get_output_idxs_from_address(to_remote_address)
if output_idxs:
output_idx = output_idxs.pop()
prevout = ctx.txid() + ':%d' % output_idx
sweep_tx = lambda: create_sweeptx_their_ctx_to_remote(
sweep_address=sweep_address,
ctx=ctx,
output_idx=output_idx,
our_payment_privkey=our_payment_privkey,
config=chan.lnworker.config,
has_anchors=chan.has_anchors()
)
txs[prevout] = SweepInfo(
name='their_ctx_to_remote',
csv_delay=csv_delay,
cltv_abs=0,
gen_tx=sweep_tx)
# HTLCs
our_htlc_privkey = derive_privkey(secret=int.from_bytes(our_conf.htlc_basepoint.privkey, 'big'), per_commitment_point=their_pcp)
our_htlc_privkey = ecc.ECPrivkey.from_secret_scalar(our_htlc_privkey)
their_htlc_pubkey = derive_pubkey(their_conf.htlc_basepoint.pubkey, their_pcp)
# to_local is handled by lnwatcher
# HTLCs
def create_sweeptx_for_htlc(
*, htlc: 'UpdateAddHtlc',
is_received_htlc: bool,
@@ -408,6 +456,7 @@ def create_sweeptxs_for_their_ctx(
has_anchors=chan.has_anchors())
cltv_abs = htlc.cltv_abs if is_received_htlc and not is_revocation else 0
csv_delay = 1 if chan.has_anchors() else 0
prevout = ctx.txid() + ':%d'%ctx_output_idx
sweep_tx = lambda: create_sweeptx_their_ctx_htlc(
ctx=ctx,
@@ -418,10 +467,12 @@ def create_sweeptxs_for_their_ctx(
privkey=our_revocation_privkey if is_revocation else our_htlc_privkey.get_secret_bytes(),
is_revocation=is_revocation,
cltv_abs=cltv_abs,
config=chan.lnworker.config)
config=chan.lnworker.config,
has_anchors=chan.has_anchors(),
)
txs[prevout] = SweepInfo(
name=f'their_ctx_htlc_{ctx_output_idx}',
csv_delay=0,
name=f'their_ctx_htlc_{ctx_output_idx}{"_for_revoked_ctx" if is_revocation else ""}',
csv_delay=csv_delay,
cltv_abs=cltv_abs,
gen_tx=sweep_tx)
# received HTLCs, in their ctx --> "timeout"
@@ -488,7 +539,10 @@ 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_abs: int, config: SimpleConfig) -> Optional[PartialTransaction]:
cltv_abs: int,
config: SimpleConfig,
has_anchors: bool,
) -> Optional[PartialTransaction]:
assert type(cltv_abs) is int
preimage = preimage or b'' # preimage is required iff (not is_revocation and htlc is offered)
val = ctx.outputs()[output_idx].value
@@ -497,6 +551,8 @@ def create_sweeptx_their_ctx_htlc(
txin._trusted_value_sats = val
txin.witness_script = witness_script
txin.script_sig = b''
if has_anchors:
txin.nsequence = 1
sweep_inputs = [txin]
tx_size_bytes = 200 # TODO (depends on offered/received and is_revocation)
fee = config.estimate_fee(tx_size_bytes, allow_fallback_to_static_rates=True)
@@ -518,7 +574,9 @@ def create_sweeptx_their_ctx_htlc(
def create_sweeptx_their_ctx_to_remote(
sweep_address: str, ctx: Transaction, output_idx: int,
our_payment_privkey: ecc.ECPrivkey,
config: SimpleConfig) -> Optional[PartialTransaction]:
config: SimpleConfig,
has_anchors: bool,
) -> Optional[PartialTransaction]:
our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True)
val = ctx.outputs()[output_idx].value
prevout = TxOutpoint(txid=bfh(ctx.txid()), out_idx=output_idx)
@@ -526,15 +584,31 @@ def create_sweeptx_their_ctx_to_remote(
txin._trusted_value_sats = val
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=our_payment_pubkey.hex(), script_type='p2wpkh')
txin.script_descriptor = desc
txin.pubkeys = [bfh(our_payment_pubkey)]
txin.num_sig = 1
if not has_anchors:
txin.script_type = 'p2wpkh'
tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh
else:
txin.script_sig = b''
txin.witness_script = make_commitment_output_to_remote_witness_script(bfh(our_payment_pubkey))
txin.nsequence = 1
tx_size_bytes = 196 # approx size of p2wsh->p2wpkh
sweep_inputs = [txin]
tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh
fee = config.estimate_fee(tx_size_bytes, allow_fallback_to_static_rates=True)
outvalue = val - fee
if outvalue <= dust_threshold(): return None
sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)]
sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs)
sweep_tx.set_rbf(True)
sweep_tx.sign({our_payment_pubkey: our_payment_privkey.get_secret_bytes()})
if not has_anchors:
sweep_tx.set_rbf(True)
sweep_tx.sign({our_payment_pubkey: our_payment_privkey.get_secret_bytes()})
else:
sig = sweep_tx.sign_txin(0, our_payment_privkey.get_secret_bytes())
witness = construct_witness([sig, sweep_tx.inputs()[0].witness_script])
sweep_tx.inputs()[0].witness = bfh(witness)
if not sweep_tx.is_complete():
raise Exception('channel close sweep tx is not complete')
return sweep_tx