Browse Source

create sweep transaction outside of lnwatcher

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
ThomasV 6 years ago
parent
commit
11c3ca281c
  1. 8
      electrum/lnbase.py
  2. 179
      electrum/lnhtlc.py
  3. 17
      electrum/lnutil.py
  4. 214
      electrum/lnwatcher.py
  5. 8
      electrum/lnworker.py

8
electrum/lnbase.py

@ -519,7 +519,7 @@ class Peer(PrintError):
return local_config, per_commitment_secret_seed
@aiosafe
async def channel_establishment_flow(self, password, funding_sat, push_msat, temp_channel_id, sweep_address):
async def channel_establishment_flow(self, password, funding_sat, push_msat, temp_channel_id):
await self.initialized
local_config, per_commitment_secret_seed = self.make_local_config(funding_sat, push_msat, LOCAL)
# amounts
@ -613,7 +613,7 @@ class Peer(PrintError):
}
m = HTLCStateMachine(chan)
m.lnwatcher = self.lnwatcher
m.sweep_address = sweep_address
m.sweep_address = self.lnworker.sweep_address
sig_64, _ = m.sign_next_commitment()
self.send_message(gen_msg("funding_created",
temporary_channel_id=temp_channel_id,
@ -715,7 +715,7 @@ class Peer(PrintError):
}
m = HTLCStateMachine(chan)
m.lnwatcher = self.lnwatcher
m.sweep_address = self.lnworker.wallet.get_unused_address()
m.sweep_address = self.lnworker.sweep_address
remote_sig = funding_created['signature']
m.receive_new_commitment(remote_sig, [])
sig_64, _ = m.sign_next_commitment()
@ -728,7 +728,7 @@ class Peer(PrintError):
m.remote_state = m.remote_state._replace(ctn=0)
m.local_state = m.local_state._replace(ctn=0, current_commitment_signature=remote_sig)
self.lnworker.save_channel(m)
self.lnwatcher.watch_channel(m, m.sweep_address, partial(self.lnworker.on_channel_utxos, m))
self.lnwatcher.watch_channel(m, partial(self.lnworker.on_channel_utxos, m))
self.lnworker.on_channels_updated()
while True:
try:

179
electrum/lnhtlc.py

@ -6,19 +6,21 @@ from enum import Enum, auto
from typing import Optional
from .util import bfh, PrintError, bh2u
from .bitcoin import Hash, TYPE_SCRIPT
from .bitcoin import Hash, TYPE_SCRIPT, TYPE_ADDRESS
from .bitcoin import redeem_script_to_address
from .crypto import sha256
from . import ecc
from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore
from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, EncumberedTransaction
from .lnutil import get_per_commitment_secret_from_seed
from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey
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
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_outputs
from .lnutil import ScriptHtlc, SENT, RECEIVED
from .transaction import Transaction
from .transaction import Transaction, TxOutput, construct_witness
from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
FailHtlc = namedtuple("FailHtlc", ["htlc_id"])
@ -259,8 +261,7 @@ class HTLCStateMachine(PrintError):
if self.constraints.is_initiator and pending_fee.had(FUNDEE_ACKED):
pending_fee.set(FUNDER_SIGNED)
if self.lnwatcher:
self.lnwatcher.process_new_offchain_ctx(self, pending_remote_commitment, ours=False)
self.process_new_offchain_ctx(pending_remote_commitment, ours=False)
htlcsigs.sort()
htlcsigs = [x[1] for x in htlcsigs]
@ -314,8 +315,7 @@ class HTLCStateMachine(PrintError):
if self.constraints.is_initiator and pending_fee.had(FUNDEE_ACKED):
pending_fee.set(FUNDER_SIGNED)
if self.lnwatcher:
self.lnwatcher.process_new_offchain_ctx(self, pending_local_commitment, ours=True)
self.process_new_offchain_ctx(pending_local_commitment, ours=True)
def revoke_current_commitment(self):
@ -370,6 +370,30 @@ class HTLCStateMachine(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):
funding_address = self.get_funding_address()
outpoint = self.funding_outpoint.to_str()
ctn = (self.local_state.ctn if ours else self.remote_state.ctn) + 1
if ours:
our_per_commitment_secret = get_per_commitment_secret_from_seed(
self.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
our_cur_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True)
encumbered_sweeptx = maybe_create_sweeptx_for_our_ctx_to_local(self, ctx, our_cur_pcp, self.sweep_address)
else:
their_cur_pcp = self.remote_state.next_per_commitment_point
encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_remote(self, ctx, their_cur_pcp, self.sweep_address)
self.lnwatcher.add_offchain_ctx(ctn, funding_address, ours, outpoint, ctx.txid(), encumbered_sweeptx)
def process_new_revocation_secret(self, per_commitment_secret: bytes):
funding_address = self.get_funding_address()
outpoint = self.funding_outpoint.to_str()
ctx = self.remote_commitment_to_be_revoked
ctn = self.remote_state.ctn
encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_local(self, ctx, per_commitment_secret, self.sweep_address)
self.lnwatcher.add_revocation_secret(ctn, funding_address, outpoint, ctx.txid(), encumbered_sweeptx)
def receive_revocation(self, revocation):
"""
ReceiveRevocation processes a revocation sent by the remote party for the
@ -395,8 +419,7 @@ class HTLCStateMachine(PrintError):
prev_remote_commitment = self.pending_remote_commitment
self.remote_state.revocation_store.add_next_entry(revocation.per_commitment_secret)
if self.lnwatcher:
self.lnwatcher.process_new_revocation_secret(self, revocation.per_commitment_secret)
self.process_new_revocation_secret(revocation.per_commitment_secret)
def mark_settled(subject):
"""
@ -741,3 +764,139 @@ class HTLCStateMachine(PrintError):
der_sig = bfh(closing_tx.sign_txin(0, self.local_config.multisig_key.privkey))
sig = ecc.sig_string_from_der_sig(der_sig[:-1])
return sig, fee_sat
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.local_config.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(sweep_tx, csv_delay=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.local_config.revocation_basepoint.privkey,
per_commitment_secret)
revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True)
to_self_delay = chan.local_config.to_self_delay
delayed_pubkey = derive_pubkey(chan.remote_config.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(sweep_tx, csv_delay=0)
def maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_pcp: bytes,
sweep_address) -> Optional[EncumberedTransaction]:
assert isinstance(our_pcp, bytes)
delayed_bp_privkey = ecc.ECPrivkey(chan.local_config.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.remote_config.revocation_basepoint.pubkey,
our_pcp)
to_self_delay = chan.remote_config.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)
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=our_localdelayed_privkey.get_secret_bytes(),
is_revocation=False,
to_self_delay=to_self_delay)
return EncumberedTransaction(sweep_tx, csv_delay=to_self_delay)
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

