commit 53c6fc8cf14a914e961bd2f137540684244b461c
parent 777e350fae40afadb367914b7127a6ebe3f0241b
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:
4 files changed, 51 insertions(+), 16 deletions(-)
diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
@@ -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
@@ -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
@@ -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.
diff --git a/electrum/lnutil.py b/electrum/lnutil.py
@@ -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():
diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh
@@ -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
@@ -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
@@ -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')
@@ -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
diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py
@@ -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]
@@ -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):
@@ -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):
@@ -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()