commit fde9f91902bcfa05bbc431f62f7d0bd7af3c92d2
parent 3019aa35cf3b6deca9ec42d30af782d8a74a9389
Author: ThomasV <thomasv@electrum.org>
Date: Sat, 15 Dec 2018 11:38:46 +0100
lnchannel: store pre-signed sweep transactions after each new commitment
Diffstat:
5 files changed, 55 insertions(+), 47 deletions(-)
diff --git a/electrum/lnbase.py b/electrum/lnbase.py
@@ -499,9 +499,10 @@ class Peer(PrintError):
"constraints": ChannelConstraints(capacity=funding_sat, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth, feerate=feerate),
"remote_commitment_to_be_revoked": None,
}
- chan = Channel(chan_dict, payment_completed=self.lnworker.payment_completed)
+ chan = Channel(chan_dict,
+ sweep_address=self.lnworker.sweep_address,
+ 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",
@@ -590,9 +591,10 @@ class Peer(PrintError):
"constraints": ChannelConstraints(capacity=funding_sat, is_initiator=False, funding_txn_minimum_depth=min_depth, feerate=feerate),
"remote_commitment_to_be_revoked": None,
}
- chan = Channel(chan_dict, payment_completed=self.lnworker.payment_completed)
+ chan = Channel(chan_dict,
+ sweep_adddress=self.lnworker.sweep_address,
+ 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, [])
diff --git a/electrum/lnchan.py b/electrum/lnchan.py
@@ -44,6 +44,7 @@ from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closin
from .lnutil import ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script
from .transaction import Transaction
from .lnsweep import create_sweeptxs_for_their_just_revoked_ctx
+from .lnsweep import create_sweeptxs_for_our_latest_ctx, create_sweeptxs_for_their_latest_ctx
class ChannelJsonEncoder(json.JSONEncoder):
@@ -146,10 +147,11 @@ class Channel(PrintError):
except:
return super().diagnostic_name()
- def __init__(self, state, name = None, payment_completed : Optional[Callable[[HTLCOwner, UpdateAddHtlc, bytes], None]] = None):
+ def __init__(self, state, sweep_address = None, name = None, payment_completed : Optional[Callable[[HTLCOwner, UpdateAddHtlc, bytes], None]] = None):
self.preimages = {}
if not payment_completed:
payment_completed = lambda this, x, y, z: None
+ self.sweep_address = sweep_address
self.payment_completed = payment_completed
assert 'local_state' not in state
self.config = {}
@@ -203,9 +205,18 @@ class Channel(PrintError):
for sub in (LOCAL, REMOTE):
self.log[sub].locked_in.update(self.log[sub].adds.keys())
- # used in lnworker.on_channel_closed
- self.local_commitment = self.current_commitment(LOCAL)
- self.remote_commitment = self.current_commitment(REMOTE)
+ self.set_local_commitment(self.current_commitment(LOCAL))
+ self.set_remote_commitment(self.current_commitment(REMOTE))
+
+ def set_local_commitment(self, ctx):
+ self.local_commitment = ctx
+ if self.sweep_address is not None:
+ self.local_sweeptxs = create_sweeptxs_for_our_latest_ctx(self, self.local_commitment, self.sweep_address)
+
+ def set_remote_commitment(self, ctx):
+ self.remote_commitment = ctx
+ if self.sweep_address is not None:
+ self.remote_sweeptxs = create_sweeptxs_for_their_latest_ctx(self, self.remote_commitment, self.sweep_address)
def set_state(self, state: str):
if self._state == 'FORCE_CLOSING':
@@ -389,7 +400,7 @@ class Channel(PrintError):
if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
self.pending_fee[FUNDER_SIGNED] = True
- self.local_commitment = self.pending_commitment(LOCAL)
+ self.set_local_commitment(self.pending_commitment(LOCAL))
def verify_htlc(self, htlc: UpdateAddHtlc, htlc_sigs: Sequence[bytes], we_receive: bool) -> int:
_, this_point, _ = self.points()
@@ -438,7 +449,7 @@ class Channel(PrintError):
feerate=new_feerate
)
- self.local_commitment = self.pending_commitment(LOCAL)
+ self.set_local_commitment(self.pending_commitment(LOCAL))
return RevokeAndAck(last_secret, next_point), "current htlcs"
@@ -530,8 +541,8 @@ class Channel(PrintError):
if self.constraints.is_initiator:
self.pending_fee[FUNDEE_ACKED] = True
- self.local_commitment = self.pending_commitment(LOCAL)
- self.remote_commitment = self.pending_commitment(REMOTE)
+ self.set_local_commitment(self.pending_commitment(LOCAL))
+ self.set_remote_commitment(self.pending_commitment(REMOTE))
self.remote_commitment_to_be_revoked = prev_remote_commitment
return received_this_batch, sent_this_batch
diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
@@ -143,7 +143,7 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
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 = []
+ txs = {}
# to_local
sweep_tx = maybe_create_sweeptx_that_spends_to_local_in_our_ctx(ctx=ctx,
sweep_address=sweep_address,
@@ -151,7 +151,7 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
remote_revocation_pubkey=other_revocation_pubkey,
to_self_delay=to_self_delay)
if sweep_tx:
- txs.append(EncumberedTransaction('our_ctx_to_local', sweep_tx, csv_delay=to_self_delay, cltv_expiry=0))
+ txs[sweep_tx.prevout(0)] = 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:
@@ -184,16 +184,16 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
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(EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0))
- txs.append(EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry))
+ 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[htlc_tx.prevout(0)] = 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(EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0))
- txs.append(EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=0))
+ 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[htlc_tx.prevout(0)] = EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=0)
return txs
@@ -232,7 +232,7 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
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 = []
+ txs = {}
if per_commitment_secret: # breach
# to_local
other_revocation_privkey = derive_blinded_privkey(other_conf.revocation_basepoint.privkey,
@@ -250,7 +250,7 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
sweep_address=sweep_address,
our_payment_privkey=other_payment_privkey)
if sweep_tx:
- txs.append(EncumberedTransaction('their_ctx_to_remote', sweep_tx, csv_delay=0, cltv_expiry=0))
+ txs[sweep_tx.prevout(0)] = 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
@@ -286,13 +286,13 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
for htlc in received_htlcs:
sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True)
if sweep_tx:
- txs.append(EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry))
+ txs[prevout] = 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(EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=0))
+ txs[prevout] = EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=0)
return txs
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -39,7 +39,6 @@ from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
from .i18n import _
from .lnrouter import RouteEdge, is_route_sane_to_use
from .address_synchronizer import TX_HEIGHT_LOCAL
-from .lnsweep import create_sweeptxs_for_our_latest_ctx, create_sweeptxs_for_their_latest_ctx
if TYPE_CHECKING:
from .network import Network
@@ -78,11 +77,9 @@ class LNWorker(PrintError):
self.peers = {} # type: Dict[bytes, Peer] # pubkey -> Peer
self.channels = {} # type: Dict[bytes, Channel]
for x in wallet.storage.get("channels", []):
- c = Channel(x, payment_completed=self.payment_completed)
+ c = Channel(x, sweep_address=self.sweep_address, payment_completed=self.payment_completed)
self.channels[c.channel_id] = c
-
c.lnwatcher = network.lnwatcher
- c.sweep_address = self.sweep_address
self.invoices = wallet.storage.get('lightning_invoices', {}) # type: Dict[str, Tuple[str,str]] # RHASH -> (preimage, invoice)
for chan_id, chan in self.channels.items():
self.network.lnwatcher.watch_channel(chan.get_funding_address(), chan.funding_outpoint.to_str())
@@ -314,34 +311,28 @@ class LNWorker(PrintError):
self.network.trigger_callback('channel', chan)
# remove from channel_db
self.channel_db.remove_channel(chan.short_channel_id)
- # sweep
- our_ctx = chan.local_commitment
- their_ctx = chan.remote_commitment
- if txid == our_ctx.txid():
+ # detect who closed
+ if txid == chan.local_commitment.txid():
self.print_error('we force closed', funding_outpoint)
- # we force closed
- encumbered_sweeptxs = create_sweeptxs_for_our_latest_ctx(chan, our_ctx, chan.sweep_address)
- elif txid == their_ctx.txid():
+ encumbered_sweeptxs = chan.local_sweeptxs
+ elif txid == chan.remote_commitment.txid():
self.print_error('they force closed', funding_outpoint)
- # they force closed
- encumbered_sweeptxs = create_sweeptxs_for_their_latest_ctx(chan, their_ctx, chan.sweep_address)
+ encumbered_sweeptxs = chan.remote_sweeptxs
else:
- # cooperative close or breach
self.print_error('not sure who closed', funding_outpoint)
- encumbered_sweeptxs = []
-
- local_height = self.network.get_local_height()
- for e_tx in encumbered_sweeptxs:
- txin = e_tx.tx.inputs()[0]
- prev_txid = txin['prevout_hash']
- txin_outpoint = Transaction.get_outpoint_from_txin(txin)
- spender = spenders.get(txin_outpoint)
+ return
+ # sweep
+ for prevout, spender in spenders.items():
+ e_tx = encumbered_sweeptxs.get(prevout)
+ if e_tx is None:
+ continue
if spender is not None:
- self.print_error('prev_tx already spent', prev_txid)
+ self.print_error('outpoint already spent', prevout)
continue
- num_conf = self.network.lnwatcher.get_tx_height(prev_txid).conf
+ prev_txid, prev_index = prevout.split(':')
broadcast = True
if e_tx.cltv_expiry:
+ local_height = self.network.get_local_height()
if local_height > e_tx.cltv_expiry:
self.print_error(e_tx.name, 'CLTV ({} > {}) fulfilled'.format(local_height, e_tx.cltv_expiry))
else:
@@ -349,13 +340,14 @@ class LNWorker(PrintError):
.format(e_tx.name, local_height, e_tx.cltv_expiry, funding_outpoint[:8], prev_txid[:8]))
broadcast = False
if e_tx.csv_delay:
+ num_conf = self.network.lnwatcher.get_tx_height(prev_txid).conf
if num_conf < e_tx.csv_delay:
self.print_error(e_tx.name, 'waiting for {}: CSV ({} >= {}), funding outpoint {} and tx {}'
.format(e_tx.name, num_conf, e_tx.csv_delay, funding_outpoint[:8], prev_txid[:8]))
broadcast = False
if broadcast:
if not await self.network.lnwatcher.broadcast_or_log(funding_outpoint, e_tx):
- self.print_error(e_tx.name, f'could not publish encumbered tx: {str(e_tx)}, prev_txid: {prev_txid}, local_height', local_height)
+ self.print_error(e_tx.name, f'could not publish encumbered tx: {str(sweep_tx)}, prev_txid: {prev_txid}')
@log_exceptions
diff --git a/electrum/transaction.py b/electrum/transaction.py
@@ -933,6 +933,9 @@ class Transaction:
prevout_n = txin['prevout_n']
return prevout_hash + ':%d' % prevout_n
+ def prevout(self, index):
+ return self.get_outpoint_from_txin(self.inputs()[index])
+
@classmethod
def serialize_input(self, txin, script):
# Prev hash and index