Compare commits
10 Commits
8009253582
...
1643acc63a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1643acc63a | ||
|
|
5a29dee9b3 | ||
|
|
38f0f25f8e | ||
|
|
4e95f5e4c0 | ||
|
|
3887bf57fd | ||
|
|
6f292eceaf | ||
|
|
c8b72f0322 | ||
|
|
caa82f37da | ||
|
|
da63e34353 | ||
|
|
1807bc6f25 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -22,5 +22,5 @@ bin/
|
||||
.coverage
|
||||
|
||||
# kivy
|
||||
gui/kivy/theming/light-0.png
|
||||
gui/kivy/theming/light.atlas
|
||||
electrum/gui/kivy/theming/light-0.png
|
||||
electrum/gui/kivy/theming/light.atlas
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import queue
|
||||
import sys
|
||||
import datetime
|
||||
import copy
|
||||
|
||||
@ -3,7 +3,7 @@ from kivy.lang import Builder
|
||||
from kivy.factory import Factory
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.clock import Clock
|
||||
from electrum_gui.kivy.uix.context_menu import ContextMenu
|
||||
from electrum.gui.kivy.uix.context_menu import ContextMenu
|
||||
|
||||
Builder.load_string('''
|
||||
<LightningChannelItem@CardItem>
|
||||
@ -1,7 +1,7 @@
|
||||
import binascii
|
||||
from kivy.lang import Builder
|
||||
from kivy.factory import Factory
|
||||
from electrum_gui.kivy.i18n import _
|
||||
from electrum.gui.kivy.i18n import _
|
||||
from kivy.clock import mainthread
|
||||
from electrum.lnaddr import lndecode
|
||||
|
||||
@ -34,6 +34,8 @@ class ChannelsList(MyTreeWidget):
|
||||
print('ID', bh2u(channel_id))
|
||||
def close():
|
||||
suc, msg = self.parent.wallet.lnworker.close_channel(channel_id)
|
||||
if not suc:
|
||||
print('channel close broadcast failed:', msg)
|
||||
assert suc # TODO show error message in dialog
|
||||
menu.addAction(_("Close channel"), close)
|
||||
menu.exec_(self.viewport().mapToGlobal(position))
|
||||
@ -63,10 +65,11 @@ class ChannelsList(MyTreeWidget):
|
||||
h.addWidget(b)
|
||||
return h
|
||||
|
||||
def on_update(self):
|
||||
def update_status(self):
|
||||
n = len(self.parent.network.lightning_nodes)
|
||||
nc = len(self.parent.network.channel_db)
|
||||
np = len(self.parent.wallet.lnworker.peers)
|
||||
self.status.setText(_('{} peers, {} nodes').format(np, n))
|
||||
self.status.setText(_('{} peers, {} nodes, {} channels').format(np, n, nc))
|
||||
|
||||
def new_channel_dialog(self):
|
||||
d = WindowModalDialog(self.parent, _('Open Channel'))
|
||||
@ -125,6 +125,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
|
||||
self.create_status_bar()
|
||||
self.need_update = threading.Event()
|
||||
self.need_update_ln = threading.Event()
|
||||
|
||||
self.decimal_point = config.get('decimal_point', 5)
|
||||
self.num_zeros = int(config.get('num_zeros',0))
|
||||
@ -186,7 +187,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.network_signal.connect(self.on_network_qt)
|
||||
interests = ['updated', 'new_transaction', 'status',
|
||||
'banner', 'verified', 'fee', 'on_quotes',
|
||||
'on_history', 'channel', 'channels']
|
||||
'on_history', 'channel', 'channels', 'ln_status']
|
||||
# To avoid leaking references to "self" that prevent the
|
||||
# window from being GC-ed when closed, callbacks should be
|
||||
# methods of this class only, and specifically not be
|
||||
@ -302,6 +303,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.channels_list.update_rows.emit(*args)
|
||||
elif event == 'channel':
|
||||
self.channels_list.update_single_row.emit(*args)
|
||||
elif event == 'ln_status':
|
||||
self.need_update_ln.set()
|
||||
else:
|
||||
self.print_error("unexpected network message:", event, args)
|
||||
|
||||
@ -645,6 +648,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
if self.need_update.is_set():
|
||||
self.need_update.clear()
|
||||
self.update_wallet()
|
||||
if self.need_update_ln.is_set():
|
||||
self.need_update_ln.clear()
|
||||
self.channels_list.update_status()
|
||||
# resolve aliases
|
||||
# FIXME this is a blocking network call that has a timeout of 5 sec
|
||||
self.payto_e.resolve()
|
||||
|
||||
@ -282,7 +282,6 @@ def aiosafe(f):
|
||||
class Peer(PrintError):
|
||||
|
||||
def __init__(self, lnworker, host, port, pubkey, request_initial_sync=False):
|
||||
self.channel_update_event = asyncio.Event()
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.pubkey = pubkey
|
||||
@ -457,17 +456,17 @@ class Peer(PrintError):
|
||||
'addresses': addresses
|
||||
}
|
||||
self.print_error('node announcement', binascii.hexlify(pubkey), alias, addresses)
|
||||
self.network.trigger_callback('ln_status')
|
||||
|
||||
def on_init(self, payload):
|
||||
pass
|
||||
|
||||
def on_channel_update(self, payload):
|
||||
self.channel_db.on_channel_update(payload)
|
||||
self.channel_update_event.set()
|
||||
|
||||
def on_channel_announcement(self, payload):
|
||||
self.channel_db.on_channel_announcement(payload)
|
||||
self.channel_update_event.set()
|
||||
self.network.trigger_callback('ln_status')
|
||||
|
||||
def on_announcement_signatures(self, payload):
|
||||
channel_id = payload['channel_id']
|
||||
@ -518,7 +517,7 @@ class Peer(PrintError):
|
||||
delayed_basepoint=keypair_generator(keyfamilydelaybase, 0),
|
||||
revocation_basepoint=keypair_generator(keyfamilyrevocationbase, 0),
|
||||
to_self_delay=143,
|
||||
dust_limit_sat=10,
|
||||
dust_limit_sat=546,
|
||||
max_htlc_value_in_flight_msat=0xffffffffffffffff,
|
||||
max_accepted_htlcs=5
|
||||
)
|
||||
@ -546,7 +545,7 @@ class Peer(PrintError):
|
||||
to_self_delay=local_config.to_self_delay,
|
||||
max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat,
|
||||
channel_flags=0x01, # publicly announcing channel
|
||||
channel_reserve_satoshis=10
|
||||
channel_reserve_satoshis=546
|
||||
)
|
||||
self.send_message(msg)
|
||||
payload = await self.channel_accepted[temp_channel_id].get()
|
||||
@ -862,8 +861,9 @@ class Peer(PrintError):
|
||||
if failure_coro.done():
|
||||
sig_64, htlc_sigs = chan.sign_next_commitment()
|
||||
self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sigs[0]))
|
||||
self.revoke(chan)
|
||||
while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"":
|
||||
self.revoke(chan)
|
||||
pass
|
||||
# TODO process above commitment transactions
|
||||
await self.receive_revoke(chan)
|
||||
chan.fail_htlc(htlc)
|
||||
@ -880,12 +880,12 @@ class Peer(PrintError):
|
||||
preimage = update_fulfill_htlc_msg["payment_preimage"]
|
||||
chan.receive_htlc_settle(preimage, int.from_bytes(update_fulfill_htlc_msg["id"], "big"))
|
||||
|
||||
while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"":
|
||||
self.revoke(chan)
|
||||
# TODO process above commitment transactions
|
||||
|
||||
self.revoke(chan)
|
||||
|
||||
while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"":
|
||||
pass
|
||||
# TODO process above commitment transactions
|
||||
|
||||
bare_ctx = chan.make_commitment(chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point,
|
||||
msat_remote, msat_local)
|
||||
|
||||
@ -935,13 +935,13 @@ class Peer(PrintError):
|
||||
|
||||
chan.receive_htlc(htlc)
|
||||
|
||||
assert (await self.receive_commitment(chan)) == 1
|
||||
assert (await self.receive_commitment(chan)) <= 1
|
||||
|
||||
self.revoke(chan)
|
||||
|
||||
sig_64, htlc_sigs = chan.sign_next_commitment()
|
||||
htlc_sig = htlc_sigs[0]
|
||||
self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig))
|
||||
htlc_sig = b''.join(htlc_sigs)
|
||||
self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=len(htlc_sigs), htlc_signature=htlc_sig))
|
||||
|
||||
await self.receive_revoke(chan)
|
||||
|
||||
@ -188,33 +188,32 @@ class HTLCStateMachine(PrintError):
|
||||
self.pending_ack_fee_update = self.pending_fee_update
|
||||
self.pending_fee_update = None
|
||||
|
||||
with PendingFeerateApplied(self):
|
||||
sig_64 = sign_and_get_sig_string(self.pending_remote_commitment, self.local_config, self.remote_config)
|
||||
sig_64 = sign_and_get_sig_string(self.pending_remote_commitment, self.local_config, self.remote_config)
|
||||
|
||||
their_remote_htlc_privkey_number = derive_privkey(
|
||||
int.from_bytes(self.local_config.htlc_basepoint.privkey, 'big'),
|
||||
self.remote_state.next_per_commitment_point)
|
||||
their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
|
||||
their_remote_htlc_privkey_number = derive_privkey(
|
||||
int.from_bytes(self.local_config.htlc_basepoint.privkey, 'big'),
|
||||
self.remote_state.next_per_commitment_point)
|
||||
their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
|
||||
|
||||
for_us = False
|
||||
for_us = False
|
||||
|
||||
htlcsigs = []
|
||||
for we_receive, htlcs in zip([True, False], [self.htlcs_in_remote, self.htlcs_in_local]):
|
||||
assert len(htlcs) <= 1
|
||||
for htlc in htlcs:
|
||||
weight = HTLC_SUCCESS_WEIGHT if we_receive else HTLC_TIMEOUT_WEIGHT
|
||||
fee = self.remote_state.feerate // 1000 * weight
|
||||
if htlc.amount_msat // 1000 < self.remote_config.dust_limit_sat + fee:
|
||||
print("value too small, skipping. htlc amt: {}, weight: {}, remote feerate {}, remote dust limit {}".format( htlc.amount_msat, weight, self.remote_state.feerate, self.remote_config.dust_limit_sat))
|
||||
continue
|
||||
original_htlc_output_index = 0
|
||||
args = [self.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat + htlc.total_fee, htlc.cltv_expiry, htlc.payment_hash, self.pending_remote_commitment, original_htlc_output_index]
|
||||
htlc_tx = make_htlc_tx_with_open_channel(self, *args)
|
||||
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
|
||||
htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
|
||||
htlcsigs.append(htlc_sig)
|
||||
htlcsigs = []
|
||||
for we_receive, htlcs in zip([True, False], [self.htlcs_in_remote, self.htlcs_in_local]):
|
||||
assert len(htlcs) <= 1
|
||||
for htlc in htlcs:
|
||||
weight = HTLC_SUCCESS_WEIGHT if we_receive else HTLC_TIMEOUT_WEIGHT
|
||||
fee = self.remote_state.feerate // 1000 * weight
|
||||
if htlc.amount_msat // 1000 < self.remote_config.dust_limit_sat + fee:
|
||||
print("value too small, skipping. htlc amt: {}, weight: {}, remote feerate {}, remote dust limit {}".format( htlc.amount_msat, weight, self.remote_state.feerate, self.remote_config.dust_limit_sat))
|
||||
continue
|
||||
original_htlc_output_index = 0
|
||||
args = [self.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat + htlc.total_fee, htlc.cltv_expiry, htlc.payment_hash, self.pending_remote_commitment, original_htlc_output_index]
|
||||
htlc_tx = make_htlc_tx_with_open_channel(self, *args)
|
||||
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
|
||||
htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
|
||||
htlcsigs.append(htlc_sig)
|
||||
|
||||
return sig_64, htlcsigs
|
||||
return sig_64, htlcsigs
|
||||
|
||||
def receive_new_commitment(self, sig, htlc_sigs):
|
||||
"""
|
||||
@ -238,27 +237,26 @@ class HTLCStateMachine(PrintError):
|
||||
self.pending_ack_fee_update = self.pending_fee_update
|
||||
self.pending_fee_update = None
|
||||
|
||||
with PendingFeerateApplied(self):
|
||||
preimage_hex = self.pending_local_commitment.serialize_preimage(0)
|
||||
pre_hash = Hash(bfh(preimage_hex))
|
||||
if not ecc.verify_signature(self.remote_config.multisig_key.pubkey, sig, pre_hash):
|
||||
raise Exception('failed verifying signature of our updated commitment transaction: ' + str(sig))
|
||||
preimage_hex = self.pending_local_commitment.serialize_preimage(0)
|
||||
pre_hash = Hash(bfh(preimage_hex))
|
||||
if not ecc.verify_signature(self.remote_config.multisig_key.pubkey, sig, pre_hash):
|
||||
raise Exception('failed verifying signature of our updated commitment transaction: ' + str(sig))
|
||||
|
||||
_, this_point, _ = self.points
|
||||
_, this_point, _ = self.points
|
||||
|
||||
if len(self.htlcs_in_remote) > 0 and len(self.pending_local_commitment.outputs()) == 3:
|
||||
print("CHECKING HTLC SIGS")
|
||||
we_receive = True
|
||||
payment_hash = self.htlcs_in_remote[0].payment_hash
|
||||
amount_msat = self.htlcs_in_remote[0].amount_msat
|
||||
cltv_expiry = self.htlcs_in_remote[0].cltv_expiry
|
||||
htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, amount_msat, cltv_expiry, payment_hash, self.pending_local_commitment, 0)
|
||||
pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
|
||||
remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
|
||||
if not ecc.verify_signature(remote_htlc_pubkey, htlc_sigs[0], pre_hash):
|
||||
raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs")
|
||||
if len(self.htlcs_in_remote) > 0 and len(self.pending_local_commitment.outputs()) == 3:
|
||||
print("CHECKING HTLC SIGS")
|
||||
we_receive = True
|
||||
payment_hash = self.htlcs_in_remote[0].payment_hash
|
||||
amount_msat = self.htlcs_in_remote[0].amount_msat
|
||||
cltv_expiry = self.htlcs_in_remote[0].cltv_expiry
|
||||
htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, amount_msat, cltv_expiry, payment_hash, self.pending_local_commitment, 0)
|
||||
pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
|
||||
remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
|
||||
if not ecc.verify_signature(remote_htlc_pubkey, htlc_sigs[0], pre_hash):
|
||||
raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs")
|
||||
|
||||
# TODO check htlc in htlcs_in_local
|
||||
# TODO check htlc in htlcs_in_local
|
||||
|
||||
def revoke_current_commitment(self):
|
||||
"""
|
||||
@ -100,6 +100,9 @@ class ChannelDB(PrintError):
|
||||
self._id_to_channel_info = {}
|
||||
self._channels_for_node = defaultdict(set) # node -> set(short_channel_id)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._id_to_channel_info)
|
||||
|
||||
def get_channel_info(self, channel_id):
|
||||
return self._id_to_channel_info.get(channel_id, None)
|
||||
|
||||
@ -103,6 +103,8 @@ class LNChanCloseHandler(PrintError):
|
||||
break
|
||||
|
||||
# TODO batch sweeps
|
||||
# TODO sweep HTLC outputs
|
||||
# TODO implement nursery that waits for timelocks
|
||||
def inspect_spending_tx(self, ctx, txin_idx: int):
|
||||
chan = self.chan
|
||||
ctn = extract_ctn_from_tx(ctx, txin_idx,
|
||||
@ -114,22 +116,27 @@ class LNChanCloseHandler(PrintError):
|
||||
.format(ctx.txid(), ctn, latest_local_ctn, latest_remote_ctn))
|
||||
# see if it is a normal unilateral close by them
|
||||
if ctn == latest_remote_ctn:
|
||||
# note that we might also get here if this is our ctx and the ctn just happens to match
|
||||
their_cur_pcp = chan.remote_state.current_per_commitment_point
|
||||
self.find_and_sweep_their_ctx_to_remote(ctx, their_cur_pcp)
|
||||
# see if we have a revoked secret for this ctn
|
||||
if their_cur_pcp is not None:
|
||||
self.find_and_sweep_their_ctx_to_remote(ctx, their_cur_pcp)
|
||||
# see if we have a revoked secret for this ctn ("breach")
|
||||
try:
|
||||
per_commitment_secret = chan.remote_state.revocation_store.retrieve_secret(
|
||||
RevocationStore.START_INDEX - ctn)
|
||||
except UnableToDeriveSecret:
|
||||
self.print_error("revocation store does not have secret for ctx {}".format(ctx.txid()))
|
||||
else:
|
||||
# FIXME what if we closed unilaterally?
|
||||
#self.print_error("ctx {} is breach!! by them and we have the revocation secret. "
|
||||
# "yay, free money".format(ctx.txid()))
|
||||
# note that we might also get here if this is our ctx and we just happen to have
|
||||
# the secret for the symmetric ctn
|
||||
their_pcp = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
|
||||
self.find_and_sweep_their_ctx_to_remote(ctx, their_pcp)
|
||||
self.find_and_sweep_their_ctx_to_local(ctx, per_commitment_secret)
|
||||
# TODO sweep other outputs
|
||||
# see if it's our ctx
|
||||
our_per_commitment_secret = get_per_commitment_secret_from_seed(
|
||||
chan.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
|
||||
our_per_commitment_point = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True)
|
||||
self.find_and_sweep_our_ctx_to_local(ctx, our_per_commitment_point)
|
||||
|
||||
def find_and_sweep_their_ctx_to_remote(self, ctx, their_pcp: bytes):
|
||||
payment_bp_privkey = ecc.ECPrivkey(self.chan.local_config.payment_basepoint.privkey)
|
||||
@ -137,17 +144,19 @@ class LNChanCloseHandler(PrintError):
|
||||
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:
|
||||
for output_idx, (type_, addr, val) in enumerate(ctx.outputs()):
|
||||
if type_ == TYPE_ADDRESS and addr == to_remote_address:
|
||||
self.print_error("found to_remote output paying to us: ctx {}:{}".
|
||||
format(ctx.txid(), output_idx))
|
||||
#self.print_error("ctx {} is normal unilateral close by them".format(ctx.txid()))
|
||||
break
|
||||
else:
|
||||
return
|
||||
self.sweep_their_ctx_to_remote(ctx, output_idx, our_payment_privkey)
|
||||
sweep_tx = self.create_sweeptx_their_ctx_to_remote(ctx, output_idx, our_payment_privkey)
|
||||
self.network.broadcast_transaction(sweep_tx,
|
||||
lambda res: self.print_tx_broadcast_result('sweep_their_ctx_to_remote', res))
|
||||
|
||||
def sweep_their_ctx_to_remote(self, ctx, output_idx: int, our_payment_privkey: ecc.ECPrivkey):
|
||||
def create_sweeptx_their_ctx_to_remote(self, ctx, output_idx: int, our_payment_privkey: ecc.ECPrivkey):
|
||||
our_payment_pubkey = our_payment_privkey.get_public_key_hex(compressed=True)
|
||||
val = ctx.outputs()[output_idx][2]
|
||||
sweep_inputs = [{
|
||||
@ -173,8 +182,7 @@ class LNChanCloseHandler(PrintError):
|
||||
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')
|
||||
self.network.broadcast_transaction(sweep_tx,
|
||||
lambda res: self.print_tx_broadcast_result('sweep_their_ctx_to_remote', res))
|
||||
return sweep_tx
|
||||
|
||||
def find_and_sweep_their_ctx_to_local(self, ctx, per_commitment_secret: bytes):
|
||||
per_commitment_point = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
|
||||
@ -187,17 +195,52 @@ class LNChanCloseHandler(PrintError):
|
||||
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, (type, addr, val) in enumerate(ctx.outputs()):
|
||||
if type == TYPE_ADDRESS and addr == to_local_address:
|
||||
for output_idx, (type_, addr, val) in enumerate(ctx.outputs()):
|
||||
if type_ == TYPE_ADDRESS and addr == to_local_address:
|
||||
self.print_error("found to_local output paying to them: ctx {}:{}".
|
||||
format(ctx.txid(), output_idx))
|
||||
break
|
||||
else:
|
||||
self.print_error('could not find to_local output in their ctx {}'.format(ctx.txid()))
|
||||
return
|
||||
self.sweep_their_ctx_to_local(ctx, output_idx, witness_script, revocation_privkey)
|
||||
sweep_tx = self.create_sweeptx_ctx_to_local(ctx, output_idx, witness_script, revocation_privkey, True)
|
||||
self.network.broadcast_transaction(sweep_tx,
|
||||
lambda res: self.print_tx_broadcast_result('sweep_their_ctx_to_local', res))
|
||||
|
||||
def sweep_their_ctx_to_local(self, ctx, output_idx: int, witness_script: str, revocation_privkey: bytes):
|
||||
def find_and_sweep_our_ctx_to_local(self, ctx, our_pcp: bytes):
|
||||
delayed_bp_privkey = ecc.ECPrivkey(self.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(self.chan.remote_config.revocation_basepoint.pubkey,
|
||||
our_pcp)
|
||||
to_self_delay = self.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, (type_, addr, val) in enumerate(ctx.outputs()):
|
||||
if type_ == TYPE_ADDRESS and addr == to_local_address:
|
||||
self.print_error("found to_local output paying to us (CSV-locked): ctx {}:{}".
|
||||
format(ctx.txid(), output_idx))
|
||||
break
|
||||
else:
|
||||
self.print_error('could not find to_local output in our ctx {}'.format(ctx.txid()))
|
||||
return
|
||||
# TODO if the CSV lock is still pending, this will fail
|
||||
sweep_tx = self.create_sweeptx_ctx_to_local(ctx, output_idx, witness_script,
|
||||
our_localdelayed_privkey.get_secret_bytes(),
|
||||
False, to_self_delay)
|
||||
self.network.broadcast_transaction(sweep_tx,
|
||||
lambda res: self.print_tx_broadcast_result('sweep_our_ctx_to_local', res))
|
||||
|
||||
def create_sweeptx_ctx_to_local(self, ctx, output_idx: int, witness_script: str,
|
||||
privkey: bytes, is_revocation: bool, to_self_delay: int=None):
|
||||
"""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][2]
|
||||
sweep_inputs = [{
|
||||
'scriptSig': '',
|
||||
@ -210,7 +253,9 @@ class LNChanCloseHandler(PrintError):
|
||||
'coinbase': False,
|
||||
'preimage_script': witness_script,
|
||||
}]
|
||||
tx_size_bytes = 200 # TODO calc size
|
||||
if to_self_delay is not None:
|
||||
sweep_inputs[0]['sequence'] = to_self_delay
|
||||
tx_size_bytes = 121 # approx size of to_local -> p2wpkh
|
||||
try:
|
||||
fee = self.network.config.estimate_fee(tx_size_bytes)
|
||||
except NoDynamicFeeEstimates:
|
||||
@ -218,13 +263,11 @@ class LNChanCloseHandler(PrintError):
|
||||
fee = self.network.config.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes)
|
||||
sweep_outputs = [(TYPE_ADDRESS, self.wallet.get_receiving_address(), val - fee)]
|
||||
locktime = self.network.get_local_height()
|
||||
sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, locktime=locktime)
|
||||
sweep_tx.set_rbf(True)
|
||||
revocation_sig = sweep_tx.sign_txin(0, revocation_privkey)
|
||||
witness = transaction.construct_witness([revocation_sig, 1, witness_script])
|
||||
sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, locktime=locktime, 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
|
||||
self.network.broadcast_transaction(sweep_tx,
|
||||
lambda res: self.print_tx_broadcast_result('sweep_their_ctx_to_local', res))
|
||||
return sweep_tx
|
||||
|
||||
def print_tx_broadcast_result(self, name, res):
|
||||
error = res.get('error')
|
||||
@ -57,7 +57,7 @@ class LNWorker(PrintError):
|
||||
peer = Peer(self, host, int(port), node_id, request_initial_sync=self.config.get("request_initial_sync", True))
|
||||
self.network.futures.append(asyncio.run_coroutine_threadsafe(peer.main_loop(), asyncio.get_event_loop()))
|
||||
self.peers[node_id] = peer
|
||||
self.lock = threading.Lock()
|
||||
self.network.trigger_callback('ln_status')
|
||||
|
||||
def save_channel(self, openchannel):
|
||||
assert type(openchannel) is HTLCStateMachine
|
||||
@ -183,7 +183,7 @@ class LNWorker(PrintError):
|
||||
# but in this case, we want the current one. So substract one ctn number
|
||||
old_local_state = chan.local_state
|
||||
chan.local_state=chan.local_state._replace(ctn=chan.local_state.ctn - 1)
|
||||
tx = chan.local_commitment
|
||||
tx = chan.pending_local_commitment
|
||||
chan.local_state = old_local_state
|
||||
tx.sign({bh2u(chan.local_config.multisig_key.pubkey): (chan.local_config.multisig_key.privkey, True)})
|
||||
remote_sig = chan.local_state.current_commitment_signature
|
||||
@ -1,8 +1,8 @@
|
||||
from hashlib import sha256
|
||||
from lib.lnaddr import shorten_amount, unshorten_amount, LnAddr, lnencode, lndecode, u5_to_bitarray, bitarray_to_u5
|
||||
from electrum.lnaddr import shorten_amount, unshorten_amount, LnAddr, lnencode, lndecode, u5_to_bitarray, bitarray_to_u5
|
||||
from decimal import Decimal
|
||||
from binascii import unhexlify, hexlify
|
||||
from lib.segwit_addr import bech32_encode, bech32_decode
|
||||
from electrum.segwit_addr import bech32_encode, bech32_decode
|
||||
import pprint
|
||||
import unittest
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# ported from lnd 42de4400bff5105352d0552155f73589166d162b
|
||||
|
||||
import unittest
|
||||
import lib.bitcoin as bitcoin
|
||||
import lib.lnbase as lnbase
|
||||
import lib.lnhtlc as lnhtlc
|
||||
import lib.lnutil as lnutil
|
||||
import lib.util as util
|
||||
import electrum.bitcoin as bitcoin
|
||||
import electrum.lnbase as lnbase
|
||||
import electrum.lnhtlc as lnhtlc
|
||||
import electrum.lnutil as lnutil
|
||||
import electrum.util as util
|
||||
import os
|
||||
import binascii
|
||||
|
||||
@ -319,7 +319,7 @@ class TestLNHTLCDust(unittest.TestCase):
|
||||
self.assertEqual(len(alice_channel.local_commitment.outputs()), 3)
|
||||
self.assertEqual(len(bob_channel.local_commitment.outputs()), 2)
|
||||
default_fee = calc_static_fee(0)
|
||||
self.assertEqual(bob_channel.local_commit_fee, default_fee)
|
||||
self.assertEqual(bob_channel.local_commit_fee, default_fee + htlcAmt)
|
||||
bob_channel.settle_htlc(paymentPreimage, htlc.htlc_id)
|
||||
alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex)
|
||||
force_state_transition(bob_channel, alice_channel)
|
||||
@ -1,9 +1,9 @@
|
||||
import unittest
|
||||
|
||||
from lib.util import bh2u, bfh
|
||||
from lib.lnbase import Peer
|
||||
from lib.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop
|
||||
from lib import bitcoin, lnrouter
|
||||
from electrum.util import bh2u, bfh
|
||||
from electrum.lnbase import Peer
|
||||
from electrum.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop
|
||||
from electrum import bitcoin, lnrouter
|
||||
|
||||
class Test_LNRouter(unittest.TestCase):
|
||||
|
||||
@ -26,6 +26,7 @@ class Test_LNRouter(unittest.TestCase):
|
||||
def test_find_path_for_payment(self):
|
||||
class fake_network:
|
||||
channel_db = lnrouter.ChannelDB()
|
||||
trigger_callback = lambda x: None
|
||||
class fake_ln_worker:
|
||||
path_finder = lnrouter.LNPathFinder(fake_network.channel_db)
|
||||
privkey = bitcoin.sha256('privkeyseed')
|
||||
@ -1,12 +1,12 @@
|
||||
import unittest
|
||||
import json
|
||||
from lib import bitcoin
|
||||
from lib.lnutil import (RevocationStore, get_per_commitment_secret_from_seed, make_offered_htlc,
|
||||
from electrum import bitcoin
|
||||
from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_seed, make_offered_htlc,
|
||||
make_received_htlc, make_commitment, make_htlc_tx_witness, make_htlc_tx_output,
|
||||
make_htlc_tx_inputs, secret_to_pubkey, derive_blinded_pubkey, derive_privkey,
|
||||
derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret)
|
||||
from lib.util import bh2u, bfh
|
||||
from lib.transaction import Transaction
|
||||
from electrum.util import bh2u, bfh
|
||||
from electrum.transaction import Transaction
|
||||
|
||||
funding_tx_id = '8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be'
|
||||
funding_output_index = 0
|
||||
@ -736,7 +736,7 @@ class Transaction:
|
||||
self._outputs = outputs
|
||||
self.locktime = locktime
|
||||
self.version = version
|
||||
# TODO set_rbf by default ?
|
||||
# TODO set_rbf by default ? note: inputs might have nSequence set
|
||||
# TODO maybe BIP_LI01_sort here? but note: add_outputs and add_inputs
|
||||
return self
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user