tlnchannel: test for max htlc value (needs to be below protocol maximum) - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 53c6fc8cf14a914e961bd2f137540684244b461c DIR parent 777e350fae40afadb367914b7127a6ebe3f0241b HTML Author: SomberNight <somber.night@protonmail.com> Date: Thu, 26 Mar 2020 06:25:26 +0100 lnchannel: test for max htlc value (needs to be below protocol maximum) Diffstat: M electrum/lnchannel.py | 18 +++++++++++------- M electrum/lnutil.py | 1 + M electrum/tests/regtest/regtest.sh | 8 ++++---- M electrum/tests/test_lnchannel.py | 40 +++++++++++++++++++++++++++---- 4 files changed, 51 insertions(+), 16 deletions(-) --- DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py t@@ -44,13 +44,14 @@ from .logging import Logger from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailureMessage from . import lnutil from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, - get_per_commitment_secret_from_seed, secret_to_pubkey, derive_privkey, make_closing_tx, - sign_and_get_sig_string, RevocationStore, derive_blinded_pubkey, Direction, derive_pubkey, - make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc, - HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc, - funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs, - ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script, - ShortChannelID, map_htlcs_to_ctx_output_idxs, LNPeerAddr, BarePaymentAttemptLog) + get_per_commitment_secret_from_seed, secret_to_pubkey, derive_privkey, make_closing_tx, + sign_and_get_sig_string, RevocationStore, derive_blinded_pubkey, Direction, derive_pubkey, + make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc, + HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc, + funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs, + ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script, + ShortChannelID, map_htlcs_to_ctx_output_idxs, LNPeerAddr, BarePaymentAttemptLog, + LN_MAX_HTLC_VALUE_MSAT) from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo from .lnhtlc import HTLCManager t@@ -159,6 +160,7 @@ class Channel(Logger): self.revocation_store = RevocationStore(state["revocation_store"]) self._can_send_ctx_updates = True # type: bool self._receive_fail_reasons = {} # type: Dict[int, BarePaymentAttemptLog] + self._ignore_max_htlc_value = False # used in tests def get_id_for_log(self) -> str: scid = self.short_channel_id t@@ -425,6 +427,8 @@ class Channel(Logger): 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 < self.config[REMOTE].htlc_minimum_msat: raise PaymentFailure(f'HTLC value too small: {amount_msat} msat') + if amount_msat > LN_MAX_HTLC_VALUE_MSAT and not self._ignore_max_htlc_value: + raise PaymentFailure(f"HTLC value over protocol maximum: {amount_msat} > {LN_MAX_HTLC_VALUE_MSAT} msat") def can_pay(self, amount_msat: int) -> bool: """Returns whether we can initiate a new payment of given value. DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py t@@ -34,6 +34,7 @@ HTLC_TIMEOUT_WEIGHT = 663 HTLC_SUCCESS_WEIGHT = 703 LN_MAX_FUNDING_SAT = pow(2, 24) - 1 +LN_MAX_HTLC_VALUE_MSAT = pow(2, 32) - 1 # dummy address for fee estimation of funding tx def ln_dummy_address(): DIR diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh t@@ -171,7 +171,7 @@ if [[ $1 == "redeem_htlcs" ]]; then new_blocks 3 wait_until_channel_open alice # alice pays bob - invoice=$($bob add_lightning_request 0.05 -m "test") + invoice=$($bob add_lightning_request 0.04 -m "test") $alice lnpay $invoice --timeout=1 || true unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent') if [[ "$unsettled" == "0" ]]; then t@@ -213,7 +213,7 @@ if [[ $1 == "breach_with_unspent_htlc" ]]; then new_blocks 3 wait_until_channel_open alice echo "alice pays bob" - invoice=$($bob add_lightning_request 0.05 -m "test") + invoice=$($bob add_lightning_request 0.04 -m "test") $alice lnpay $invoice --timeout=1 || true unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent') if [[ "$unsettled" == "0" ]]; then t@@ -242,7 +242,7 @@ if [[ $1 == "breach_with_spent_htlc" ]]; then new_blocks 3 wait_until_channel_open alice echo "alice pays bob" - invoice=$($bob add_lightning_request 0.05 -m "test") + invoice=$($bob add_lightning_request 0.04 -m "test") $alice lnpay $invoice --timeout=1 || true ctx=$($alice get_channel_ctx $channel --iknowwhatimdoing) unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent') t@@ -284,7 +284,7 @@ if [[ $1 == "breach_with_spent_htlc" ]]; then $bob daemon -d sleep 1 $bob load_wallet - wait_for_balance bob 0.049 + wait_for_balance bob 0.039 $bob getbalance fi DIR diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py t@@ -105,12 +105,12 @@ def bip32(sequence): assert type(k) is bytes return k -def create_test_channels(feerate=6000, local=None, remote=None): +def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None): funding_txid = binascii.hexlify(b"\x01"*32).decode("ascii") funding_index = 0 - funding_sat = ((local + remote) // 1000) if local is not None and remote is not None else (bitcoin.COIN * 10) - local_amount = local if local is not None else (funding_sat * 1000 // 2) - remote_amount = remote if remote is not None else (funding_sat * 1000 // 2) + funding_sat = ((local_msat + remote_msat) // 1000) if local_msat is not None and remote_msat is not None else (bitcoin.COIN * 10) + local_amount = local_msat if local_msat is not None else (funding_sat * 1000 // 2) + remote_amount = remote_msat if remote_msat is not None else (funding_sat * 1000 // 2) alice_raw = [ bip32("m/" + str(i)) for i in range(5) ] bob_raw = [ bip32("m/" + str(i)) for i in range(5,11) ] alice_privkeys = [lnutil.Keypair(lnutil.privkey_to_pubkey(x), x) for x in alice_raw] t@@ -164,6 +164,10 @@ def create_test_channels(feerate=6000, local=None, remote=None): # TODO: sweep_address in lnchannel.py should use static_remotekey alice.sweep_address = bitcoin.pubkey_to_address('p2wpkh', alice.config[LOCAL].payment_basepoint.pubkey.hex()) bob.sweep_address = bitcoin.pubkey_to_address('p2wpkh', bob.config[LOCAL].payment_basepoint.pubkey.hex()) + + alice._ignore_max_htlc_value = True + bob._ignore_max_htlc_value = True + return alice, bob class TestFee(ElectrumTestCase): t@@ -172,7 +176,9 @@ class TestFee(ElectrumTestCase): https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2 """ def test_fee(self): - alice_channel, bob_channel = create_test_channels(253, 10000000000, 5000000000) + alice_channel, bob_channel = create_test_channels(feerate=253, + local_msat=10000000000, + remote_msat=5000000000) self.assertIn(9999817, [x.value for x in alice_channel.get_latest_commitment(LOCAL).outputs()]) class TestChannel(ElectrumTestCase): t@@ -649,6 +655,30 @@ class TestAvailableToSpend(ElectrumTestCase): self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) alice_channel.add_htlc(htlc_dict) + def test_max_htlc_value(self): + alice_channel, bob_channel = create_test_channels() + paymentPreimage = b"\x01" * 32 + paymentHash = bitcoin.sha256(paymentPreimage) + htlc_dict = { + 'payment_hash' : paymentHash, + 'amount_msat' : one_bitcoin_in_msat * 41 // 10, + 'cltv_expiry' : 5, + 'timestamp' : 0, + } + + alice_channel._ignore_max_htlc_value = False + bob_channel._ignore_max_htlc_value = False + with self.assertRaises(lnutil.PaymentFailure): + alice_channel.add_htlc(htlc_dict) + with self.assertRaises(lnutil.PaymentFailure): + bob_channel.receive_htlc(htlc_dict) + + alice_channel._ignore_max_htlc_value = True + bob_channel._ignore_max_htlc_value = True + alice_channel.add_htlc(htlc_dict) + bob_channel.receive_htlc(htlc_dict) + + class TestChanReserve(ElectrumTestCase): def setUp(self): alice_channel, bob_channel = create_test_channels()