mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-04 21:05:11 +00:00
move sweeping methods from lnchan.py to new file
also sweep "received" htlcs from "our" ctx also sweep htlcs from their ctx (non-breach) extract ctn; included_htlcs_in_their_latest_ctxs
This commit is contained in:
parent
bc72966442
commit
595cfcbb65
9 changed files with 645 additions and 301 deletions
|
@ -500,6 +500,7 @@ class Peer(PrintError):
|
|||
chan = Channel(chan_dict, payment_completed=self.lnworker.payment_completed)
|
||||
chan.lnwatcher = self.lnwatcher
|
||||
chan.sweep_address = self.lnworker.sweep_address
|
||||
chan.get_preimage_and_invoice = self.lnworker.get_invoice # FIXME hack.
|
||||
sig_64, _ = chan.sign_next_commitment()
|
||||
self.send_message("funding_created",
|
||||
temporary_channel_id=temp_channel_id,
|
||||
|
@ -590,6 +591,7 @@ class Peer(PrintError):
|
|||
chan = Channel(chan_dict, payment_completed=self.lnworker.payment_completed)
|
||||
chan.lnwatcher = self.lnwatcher
|
||||
chan.sweep_address = self.lnworker.sweep_address
|
||||
chan.get_preimage_and_invoice = self.lnworker.get_invoice # FIXME hack.
|
||||
remote_sig = funding_created['signature']
|
||||
chan.receive_new_commitment(remote_sig, [])
|
||||
sig_64, _ = chan.sign_next_commitment()
|
||||
|
|
|
@ -26,7 +26,7 @@ from collections import namedtuple, defaultdict
|
|||
import binascii
|
||||
import json
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, Dict, List, Tuple, NamedTuple, Set, Callable, Iterable
|
||||
from typing import Optional, Dict, List, Tuple, NamedTuple, Set, Callable, Iterable, Sequence
|
||||
from copy import deepcopy
|
||||
|
||||
from .util import bfh, PrintError, bh2u
|
||||
|
@ -34,17 +34,18 @@ from .bitcoin import TYPE_SCRIPT, TYPE_ADDRESS
|
|||
from .bitcoin import redeem_script_to_address
|
||||
from .crypto import sha256, sha256d
|
||||
from . import ecc
|
||||
from .lnutil import Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, EncumberedTransaction
|
||||
from .lnutil import Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore
|
||||
from .lnutil import get_per_commitment_secret_from_seed
|
||||
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, privkey_to_pubkey, make_htlc_tx_witness
|
||||
from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey
|
||||
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_commitment_outputs
|
||||
from .lnutil import ScriptHtlc, SENT, RECEIVED, PaymentFailure, calc_onchain_fees, RemoteMisbehaving
|
||||
from .transaction import Transaction, TxOutput, construct_witness
|
||||
from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
|
||||
from .lnutil import ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script
|
||||
from .transaction import Transaction
|
||||
from .lnsweep import (create_sweeptxs_for_our_latest_ctx, create_sweeptxs_for_their_latest_ctx,
|
||||
create_sweeptxs_for_their_just_revoked_ctx)
|
||||
|
||||
|
||||
class ChannelJsonEncoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
|
@ -309,13 +310,16 @@ class Channel(PrintError):
|
|||
htlcsigs = []
|
||||
for we_receive, htlcs in zip([True, False], [self.included_htlcs(REMOTE, REMOTE), self.included_htlcs(REMOTE, LOCAL)]):
|
||||
for htlc in htlcs:
|
||||
args = [self.config[REMOTE].next_per_commitment_point, for_us, we_receive, pending_remote_commitment, htlc]
|
||||
_script, htlc_tx = make_htlc_tx_with_open_channel(self, *args)
|
||||
_script, htlc_tx = make_htlc_tx_with_open_channel(chan=self,
|
||||
pcp=self.config[REMOTE].next_per_commitment_point,
|
||||
for_us=for_us,
|
||||
we_receive=we_receive,
|
||||
commit=pending_remote_commitment,
|
||||
htlc=htlc)
|
||||
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
|
||||
htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
|
||||
htlcsigs.append((pending_remote_commitment.htlc_output_indices[htlc.payment_hash], htlc_sig))
|
||||
|
||||
self.process_new_offchain_ctx(pending_remote_commitment, ours=False)
|
||||
htlc_output_idx = htlc_tx.inputs()[0]['prevout_n']
|
||||
htlcsigs.append((htlc_output_idx, htlc_sig))
|
||||
|
||||
htlcsigs.sort()
|
||||
htlcsigs = [x[1] for x in htlcsigs]
|
||||
|
@ -383,11 +387,14 @@ class Channel(PrintError):
|
|||
if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
|
||||
self.pending_fee[FUNDER_SIGNED] = True
|
||||
|
||||
self.process_new_offchain_ctx(pending_local_commitment, ours=True)
|
||||
|
||||
def verify_htlc(self, htlc, htlc_sigs, we_receive):
|
||||
_, this_point, _ = self.points()
|
||||
_script, htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, self.pending_commitment(LOCAL), htlc)
|
||||
def verify_htlc(self, htlc: UpdateAddHtlc, htlc_sigs: Sequence[bytes], we_receive: bool) -> int:
|
||||
_, this_point, _ = self.points
|
||||
_script, htlc_tx = make_htlc_tx_with_open_channel(chan=self,
|
||||
pcp=this_point,
|
||||
for_us=True,
|
||||
we_receive=we_receive,
|
||||
commit=self.pending_commitment(LOCAL),
|
||||
htlc=htlc)
|
||||
pre_hash = sha256d(bfh(htlc_tx.serialize_preimage(0)))
|
||||
remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, this_point)
|
||||
for idx, sig in enumerate(htlc_sigs):
|
||||
|
@ -396,6 +403,13 @@ class Channel(PrintError):
|
|||
else:
|
||||
raise Exception(f'failed verifying HTLC signatures: {htlc}')
|
||||
|
||||
def get_remote_htlc_sig_for_htlc(self, htlc: UpdateAddHtlc, we_receive: bool) -> bytes:
|
||||
data = self.config[LOCAL].current_htlc_signatures
|
||||
htlc_sigs = [data[i:i + 64] for i in range(0, len(data), 64)]
|
||||
idx = self.verify_htlc(htlc, htlc_sigs, we_receive=we_receive)
|
||||
remote_htlc_sig = ecc.der_sig_from_sig_string(htlc_sigs[idx]) + b'\x01'
|
||||
return remote_htlc_sig
|
||||
|
||||
def revoke_current_commitment(self):
|
||||
self.print_error("revoke_current_commitment")
|
||||
|
||||
|
@ -435,36 +449,18 @@ class Channel(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):
|
||||
def process_new_revocation_secret(self, per_commitment_secret: bytes):
|
||||
if not self.lnwatcher:
|
||||
return
|
||||
outpoint = self.funding_outpoint.to_str()
|
||||
if ours:
|
||||
ctn = self.config[LOCAL].ctn + 1
|
||||
our_per_commitment_secret = get_per_commitment_secret_from_seed(
|
||||
self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
|
||||
our_cur_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True)
|
||||
encumbered_sweeptxs = create_sweeptxs_for_our_ctx(self, ctx, our_cur_pcp, self.sweep_address)
|
||||
else:
|
||||
their_cur_pcp = self.config[REMOTE].next_per_commitment_point
|
||||
encumbered_sweeptxs = [(None, maybe_create_sweeptx_for_their_ctx_to_remote(self, ctx, their_cur_pcp, self.sweep_address))]
|
||||
ctx = self.remote_commitment_to_be_revoked # FIXME can't we just reconstruct it?
|
||||
encumbered_sweeptxs = create_sweeptxs_for_their_just_revoked_ctx(self, ctx, per_commitment_secret, self.sweep_address)
|
||||
for prev_txid, encumbered_tx in encumbered_sweeptxs:
|
||||
if prev_txid is None:
|
||||
prev_txid = ctx.txid()
|
||||
if encumbered_tx is not None:
|
||||
self.lnwatcher.add_sweep_tx(outpoint, prev_txid, encumbered_tx.to_json())
|
||||
|
||||
def process_new_revocation_secret(self, per_commitment_secret: bytes):
|
||||
if not self.lnwatcher:
|
||||
return
|
||||
outpoint = self.funding_outpoint.to_str()
|
||||
ctx = self.remote_commitment_to_be_revoked
|
||||
encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_local(self, ctx, per_commitment_secret, self.sweep_address)
|
||||
if encumbered_sweeptx:
|
||||
self.lnwatcher.add_sweep_tx(outpoint, ctx.txid(), encumbered_sweeptx.to_json())
|
||||
|
||||
def receive_revocation(self, revocation) -> Tuple[int, int]:
|
||||
self.print_error("receive_revocation")
|
||||
|
||||
|
@ -476,12 +472,6 @@ class Channel(PrintError):
|
|||
self.log = old_logs
|
||||
raise Exception('revoked secret not for current point')
|
||||
|
||||
if self.pending_fee is not None:
|
||||
if not self.constraints.is_initiator:
|
||||
self.pending_fee[FUNDEE_SIGNED] = True
|
||||
if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
|
||||
self.pending_fee[FUNDER_SIGNED] = True
|
||||
|
||||
# FIXME not sure this is correct... but it seems to work
|
||||
# if there are update_add_htlc msgs between commitment_signed and rev_ack,
|
||||
# this might break
|
||||
|
@ -490,6 +480,14 @@ class Channel(PrintError):
|
|||
self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret)
|
||||
self.process_new_revocation_secret(revocation.per_commitment_secret)
|
||||
|
||||
##### start applying fee/htlc changes
|
||||
|
||||
if self.pending_fee is not None:
|
||||
if not self.constraints.is_initiator:
|
||||
self.pending_fee[FUNDEE_SIGNED] = True
|
||||
if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
|
||||
self.pending_fee[FUNDER_SIGNED] = True
|
||||
|
||||
def mark_settled(subject):
|
||||
"""
|
||||
find pending settlements for subject (LOCAL or REMOTE) and mark them settled, return value of settled htlcs
|
||||
|
@ -768,21 +766,19 @@ class Channel(PrintError):
|
|||
other_htlc_pubkey = derive_pubkey(other_config.htlc_basepoint.pubkey, this_point)
|
||||
this_htlc_pubkey = derive_pubkey(this_config.htlc_basepoint.pubkey, this_point)
|
||||
other_revocation_pubkey = derive_blinded_pubkey(other_config.revocation_basepoint.pubkey, this_point)
|
||||
htlcs = []
|
||||
htlcs = [] # type: List[ScriptHtlc]
|
||||
def append_htlc(htlc: UpdateAddHtlc, is_received_htlc: bool):
|
||||
htlcs.append(ScriptHtlc(make_htlc_output_witness_script(
|
||||
is_received_htlc=is_received_htlc,
|
||||
remote_revocation_pubkey=other_revocation_pubkey,
|
||||
remote_htlc_pubkey=other_htlc_pubkey,
|
||||
local_htlc_pubkey=this_htlc_pubkey,
|
||||
payment_hash=htlc.payment_hash,
|
||||
cltv_expiry=htlc.cltv_expiry), htlc))
|
||||
for htlc in self.included_htlcs(subject, -subject):
|
||||
htlcs.append( ScriptHtlc( make_received_htlc(
|
||||
other_revocation_pubkey,
|
||||
other_htlc_pubkey,
|
||||
this_htlc_pubkey,
|
||||
htlc.payment_hash,
|
||||
htlc.cltv_expiry), htlc))
|
||||
append_htlc(htlc, is_received_htlc=True)
|
||||
for htlc in self.included_htlcs(subject, subject):
|
||||
htlcs.append(
|
||||
ScriptHtlc( make_offered_htlc(
|
||||
other_revocation_pubkey,
|
||||
other_htlc_pubkey,
|
||||
this_htlc_pubkey,
|
||||
htlc.payment_hash), htlc))
|
||||
append_htlc(htlc, is_received_htlc=False)
|
||||
if subject != LOCAL:
|
||||
remote_msat, local_msat = local_msat, remote_msat
|
||||
payment_pubkey = derive_pubkey(other_config.payment_basepoint.pubkey, this_point)
|
||||
|
@ -851,209 +847,15 @@ class Channel(PrintError):
|
|||
assert tx.is_complete()
|
||||
return tx
|
||||
|
||||
def included_htlcs_in_latest_ctxs(self):
|
||||
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. """
|
||||
old_htlcs = list(self.included_htlcs(REMOTE, REMOTE, only_pending=False)) \
|
||||
+ list(self.included_htlcs(REMOTE, LOCAL, only_pending=False))
|
||||
old_htlcs = list(self.included_htlcs(REMOTE, htlc_initiator, only_pending=False))
|
||||
|
||||
old_logs = dict(self.lock_in_htlc_changes(LOCAL))
|
||||
new_htlcs = list(self.included_htlcs(REMOTE, REMOTE)) \
|
||||
+ list(self.included_htlcs(REMOTE, LOCAL))
|
||||
new_htlcs = list(self.included_htlcs(REMOTE, htlc_initiator))
|
||||
self.log = old_logs
|
||||
|
||||
return {self.config[REMOTE].ctn: old_htlcs,
|
||||
self.config[REMOTE].ctn+1: new_htlcs, }
|
||||
|
||||
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.config[LOCAL].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('their_ctx_to_remote', sweep_tx, csv_delay=0, cltv_expiry=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.config[LOCAL].revocation_basepoint.privkey,
|
||||
per_commitment_secret)
|
||||
revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True)
|
||||
to_self_delay = chan.config[LOCAL].to_self_delay
|
||||
delayed_pubkey = derive_pubkey(chan.config[REMOTE].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('their_ctx_to_local', sweep_tx, csv_delay=0, cltv_expiry=0)
|
||||
|
||||
|
||||
def create_sweeptxs_for_our_ctx(chan, ctx, our_pcp: bytes, sweep_address) \
|
||||
-> List[Tuple[Optional[str],EncumberedTransaction]]:
|
||||
assert isinstance(our_pcp, bytes)
|
||||
delayed_bp_privkey = ecc.ECPrivkey(chan.config[LOCAL].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.config[REMOTE].revocation_basepoint.pubkey,
|
||||
our_pcp)
|
||||
to_self_delay = chan.config[REMOTE].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)
|
||||
txs = []
|
||||
for output_idx, o in enumerate(ctx.outputs()):
|
||||
if o.type == TYPE_ADDRESS and o.address == to_local_address:
|
||||
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)
|
||||
|
||||
txs.append((None, EncumberedTransaction('our_ctx_to_local', sweep_tx, csv_delay=to_self_delay, cltv_expiry=0)))
|
||||
break
|
||||
|
||||
# TODO htlc successes
|
||||
htlcs = list(chan.included_htlcs(LOCAL, LOCAL)) # timeouts
|
||||
for htlc in htlcs:
|
||||
witness_script, htlc_tx = make_htlc_tx_with_open_channel(
|
||||
chan,
|
||||
our_pcp,
|
||||
True, # for_us
|
||||
False, # we_receive
|
||||
ctx, htlc)
|
||||
|
||||
data = chan.config[LOCAL].current_htlc_signatures
|
||||
htlc_sigs = [data[i:i+64] for i in range(0, len(data), 64)]
|
||||
idx = chan.verify_htlc(htlc, htlc_sigs, False)
|
||||
remote_htlc_sig = ecc.der_sig_from_sig_string(htlc_sigs[idx]) + b'\x01'
|
||||
|
||||
remote_revocation_pubkey = derive_blinded_pubkey(chan.config[REMOTE].revocation_basepoint.pubkey, our_pcp)
|
||||
remote_htlc_pubkey = derive_pubkey(chan.config[REMOTE].htlc_basepoint.pubkey, our_pcp)
|
||||
local_htlc_key = derive_privkey(
|
||||
int.from_bytes(chan.config[LOCAL].htlc_basepoint.privkey, 'big'),
|
||||
our_pcp).to_bytes(32, 'big')
|
||||
program = make_offered_htlc(remote_revocation_pubkey, remote_htlc_pubkey, privkey_to_pubkey(local_htlc_key), htlc.payment_hash)
|
||||
local_htlc_sig = bfh(htlc_tx.sign_txin(0, local_htlc_key))
|
||||
|
||||
htlc_tx.inputs()[0]['witness'] = bh2u(make_htlc_tx_witness(remote_htlc_sig, local_htlc_sig, b'', program))
|
||||
|
||||
tx_size_bytes = 999 # TODO
|
||||
fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
|
||||
fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes)
|
||||
second_stage_outputs = [TxOutput(TYPE_ADDRESS, chan.sweep_address, htlc.amount_msat // 1000 - fee)]
|
||||
assert to_self_delay is not None
|
||||
second_stage_inputs = [{
|
||||
'scriptSig': '',
|
||||
'type': 'p2wsh',
|
||||
'signatures': [],
|
||||
'num_sig': 0,
|
||||
'prevout_n': 0,
|
||||
'prevout_hash': htlc_tx.txid(),
|
||||
'value': htlc_tx.outputs()[0].value,
|
||||
'coinbase': False,
|
||||
'preimage_script': bh2u(witness_script),
|
||||
'sequence': to_self_delay,
|
||||
}]
|
||||
tx = Transaction.from_io(second_stage_inputs, second_stage_outputs, version=2)
|
||||
|
||||
local_delaykey = derive_privkey(
|
||||
int.from_bytes(chan.config[LOCAL].delayed_basepoint.privkey, 'big'),
|
||||
our_pcp).to_bytes(32, 'big')
|
||||
assert local_delaykey == our_localdelayed_privkey.get_secret_bytes()
|
||||
|
||||
witness = construct_witness([bfh(tx.sign_txin(0, local_delaykey)), 0, witness_script])
|
||||
tx.inputs()[0]['witness'] = witness
|
||||
assert tx.is_complete()
|
||||
|
||||
txs.append((htlc_tx.txid(), EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', tx, csv_delay=to_self_delay, cltv_expiry=0)))
|
||||
txs.append((ctx.txid(), EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry)))
|
||||
|
||||
return txs
|
||||
|
||||
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
|
||||
|
|
493
electrum/lnsweep.py
Normal file
493
electrum/lnsweep.py
Normal file
|
@ -0,0 +1,493 @@
|
|||
# Copyright (C) 2018 The Electrum developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from typing import Optional, Dict, List, Tuple, TYPE_CHECKING
|
||||
|
||||
from .util import bfh, bh2u, print_error
|
||||
from .bitcoin import TYPE_ADDRESS, redeem_script_to_address, dust_threshold
|
||||
from . import ecc
|
||||
from .lnutil import (EncumberedTransaction,
|
||||
make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script,
|
||||
derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey,
|
||||
make_htlc_tx_witness, make_htlc_tx_with_open_channel,
|
||||
LOCAL, REMOTE, make_htlc_output_witness_script, UnknownPaymentHash,
|
||||
get_ordered_channel_configs, privkey_to_pubkey, get_per_commitment_secret_from_seed,
|
||||
RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret)
|
||||
from .transaction import Transaction, TxOutput, construct_witness
|
||||
from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .lnchan import Channel, UpdateAddHtlc
|
||||
|
||||
|
||||
def maybe_create_sweeptx_for_their_ctx_to_remote(ctx: Transaction, sweep_address: str,
|
||||
our_payment_privkey: ecc.ECPrivkey) -> Optional[Transaction]:
|
||||
our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True)
|
||||
to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey)
|
||||
output_idx = ctx.get_output_idx_from_address(to_remote_address)
|
||||
if output_idx is None: return None
|
||||
sweep_tx = create_sweeptx_their_ctx_to_remote(sweep_address=sweep_address,
|
||||
ctx=ctx,
|
||||
output_idx=output_idx,
|
||||
our_payment_privkey=our_payment_privkey)
|
||||
return sweep_tx
|
||||
|
||||
|
||||
def maybe_create_sweeptx_for_their_ctx_to_local(ctx: Transaction, revocation_privkey: bytes,
|
||||
to_self_delay: int, delayed_pubkey: bytes,
|
||||
sweep_address: str) -> Optional[EncumberedTransaction]:
|
||||
revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True)
|
||||
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)
|
||||
output_idx = ctx.get_output_idx_from_address(to_local_address)
|
||||
if output_idx is None: return None
|
||||
sweep_tx = create_sweeptx_ctx_to_local(sweep_address=sweep_address,
|
||||
ctx=ctx,
|
||||
output_idx=output_idx,
|
||||
witness_script=witness_script,
|
||||
privkey=revocation_privkey,
|
||||
is_revocation=True)
|
||||
if sweep_tx is None: return None
|
||||
return EncumberedTransaction('their_ctx_to_local', sweep_tx, csv_delay=0, cltv_expiry=0)
|
||||
|
||||
|
||||
def create_sweeptxs_for_their_just_revoked_ctx(chan: 'Channel', ctx: Transaction, per_commitment_secret: bytes,
|
||||
sweep_address: str) -> List[Tuple[Optional[str],EncumberedTransaction]]:
|
||||
"""Presign sweeping transactions using the just received revoked pcs.
|
||||
These will only be utilised if the remote breaches.
|
||||
Sweep 'lo_local', and all the HTLCs (two cases: directly from ctx, or from HTLC tx).
|
||||
"""
|
||||
# prep
|
||||
pcp = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
|
||||
this_conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=False)
|
||||
other_revocation_privkey = derive_blinded_privkey(other_conf.revocation_basepoint.privkey,
|
||||
per_commitment_secret)
|
||||
to_self_delay = other_conf.to_self_delay
|
||||
this_delayed_pubkey = derive_pubkey(this_conf.delayed_basepoint.pubkey, pcp)
|
||||
txs = []
|
||||
# to_local
|
||||
sweep_tx = maybe_create_sweeptx_for_their_ctx_to_local(ctx=ctx,
|
||||
revocation_privkey=other_revocation_privkey,
|
||||
to_self_delay=to_self_delay,
|
||||
delayed_pubkey=this_delayed_pubkey,
|
||||
sweep_address=sweep_address)
|
||||
if sweep_tx:
|
||||
txs.append((None, EncumberedTransaction('their_ctx_to_local', sweep_tx, csv_delay=0, cltv_expiry=0)))
|
||||
# HTLCs
|
||||
def create_sweeptx_for_htlc(htlc: UpdateAddHtlc, is_received_htlc: bool) -> Tuple[Optional[Transaction],
|
||||
Optional[Transaction],
|
||||
Transaction]:
|
||||
htlc_tx_witness_script, htlc_tx = make_htlc_tx_with_open_channel(chan=chan,
|
||||
pcp=pcp,
|
||||
for_us=False,
|
||||
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(
|
||||
htlc_tx=htlc_tx,
|
||||
htlctx_witness_script=htlc_tx_witness_script,
|
||||
sweep_address=sweep_address,
|
||||
privkey=other_revocation_privkey,
|
||||
is_revocation=True)
|
||||
return direct_sweep_tx, secondstage_sweep_tx, htlc_tx
|
||||
# received HTLCs, in their ctx
|
||||
# TODO consider carefully if "included_htlcs" is what we need here
|
||||
received_htlcs = list(chan.included_htlcs(REMOTE, LOCAL)) # type: List[UpdateAddHtlc]
|
||||
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((ctx.txid(), EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', direct_sweep_tx, csv_delay=0, cltv_expiry=0)))
|
||||
if secondstage_sweep_tx:
|
||||
txs.append((htlc_tx.txid(), EncumberedTransaction(f'their_htlctx_{bh2u(htlc.payment_hash)}', secondstage_sweep_tx, csv_delay=0, cltv_expiry=0)))
|
||||
# offered HTLCs, in their ctx
|
||||
# TODO consider carefully if "included_htlcs" is what we need here
|
||||
offered_htlcs = list(chan.included_htlcs(REMOTE, REMOTE)) # type: List[UpdateAddHtlc]
|
||||
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((ctx.txid(), EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', direct_sweep_tx, csv_delay=0, cltv_expiry=0)))
|
||||
if secondstage_sweep_tx:
|
||||
txs.append((htlc_tx.txid(), EncumberedTransaction(f'their_htlctx_{bh2u(htlc.payment_hash)}', secondstage_sweep_tx, csv_delay=0, cltv_expiry=0)))
|
||||
return txs
|
||||
|
||||
|
||||
def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
|
||||
sweep_address: str) -> List[Tuple[Optional[str],EncumberedTransaction]]:
|
||||
"""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).
|
||||
'to_local' can be swept even if this is a breach (by us),
|
||||
but HTLCs cannot (old HTLCs are no longer stored).
|
||||
"""
|
||||
this_conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=True)
|
||||
ctn = extract_ctn_from_tx_and_chan(ctx, chan)
|
||||
our_per_commitment_secret = get_per_commitment_secret_from_seed(
|
||||
this_conf.per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
|
||||
our_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True)
|
||||
# prep
|
||||
this_delayed_bp_privkey = ecc.ECPrivkey(this_conf.delayed_basepoint.privkey)
|
||||
this_localdelayed_privkey = derive_privkey(this_delayed_bp_privkey.secret_scalar, our_pcp)
|
||||
this_localdelayed_privkey = ecc.ECPrivkey.from_secret_scalar(this_localdelayed_privkey)
|
||||
other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, our_pcp)
|
||||
to_self_delay = chan.config[REMOTE].to_self_delay
|
||||
this_htlc_privkey = derive_privkey(secret=int.from_bytes(this_conf.htlc_basepoint.privkey, 'big'),
|
||||
per_commitment_point=our_pcp).to_bytes(32, 'big')
|
||||
txs = []
|
||||
# to_local
|
||||
sweep_tx = maybe_create_sweeptx_that_spends_to_local_in_our_ctx(ctx=ctx,
|
||||
sweep_address=sweep_address,
|
||||
our_localdelayed_privkey=this_localdelayed_privkey,
|
||||
remote_revocation_pubkey=other_revocation_pubkey,
|
||||
to_self_delay=to_self_delay)
|
||||
if sweep_tx:
|
||||
txs.append((None, EncumberedTransaction('our_ctx_to_local', sweep_tx, csv_delay=to_self_delay, cltv_expiry=0)))
|
||||
# HTLCs
|
||||
def create_txns_for_htlc(htlc: UpdateAddHtlc, is_received_htlc: bool) -> Tuple[Optional[Transaction], Optional[Transaction]]:
|
||||
if is_received_htlc:
|
||||
try:
|
||||
preimage, invoice = chan.get_preimage_and_invoice(htlc.payment_hash)
|
||||
except UnknownPaymentHash as e:
|
||||
print_error(f'trying to sweep htlc from our latest ctx but getting {repr(e)}')
|
||||
return None, None
|
||||
else:
|
||||
preimage = None
|
||||
htlctx_witness_script, htlc_tx = create_htlctx_that_spends_from_our_ctx(
|
||||
chan=chan,
|
||||
our_pcp=our_pcp,
|
||||
ctx=ctx,
|
||||
htlc=htlc,
|
||||
local_htlc_privkey=this_htlc_privkey,
|
||||
preimage=preimage,
|
||||
is_received_htlc=is_received_htlc)
|
||||
to_wallet_tx = create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
|
||||
to_self_delay=to_self_delay,
|
||||
htlc_tx=htlc_tx,
|
||||
htlctx_witness_script=htlctx_witness_script,
|
||||
sweep_address=sweep_address,
|
||||
privkey=this_localdelayed_privkey.get_secret_bytes(),
|
||||
is_revocation=False)
|
||||
return htlc_tx, to_wallet_tx
|
||||
# offered HTLCs, in our ctx --> "timeout"
|
||||
# TODO consider carefully if "included_htlcs" is what we need here
|
||||
offered_htlcs = list(chan.included_htlcs(LOCAL, LOCAL)) # type: List[UpdateAddHtlc]
|
||||
for htlc in offered_htlcs:
|
||||
htlc_tx, to_wallet_tx = create_txns_for_htlc(htlc, is_received_htlc=False)
|
||||
if htlc_tx and to_wallet_tx:
|
||||
txs.append((htlc_tx.txid(), EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0)))
|
||||
txs.append((ctx.txid(), EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry)))
|
||||
# received HTLCs, in our ctx --> "success"
|
||||
# TODO consider carefully if "included_htlcs" is what we need here
|
||||
received_htlcs = list(chan.included_htlcs(LOCAL, REMOTE)) # type: List[UpdateAddHtlc]
|
||||
for htlc in received_htlcs:
|
||||
htlc_tx, to_wallet_tx = create_txns_for_htlc(htlc, is_received_htlc=True)
|
||||
if htlc_tx and to_wallet_tx:
|
||||
txs.append((htlc_tx.txid(), EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0)))
|
||||
txs.append((ctx.txid(), EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=0)))
|
||||
return txs
|
||||
|
||||
|
||||
def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
|
||||
sweep_address: str) -> List[Tuple[Optional[str],EncumberedTransaction]]:
|
||||
"""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
|
||||
lnchan does not store old HTLCs.
|
||||
"""
|
||||
this_conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=False)
|
||||
ctn = extract_ctn_from_tx_and_chan(ctx, chan)
|
||||
# note: the remote sometimes has two valid non-revoked commitment transactions,
|
||||
# either of which could be broadcast (this_conf.ctn, this_conf.ctn+1)
|
||||
per_commitment_secret = None
|
||||
if ctn == this_conf.ctn:
|
||||
their_pcp = this_conf.current_per_commitment_point
|
||||
elif ctn == this_conf.ctn + 1:
|
||||
their_pcp = this_conf.next_per_commitment_point
|
||||
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)
|
||||
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_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)
|
||||
other_payment_privkey = derive_privkey(other_payment_bp_privkey.secret_scalar, their_pcp)
|
||||
other_payment_privkey = ecc.ECPrivkey.from_secret_scalar(other_payment_privkey)
|
||||
|
||||
txs = []
|
||||
if per_commitment_secret: # breach
|
||||
# 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.append((None, EncumberedTransaction('their_ctx_to_local', sweep_tx, csv_delay=0, cltv_expiry=0)))
|
||||
# to_remote
|
||||
sweep_tx = maybe_create_sweeptx_for_their_ctx_to_remote(ctx=ctx,
|
||||
sweep_address=sweep_address,
|
||||
our_payment_privkey=other_payment_privkey)
|
||||
if sweep_tx:
|
||||
txs.append((None, EncumberedTransaction('their_ctx_to_remote', sweep_tx, csv_delay=0, cltv_expiry=0)))
|
||||
# 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:
|
||||
try:
|
||||
preimage, invoice = chan.get_preimage_and_invoice(htlc.payment_hash)
|
||||
except UnknownPaymentHash as e:
|
||||
print_error(f'trying to sweep htlc from their latest ctx but getting {repr(e)}')
|
||||
return None
|
||||
else:
|
||||
preimage = None
|
||||
htlc_output_witness_script = make_htlc_output_witness_script(
|
||||
is_received_htlc=is_received_htlc,
|
||||
remote_revocation_pubkey=other_revocation_pubkey,
|
||||
remote_htlc_pubkey=other_htlc_privkey.get_public_key_bytes(compressed=True),
|
||||
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)
|
||||
return sweep_tx
|
||||
# received HTLCs, in their ctx --> "timeout"
|
||||
received_htlcs = chan.included_htlcs_in_their_latest_ctxs(LOCAL)[ctn] # type: List[UpdateAddHtlc]
|
||||
for htlc in received_htlcs:
|
||||
sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True)
|
||||
if sweep_tx:
|
||||
txs.append((ctx.txid(), EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry)))
|
||||
# offered HTLCs, in their ctx --> "success"
|
||||
offered_htlcs = chan.included_htlcs_in_their_latest_ctxs(REMOTE)[ctn] # type: List[UpdateAddHtlc]
|
||||
for htlc in offered_htlcs:
|
||||
sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False)
|
||||
if sweep_tx:
|
||||
txs.append((ctx.txid(), EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=0)))
|
||||
return txs
|
||||
|
||||
|
||||
def maybe_create_sweeptx_that_spends_to_local_in_our_ctx(
|
||||
ctx: Transaction, sweep_address: str, our_localdelayed_privkey: ecc.ECPrivkey,
|
||||
remote_revocation_pubkey: bytes, to_self_delay: int) -> Optional[Transaction]:
|
||||
our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True)
|
||||
to_local_witness_script = bh2u(make_commitment_output_to_local_witness_script(
|
||||
remote_revocation_pubkey, to_self_delay, our_localdelayed_pubkey))
|
||||
to_local_address = redeem_script_to_address('p2wsh', to_local_witness_script)
|
||||
output_idx = ctx.get_output_idx_from_address(to_local_address)
|
||||
if output_idx is None: return None
|
||||
sweep_tx = create_sweeptx_ctx_to_local(sweep_address=sweep_address,
|
||||
ctx=ctx,
|
||||
output_idx=output_idx,
|
||||
witness_script=to_local_witness_script,
|
||||
privkey=our_localdelayed_privkey.get_secret_bytes(),
|
||||
is_revocation=False,
|
||||
to_self_delay=to_self_delay)
|
||||
if sweep_tx is None: return None
|
||||
return sweep_tx
|
||||
|
||||
|
||||
def create_htlctx_that_spends_from_our_ctx(chan: 'Channel', our_pcp: bytes,
|
||||
ctx: Transaction, htlc: 'UpdateAddHtlc',
|
||||
local_htlc_privkey: bytes, preimage: Optional[bytes],
|
||||
is_received_htlc: bool) -> Tuple[bytes, Transaction]:
|
||||
assert is_received_htlc == bool(preimage), 'preimage is required iff htlc is received'
|
||||
preimage = preimage or b''
|
||||
witness_script, htlc_tx = make_htlc_tx_with_open_channel(chan=chan,
|
||||
pcp=our_pcp,
|
||||
for_us=True,
|
||||
we_receive=is_received_htlc,
|
||||
commit=ctx,
|
||||
htlc=htlc)
|
||||
remote_htlc_sig = chan.get_remote_htlc_sig_for_htlc(htlc, we_receive=is_received_htlc)
|
||||
local_htlc_sig = bfh(htlc_tx.sign_txin(0, local_htlc_privkey))
|
||||
txin = htlc_tx.inputs()[0]
|
||||
witness_program = bfh(Transaction.get_preimage_script(txin))
|
||||
txin['witness'] = bh2u(make_htlc_tx_witness(remote_htlc_sig, local_htlc_sig, preimage, witness_program))
|
||||
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]) -> 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)
|
||||
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,
|
||||
fee_per_kb: int=None) -> Optional[Transaction]:
|
||||
preimage = preimage or b'' # preimage is required iff (not is_revocation and htlc is offered)
|
||||
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': bh2u(witness_script),
|
||||
}]
|
||||
tx_size_bytes = 200 # TODO (depends on offered/received and is_revocation)
|
||||
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)
|
||||
outvalue = val - fee
|
||||
if outvalue <= dust_threshold(): return None
|
||||
sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
|
||||
tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2)
|
||||
|
||||
sig = bfh(tx.sign_txin(0, privkey))
|
||||
if not is_revocation:
|
||||
witness = construct_witness([sig, preimage, witness_script])
|
||||
else:
|
||||
revocation_pubkey = privkey_to_pubkey(privkey)
|
||||
witness = construct_witness([sig, revocation_pubkey, witness_script])
|
||||
tx.inputs()[0]['witness'] = witness
|
||||
assert tx.is_complete()
|
||||
return tx
|
||||
|
||||
|
||||
def create_sweeptx_their_ctx_to_remote(sweep_address: str, ctx: Transaction, output_idx: int,
|
||||
our_payment_privkey: ecc.ECPrivkey,
|
||||
fee_per_kb: int=None) -> Optional[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)
|
||||
outvalue = val - fee
|
||||
if outvalue <= dust_threshold(): return None
|
||||
sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
|
||||
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(sweep_address: str, ctx: Transaction, output_idx: int, witness_script: str,
|
||||
privkey: bytes, is_revocation: bool,
|
||||
to_self_delay: int=None,
|
||||
fee_per_kb: int=None) -> Optional[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 not is_revocation:
|
||||
assert isinstance(to_self_delay, int)
|
||||
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)
|
||||
outvalue = val - fee
|
||||
if outvalue <= dust_threshold(): return None
|
||||
sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
|
||||
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
|
||||
|
||||
|
||||
def create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
|
||||
htlc_tx: Transaction, htlctx_witness_script: bytes, sweep_address: str,
|
||||
privkey: bytes, is_revocation: bool, to_self_delay: int=None,
|
||||
fee_per_kb: int=None) -> Optional[Transaction]:
|
||||
val = htlc_tx.outputs()[0].value
|
||||
sweep_inputs = [{
|
||||
'scriptSig': '',
|
||||
'type': 'p2wsh',
|
||||
'signatures': [],
|
||||
'num_sig': 0,
|
||||
'prevout_n': 0,
|
||||
'prevout_hash': htlc_tx.txid(),
|
||||
'value': val,
|
||||
'coinbase': False,
|
||||
'preimage_script': bh2u(htlctx_witness_script),
|
||||
}]
|
||||
if not is_revocation:
|
||||
assert isinstance(to_self_delay, int)
|
||||
sweep_inputs[0]['sequence'] = to_self_delay
|
||||
tx_size_bytes = 200 # TODO
|
||||
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)
|
||||
outvalue = val - fee
|
||||
if outvalue <= dust_threshold(): return None
|
||||
sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
|
||||
tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2)
|
||||
|
||||
sig = bfh(tx.sign_txin(0, privkey))
|
||||
witness = construct_witness([sig, int(is_revocation), htlctx_witness_script])
|
||||
tx.inputs()[0]['witness'] = witness
|
||||
assert tx.is_complete()
|
||||
return tx
|
|
@ -5,7 +5,7 @@
|
|||
from enum import IntFlag, IntEnum
|
||||
import json
|
||||
from collections import namedtuple
|
||||
from typing import NamedTuple, List, Tuple, Mapping, Optional
|
||||
from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Union
|
||||
import re
|
||||
|
||||
from .util import bfh, bh2u, inv_dict
|
||||
|
@ -13,13 +13,16 @@ from .crypto import sha256
|
|||
from .transaction import Transaction
|
||||
from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number
|
||||
from . import ecc, bitcoin, crypto, transaction
|
||||
from .transaction import opcodes, TxOutput
|
||||
from .bitcoin import push_script
|
||||
from .transaction import opcodes, TxOutput, Transaction
|
||||
from .bitcoin import push_script, redeem_script_to_address, TYPE_ADDRESS
|
||||
from . import segwit_addr
|
||||
from .i18n import _
|
||||
from .lnaddr import lndecode
|
||||
from .keystore import BIP32_KeyStore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .lnchan import Channel, UpdateAddHtlc
|
||||
|
||||
|
||||
HTLC_TIMEOUT_WEIGHT = 663
|
||||
HTLC_SUCCESS_WEIGHT = 703
|
||||
|
@ -238,18 +241,18 @@ def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_dela
|
|||
output = TxOutput(bitcoin.TYPE_ADDRESS, p2wsh, final_amount_sat)
|
||||
return script, output
|
||||
|
||||
def make_htlc_tx_witness(remotehtlcsig, localhtlcsig, payment_preimage, witness_script):
|
||||
def make_htlc_tx_witness(remotehtlcsig: bytes, localhtlcsig: bytes,
|
||||
payment_preimage: bytes, witness_script: bytes) -> bytes:
|
||||
assert type(remotehtlcsig) is bytes
|
||||
assert type(localhtlcsig) is bytes
|
||||
assert type(payment_preimage) is bytes
|
||||
assert type(witness_script) is bytes
|
||||
return bfh(transaction.construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script]))
|
||||
|
||||
def make_htlc_tx_inputs(htlc_output_txid, htlc_output_index, revocationpubkey, local_delayedpubkey, amount_msat, witness_script):
|
||||
def make_htlc_tx_inputs(htlc_output_txid: str, htlc_output_index: int,
|
||||
amount_msat: int, witness_script: str) -> List[dict]:
|
||||
assert type(htlc_output_txid) is str
|
||||
assert type(htlc_output_index) is int
|
||||
assert type(revocationpubkey) is bytes
|
||||
assert type(local_delayedpubkey) is bytes
|
||||
assert type(amount_msat) is int
|
||||
assert type(witness_script) is str
|
||||
c_inputs = [{
|
||||
|
@ -272,7 +275,8 @@ def make_htlc_tx(cltv_timeout, inputs, output):
|
|||
tx = Transaction.from_io(inputs, c_outputs, locktime=cltv_timeout, version=2)
|
||||
return tx
|
||||
|
||||
def make_offered_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash):
|
||||
def make_offered_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
|
||||
local_htlcpubkey: bytes, payment_hash: bytes) -> bytes:
|
||||
assert type(revocation_pubkey) is bytes
|
||||
assert type(remote_htlcpubkey) is bytes
|
||||
assert type(local_htlcpubkey) is bytes
|
||||
|
@ -285,7 +289,8 @@ def make_offered_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, pa
|
|||
+ bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_HASH160])\
|
||||
+ bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) + bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF])
|
||||
|
||||
def make_received_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash, cltv_expiry):
|
||||
def make_received_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
|
||||
local_htlcpubkey: bytes, payment_hash: bytes, cltv_expiry: int) -> bytes:
|
||||
for i in [revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash]:
|
||||
assert type(i) is bytes
|
||||
assert type(cltv_expiry) is int
|
||||
|
@ -307,12 +312,34 @@ def make_received_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, p
|
|||
+ bitcoin.add_number_to_script(cltv_expiry) \
|
||||
+ bytes([opcodes.OP_CLTV, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF])
|
||||
|
||||
def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, commit, htlc):
|
||||
amount_msat, cltv_expiry, payment_hash = htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash
|
||||
def make_htlc_output_witness_script(is_received_htlc: bool, remote_revocation_pubkey: bytes, remote_htlc_pubkey: bytes,
|
||||
local_htlc_pubkey: bytes, payment_hash: bytes, cltv_expiry: Optional[int]) -> bytes:
|
||||
if is_received_htlc:
|
||||
return make_received_htlc(revocation_pubkey=remote_revocation_pubkey,
|
||||
remote_htlcpubkey=remote_htlc_pubkey,
|
||||
local_htlcpubkey=local_htlc_pubkey,
|
||||
payment_hash=payment_hash,
|
||||
cltv_expiry=cltv_expiry)
|
||||
else:
|
||||
return make_offered_htlc(revocation_pubkey=remote_revocation_pubkey,
|
||||
remote_htlcpubkey=remote_htlc_pubkey,
|
||||
local_htlcpubkey=local_htlc_pubkey,
|
||||
payment_hash=payment_hash)
|
||||
|
||||
|
||||
def get_ordered_channel_configs(chan: 'Channel', for_us: bool) -> Tuple[Union[LocalConfig, RemoteConfig],
|
||||
Union[LocalConfig, RemoteConfig]]:
|
||||
conf = chan.config[LOCAL] if for_us else chan.config[REMOTE]
|
||||
other_conf = chan.config[LOCAL] if not for_us else chan.config[REMOTE]
|
||||
return conf, other_conf
|
||||
|
||||
|
||||
def make_htlc_tx_with_open_channel(chan: 'Channel', pcp: bytes, for_us: bool,
|
||||
we_receive: bool, commit: Transaction,
|
||||
htlc: 'UpdateAddHtlc') -> Tuple[bytes, Transaction]:
|
||||
amount_msat, cltv_expiry, payment_hash = htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash
|
||||
conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=for_us)
|
||||
|
||||
revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp)
|
||||
delayedpubkey = derive_pubkey(conf.delayed_basepoint.pubkey, pcp)
|
||||
other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp)
|
||||
other_htlc_pubkey = derive_pubkey(other_conf.htlc_basepoint.pubkey, pcp)
|
||||
|
@ -323,19 +350,23 @@ def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, commit, htlc):
|
|||
script, htlc_tx_output = make_htlc_tx_output(
|
||||
amount_msat = amount_msat,
|
||||
local_feerate = chan.pending_feerate(LOCAL if for_us else REMOTE),
|
||||
revocationpubkey=revocation_pubkey,
|
||||
revocationpubkey=other_revocation_pubkey,
|
||||
local_delayedpubkey=delayedpubkey,
|
||||
success = is_htlc_success,
|
||||
to_self_delay = other_conf.to_self_delay)
|
||||
if is_htlc_success:
|
||||
preimage_script = make_received_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash, cltv_expiry)
|
||||
else:
|
||||
preimage_script = make_offered_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash)
|
||||
output_idx = commit.htlc_output_indices[htlc.payment_hash]
|
||||
preimage_script = make_htlc_output_witness_script(is_received_htlc=is_htlc_success,
|
||||
remote_revocation_pubkey=other_revocation_pubkey,
|
||||
remote_htlc_pubkey=other_htlc_pubkey,
|
||||
local_htlc_pubkey=htlc_pubkey,
|
||||
payment_hash=payment_hash,
|
||||
cltv_expiry=cltv_expiry)
|
||||
htlc_address = redeem_script_to_address('p2wsh', bh2u(preimage_script))
|
||||
# FIXME handle htlc_address collision
|
||||
# also: https://github.com/lightningnetwork/lightning-rfc/issues/448
|
||||
prevout_idx = commit.get_output_idx_from_address(htlc_address)
|
||||
assert prevout_idx is not None
|
||||
htlc_tx_inputs = make_htlc_tx_inputs(
|
||||
commit.txid(), output_idx,
|
||||
revocationpubkey=revocation_pubkey,
|
||||
local_delayedpubkey=delayedpubkey,
|
||||
commit.txid(), prevout_idx,
|
||||
amount_msat=amount_msat,
|
||||
witness_script=bh2u(preimage_script))
|
||||
if is_htlc_success:
|
||||
|
@ -401,7 +432,7 @@ def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey,
|
|||
delayed_pubkey, to_self_delay, funding_txid,
|
||||
funding_pos, funding_sat, local_amount, remote_amount,
|
||||
dust_limit_sat, fees_per_participant,
|
||||
htlcs):
|
||||
htlcs: List[ScriptHtlc]) -> Transaction:
|
||||
c_input = make_funding_input(local_funding_pubkey, remote_funding_pubkey,
|
||||
funding_pos, funding_txid, funding_sat)
|
||||
obs = get_obscured_ctn(ctn, funder_payment_basepoint, fundee_payment_basepoint)
|
||||
|
@ -423,15 +454,6 @@ def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey,
|
|||
|
||||
# create commitment tx
|
||||
tx = Transaction.from_io(c_inputs, c_outputs_filtered, locktime=locktime, version=2)
|
||||
|
||||
tx.htlc_output_indices = {}
|
||||
assert len(htlcs) == len(htlc_outputs)
|
||||
for script_htlc, output in zip(htlcs, htlc_outputs):
|
||||
if output in tx.outputs():
|
||||
# minus the first two outputs (to_local, to_remote)
|
||||
assert script_htlc.htlc.payment_hash not in tx.htlc_output_indices
|
||||
tx.htlc_output_indices[script_htlc.htlc.payment_hash] = tx.outputs().index(output)
|
||||
|
||||
return tx
|
||||
|
||||
def make_commitment_output_to_local_witness_script(
|
||||
|
@ -487,7 +509,7 @@ def extract_ctn_from_tx(tx, txin_index: int, funder_payment_basepoint: bytes,
|
|||
obs = ((sequence & 0xffffff) << 24) + (locktime & 0xffffff)
|
||||
return get_obscured_ctn(obs, funder_payment_basepoint, fundee_payment_basepoint)
|
||||
|
||||
def extract_ctn_from_tx_and_chan(tx, chan) -> int:
|
||||
def extract_ctn_from_tx_and_chan(tx: Transaction, chan: 'Channel') -> int:
|
||||
funder_conf = chan.config[LOCAL] if chan.constraints.is_initiator else chan.config[REMOTE]
|
||||
fundee_conf = chan.config[LOCAL] if not chan.constraints.is_initiator else chan.config[REMOTE]
|
||||
return extract_ctn_from_tx(tx, txin_index=0,
|
||||
|
|
|
@ -36,9 +36,6 @@ class TxMinedDepth(IntEnum):
|
|||
|
||||
|
||||
class LNWatcher(AddressSynchronizer):
|
||||
# TODO if verifier gets an incorrect merkle proof, that tx will never verify!!
|
||||
# similarly, what if server ignores request for merkle proof?
|
||||
# maybe we should disconnect from server in these cases
|
||||
verbosity_filter = 'W'
|
||||
|
||||
def __init__(self, network: 'Network'):
|
||||
|
@ -181,6 +178,7 @@ class LNWatcher(AddressSynchronizer):
|
|||
if self.get_tx_mined_depth(prev_txid) == TxMinedDepth.DEEP:
|
||||
self.print_error('have no follow-up transactions and prevtx', prev_txid, 'mined deep, returning')
|
||||
return False
|
||||
return True
|
||||
# check if any response applies
|
||||
keep_watching_this = False
|
||||
local_height = self.network.get_local_height()
|
||||
|
@ -241,7 +239,7 @@ class LNWatcher(AddressSynchronizer):
|
|||
|
||||
def get_tx_mined_depth(self, txid: str):
|
||||
if not txid:
|
||||
return TxMinedStatus.FREE
|
||||
return TxMinedDepth.FREE
|
||||
tx_mined_depth = self.get_tx_height(txid)
|
||||
height, conf = tx_mined_depth.height, tx_mined_depth.conf
|
||||
if conf > 100:
|
||||
|
|
|
@ -531,6 +531,8 @@ class LNWorker(PrintError):
|
|||
return routing_hints
|
||||
|
||||
def delete_invoice(self, payment_hash_hex: str):
|
||||
# FIXME we will now LOSE the preimage!! is this feature a good idea?
|
||||
# maybe instead of deleting, we could have a feature to "hide" invoices (e.g. for GUI)
|
||||
try:
|
||||
del self.invoices[payment_hash_hex]
|
||||
except KeyError:
|
||||
|
|
|
@ -199,10 +199,10 @@ class TestChannel(unittest.TestCase):
|
|||
alice_channel, bob_channel = self.alice_channel, self.bob_channel
|
||||
htlc = self.htlc
|
||||
|
||||
ctn_to_htlcs = alice_channel.included_htlcs_in_latest_ctxs()
|
||||
self.assertEqual(list(ctn_to_htlcs.keys()), [0,1])
|
||||
self.assertEqual(ctn_to_htlcs[0], [])
|
||||
self.assertEqual(ctn_to_htlcs[1], [htlc])
|
||||
self.assertEqual({0: [], 1: [htlc]}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
|
||||
self.assertEqual({0: [], 1: []}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
|
||||
self.assertEqual({0: [], 1: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
|
||||
self.assertEqual({0: [], 1: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
|
||||
|
||||
# Next alice commits this change by sending a signature message. Since
|
||||
# we expect the messages to be ordered, Bob will receive the HTLC we
|
||||
|
@ -217,6 +217,11 @@ class TestChannel(unittest.TestCase):
|
|||
# from Alice.
|
||||
bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs)
|
||||
|
||||
self.assertEqual({0: [], 1: [htlc]}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
|
||||
self.assertEqual({0: [], 1: [htlc]}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
|
||||
self.assertEqual({0: [], 1: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
|
||||
self.assertEqual({0: [], 1: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
|
||||
|
||||
# Bob revokes his prior commitment given to him by Alice, since he now
|
||||
# has a valid signature for a newer commitment.
|
||||
bobRevocation, _ = bob_channel.revoke_current_commitment()
|
||||
|
@ -279,10 +284,10 @@ class TestChannel(unittest.TestCase):
|
|||
|
||||
bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
|
||||
|
||||
ctn_to_htlcs = bob_channel.included_htlcs_in_latest_ctxs()
|
||||
self.assertEqual(list(ctn_to_htlcs.keys()), [1,2])
|
||||
self.assertEqual(len(ctn_to_htlcs[1]), 1)
|
||||
self.assertEqual(len(ctn_to_htlcs[2]), 0)
|
||||
self.assertEqual({1: [htlc], 2: []}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
|
||||
self.assertEqual({1: [htlc], 2: []}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
|
||||
self.assertEqual({1: [], 2: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
|
||||
self.assertEqual({1: [], 2: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
|
||||
|
||||
alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
|
||||
|
||||
|
|
|
@ -552,8 +552,6 @@ class TestLNUtil(unittest.TestCase):
|
|||
our_htlc_tx_inputs = make_htlc_tx_inputs(
|
||||
htlc_output_txid=our_commit_tx.txid(),
|
||||
htlc_output_index=htlc_output_index,
|
||||
revocationpubkey=local_revocation_pubkey,
|
||||
local_delayedpubkey=local_delayedpubkey,
|
||||
amount_msat=amount_msat,
|
||||
witness_script=bh2u(htlc))
|
||||
our_htlc_tx = make_htlc_tx(cltv_timeout,
|
||||
|
|
|
@ -1190,6 +1190,28 @@ class Transaction:
|
|||
return (addr in (o.address for o in self.outputs())) \
|
||||
or (addr in (txin.get("address") for txin in self.inputs()))
|
||||
|
||||
def get_output_idx_from_scriptpubkey(self, script: str) -> Optional[int]:
|
||||
"""Returns the index of an output with given script.
|
||||
If there are no such outputs, returns None;
|
||||
if there are multiple, returns one of them.
|
||||
"""
|
||||
assert isinstance(script, str) # hex
|
||||
# build cache if there isn't one yet
|
||||
# note: can become stale and return incorrect data
|
||||
# if the tx is modified later; that's out of scope.
|
||||
if not hasattr(self, '_script_to_output_idx'):
|
||||
d = {}
|
||||
for output_idx, o in enumerate(self.outputs()):
|
||||
o_script = self.pay_script(o.type, o.address)
|
||||
assert isinstance(o_script, str)
|
||||
d[o_script] = output_idx
|
||||
self._script_to_output_idx = d
|
||||
return self._script_to_output_idx.get(script)
|
||||
|
||||
def get_output_idx_from_address(self, addr: str) -> Optional:
|
||||
script = bitcoin.address_to_script(addr)
|
||||
return self.get_output_idx_from_scriptpubkey(script)
|
||||
|
||||
def as_dict(self):
|
||||
if self.raw is None:
|
||||
self.raw = self.serialize()
|
||||
|
|
Loading…
Add table
Reference in a new issue