17
electrum/lnutil.py

@ -543,3 +543,20 @@ class LnKeyFamily(IntEnum):
def generate_keypair(ln_keystore: BIP32_KeyStore, key_family: LnKeyFamily, index: int) -> Keypair:
return Keypair(*ln_keystore.get_keypair([key_family, 0, index], None))
from typing import Optional
class EncumberedTransaction(NamedTuple("EncumberedTransaction", [('tx', Transaction),
('csv_delay', Optional[int])])):
def to_json(self) -> dict:
return {
'tx': str(self.tx),
'csv_delay': self.csv_delay,
}
@classmethod
def from_json(cls, d: dict):
d2 = dict(d)
d2['tx'] = Transaction(d['tx'])
return EncumberedTransaction(**d2)

214
electrum/lnwatcher.py

@ -1,20 +1,11 @@
import threading
from typing import Optional, NamedTuple, Iterable
from typing import NamedTuple, Iterable
import os
from collections import defaultdict
from .util import PrintError, bh2u, bfh, NoDynamicFeeEstimates, aiosafe
from .lnutil import (extract_ctn_from_tx_and_chan, derive_privkey,
get_per_commitment_secret_from_seed, derive_pubkey,
make_commitment_output_to_remote_address,
RevocationStore, Outpoint)
from . import lnutil
from .bitcoin import redeem_script_to_address, TYPE_ADDRESS
from . import transaction
from .transaction import Transaction, TxOutput
from . import ecc
from .lnutil import EncumberedTransaction, Outpoint
from . import wallet
from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
from .storage import WalletStorage
from .address_synchronizer import AddressSynchronizer
@ -22,23 +13,9 @@ from .address_synchronizer import AddressSynchronizer
TX_MINED_STATUS_DEEP, TX_MINED_STATUS_SHALLOW, TX_MINED_STATUS_MEMPOOL, TX_MINED_STATUS_FREE = range(0, 4)
class EncumberedTransaction(NamedTuple("EncumberedTransaction", [('tx', Transaction),
('csv_delay', Optional[int])])):
def to_json(self) -> dict:
return {
'tx': str(self.tx),
'csv_delay': self.csv_delay,
}
@classmethod
def from_json(cls, d: dict):
d2 = dict(d)
d2['tx'] = Transaction(d['tx'])
return EncumberedTransaction(**d2)
class ChannelWatchInfo(NamedTuple("ChannelWatchInfo", [('outpoint', Outpoint),
('sweep_address', str),
('local_pubkey', bytes),
('remote_pubkey', bytes),
('last_ctn_our_ctx', int),
@ -47,7 +24,6 @@ class ChannelWatchInfo(NamedTuple("ChannelWatchInfo", [('outpoint', Outpoint),
def to_json(self) -> dict:
return {
'outpoint': self.outpoint,
'sweep_address': self.sweep_address,
'local_pubkey': bh2u(self.local_pubkey),
'remote_pubkey': bh2u(self.remote_pubkey),
'last_ctn_our_ctx': self.last_ctn_our_ctx,
@ -112,13 +88,12 @@ class LNWatcher(PrintError):
storage.put('sweepstore', sweepstore)
storage.write()
def watch_channel(self, chan, sweep_address, callback_funding_txo_spent):
def watch_channel(self, chan, callback_funding_txo_spent):
address = chan.get_funding_address()
self.watch_address(address)
with self.lock:
if address not in self.channel_info:
self.channel_info[address] = ChannelWatchInfo(outpoint=chan.funding_outpoint,
sweep_address=sweep_address,
local_pubkey=chan.local_config.payment_basepoint.pubkey,
remote_pubkey=chan.remote_config.payment_basepoint.pubkey,
last_ctn_our_ctx=0,
@ -212,17 +187,6 @@ class LNWatcher(PrintError):
.format(num_conf, e_tx.csv_delay, funding_outpoint, ctx.txid()))
return keep_watching_this
def _get_sweep_address_for_chan(self, chan) -> str:
funding_address = chan.get_funding_address()
try:
channel_info = self.channel_info[funding_address]
except KeyError:
# this is used during channel opening, as we only start watching
# the channel once it gets into the "opening" state, but we need to
# process the first ctx before that.
return chan.sweep_address
return channel_info.sweep_address
def _get_last_ctn_for_processed_ctx(self, funding_address: str, ours: bool) -> int:
try:
ci = self.channel_info[funding_address]
@ -259,45 +223,19 @@ class LNWatcher(PrintError):
ci = ci._replace(last_ctn_revoked_pcs=ci.last_ctn_revoked_pcs + 1)
self.channel_info[funding_address] = ci
# TODO batch sweeps
# TODO sweep HTLC outputs
def process_new_offchain_ctx(self, chan, ctx, ours: bool):
funding_address = chan.get_funding_address()
ctn = extract_ctn_from_tx_and_chan(ctx, chan)
latest_ctn_on_channel = chan.local_state.ctn if ours else chan.remote_state.ctn
def add_offchain_ctx(self, ctn, funding_address, ours, outpoint, ctx_id, encumbered_sweeptx):
last_ctn_watcher_saw = self._get_last_ctn_for_processed_ctx(funding_address, ours)
if latest_ctn_on_channel + 1 != ctn:
raise Exception('unexpected ctn {}. latest is {}. our ctx: {}'.format(ctn, latest_ctn_on_channel, ours))
if last_ctn_watcher_saw + 1 != ctn:
raise Exception('watcher skipping ctns!! ctn {}. last seen {}. our ctx: {}'.format(ctn, last_ctn_watcher_saw, ours))
#self.print_error("process_new_offchain_ctx. funding {}, ours {}, ctn {}, ctx {}"
# .format(chan.funding_outpoint.to_str(), ours, ctn, ctx.txid()))
sweep_address = self._get_sweep_address_for_chan(chan)
if ours:
our_per_commitment_secret = get_per_commitment_secret_from_seed(
chan.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
our_cur_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True)
encumbered_sweeptx = maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_cur_pcp, sweep_address)
else:
their_cur_pcp = chan.remote_state.next_per_commitment_point
encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_remote(chan, ctx, their_cur_pcp, sweep_address)
self.add_to_sweepstore(chan.funding_outpoint.to_str(), ctx.txid(), encumbered_sweeptx)
self.add_to_sweepstore(outpoint, ctx_id, encumbered_sweeptx)
self._inc_last_ctn_for_processed_ctx(funding_address, ours)
self.write_to_disk()
def process_new_revocation_secret(self, chan, per_commitment_secret: bytes):
funding_address = chan.get_funding_address()
ctx = chan.remote_commitment_to_be_revoked
ctn = extract_ctn_from_tx_and_chan(ctx, chan)
latest_ctn_on_channel = chan.remote_state.ctn
def add_revocation_secret(self, ctn, funding_address, outpoint, ctx_id, encumbered_sweeptx):
last_ctn_watcher_saw = self._get_last_ctn_for_revoked_secret(funding_address)
if latest_ctn_on_channel != ctn:
raise Exception('unexpected ctn {}. latest is {}'.format(ctn, latest_ctn_on_channel))
if last_ctn_watcher_saw + 1 != ctn:
raise Exception('watcher skipping ctns!! ctn {}. last seen {}'.format(ctn, last_ctn_watcher_saw))
sweep_address = self._get_sweep_address_for_chan(chan)
encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_local(chan, ctx, per_commitment_secret, sweep_address)
self.add_to_sweepstore(chan.funding_outpoint.to_str(), ctx.txid(), encumbered_sweeptx)
self.add_to_sweepstore(outpoint, ctx_id, encumbered_sweeptx)
self._inc_last_ctn_for_revoked_secret(funding_address)
self.write_to_disk()
@ -336,141 +274,3 @@ class LNWatcher(PrintError):
def print_tx_broadcast_result(self, res):
success, msg = res
self.print_error('broadcast: {}, {}'.format('success' if success else 'failure', msg))
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.local_config.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(sweep_tx, csv_delay=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 = lnutil.derive_blinded_privkey(chan.local_config.revocation_basepoint.privkey,
per_commitment_secret)
revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True)
to_self_delay = chan.local_config.to_self_delay
delayed_pubkey = derive_pubkey(chan.remote_config.delayed_basepoint.pubkey,
per_commitment_point)
witness_script = bh2u(lnutil.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(sweep_tx, csv_delay=0)
def maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_pcp: bytes,
sweep_address) -> Optional[EncumberedTransaction]:
assert isinstance(our_pcp, bytes)
delayed_bp_privkey = ecc.ECPrivkey(chan.local_config.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 = lnutil.derive_blinded_pubkey(chan.remote_config.revocation_basepoint.pubkey,
our_pcp)
to_self_delay = chan.remote_config.to_self_delay
witness_script = bh2u(lnutil.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)
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=our_localdelayed_privkey.get_secret_bytes(),
is_revocation=False,
to_self_delay=to_self_delay)
return EncumberedTransaction(sweep_tx, csv_delay=to_self_delay)
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 = transaction.construct_witness([sig, int(is_revocation), witness_script])
sweep_tx.inputs()[0]['witness'] = witness
return sweep_tx

8
electrum/lnworker.py

@ -52,9 +52,10 @@ class LNWorker(PrintError):
self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))}
for c in self.channels.values():
c.lnwatcher = network.lnwatcher
c.sweep_address = self.sweep_address
self.invoices = wallet.storage.get('lightning_invoices', {})
for chan_id, chan in self.channels.items():
self.network.lnwatcher.watch_channel(chan, self.sweep_address, partial(self.on_channel_utxos, chan))
self.network.lnwatcher.watch_channel(chan, partial(self.on_channel_utxos, chan))
self._last_tried_peer = {} # LNPeerAddr -> unix timestamp
self._add_peers_from_config()
# wait until we see confirmations
@ -184,13 +185,12 @@ class LNWorker(PrintError):
openingchannel = await peer.channel_establishment_flow(password,
funding_sat=local_amount_sat + push_sat,
push_msat=push_sat * 1000,
temp_channel_id=os.urandom(32),
sweep_address=self.sweep_address)
temp_channel_id=os.urandom(32))
if not openingchannel:
self.print_error("Channel_establishment_flow returned None")
return
self.save_channel(openingchannel)
self.network.lnwatcher.watch_channel(openingchannel, self.sweep_address, partial(self.on_channel_utxos, openingchannel))
self.network.lnwatcher.watch_channel(openingchannel, partial(self.on_channel_utxos, openingchannel))
self.on_channels_updated()
def on_channels_updated(self):

Loading…
Cancel
Save