Compare commits

...

10 Commits

19 changed files with 157 additions and 104 deletions

4
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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'))

View File

@ -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()

View File

@ -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)

View File

@ -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):
"""

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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