electrum

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

test_mpp_split.py (4552B)


      1 import random
      2 
      3 import electrum.mpp_split as mpp_split  # side effect for PART_PENALTY
      4 from electrum.lnutil import NoPathFound
      5 
      6 from . import ElectrumTestCase
      7 
      8 PART_PENALTY = mpp_split.PART_PENALTY
      9 
     10 
     11 class TestMppSplit(ElectrumTestCase):
     12     def setUp(self):
     13         super().setUp()
     14         # to make tests reproducible:
     15         random.seed(0)
     16         self.channels_with_funds = {
     17             0: 1_000_000_000,
     18             1: 500_000_000,
     19             2: 302_000_000,
     20             3: 101_000_000,
     21         }
     22 
     23     def tearDown(self):
     24         super().tearDown()
     25         # undo side effect
     26         mpp_split.PART_PENALTY = PART_PENALTY
     27 
     28     def test_suggest_splits(self):
     29         with self.subTest(msg="do a payment with the maximal amount spendable over a single channel"):
     30             splits = mpp_split.suggest_splits(1_000_000_000, self.channels_with_funds, exclude_single_parts=True)
     31             self.assertEqual({0: 660_000_000, 1: 340_000_000, 2: 0, 3: 0}, splits[0][0])
     32 
     33         with self.subTest(msg="do a payment with a larger amount than what is supported by a single channel"):
     34             splits = mpp_split.suggest_splits(1_100_000_000, self.channels_with_funds, exclude_single_parts=True)
     35             self.assertEqual(2, mpp_split.number_nonzero_parts(splits[0][0]))
     36 
     37         with self.subTest(msg="do a payment with the maximal amount spendable over all channels"):
     38             splits = mpp_split.suggest_splits(sum(self.channels_with_funds.values()), self.channels_with_funds, exclude_single_parts=True)
     39             self.assertEqual({0: 1_000_000_000, 1: 500_000_000, 2: 302_000_000, 3: 101_000_000}, splits[0][0])
     40 
     41         with self.subTest(msg="do a payment with the amount supported by all channels"):
     42             splits = mpp_split.suggest_splits(101_000_000, self.channels_with_funds, exclude_single_parts=False)
     43             for s in splits[:4]:
     44                 self.assertEqual(1, mpp_split.number_nonzero_parts(s[0]))
     45 
     46     def test_saturation(self):
     47         """Split configurations which spend the full amount in a channel should be avoided."""
     48         channels_with_funds = {0: 159_799_733_076, 1: 499_986_152_000}
     49         splits = mpp_split.suggest_splits(600_000_000_000, channels_with_funds, exclude_single_parts=True)
     50 
     51         uses_full_amount = False
     52         for c, a in splits[0][0].items():
     53             if a == channels_with_funds[c]:
     54                 uses_full_amount |= True
     55 
     56         self.assertFalse(uses_full_amount)
     57 
     58     def test_payment_below_min_part_size(self):
     59         amount = mpp_split.MIN_PART_MSAT // 2
     60         splits = mpp_split.suggest_splits(amount, self.channels_with_funds, exclude_single_parts=False)
     61         # we only get four configurations that end up spending the full amount
     62         # in a single channel
     63         self.assertEqual(4, len(splits))
     64 
     65     def test_suggest_part_penalty(self):
     66         """Test is mainly for documentation purposes.
     67         Decreasing the part penalty from 1.0 towards 0.0 leads to an increase
     68         in the number of parts a payment is split. A configuration which has
     69         about equally distributed amounts will result."""
     70         with self.subTest(msg="split payments with intermediate part penalty"):
     71             mpp_split.PART_PENALTY = 1.0
     72             splits = mpp_split.suggest_splits(1_100_000_000, self.channels_with_funds)
     73             self.assertEqual(2, mpp_split.number_nonzero_parts(splits[0][0]))
     74 
     75         with self.subTest(msg="split payments with intermediate part penalty"):
     76             mpp_split.PART_PENALTY = 0.3
     77             splits = mpp_split.suggest_splits(1_100_000_000, self.channels_with_funds)
     78             self.assertEqual(3, mpp_split.number_nonzero_parts(splits[0][0]))
     79 
     80         with self.subTest(msg="split payments with no part penalty"):
     81             mpp_split.PART_PENALTY = 0.0
     82             splits = mpp_split.suggest_splits(1_100_000_000, self.channels_with_funds)
     83             self.assertEqual(4, mpp_split.number_nonzero_parts(splits[0][0]))
     84 
     85     def test_suggest_splits_single_channel(self):
     86         channels_with_funds = {
     87             0: 1_000_000_000,
     88         }
     89 
     90         with self.subTest(msg="do a payment with the maximal amount spendable on a single channel"):
     91             splits = mpp_split.suggest_splits(1_000_000_000, channels_with_funds, exclude_single_parts=False)
     92             self.assertEqual({0: 1_000_000_000}, splits[0][0])
     93         with self.subTest(msg="test sending an amount greater than what we have available"):
     94             self.assertRaises(NoPathFound, mpp_split.suggest_splits, *(1_100_000_000, channels_with_funds))