electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit e6bd3959e08c57ec5eea31e92ac12a4a6f42c3c3
parent 0a08ccc1c6ff910661059a47fa79b1462a927512
Author: Janus <ysangkok@gmail.com>
Date:   Mon, 28 Jan 2019 20:13:09 +0100

ln: handle channel limits better, show remote limits in details dialog, replace rusty's testnet peer (doesn't work currently)

Diffstat:
Melectrum/gui/qt/channel_details.py | 10++++++++++
Melectrum/lnbase.py | 36++++++++++++++++++++++++++++--------
Melectrum/lnchan.py | 2+-
Melectrum/lnutil.py | 10++++++++++
Melectrum/lnworker.py | 2+-
5 files changed, 50 insertions(+), 10 deletions(-)

diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py @@ -157,6 +157,16 @@ class ChannelDetailsDialog(QtWidgets.QDialog): form_layout.addRow(_('Received (mSAT):'), self.received_label) self.sent_label = SelectableLabel() form_layout.addRow(_('Sent (mSAT):'), self.sent_label) + self.htlc_minimum_msat = SelectableLabel(str(chan.config[REMOTE].htlc_minimum_msat)) + form_layout.addRow(_('Minimum HTLC value accepted by peer (mSAT):'), self.htlc_minimum_msat) + self.max_htlcs = SelectableLabel(str(chan.config[REMOTE].max_accepted_htlcs)) + form_layout.addRow(_('Maximum number of concurrent HTLCs accepted by peer:'), self.max_htlcs) + self.max_htlc_value = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].max_htlc_value_in_flight_msat / 1000)) + form_layout.addRow(_('Maximum value of in-flight HTLCs accepted by peer:'), self.max_htlc_value) + self.dust_limit = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].dust_limit_sat)) + form_layout.addRow(_('Remote dust limit:'), self.dust_limit) + self.reserve = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].reserve_sat)) + form_layout.addRow(_('Remote channel reserve:'), self.reserve) # add htlc tree view to vbox (wouldn't scale correctly in QFormLayout) form_layout.addRow(_('Payments (HTLCs):'), None) diff --git a/electrum/lnbase.py b/electrum/lnbase.py @@ -33,7 +33,9 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, secret_to_pubkey, LNPeerAddr, PaymentFailure, LnLocalFeatures, LOCAL, REMOTE, HTLCOwner, generate_keypair, LnKeyFamily, get_ln_flag_pair_of_bit, privkey_to_pubkey, UnknownPaymentHash, MIN_FINAL_CLTV_EXPIRY_ACCEPTED, - LightningPeerConnectionClosed, HandshakeFailed, LNPeerAddr, NotFoundChanAnnouncementForUpdate) + LightningPeerConnectionClosed, HandshakeFailed, LNPeerAddr, NotFoundChanAnnouncementForUpdate, + MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED, MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED, + MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED) from .lntransport import LNTransport, LNTransportBase if TYPE_CHECKING: @@ -400,7 +402,7 @@ class Peer(PrintError): revocation_basepoint=keypair_generator(LnKeyFamily.REVOCATION_BASE), to_self_delay=9, dust_limit_sat=546, - max_htlc_value_in_flight_msat=0xffffffffffffffff, + max_htlc_value_in_flight_msat=200000000, max_accepted_htlcs=5, initial_msat=initial_msat, ctn=-1, @@ -418,6 +420,7 @@ class Peer(PrintError): @log_exceptions async def channel_establishment_flow(self, password: Optional[str], funding_sat: int, push_msat: int, temp_channel_id: bytes) -> Channel: + assert push_msat == 0, "push_msat not supported currently" wallet = self.lnworker.wallet # dry run creating funding tx to see if we even have enough funds funding_tx_test = wallet.mktx([TxOutput(bitcoin.TYPE_ADDRESS, wallet.dummy_address(), funding_sat)], @@ -448,33 +451,49 @@ class Peer(PrintError): max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat, channel_flags=0x00, # not willing to announce channel channel_reserve_satoshis=local_config.reserve_sat, + htlc_minimum_msat=1, ) payload = await self.channel_accepted[temp_channel_id].get() if payload.get('error'): raise Exception('Remote Lightning peer reported error: ' + repr(payload.get('error'))) remote_per_commitment_point = payload['first_per_commitment_point'] funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big') + assert funding_txn_minimum_depth > 0, funding_txn_minimum_depth remote_dust_limit_sat = int.from_bytes(payload['dust_limit_satoshis'], byteorder='big') - assert remote_dust_limit_sat < 600, remote_dust_limit_sat - assert int.from_bytes(payload['htlc_minimum_msat'], 'big') < 600 * 1000 + remote_reserve_sat = self.validate_remote_reserve(payload["channel_reserve_satoshis"], remote_dust_limit_sat, funding_sat) + if remote_dust_limit_sat > remote_reserve_sat: + raise Exception(f"Remote Lightning peer reports dust_limit_sat > reserve_sat which is a BOLT-02 protocol violation.") + htlc_min = int.from_bytes(payload['htlc_minimum_msat'], 'big') + if htlc_min > MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED: + raise Exception(f"Remote Lightning peer reports htlc_minimum_msat={htlc_min} mSAT," + + f" which is above Electrums required maximum limit of that parameter ({MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED} mSAT).") remote_max = int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big') - assert remote_max >= 198 * 1000 * 1000, remote_max + if remote_max < MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED: + raise Exception(f"Remote Lightning peer reports max_htlc_value_in_flight_msat at only {remote_max} mSAT" + + f" which is below Electrums required minimum ({MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED} mSAT).") + max_accepted_htlcs = int.from_bytes(payload["max_accepted_htlcs"], 'big') + if max_accepted_htlcs > 483: + raise Exception("Remote Lightning peer reports max_accepted_htlcs > 483, which is a BOLT-02 protocol violation.") + remote_to_self_delay = int.from_bytes(payload['to_self_delay'], byteorder='big') + if remote_to_self_delay > MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED: + raise Exception(f"Remote Lightning peer reports to_self_delay={remote_to_self_delay}," + + f" which is above Electrums required maximum ({MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED})") their_revocation_store = RevocationStore() - remote_reserve_sat = self.validate_remote_reserve(payload["channel_reserve_satoshis"], remote_dust_limit_sat, funding_sat) remote_config = RemoteConfig( payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']), multisig_key=OnlyPubkeyKeypair(payload["funding_pubkey"]), htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']), delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']), revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']), - to_self_delay=int.from_bytes(payload['to_self_delay'], byteorder='big'), + to_self_delay=remote_to_self_delay, dust_limit_sat=remote_dust_limit_sat, max_htlc_value_in_flight_msat=remote_max, - max_accepted_htlcs=int.from_bytes(payload["max_accepted_htlcs"], 'big'), + max_accepted_htlcs=max_accepted_htlcs, initial_msat=push_msat, ctn = -1, next_htlc_id = 0, reserve_sat = remote_reserve_sat, + htlc_minimum_msat = htlc_min, next_per_commitment_point=remote_per_commitment_point, current_per_commitment_point=None, @@ -531,6 +550,7 @@ class Peer(PrintError): raise Exception('wrong chain_hash') funding_sat = int.from_bytes(payload['funding_satoshis'], 'big') push_msat = int.from_bytes(payload['push_msat'], 'big') + assert push_msat == 0, "push_msat not supported currently" feerate = int.from_bytes(payload['feerate_per_kw'], 'big') temp_chan_id = payload['temporary_channel_id'] diff --git a/electrum/lnchan.py b/electrum/lnchan.py @@ -206,7 +206,7 @@ class Channel(PrintError): current_htlc_sum = htlcsum(self.hm.htlcs_by_direction(LOCAL, SENT)) + htlcsum(self.hm.htlcs_by_direction(LOCAL, RECEIVED)) if current_htlc_sum + amount_msat > self.config[REMOTE].max_htlc_value_in_flight_msat: raise PaymentFailure(f'HTLC value sum (sum of pending htlcs: {current_htlc_sum/1000} sat plus new htlc: {amount_msat/1000} sat) would exceed max allowed: {self.config[REMOTE].max_htlc_value_in_flight_msat/1000} sat') - if amount_msat <= 0: # FIXME htlc_minimum_msat + if amount_msat < self.config[REMOTE].htlc_minimum_msat: raise PaymentFailure(f'HTLC value too small: {amount_msat} msat') def can_pay(self, amount_msat): diff --git a/electrum/lnutil.py b/electrum/lnutil.py @@ -71,6 +71,7 @@ class RemoteConfig(NamedTuple): max_accepted_htlcs: int initial_msat: int reserve_sat: int + htlc_minimum_msat: int # specific to "REMOTE" config next_per_commitment_point: bytes revocation_store: 'RevocationStore' @@ -103,6 +104,15 @@ MIN_FINAL_CLTV_EXPIRY_ACCEPTED = 144 MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE = MIN_FINAL_CLTV_EXPIRY_ACCEPTED + 1 +# When we open a channel, the remote peer has to support at least this +# value of mSATs in HTLCs accumulated on the channel, or we refuse opening. +# Number is based on observed testnet limit https://github.com/spesmilo/electrum/issues/5032 +MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED = 19_800 * 1000 + +MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED = 1000 + +MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED = 2016 + class RevocationStore: """ Taken from LND, see license in lnchan.py. """ diff --git a/electrum/lnworker.py b/electrum/lnworker.py @@ -53,7 +53,7 @@ GRAPH_DOWNLOAD_SECONDS = 600 FALLBACK_NODE_LIST_TESTNET = ( LNPeerAddr('ecdsa.net', 9735, bfh('038370f0e7a03eded3e1d41dc081084a87f0afa1c5b22090b4f3abb391eb15d8ff')), - LNPeerAddr('165.227.30.200', 9735, bfh('023ea0a53af875580899da0ab0a21455d9c19160c4ea1b7774c9d4be6810b02d2c')), + LNPeerAddr('180.181.208.42', 9735, bfh('038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9')), ) FALLBACK_NODE_LIST_MAINNET = ( LNPeerAddr('104.198.32.198', 9735, bfh('02f6725f9c1c40333b67faea92fd211c183050f28df32cac3f9d69685fe9665432')), # Blockstream