ln: handle channel limits better, show remote limits in details dialog, replace rusty's testnet peer (doesn't work currently)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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_max = int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big')
|
||||
assert remote_max >= 198 * 1000 * 1000, remote_max
|
||||
their_revocation_store = RevocationStore()
|
||||
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')
|
||||
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_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']
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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. """
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user