Browse Source

ln: remove EncumberedTransaction

regtest_lnd
Janus 6 years ago
committed by SomberNight
parent
commit
7fe8981ade
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 69
      electrum/lnsweep.py
  2. 31
      electrum/lnutil.py
  3. 14
      electrum/lnwatcher.py
  4. 4
      electrum/tests/test_lnutil.py
  5. 4
      electrum/tests/test_transaction.py
  6. 20
      electrum/transaction.py

69
electrum/lnsweep.py

@ -7,8 +7,7 @@ from typing import Optional, Dict, List, Tuple, TYPE_CHECKING
from .util import bfh, bh2u, print_error from .util import bfh, bh2u, print_error
from .bitcoin import TYPE_ADDRESS, redeem_script_to_address, dust_threshold from .bitcoin import TYPE_ADDRESS, redeem_script_to_address, dust_threshold
from . import ecc from . import ecc
from .lnutil import (EncumberedTransaction, from .lnutil import (make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script,
make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script,
derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey, derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey,
make_htlc_tx_witness, make_htlc_tx_with_open_channel, make_htlc_tx_witness, make_htlc_tx_with_open_channel,
LOCAL, REMOTE, make_htlc_output_witness_script, UnknownPaymentHash, LOCAL, REMOTE, make_htlc_output_witness_script, UnknownPaymentHash,
@ -49,11 +48,12 @@ def maybe_create_sweeptx_for_their_ctx_to_local(ctx: Transaction, revocation_pri
witness_script=witness_script, witness_script=witness_script,
privkey=revocation_privkey, privkey=revocation_privkey,
is_revocation=True) is_revocation=True)
if sweep_tx is None: return None
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_just_revoked_ctx(chan: 'Channel', ctx: Transaction, per_commitment_secret: bytes,
sweep_address: str) -> Dict[str,EncumberedTransaction]: 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 'lo_local', and all the HTLCs (two cases: directly from ctx, or from HTLC tx). Sweep 'lo_local', and all the HTLCs (two cases: directly from ctx, or from HTLC tx).
@ -73,7 +73,7 @@ def create_sweeptxs_for_their_just_revoked_ctx(chan: 'Channel', ctx: Transaction
delayed_pubkey=this_delayed_pubkey, delayed_pubkey=this_delayed_pubkey,
sweep_address=sweep_address) sweep_address=sweep_address)
if sweep_tx: if sweep_tx:
txs[ctx.txid()] = EncumberedTransaction('their_ctx_to_local', sweep_tx, csv_delay=0, cltv_expiry=0) txs[ctx.txid()] = sweep_tx
# HTLCs # HTLCs
def create_sweeptx_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Tuple[Optional[Transaction], def create_sweeptx_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Tuple[Optional[Transaction],
Optional[Transaction], Optional[Transaction],
@ -96,6 +96,8 @@ def create_sweeptxs_for_their_just_revoked_ctx(chan: 'Channel', ctx: Transaction
is_revocation=True) is_revocation=True)
# sweep from htlc tx # sweep from htlc tx
secondstage_sweep_tx = create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx( secondstage_sweep_tx = create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
'their_htlctx_',
to_self_delay=0,
htlc_tx=htlc_tx, htlc_tx=htlc_tx,
htlctx_witness_script=htlc_tx_witness_script, htlctx_witness_script=htlc_tx_witness_script,
sweep_address=sweep_address, sweep_address=sweep_address,
@ -109,22 +111,22 @@ def create_sweeptxs_for_their_just_revoked_ctx(chan: 'Channel', ctx: Transaction
for htlc in received_htlcs: for htlc in received_htlcs:
direct_sweep_tx, secondstage_sweep_tx, htlc_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True) direct_sweep_tx, secondstage_sweep_tx, htlc_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True)
if direct_sweep_tx: if direct_sweep_tx:
txs[ctx.txid()] = EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', direct_sweep_tx, csv_delay=0, cltv_expiry=0) txs[ctx.txid()] = direct_sweep_tx
if secondstage_sweep_tx: if secondstage_sweep_tx:
txs[htlc_tx.txid()] = EncumberedTransaction(f'their_htlctx_{bh2u(htlc.payment_hash)}', secondstage_sweep_tx, csv_delay=0, cltv_expiry=0) txs[htlc_tx.txid()] = secondstage_sweep_tx
# offered HTLCs, in their ctx # offered HTLCs, in their ctx
offered_htlcs = chan.included_htlcs(REMOTE, REMOTE, False) offered_htlcs = chan.included_htlcs(REMOTE, REMOTE, False)
for htlc in offered_htlcs: for htlc in offered_htlcs:
direct_sweep_tx, secondstage_sweep_tx, htlc_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False) direct_sweep_tx, secondstage_sweep_tx, htlc_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False)
if direct_sweep_tx: if direct_sweep_tx:
txs[ctx.txid()] = EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', direct_sweep_tx, csv_delay=0, cltv_expiry=0) txs[ctx.txid()] = direct_sweep_tx
if secondstage_sweep_tx: if secondstage_sweep_tx:
txs[htlc_tx.txid()] = EncumberedTransaction(f'their_htlctx_{bh2u(htlc.payment_hash)}', secondstage_sweep_tx, csv_delay=0, cltv_expiry=0) txs[htlc_tx.txid()] = secondstage_sweep_tx
return txs return txs
def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction, def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
sweep_address: str) -> Dict[str,EncumberedTransaction]: 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),
@ -151,7 +153,7 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
remote_revocation_pubkey=other_revocation_pubkey, remote_revocation_pubkey=other_revocation_pubkey,
to_self_delay=to_self_delay) to_self_delay=to_self_delay)
if sweep_tx: if sweep_tx:
txs[sweep_tx.prevout(0)] = EncumberedTransaction('our_ctx_to_local', sweep_tx, csv_delay=to_self_delay, cltv_expiry=0) txs[sweep_tx.prevout(0)] = sweep_tx
# HTLCs # HTLCs
def create_txns_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Tuple[Optional[Transaction], Optional[Transaction]]: def create_txns_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Tuple[Optional[Transaction], Optional[Transaction]]:
if is_received_htlc: if is_received_htlc:
@ -171,6 +173,7 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
preimage=preimage, preimage=preimage,
is_received_htlc=is_received_htlc) is_received_htlc=is_received_htlc)
to_wallet_tx = create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx( to_wallet_tx = create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
'our_ctx_htlc_tx_',
to_self_delay=to_self_delay, to_self_delay=to_self_delay,
htlc_tx=htlc_tx, htlc_tx=htlc_tx,
htlctx_witness_script=htlctx_witness_script, htlctx_witness_script=htlctx_witness_script,
@ -184,21 +187,21 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
for htlc in offered_htlcs: for htlc in offered_htlcs:
htlc_tx, to_wallet_tx = create_txns_for_htlc(htlc, is_received_htlc=False) htlc_tx, to_wallet_tx = create_txns_for_htlc(htlc, is_received_htlc=False)
if htlc_tx and to_wallet_tx: if htlc_tx and to_wallet_tx:
txs[to_wallet_tx.prevout(0)] = EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0) txs[to_wallet_tx.prevout(0)] = to_wallet_tx
txs[htlc_tx.prevout(0)] = EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry) txs[htlc_tx.prevout(0)] = htlc_tx
# received HTLCs, in our ctx --> "success" # received HTLCs, in our ctx --> "success"
# TODO consider carefully if "included_htlcs" is what we need here # TODO consider carefully if "included_htlcs" is what we need here
received_htlcs = list(chan.included_htlcs(LOCAL, REMOTE)) # type: List[UpdateAddHtlc] received_htlcs = list(chan.included_htlcs(LOCAL, REMOTE)) # type: List[UpdateAddHtlc]
for htlc in received_htlcs: for htlc in received_htlcs:
htlc_tx, to_wallet_tx = create_txns_for_htlc(htlc, is_received_htlc=True) htlc_tx, to_wallet_tx = create_txns_for_htlc(htlc, is_received_htlc=True)
if htlc_tx and to_wallet_tx: if htlc_tx and to_wallet_tx:
txs[to_wallet_tx.prevout(0)] = EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0) txs[to_wallet_tx.prevout(0)] = to_wallet_tx
txs[htlc_tx.prevout(0)] = EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=0) txs[htlc_tx.prevout(0)] = htlc_tx
return txs return txs
def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction, def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
sweep_address: str) -> Dict[str,EncumberedTransaction]: 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'.
@ -244,13 +247,13 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
delayed_pubkey=this_delayed_pubkey, delayed_pubkey=this_delayed_pubkey,
sweep_address=sweep_address) sweep_address=sweep_address)
if sweep_tx: if sweep_tx:
txs[sweep_tx.prevout(0)] = EncumberedTransaction('their_ctx_to_local', sweep_tx, csv_delay=0, cltv_expiry=0) 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,
our_payment_privkey=other_payment_privkey) our_payment_privkey=other_payment_privkey)
if sweep_tx: if sweep_tx:
txs[sweep_tx.prevout(0)] = EncumberedTransaction('their_ctx_to_remote', sweep_tx, csv_delay=0, cltv_expiry=0) txs[sweep_tx.prevout(0)] = sweep_tx
# HTLCs # HTLCs
# from their ctx, we can only redeem HTLCs if the ctx was not revoked, # 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 # as old HTLCs are not stored. (if it was revoked, then we should have presigned txns
@ -279,20 +282,21 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
htlc_output_witness_script=htlc_output_witness_script, htlc_output_witness_script=htlc_output_witness_script,
privkey=other_htlc_privkey.get_secret_bytes(), privkey=other_htlc_privkey.get_secret_bytes(),
preimage=preimage, preimage=preimage,
is_revocation=False) is_revocation=False,
cltv_expiry=htlc.cltv_expiry if is_received_htlc else 0)
return sweep_tx return sweep_tx
# received HTLCs, in their ctx --> "timeout" # received HTLCs, in their ctx --> "timeout"
received_htlcs = chan.included_htlcs_in_their_latest_ctxs(LOCAL)[ctn] # type: List[UpdateAddHtlc] received_htlcs = chan.included_htlcs_in_their_latest_ctxs(LOCAL)[ctn] # type: List[UpdateAddHtlc]
for htlc in received_htlcs: for htlc in received_htlcs:
sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True) sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True)
if sweep_tx: if sweep_tx:
txs[sweep_tx.prevout(0)] = EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry) txs[sweep_tx.prevout(0)] = sweep_tx
# offered HTLCs, in their ctx --> "success" # offered HTLCs, in their ctx --> "success"
offered_htlcs = chan.included_htlcs_in_their_latest_ctxs(REMOTE)[ctn] # type: List[UpdateAddHtlc] offered_htlcs = chan.included_htlcs_in_their_latest_ctxs(REMOTE)[ctn] # type: List[UpdateAddHtlc]
for htlc in offered_htlcs: for htlc in offered_htlcs:
sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False) sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False)
if sweep_tx: if sweep_tx:
txs[sweep_tx.prevout(0)] = EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=0) txs[sweep_tx.prevout(0)] = sweep_tx
return txs return txs
@ -327,7 +331,9 @@ def create_htlctx_that_spends_from_our_ctx(chan: 'Channel', our_pcp: bytes,
for_us=True, for_us=True,
we_receive=is_received_htlc, we_receive=is_received_htlc,
commit=ctx, commit=ctx,
htlc=htlc) htlc=htlc,
name=f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}',
cltv_expiry=0 if is_received_htlc else htlc.cltv_expiry)
remote_htlc_sig = chan.get_remote_htlc_sig_for_htlc(htlc, we_receive=is_received_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)) local_htlc_sig = bfh(htlc_tx.sign_txin(0, local_htlc_privkey))
txin = htlc_tx.inputs()[0] txin = htlc_tx.inputs()[0]
@ -339,7 +345,7 @@ def create_htlctx_that_spends_from_our_ctx(chan: 'Channel', our_pcp: bytes,
def maybe_create_sweeptx_for_their_ctx_htlc(ctx: Transaction, sweep_address: str, def maybe_create_sweeptx_for_their_ctx_htlc(ctx: Transaction, sweep_address: str,
htlc_output_witness_script: bytes, htlc_output_witness_script: bytes,
privkey: bytes, is_revocation: bool, privkey: bytes, is_revocation: bool,
preimage: Optional[bytes]) -> Optional[Transaction]: preimage: Optional[bytes], cltv_expiry: int = 0) -> Optional[Transaction]:
htlc_address = redeem_script_to_address('p2wsh', bh2u(htlc_output_witness_script)) htlc_address = redeem_script_to_address('p2wsh', bh2u(htlc_output_witness_script))
# FIXME handle htlc_address collision # FIXME handle htlc_address collision
# also: https://github.com/lightningnetwork/lightning-rfc/issues/448 # also: https://github.com/lightningnetwork/lightning-rfc/issues/448
@ -351,13 +357,14 @@ def maybe_create_sweeptx_for_their_ctx_htlc(ctx: Transaction, sweep_address: str
preimage=preimage, preimage=preimage,
output_idx=output_idx, output_idx=output_idx,
privkey=privkey, privkey=privkey,
is_revocation=is_revocation) is_revocation=is_revocation,
cltv_expiry=cltv_expiry)
return sweep_tx return sweep_tx
def create_sweeptx_their_ctx_htlc(ctx: Transaction, witness_script: bytes, sweep_address: str, def create_sweeptx_their_ctx_htlc(ctx: Transaction, witness_script: bytes, sweep_address: str,
preimage: Optional[bytes], output_idx: int, preimage: Optional[bytes], output_idx: int,
privkey: bytes, is_revocation: bool, privkey: bytes, is_revocation: bool, cltv_expiry: int,
fee_per_kb: int=None) -> Optional[Transaction]: fee_per_kb: int=None) -> Optional[Transaction]:
preimage = preimage or b'' # preimage is required iff (not is_revocation and htlc is offered) preimage = preimage or b'' # preimage is required iff (not is_revocation and htlc is offered)
val = ctx.outputs()[output_idx].value val = ctx.outputs()[output_idx].value
@ -378,7 +385,7 @@ def create_sweeptx_their_ctx_htlc(ctx: Transaction, witness_script: bytes, sweep
outvalue = val - fee outvalue = val - fee
if outvalue <= dust_threshold(): return None if outvalue <= dust_threshold(): return None
sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)] sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2) tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2, name=f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', cltv_expiry=cltv_expiry)
sig = bfh(tx.sign_txin(0, privkey)) sig = bfh(tx.sign_txin(0, privkey))
if not is_revocation: if not is_revocation:
@ -412,7 +419,7 @@ def create_sweeptx_their_ctx_to_remote(sweep_address: str, ctx: Transaction, out
outvalue = val - fee outvalue = val - fee
if outvalue <= dust_threshold(): return None if outvalue <= dust_threshold(): return None
sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)] sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs) sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, name='their_ctx_to_remote')
sweep_tx.set_rbf(True) sweep_tx.set_rbf(True)
sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)}) sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)})
if not sweep_tx.is_complete(): if not sweep_tx.is_complete():
@ -451,7 +458,10 @@ def create_sweeptx_ctx_to_local(sweep_address: str, ctx: Transaction, output_idx
outvalue = val - fee outvalue = val - fee
if outvalue <= dust_threshold(): return None if outvalue <= dust_threshold(): return None
sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)] sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2) if is_revocation:
sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2, name='their_ctx_to_local')
else:
sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2, name='our_ctx_to_local', csv_delay=to_self_delay)
sig = sweep_tx.sign_txin(0, privkey) sig = sweep_tx.sign_txin(0, privkey)
witness = construct_witness([sig, int(is_revocation), witness_script]) witness = construct_witness([sig, int(is_revocation), witness_script])
sweep_tx.inputs()[0]['witness'] = witness sweep_tx.inputs()[0]['witness'] = witness
@ -459,8 +469,9 @@ def create_sweeptx_ctx_to_local(sweep_address: str, ctx: Transaction, output_idx
def create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx( def create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
name_prefix: str,
htlc_tx: Transaction, htlctx_witness_script: bytes, sweep_address: str, htlc_tx: Transaction, htlctx_witness_script: bytes, sweep_address: str,
privkey: bytes, is_revocation: bool, to_self_delay: int=None, privkey: bytes, is_revocation: bool, to_self_delay: int,
fee_per_kb: int=None) -> Optional[Transaction]: fee_per_kb: int=None) -> Optional[Transaction]:
val = htlc_tx.outputs()[0].value val = htlc_tx.outputs()[0].value
sweep_inputs = [{ sweep_inputs = [{
@ -483,7 +494,7 @@ def create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
outvalue = val - fee outvalue = val - fee
if outvalue <= dust_threshold(): return None if outvalue <= dust_threshold(): return None
sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)] sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2) tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2, name=name_prefix + htlc_tx.txid(), csv_delay=to_self_delay)
sig = bfh(tx.sign_txin(0, privkey)) sig = bfh(tx.sign_txin(0, privkey))
witness = construct_witness([sig, int(is_revocation), htlctx_witness_script]) witness = construct_witness([sig, int(is_revocation), htlctx_witness_script])

