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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user