31
electrum/lnutil.py

@ -269,10 +269,10 @@ def make_htlc_tx_inputs(htlc_output_txid: str, htlc_output_index: int,
}] }]
return c_inputs return c_inputs
def make_htlc_tx(cltv_timeout, inputs, output): def make_htlc_tx(cltv_timeout, inputs, output, name, cltv_expiry):
assert type(cltv_timeout) is int assert type(cltv_timeout) is int
c_outputs = [output] c_outputs = [output]
tx = Transaction.from_io(inputs, c_outputs, locktime=cltv_timeout, version=2) tx = Transaction.from_io(inputs, c_outputs, locktime=cltv_timeout, version=2, name=name, cltv_expiry=cltv_expiry)
return tx return tx
def make_offered_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes, def make_offered_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
@ -336,7 +336,7 @@ def get_ordered_channel_configs(chan: 'Channel', for_us: bool) -> Tuple[Union[Lo
def make_htlc_tx_with_open_channel(chan: 'Channel', pcp: bytes, for_us: bool, def make_htlc_tx_with_open_channel(chan: 'Channel', pcp: bytes, for_us: bool,
we_receive: bool, commit: Transaction, we_receive: bool, commit: Transaction,
htlc: 'UpdateAddHtlc') -> Tuple[bytes, Transaction]: htlc: 'UpdateAddHtlc', name: str = None, cltv_expiry: int = 0) -> Tuple[bytes, Transaction]:
amount_msat, cltv_expiry, payment_hash = htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash 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) conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=for_us)
@ -371,7 +371,8 @@ def make_htlc_tx_with_open_channel(chan: 'Channel', pcp: bytes, for_us: bool,
witness_script=bh2u(preimage_script)) witness_script=bh2u(preimage_script))
if is_htlc_success: if is_htlc_success:
cltv_expiry = 0 cltv_expiry = 0
htlc_tx = make_htlc_tx(cltv_expiry, inputs=htlc_tx_inputs, output=htlc_tx_output) htlc_tx = make_htlc_tx(cltv_expiry, inputs=htlc_tx_inputs, output=htlc_tx_output,
name=name, cltv_expiry=cltv_expiry)
return script, htlc_tx return script, htlc_tx
def make_funding_input(local_funding_pubkey: bytes, remote_funding_pubkey: bytes, def make_funding_input(local_funding_pubkey: bytes, remote_funding_pubkey: bytes,
@ -639,28 +640,6 @@ def generate_keypair(ln_keystore: BIP32_KeyStore, key_family: LnKeyFamily, index
return Keypair(*ln_keystore.get_keypair([key_family, 0, index], None)) return Keypair(*ln_keystore.get_keypair([key_family, 0, index], None))
class EncumberedTransaction(NamedTuple("EncumberedTransaction", [('name', str),
('tx', Transaction),
('csv_delay', int),
('cltv_expiry', int),])):
def to_json(self) -> dict:
return {
'name': self.name,
'tx': str(self.tx),
'csv_delay': self.csv_delay,
'cltv_expiry': self.cltv_expiry,
}
@classmethod
def from_json(cls, d: dict):
d2 = dict(d)
d2['tx'] = Transaction(d['tx'])
return EncumberedTransaction(**d2)
def __str__(self):
return super().__str__()[:-1] + ", txid: " + self.tx.txid() + ")"
NUM_MAX_HOPS_IN_PAYMENT_PATH = 20 NUM_MAX_HOPS_IN_PAYMENT_PATH = 20
NUM_MAX_EDGES_IN_PAYMENT_PATH = NUM_MAX_HOPS_IN_PAYMENT_PATH + 1 NUM_MAX_EDGES_IN_PAYMENT_PATH = NUM_MAX_HOPS_IN_PAYMENT_PATH + 1

14
electrum/lnwatcher.py

@ -13,10 +13,10 @@ from typing import NamedTuple, Dict
import jsonrpclib import jsonrpclib
from .util import PrintError, bh2u, bfh, log_exceptions, ignore_exceptions from .util import PrintError, bh2u, bfh, log_exceptions, ignore_exceptions
from .lnutil import EncumberedTransaction
from . import wallet from . import wallet
from .storage import WalletStorage from .storage import WalletStorage
from .address_synchronizer import AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED from .address_synchronizer import AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED
from .transaction import Transaction
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
@ -46,14 +46,14 @@ class LNWatcher(AddressSynchronizer):
self.start_network(network) self.start_network(network)
self.lock = threading.RLock() self.lock = threading.RLock()
self.channel_info = storage.get('channel_info', {}) # access with 'lock' self.channel_info = storage.get('channel_info', {}) # access with 'lock'
# [funding_outpoint_str][prev_txid] -> set of EncumberedTransaction # [funding_outpoint_str][prev_txid] -> set of Transaction
# prev_txid is the txid of a tx that is watched for confirmations # prev_txid is the txid of a tx that is watched for confirmations
# access with 'lock' # access with 'lock'
self.sweepstore = defaultdict(lambda: defaultdict(set)) self.sweepstore = defaultdict(lambda: defaultdict(set))
for funding_outpoint, ctxs in storage.get('sweepstore', {}).items(): for funding_outpoint, ctxs in storage.get('sweepstore', {}).items():
for txid, set_of_txns in ctxs.items(): for txid, set_of_txns in ctxs.items():
for e_tx in set_of_txns: for e_tx in set_of_txns:
e_tx2 = EncumberedTransaction.from_json(e_tx) e_tx2 = Transaction.from_dict(e_tx)
self.sweepstore[funding_outpoint][txid].add(e_tx2) self.sweepstore[funding_outpoint][txid].add(e_tx2)
self.network.register_callback(self.on_network_update, self.network.register_callback(self.on_network_update,
@ -100,7 +100,7 @@ class LNWatcher(AddressSynchronizer):
for funding_outpoint, ctxs in self.sweepstore.items(): for funding_outpoint, ctxs in self.sweepstore.items():
sweepstore[funding_outpoint] = {} sweepstore[funding_outpoint] = {}
for prev_txid, set_of_txns in ctxs.items(): for prev_txid, set_of_txns in ctxs.items():
sweepstore[funding_outpoint][prev_txid] = [e_tx.to_json() for e_tx in set_of_txns] sweepstore[funding_outpoint][prev_txid] = [e_tx.as_dict() for e_tx in set_of_txns]
storage.put('sweepstore', sweepstore) storage.put('sweepstore', sweepstore)
storage.write() storage.write()
@ -184,11 +184,11 @@ class LNWatcher(AddressSynchronizer):
self.print_error(e_tx.name, f'could not publish encumbered tx: {str(e_tx)}, prev_txid: {prev_txid}') self.print_error(e_tx.name, f'could not publish encumbered tx: {str(e_tx)}, prev_txid: {prev_txid}')
async def broadcast_or_log(self, funding_outpoint, e_tx): async def broadcast_or_log(self, funding_outpoint, e_tx):
height = self.get_tx_height(e_tx.tx.txid()).height height = self.get_tx_height(e_tx.txid()).height
if height != TX_HEIGHT_LOCAL: if height != TX_HEIGHT_LOCAL:
return return
try: try:
txid = await self.network.broadcast_transaction(e_tx.tx) txid = await self.network.broadcast_transaction(e_tx)
except Exception as e: except Exception as e:
self.print_error(f'broadcast: {e_tx.name}: failure: {repr(e)}') self.print_error(f'broadcast: {e_tx.name}: failure: {repr(e)}')
else: else:
@ -199,7 +199,7 @@ class LNWatcher(AddressSynchronizer):
@with_watchtower @with_watchtower
def add_sweep_tx(self, funding_outpoint: str, prev_txid: str, sweeptx): def add_sweep_tx(self, funding_outpoint: str, prev_txid: str, sweeptx):
encumbered_sweeptx = EncumberedTransaction.from_json(sweeptx) encumbered_sweeptx = Transaction.from_dict(sweeptx)
with self.lock: with self.lock:
self.sweepstore[funding_outpoint][prev_txid].add(encumbered_sweeptx) self.sweepstore[funding_outpoint][prev_txid].add(encumbered_sweeptx)
self.write_to_disk() self.write_to_disk()

4
electrum/tests/test_lnutil.py

@ -556,7 +556,9 @@ class TestLNUtil(unittest.TestCase):
witness_script=bh2u(htlc)) witness_script=bh2u(htlc))
our_htlc_tx = make_htlc_tx(cltv_timeout, our_htlc_tx = make_htlc_tx(cltv_timeout,
inputs=our_htlc_tx_inputs, inputs=our_htlc_tx_inputs,
output=our_htlc_tx_output) output=our_htlc_tx_output,
name='test',
cltv_expiry=0)
local_sig = our_htlc_tx.sign_txin(0, local_privkey[:-1]) local_sig = our_htlc_tx.sign_txin(0, local_privkey[:-1])

4
electrum/tests/test_transaction.py

@ -86,7 +86,7 @@ class TestTransaction(SequentialTestCase):
self.assertEqual(tx.deserialize(), expected) self.assertEqual(tx.deserialize(), expected)
self.assertEqual(tx.deserialize(), None) self.assertEqual(tx.deserialize(), None)
self.assertEqual(tx.as_dict(), {'hex': unsigned_blob, 'complete': False, 'final': True}) self.assertEqual(tx.as_dict(), {'hex': unsigned_blob, 'complete': False, 'final': True, 'csv_delay': 0, 'cltv_expiry': 0, 'name': None})
self.assertEqual(tx.get_outputs_for_UI(), [TxOutputForUI('14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs', 1000000)]) self.assertEqual(tx.get_outputs_for_UI(), [TxOutputForUI('14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs', 1000000)])
self.assertTrue(tx.has_address('14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs')) self.assertTrue(tx.has_address('14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs'))
@ -127,7 +127,7 @@ class TestTransaction(SequentialTestCase):
tx = transaction.Transaction(signed_blob) tx = transaction.Transaction(signed_blob)
self.assertEqual(tx.deserialize(), expected) self.assertEqual(tx.deserialize(), expected)
self.assertEqual(tx.deserialize(), None) self.assertEqual(tx.deserialize(), None)
self.assertEqual(tx.as_dict(), {'hex': signed_blob, 'complete': True, 'final': True}) self.assertEqual(tx.as_dict(), {'hex': signed_blob, 'complete': True, 'final': True, 'csv_delay': 0, 'cltv_expiry': 0, 'name': None})
self.assertEqual(tx.serialize(), signed_blob) self.assertEqual(tx.serialize(), signed_blob)

20
electrum/transaction.py

@ -599,6 +599,9 @@ class Transaction:
self._outputs = None # type: List[TxOutput] self._outputs = None # type: List[TxOutput]
self.locktime = 0 self.locktime = 0
self.version = 2 self.version = 2
self.name = None
self.csv_delay = 0
self.cltv_expiry = 0
# by default we assume this is a partial txn; # by default we assume this is a partial txn;
# this value will get properly set when deserializing # this value will get properly set when deserializing
self.is_partial_originally = True self.is_partial_originally = True
@ -706,13 +709,16 @@ class Transaction:
return d return d
@classmethod @classmethod
def from_io(klass, inputs, outputs, locktime=0, version=None): def from_io(klass, inputs, outputs, locktime=0, version=None, name=None, csv_delay=0, cltv_expiry=0):
self = klass(None) self = klass(None)
self._inputs = inputs self._inputs = inputs
self._outputs = outputs self._outputs = outputs
self.locktime = locktime self.locktime = locktime
if version is not None: if version is not None:
self.version = version self.version = version
self.name = name
self.csv_delay = csv_delay
self.cltv_expiry = cltv_expiry
self.BIP69_sort() self.BIP69_sort()
return self return self
@ -1201,9 +1207,21 @@ class Transaction:
'hex': self.raw, 'hex': self.raw,
'complete': self.is_complete(), 'complete': self.is_complete(),
'final': self.is_final(), 'final': self.is_final(),
'name': self.name,
'csv_delay': self.csv_delay,
'cltv_expiry': self.cltv_expiry,
} }
return out return out
@classmethod
def from_dict(cls, d):
tx = cls(d['hex'])
tx.deserialize(True)
tx.name = d.get('name')
tx.csv_delay = d.get('csv_delay', 0)
tx.cltv_expiry = d.get('cltv_expiry', 0)
return tx
def tx_from_str(txt: str) -> str: def tx_from_str(txt: str) -> str:
"""Sanitizes tx-describing input (json or raw hex or base43) into """Sanitizes tx-describing input (json or raw hex or base43) into

Loading…
Cancel
Save