ttest_lnchannel.py - electrum - Electrum Bitcoin wallet
HTML git clone https://git.parazyd.org/electrum
DIR Log
DIR Files
DIR Refs
DIR Submodules
---
ttest_lnchannel.py (43778B)
---
1 # Copyright (C) 2018 The Electrum developers
2 # Copyright (C) 2015-2018 The Lightning Network Developers
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 # THE SOFTWARE.
21 #
22 # Many of these unit tests are heavily based on unit tests in lnd
23 # (around commit 42de4400bff5105352d0552155f73589166d162b).
24
25 import unittest
26 import os
27 import binascii
28 from pprint import pformat
29 import logging
30
31 from electrum import bitcoin
32 from electrum import lnpeer
33 from electrum import lnchannel
34 from electrum import lnutil
35 from electrum import bip32 as bip32_utils
36 from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
37 from electrum.logging import console_stderr_handler
38 from electrum.lnchannel import ChannelState
39 from electrum.json_db import StoredDict
40 from electrum.coinchooser import PRNG
41
42 from . import ElectrumTestCase
43
44
45 one_bitcoin_in_msat = bitcoin.COIN * 1000
46
47
48 def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator,
49 local_amount, remote_amount, privkeys, other_pubkeys,
50 seed, cur, nex, other_node_id, l_dust, r_dust, l_csv,
51 r_csv):
52 assert local_amount > 0
53 assert remote_amount > 0
54 channel_id, _ = lnpeer.channel_id_from_funding_tx(funding_txid, funding_index)
55 state = {
56 "channel_id":channel_id.hex(),
57 "short_channel_id":channel_id[:8],
58 "funding_outpoint":lnpeer.Outpoint(funding_txid, funding_index),
59 "remote_config":lnpeer.RemoteConfig(
60 payment_basepoint=other_pubkeys[0],
61 multisig_key=other_pubkeys[1],
62 htlc_basepoint=other_pubkeys[2],
63 delayed_basepoint=other_pubkeys[3],
64 revocation_basepoint=other_pubkeys[4],
65 to_self_delay=r_csv,
66 dust_limit_sat=r_dust,
67 max_htlc_value_in_flight_msat=one_bitcoin_in_msat * 5,
68 max_accepted_htlcs=5,
69 initial_msat=remote_amount,
70 reserve_sat=0,
71 htlc_minimum_msat=1,
72 next_per_commitment_point=nex,
73 current_per_commitment_point=cur,
74 upfront_shutdown_script=b'',
75 ),
76 "local_config":lnpeer.LocalConfig(
77 channel_seed = None,
78 payment_basepoint=privkeys[0],
79 multisig_key=privkeys[1],
80 htlc_basepoint=privkeys[2],
81 delayed_basepoint=privkeys[3],
82 revocation_basepoint=privkeys[4],
83 to_self_delay=l_csv,
84 dust_limit_sat=l_dust,
85 max_htlc_value_in_flight_msat=one_bitcoin_in_msat * 5,
86 max_accepted_htlcs=5,
87 initial_msat=local_amount,
88 reserve_sat=0,
89 per_commitment_secret_seed=seed,
90 funding_locked_received=True,
91 was_announced=False,
92 current_commitment_signature=None,
93 current_htlc_signatures=None,
94 htlc_minimum_msat=1,
95 upfront_shutdown_script=b'',
96 ),
97 "constraints":lnpeer.ChannelConstraints(
98 capacity=funding_sat,
99 is_initiator=is_initiator,
100 funding_txn_minimum_depth=3,
101 ),
102 "node_id":other_node_id.hex(),
103 'onion_keys': {},
104 'data_loss_protect_remote_pcp': {},
105 'state': 'PREOPENING',
106 'log': {},
107 'revocation_store': {},
108 }
109 return StoredDict(state, None, [])
110
111 def bip32(sequence):
112 node = bip32_utils.BIP32Node.from_rootseed(b"9dk", xtype='standard').subkey_at_private_derivation(sequence)
113 k = node.eckey.get_secret_bytes()
114 assert len(k) == 32
115 assert type(k) is bytes
116 return k
117
118 def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None,
119 alice_name="alice", bob_name="bob",
120 alice_pubkey=b"\x01"*33, bob_pubkey=b"\x02"*33, random_seed=None):
121 if random_seed is None: # needed for deterministic randomness
122 random_seed = os.urandom(32)
123 random_gen = PRNG(random_seed)
124 funding_txid = binascii.hexlify(random_gen.get_bytes(32)).decode("ascii")
125 funding_index = 0
126 funding_sat = ((local_msat + remote_msat) // 1000) if local_msat is not None and remote_msat is not None else (bitcoin.COIN * 10)
127 local_amount = local_msat if local_msat is not None else (funding_sat * 1000 // 2)
128 remote_amount = remote_msat if remote_msat is not None else (funding_sat * 1000 // 2)
129 alice_raw = [ bip32("m/" + str(i)) for i in range(5) ]
130 bob_raw = [ bip32("m/" + str(i)) for i in range(5,11) ]
131 alice_privkeys = [lnutil.Keypair(lnutil.privkey_to_pubkey(x), x) for x in alice_raw]
132 bob_privkeys = [lnutil.Keypair(lnutil.privkey_to_pubkey(x), x) for x in bob_raw]
133 alice_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys]
134 bob_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys]
135
136 alice_seed = random_gen.get_bytes(32)
137 bob_seed = random_gen.get_bytes(32)
138
139 alice_first = lnutil.secret_to_pubkey(
140 int.from_bytes(lnutil.get_per_commitment_secret_from_seed(
141 alice_seed, lnutil.RevocationStore.START_INDEX), "big"))
142 bob_first = lnutil.secret_to_pubkey(
143 int.from_bytes(lnutil.get_per_commitment_secret_from_seed(
144 bob_seed, lnutil.RevocationStore.START_INDEX), "big"))
145
146 alice, bob = (
147 lnchannel.Channel(
148 create_channel_state(
149 funding_txid, funding_index, funding_sat, True, local_amount,
150 remote_amount, alice_privkeys, bob_pubkeys, alice_seed, None,
151 bob_first, other_node_id=bob_pubkey, l_dust=200, r_dust=1300,
152 l_csv=5, r_csv=4
153 ),
154 name=bob_name,
155 initial_feerate=feerate),
156 lnchannel.Channel(
157 create_channel_state(
158 funding_txid, funding_index, funding_sat, False, remote_amount,
159 local_amount, bob_privkeys, alice_pubkeys, bob_seed, None,
160 alice_first, other_node_id=alice_pubkey, l_dust=1300, r_dust=200,
161 l_csv=4, r_csv=5
162 ),
163 name=alice_name,
164 initial_feerate=feerate)
165 )
166
167 alice.hm.log[LOCAL]['ctn'] = 0
168 bob.hm.log[LOCAL]['ctn'] = 0
169
170 alice._state = ChannelState.OPEN
171 bob._state = ChannelState.OPEN
172
173 a_out = alice.get_latest_commitment(LOCAL).outputs()
174 b_out = bob.get_next_commitment(REMOTE).outputs()
175 assert a_out == b_out, "\n" + pformat((a_out, b_out))
176
177 sig_from_bob, a_htlc_sigs = bob.sign_next_commitment()
178 sig_from_alice, b_htlc_sigs = alice.sign_next_commitment()
179
180 assert len(a_htlc_sigs) == 0
181 assert len(b_htlc_sigs) == 0
182
183 alice.open_with_first_pcp(bob_first, sig_from_bob)
184 bob.open_with_first_pcp(alice_first, sig_from_alice)
185
186 alice_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
187 bob_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
188
189 # from funding_locked:
190 alice.config[REMOTE].next_per_commitment_point = bob_second
191 bob.config[REMOTE].next_per_commitment_point = alice_second
192
193 alice._fallback_sweep_address = bitcoin.pubkey_to_address('p2wpkh', alice.config[LOCAL].payment_basepoint.pubkey.hex())
194 bob._fallback_sweep_address = bitcoin.pubkey_to_address('p2wpkh', bob.config[LOCAL].payment_basepoint.pubkey.hex())
195
196 alice._ignore_max_htlc_value = True
197 bob._ignore_max_htlc_value = True
198
199 return alice, bob
200
201 class TestFee(ElectrumTestCase):
202 """
203 test
204 https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2
205 """
206 def test_fee(self):
207 alice_channel, bob_channel = create_test_channels(feerate=253,
208 local_msat=10000000000,
209 remote_msat=5000000000)
210 self.assertIn(9999817, [x.value for x in alice_channel.get_latest_commitment(LOCAL).outputs()])
211
212 class TestChannel(ElectrumTestCase):
213 maxDiff = 999
214
215 def assertOutputExistsByValue(self, tx, amt_sat):
216 for o in tx.outputs():
217 if o.value == amt_sat:
218 break
219 else:
220 self.assertFalse()
221
222 @classmethod
223 def setUpClass(cls):
224 super().setUpClass()
225 console_stderr_handler.setLevel(logging.DEBUG)
226
227 def setUp(self):
228 super().setUp()
229 # Create a test channel which will be used for the duration of this
230 # unittest. The channel will be funded evenly with Alice having 5 BTC,
231 # and Bob having 5 BTC.
232 self.alice_channel, self.bob_channel = create_test_channels()
233
234 self.paymentPreimage = b"\x01" * 32
235 paymentHash = bitcoin.sha256(self.paymentPreimage)
236 self.htlc_dict = {
237 'payment_hash' : paymentHash,
238 'amount_msat' : one_bitcoin_in_msat,
239 'cltv_expiry' : 5,
240 'timestamp' : 0,
241 }
242
243 # First Alice adds the outgoing HTLC to her local channel's state
244 # update log. Then Alice sends this wire message over to Bob who adds
245 # this htlc to his remote state update log.
246 self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc_dict).htlc_id
247 self.assertNotEqual(list(self.alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED, 1).values()), [])
248
249 before = self.bob_channel.balance_minus_outgoing_htlcs(REMOTE)
250 beforeLocal = self.bob_channel.balance_minus_outgoing_htlcs(LOCAL)
251
252 self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc_dict).htlc_id
253
254 self.htlc = self.bob_channel.hm.log[REMOTE]['adds'][0]
255
256 def test_concurrent_reversed_payment(self):
257 self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
258 self.htlc_dict['amount_msat'] += 1000
259 self.bob_channel.add_htlc(self.htlc_dict)
260 self.alice_channel.receive_htlc(self.htlc_dict)
261
262 self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
263 self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
264 self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
265 self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 3)
266
267 self.alice_channel.receive_new_commitment(*self.bob_channel.sign_next_commitment())
268
269 self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 3)
270 self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
271 self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
272 self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 3)
273
274 self.alice_channel.revoke_current_commitment()
275
276 self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 3)
277 self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
278 self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
279 self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 4)
280
281 def test_SimpleAddSettleWorkflow(self):
282 alice_channel, bob_channel = self.alice_channel, self.bob_channel
283 htlc = self.htlc
284
285 alice_out = alice_channel.get_latest_commitment(LOCAL).outputs()
286 short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
287 long_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
288 self.assertLess(alice_out[long_idx].value, 5 * 10**8, alice_out)
289 self.assertEqual(alice_out[short_idx].value, 5 * 10**8, alice_out)
290
291 alice_out = alice_channel.get_latest_commitment(REMOTE).outputs()
292 short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
293 long_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
294 self.assertLess(alice_out[short_idx].value, 5 * 10**8)
295 self.assertEqual(alice_out[long_idx].value, 5 * 10**8)
296
297 self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
298
299 self.assertNotEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [])
300
301 self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 0), [])
302 self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc])
303
304 self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 0), [])
305 self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 1), [])
306
307 self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 0), [])
308 self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 1), [])
309
310 self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 0), [])
311 self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 1), [])
312
313 from electrum.lnutil import extract_ctn_from_tx_and_chan
314 tx0 = str(alice_channel.force_close_tx())
315 self.assertEqual(alice_channel.get_oldest_unrevoked_ctn(LOCAL), 0)
316 self.assertEqual(extract_ctn_from_tx_and_chan(alice_channel.force_close_tx(), alice_channel), 0)
317 self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
318
319 # Next alice commits this change by sending a signature message. Since
320 # we expect the messages to be ordered, Bob will receive the HTLC we
321 # just sent before he receives this signature, so the signature will
322 # cover the HTLC.
323 aliceSig, aliceHtlcSigs = alice_channel.sign_next_commitment()
324 self.assertEqual(len(aliceHtlcSigs), 1, "alice should generate one htlc signature")
325
326 self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
327
328 self.assertEqual(next(iter(alice_channel.hm.get_htlcs_in_next_ctx(REMOTE)))[0], RECEIVED)
329 self.assertEqual(alice_channel.hm.get_htlcs_in_next_ctx(REMOTE), bob_channel.hm.get_htlcs_in_next_ctx(LOCAL))
330 self.assertEqual(alice_channel.get_latest_commitment(REMOTE).outputs(), bob_channel.get_next_commitment(LOCAL).outputs())
331
332 # Bob receives this signature message, and checks that this covers the
333 # state he has in his remote log. This includes the HTLC just sent
334 # from Alice.
335 self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
336 bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs)
337 self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
338
339 self.assertEqual(bob_channel.get_oldest_unrevoked_ctn(REMOTE), 0)
340 self.assertEqual(bob_channel.included_htlcs(LOCAL, RECEIVED, 1), [htlc])#
341
342 self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 0), [])
343 self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc])
344
345 self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 0), [])
346 self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 1), [])
347
348 self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 0), [])
349 self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 1), [])
350
351 # Bob revokes his prior commitment given to him by Alice, since he now
352 # has a valid signature for a newer commitment.
353 bobRevocation = bob_channel.revoke_current_commitment()
354 self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
355
356 # Bob finally sends a signature for Alice's commitment transaction.
357 # This signature will cover the HTLC, since Bob will first send the
358 # revocation just created. The revocation also acks every received
359 # HTLC up to the point where Alice sent her signature.
360 bobSig, bobHtlcSigs = bob_channel.sign_next_commitment()
361 self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
362
363 self.assertEqual(len(bobHtlcSigs), 1)
364
365 self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
366
367 # so far: Alice added htlc, Alice signed.
368 self.assertEqual(len(alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
369 self.assertEqual(len(alice_channel.get_next_commitment(LOCAL).outputs()), 2)
370 self.assertEqual(len(alice_channel.get_oldest_unrevoked_commitment(REMOTE).outputs()), 2)
371 self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
372
373 # Alice then processes this revocation, sending her own revocation for
374 # her prior commitment transaction. Alice shouldn't have any HTLCs to
375 # forward since she's sending an outgoing HTLC.
376 alice_channel.receive_revocation(bobRevocation)
377
378 self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
379
380 self.assertEqual(len(alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
381 self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
382 self.assertEqual(len(alice_channel.force_close_tx().outputs()), 2)
383
384 self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
385 self.assertEqual(alice_channel.get_next_commitment(LOCAL).outputs(),
386 bob_channel.get_latest_commitment(REMOTE).outputs())
387
388 # Alice then processes bob's signature, and since she just received
389 # the revocation, she expect this signature to cover everything up to
390 # the point where she sent her signature, including the HTLC.
391 alice_channel.receive_new_commitment(bobSig, bobHtlcSigs)
392
393 self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
394 self.assertEqual(len(alice_channel.force_close_tx().outputs()), 3)
395
396 self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
397
398 tx1 = str(alice_channel.force_close_tx())
399 self.assertNotEqual(tx0, tx1)
400
401 # Alice then generates a revocation for bob.
402 aliceRevocation = alice_channel.revoke_current_commitment()
403
404 tx2 = str(alice_channel.force_close_tx())
405 # since alice already has the signature for the next one, it doesn't change her force close tx (it was already the newer one)
406 self.assertEqual(tx1, tx2)
407
408 # Finally Bob processes Alice's revocation, at this point the new HTLC
409 # is fully locked in within both commitment transactions. Bob should
410 # also be able to forward an HTLC now that the HTLC has been locked
411 # into both commitment transactions.
412 self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
413 bob_channel.receive_revocation(aliceRevocation)
414
415 # At this point, both sides should have the proper number of satoshis
416 # sent, and commitment height updated within their local channel
417 # state.
418 aliceSent = 0
419 bobSent = 0
420
421 self.assertEqual(alice_channel.total_msat(SENT), aliceSent, "alice has incorrect milli-satoshis sent")
422 self.assertEqual(alice_channel.total_msat(RECEIVED), bobSent, "alice has incorrect milli-satoshis received")
423 self.assertEqual(bob_channel.total_msat(SENT), bobSent, "bob has incorrect milli-satoshis sent")
424 self.assertEqual(bob_channel.total_msat(RECEIVED), aliceSent, "bob has incorrect milli-satoshis received")
425 self.assertEqual(bob_channel.get_oldest_unrevoked_ctn(LOCAL), 1, "bob has incorrect commitment height")
426 self.assertEqual(alice_channel.get_oldest_unrevoked_ctn(LOCAL), 1, "alice has incorrect commitment height")
427
428 # Both commitment transactions should have three outputs, and one of
429 # them should be exactly the amount of the HTLC.
430 alice_ctx = alice_channel.get_next_commitment(LOCAL)
431 bob_ctx = bob_channel.get_next_commitment(LOCAL)
432 self.assertEqual(len(alice_ctx.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_ctx.outputs()))
433 self.assertEqual(len(bob_ctx.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_ctx.outputs()))
434 self.assertOutputExistsByValue(alice_ctx, htlc.amount_msat // 1000)
435 self.assertOutputExistsByValue(bob_ctx, htlc.amount_msat // 1000)
436
437 # Now we'll repeat a similar exchange, this time with Bob settling the
438 # HTLC once he learns of the preimage.
439 preimage = self.paymentPreimage
440 bob_channel.settle_htlc(preimage, self.bobHtlcIndex)
441
442 alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex)
443
444 tx3 = str(alice_channel.force_close_tx())
445 # just settling a htlc does not change her force close tx
446 self.assertEqual(tx2, tx3)
447
448 bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
449 self.assertEqual(len(bobHtlcSigs2), 0)
450
451 self.assertEqual(list(alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED).values()), [htlc])
452 self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, alice_channel.get_oldest_unrevoked_ctn(REMOTE)), [htlc])
453
454 self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc])
455 self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 2), [htlc])
456
457 self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 1), [htlc])
458 self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 2), [])
459
460 self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 1), [])
461 self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 2), [])
462
463 self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 1), [])
464 self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 2), [])
465
466 alice_ctx_bob_version = bob_channel.get_latest_commitment(REMOTE).outputs()
467 alice_ctx_alice_version = alice_channel.get_next_commitment(LOCAL).outputs()
468 self.assertEqual(alice_ctx_alice_version, alice_ctx_bob_version)
469
470 alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
471
472 tx4 = str(alice_channel.force_close_tx())
473 self.assertNotEqual(tx3, tx4)
474
475 self.assertEqual(alice_channel.balance(LOCAL), 500000000000)
476 self.assertEqual(1, alice_channel.get_oldest_unrevoked_ctn(LOCAL))
477 self.assertEqual(len(alice_channel.included_htlcs(LOCAL, RECEIVED, ctn=2)), 0)
478 aliceRevocation2 = alice_channel.revoke_current_commitment()
479 aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
480 self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
481 self.assertEqual(len(bob_channel.get_latest_commitment(LOCAL).outputs()), 3)
482 bob_channel.receive_revocation(aliceRevocation2)
483
484 bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2)
485
486 bobRevocation2 = bob_channel.revoke_current_commitment()
487 received = lnchannel.htlcsum(bob_channel.hm.received_in_ctn(bob_channel.get_latest_ctn(LOCAL)))
488 self.assertEqual(one_bitcoin_in_msat, received)
489 alice_channel.receive_revocation(bobRevocation2)
490
491 # At this point, Bob should have 6 BTC settled, with Alice still having
492 # 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel
493 # should show 1 BTC received. They should also be at commitment height
494 # two, with the revocation window extended by 1 (5).
495 mSatTransferred = one_bitcoin_in_msat
496 self.assertEqual(alice_channel.total_msat(SENT), mSatTransferred, "alice satoshis sent incorrect")
497 self.assertEqual(alice_channel.total_msat(RECEIVED), 0, "alice satoshis received incorrect")
498 self.assertEqual(bob_channel.total_msat(RECEIVED), mSatTransferred, "bob satoshis received incorrect")
499 self.assertEqual(bob_channel.total_msat(SENT), 0, "bob satoshis sent incorrect")
500 self.assertEqual(bob_channel.get_latest_ctn(LOCAL), 2, "bob has incorrect commitment height")
501 self.assertEqual(alice_channel.get_latest_ctn(LOCAL), 2, "alice has incorrect commitment height")
502
503 alice_channel.update_fee(100000, True)
504 alice_outputs = alice_channel.get_next_commitment(REMOTE).outputs()
505 old_outputs = bob_channel.get_next_commitment(LOCAL).outputs()
506 bob_channel.update_fee(100000, False)
507 new_outputs = bob_channel.get_next_commitment(LOCAL).outputs()
508 self.assertNotEqual(old_outputs, new_outputs)
509 self.assertEqual(alice_outputs, new_outputs)
510
511 tx5 = str(alice_channel.force_close_tx())
512 # sending a fee update does not change her force close tx
513 self.assertEqual(tx4, tx5)
514
515 force_state_transition(alice_channel, bob_channel)
516
517 tx6 = str(alice_channel.force_close_tx())
518 self.assertNotEqual(tx5, tx6)
519
520 self.htlc_dict['amount_msat'] *= 5
521 bob_index = bob_channel.add_htlc(self.htlc_dict).htlc_id
522 alice_index = alice_channel.receive_htlc(self.htlc_dict).htlc_id
523
524 force_state_transition(bob_channel, alice_channel)
525
526 alice_channel.settle_htlc(self.paymentPreimage, alice_index)
527 bob_channel.receive_htlc_settle(self.paymentPreimage, bob_index)
528
529 force_state_transition(alice_channel, bob_channel)
530 self.assertEqual(alice_channel.total_msat(SENT), one_bitcoin_in_msat, "alice satoshis sent incorrect")
531 self.assertEqual(alice_channel.total_msat(RECEIVED), 5 * one_bitcoin_in_msat, "alice satoshis received incorrect")
532 self.assertEqual(bob_channel.total_msat(RECEIVED), one_bitcoin_in_msat, "bob satoshis received incorrect")
533 self.assertEqual(bob_channel.total_msat(SENT), 5 * one_bitcoin_in_msat, "bob satoshis sent incorrect")
534
535
536 def alice_to_bob_fee_update(self, fee=111):
537 aoldctx = self.alice_channel.get_next_commitment(REMOTE).outputs()
538 self.alice_channel.update_fee(fee, True)
539 anewctx = self.alice_channel.get_next_commitment(REMOTE).outputs()
540 self.assertNotEqual(aoldctx, anewctx)
541 boldctx = self.bob_channel.get_next_commitment(LOCAL).outputs()
542 self.bob_channel.update_fee(fee, False)
543 bnewctx = self.bob_channel.get_next_commitment(LOCAL).outputs()
544 self.assertNotEqual(boldctx, bnewctx)
545 self.assertEqual(anewctx, bnewctx)
546 return fee
547
548 def test_UpdateFeeSenderCommits(self):
549 alice_channel, bob_channel = self.alice_channel, self.bob_channel
550
551 old_feerate = alice_channel.get_next_feerate(LOCAL)
552
553 fee = self.alice_to_bob_fee_update()
554 self.assertEqual(alice_channel.get_next_feerate(LOCAL), old_feerate)
555
556 alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
557 #self.assertEqual(alice_channel.get_next_feerate(LOCAL), old_feerate)
558
559 bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
560
561 self.assertNotEqual(fee, bob_channel.get_oldest_unrevoked_feerate(LOCAL))
562 self.assertEqual(fee, bob_channel.get_latest_feerate(LOCAL))
563 rev = bob_channel.revoke_current_commitment()
564 self.assertEqual(fee, bob_channel.get_oldest_unrevoked_feerate(LOCAL))
565
566 alice_channel.receive_revocation(rev)
567
568
569 bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
570 alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
571
572 self.assertNotEqual(fee, alice_channel.get_oldest_unrevoked_feerate(LOCAL))
573 self.assertEqual(fee, alice_channel.get_latest_feerate(LOCAL))
574 rev = alice_channel.revoke_current_commitment()
575 self.assertEqual(fee, alice_channel.get_oldest_unrevoked_feerate(LOCAL))
576
577 bob_channel.receive_revocation(rev)
578 self.assertEqual(fee, bob_channel.get_oldest_unrevoked_feerate(LOCAL))
579 self.assertEqual(fee, bob_channel.get_latest_feerate(LOCAL))
580
581
582 def test_UpdateFeeReceiverCommits(self):
583 fee = self.alice_to_bob_fee_update()
584
585 alice_channel, bob_channel = self.alice_channel, self.bob_channel
586
587 bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
588 alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
589
590 alice_revocation = alice_channel.revoke_current_commitment()
591 bob_channel.receive_revocation(alice_revocation)
592 alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
593 bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
594
595 self.assertNotEqual(fee, bob_channel.get_oldest_unrevoked_feerate(LOCAL))
596 self.assertEqual(fee, bob_channel.get_latest_feerate(LOCAL))
597 bob_revocation = bob_channel.revoke_current_commitment()
598 self.assertEqual(fee, bob_channel.get_oldest_unrevoked_feerate(LOCAL))
599
600 bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
601 alice_channel.receive_revocation(bob_revocation)
602 alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
603
604 self.assertNotEqual(fee, alice_channel.get_oldest_unrevoked_feerate(LOCAL))
605 self.assertEqual(fee, alice_channel.get_latest_feerate(LOCAL))
606 alice_revocation = alice_channel.revoke_current_commitment()
607 self.assertEqual(fee, alice_channel.get_oldest_unrevoked_feerate(LOCAL))
608
609 bob_channel.receive_revocation(alice_revocation)
610 self.assertEqual(fee, bob_channel.get_oldest_unrevoked_feerate(LOCAL))
611 self.assertEqual(fee, bob_channel.get_latest_feerate(LOCAL))
612
613 @unittest.skip("broken probably because we havn't implemented detecting when we come out of a situation where we violate reserve")
614 def test_AddHTLCNegativeBalance(self):
615 # the test in lnd doesn't set the fee to zero.
616 # probably lnd subtracts commitment fee after deciding weather
617 # an htlc can be added. so we set the fee to zero so that
618 # the test can work.
619 self.alice_to_bob_fee_update(0)
620 force_state_transition(self.alice_channel, self.bob_channel)
621
622 self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
623 self.alice_channel.add_htlc(self.htlc_dict)
624 self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x03')
625 self.alice_channel.add_htlc(self.htlc_dict)
626 # now there are three htlcs (one was in setUp)
627
628 # Alice now has an available balance of 2 BTC. We'll add a new HTLC of
629 # value 2 BTC, which should make Alice's balance negative (since she
630 # has to pay a commitment fee).
631 new = dict(self.htlc_dict)
632 new['amount_msat'] *= 2.5
633 new['payment_hash'] = bitcoin.sha256(32 * b'\x04')
634 with self.assertRaises(lnutil.PaymentFailure) as cm:
635 self.alice_channel.add_htlc(new)
636 self.assertIn('Not enough local balance', cm.exception.args[0])
637
638
639 class TestAvailableToSpend(ElectrumTestCase):
640 def test_DesyncHTLCs(self):
641 alice_channel, bob_channel = create_test_channels()
642 self.assertEqual(499986152000, alice_channel.available_to_spend(LOCAL))
643 self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
644
645 paymentPreimage = b"\x01" * 32
646 paymentHash = bitcoin.sha256(paymentPreimage)
647 htlc_dict = {
648 'payment_hash' : paymentHash,
649 'amount_msat' : one_bitcoin_in_msat * 41 // 10,
650 'cltv_expiry' : 5,
651 'timestamp' : 0,
652 }
653
654 alice_idx = alice_channel.add_htlc(htlc_dict).htlc_id
655 bob_idx = bob_channel.receive_htlc(htlc_dict).htlc_id
656 self.assertEqual(89984088000, alice_channel.available_to_spend(LOCAL))
657 self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
658
659 force_state_transition(alice_channel, bob_channel)
660 bob_channel.fail_htlc(bob_idx)
661 alice_channel.receive_fail_htlc(alice_idx, error_bytes=None)
662 self.assertEqual(89984088000, alice_channel.available_to_spend(LOCAL))
663 self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
664 # Alice now has gotten all her original balance (5 BTC) back, however,
665 # adding a new HTLC at this point SHOULD fail, since if she adds the
666 # HTLC and signs the next state, Bob cannot assume she received the
667 # FailHTLC, and must assume she doesn't have the necessary balance
668 # available.
669 # We try adding an HTLC of value 1 BTC, which should fail because the
670 # balance is unavailable.
671 htlc_dict = {
672 'payment_hash' : paymentHash,
673 'amount_msat' : one_bitcoin_in_msat,
674 'cltv_expiry' : 5,
675 'timestamp' : 0,
676 }
677 with self.assertRaises(lnutil.PaymentFailure):
678 alice_channel.add_htlc(htlc_dict)
679 # Now do a state transition, which will ACK the FailHTLC, making Alice
680 # able to add the new HTLC.
681 force_state_transition(alice_channel, bob_channel)
682 self.assertEqual(499986152000, alice_channel.available_to_spend(LOCAL))
683 self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
684 alice_channel.add_htlc(htlc_dict)
685
686 def test_max_htlc_value(self):
687 alice_channel, bob_channel = create_test_channels()
688 paymentPreimage = b"\x01" * 32
689 paymentHash = bitcoin.sha256(paymentPreimage)
690 htlc_dict = {
691 'payment_hash' : paymentHash,
692 'amount_msat' : one_bitcoin_in_msat * 41 // 10,
693 'cltv_expiry' : 5,
694 'timestamp' : 0,
695 }
696
697 alice_channel._ignore_max_htlc_value = False
698 bob_channel._ignore_max_htlc_value = False
699 with self.assertRaises(lnutil.PaymentFailure):
700 alice_channel.add_htlc(htlc_dict)
701 with self.assertRaises(lnutil.RemoteMisbehaving):
702 bob_channel.receive_htlc(htlc_dict)
703
704 alice_channel._ignore_max_htlc_value = True
705 bob_channel._ignore_max_htlc_value = True
706 alice_channel.add_htlc(htlc_dict)
707 bob_channel.receive_htlc(htlc_dict)
708
709
710 class TestChanReserve(ElectrumTestCase):
711 def setUp(self):
712 alice_channel, bob_channel = create_test_channels()
713 alice_min_reserve = int(.5 * one_bitcoin_in_msat // 1000)
714 # We set Bob's channel reserve to a value that is larger than
715 # his current balance in the channel. This will ensure that
716 # after a channel is first opened, Bob can still receive HTLCs
717 # even though his balance is less than his channel reserve.
718 bob_min_reserve = 6 * one_bitcoin_in_msat // 1000
719 # bob min reserve was decided by alice, but applies to bob
720
721 alice_channel.config[LOCAL].reserve_sat = bob_min_reserve
722 alice_channel.config[REMOTE].reserve_sat = alice_min_reserve
723
724 bob_channel.config[LOCAL].reserve_sat = alice_min_reserve
725 bob_channel.config[REMOTE].reserve_sat = bob_min_reserve
726
727 self.alice_channel = alice_channel
728 self.bob_channel = bob_channel
729
730 @unittest.skip("broken probably because we havn't implemented detecting when we come out of a situation where we violate reserve")
731 def test_part1(self):
732 # Add an HTLC that will increase Bob's balance. This should succeed,
733 # since Alice stays above her channel reserve, and Bob increases his
734 # balance (while still being below his channel reserve).
735 #
736 # Resulting balances:
737 # Alice: 4.5
738 # Bob: 5.0
739 paymentPreimage = b"\x01" * 32
740 paymentHash = bitcoin.sha256(paymentPreimage)
741 htlc_dict = {
742 'payment_hash' : paymentHash,
743 'amount_msat' : int(.5 * one_bitcoin_in_msat),
744 'cltv_expiry' : 5,
745 'timestamp' : 0,
746 }
747 self.alice_channel.add_htlc(htlc_dict)
748 self.bob_channel.receive_htlc(htlc_dict)
749 # Force a state transition, making sure this HTLC is considered valid
750 # even though the channel reserves are not met.
751 force_state_transition(self.alice_channel, self.bob_channel)
752
753 aliceSelfBalance = self.alice_channel.balance(LOCAL)\
754 - lnchannel.htlcsum(self.alice_channel.hm.htlcs_by_direction(LOCAL, SENT).values())
755 bobBalance = self.bob_channel.balance(REMOTE)\
756 - lnchannel.htlcsum(self.alice_channel.hm.htlcs_by_direction(REMOTE, SENT).values())
757 self.assertEqual(aliceSelfBalance, one_bitcoin_in_msat*4.5)
758 self.assertEqual(bobBalance, one_bitcoin_in_msat*5)
759 # Now let Bob try to add an HTLC. This should fail, since it will
760 # decrease his balance, which is already below the channel reserve.
761 #
762 # Resulting balances:
763 # Alice: 4.5
764 # Bob: 5.0
765 with self.assertRaises(lnutil.PaymentFailure):
766 htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
767 self.bob_channel.add_htlc(htlc_dict)
768 with self.assertRaises(lnutil.RemoteMisbehaving):
769 self.alice_channel.receive_htlc(htlc_dict)
770
771 def part2(self):
772 paymentPreimage = b"\x01" * 32
773 paymentHash = bitcoin.sha256(paymentPreimage)
774 # Now we'll add HTLC of 3.5 BTC to Alice's commitment, this should put
775 # Alice's balance at 1.5 BTC.
776 #
777 # Resulting balances:
778 # Alice: 1.5
779 # Bob: 9.5
780 htlc_dict = {
781 'payment_hash' : paymentHash,
782 'amount_msat' : int(3.5 * one_bitcoin_in_msat),
783 'cltv_expiry' : 5,
784 }
785 self.alice_channel.add_htlc(htlc_dict)
786 self.bob_channel.receive_htlc(htlc_dict)
787 # Add a second HTLC of 1 BTC. This should fail because it will take
788 # Alice's balance all the way down to her channel reserve, but since
789 # she is the initiator the additional transaction fee makes her
790 # balance dip below.
791 htlc_dict['amount_msat'] = one_bitcoin_in_msat
792 with self.assertRaises(lnutil.PaymentFailure):
793 self.alice_channel.add_htlc(htlc_dict)
794 with self.assertRaises(lnutil.RemoteMisbehaving):
795 self.bob_channel.receive_htlc(htlc_dict)
796
797 def part3(self):
798 # Add a HTLC of 2 BTC to Alice, and the settle it.
799 # Resulting balances:
800 # Alice: 3.0
801 # Bob: 7.0
802 paymentPreimage = b"\x01" * 32
803 paymentHash = bitcoin.sha256(paymentPreimage)
804 htlc_dict = {
805 'payment_hash' : paymentHash,
806 'amount_msat' : int(2 * one_bitcoin_in_msat),
807 'cltv_expiry' : 5,
808 'timestamp' : 0,
809 }
810 alice_idx = self.alice_channel.add_htlc(htlc_dict).htlc_id
811 bob_idx = self.bob_channel.receive_htlc(htlc_dict).htlc_id
812 force_state_transition(self.alice_channel, self.bob_channel)
813 self.check_bals(one_bitcoin_in_msat * 3
814 - self.alice_channel.get_next_fee(LOCAL),
815 one_bitcoin_in_msat * 5)
816 self.bob_channel.settle_htlc(paymentPreimage, bob_idx)
817 self.alice_channel.receive_htlc_settle(paymentPreimage, alice_idx)
818 force_state_transition(self.alice_channel, self.bob_channel)
819 self.check_bals(one_bitcoin_in_msat * 3
820 - self.alice_channel.get_next_fee(LOCAL),
821 one_bitcoin_in_msat * 7)
822 # And now let Bob add an HTLC of 1 BTC. This will take Bob's balance
823 # all the way down to his channel reserve, but since he is not paying
824 # the fee this is okay.
825 htlc_dict['amount_msat'] = one_bitcoin_in_msat
826 self.bob_channel.add_htlc(htlc_dict)
827 self.alice_channel.receive_htlc(htlc_dict)
828 force_state_transition(self.alice_channel, self.bob_channel)
829 self.check_bals(one_bitcoin_in_msat * 3 \
830 - self.alice_channel.get_next_fee(LOCAL),
831 one_bitcoin_in_msat * 6)
832
833 def check_bals(self, amt1, amt2):
834 self.assertEqual(self.alice_channel.available_to_spend(LOCAL), amt1)
835 self.assertEqual(self.bob_channel.available_to_spend(REMOTE), amt1)
836 self.assertEqual(self.alice_channel.available_to_spend(REMOTE), amt2)
837 self.assertEqual(self.bob_channel.available_to_spend(LOCAL), amt2)
838
839 class TestDust(ElectrumTestCase):
840 def test_DustLimit(self):
841 alice_channel, bob_channel = create_test_channels()
842
843 paymentPreimage = b"\x01" * 32
844 paymentHash = bitcoin.sha256(paymentPreimage)
845 fee_per_kw = alice_channel.get_next_feerate(LOCAL)
846 self.assertEqual(fee_per_kw, 6000)
847 htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
848 self.assertEqual(htlcAmt, 4478)
849 htlc = {
850 'payment_hash' : paymentHash,
851 'amount_msat' : 1000 * htlcAmt,
852 'cltv_expiry' : 5, # also in create_test_channels
853 'timestamp' : 0,
854 }
855
856 old_values = [x.value for x in bob_channel.get_latest_commitment(LOCAL).outputs() ]
857 aliceHtlcIndex = alice_channel.add_htlc(htlc).htlc_id
858 bobHtlcIndex = bob_channel.receive_htlc(htlc).htlc_id
859 force_state_transition(alice_channel, bob_channel)
860 alice_ctx = alice_channel.get_latest_commitment(LOCAL)
861 bob_ctx = bob_channel.get_latest_commitment(LOCAL)
862 new_values = [x.value for x in bob_ctx.outputs() ]
863 self.assertNotEqual(old_values, new_values)
864 self.assertEqual(len(alice_ctx.outputs()), 3)
865 self.assertEqual(len(bob_ctx.outputs()), 2)
866 default_fee = calc_static_fee(0)
867 self.assertEqual(bob_channel.get_next_fee(LOCAL), default_fee + htlcAmt)
868 bob_channel.settle_htlc(paymentPreimage, bobHtlcIndex)
869 alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex)
870 force_state_transition(bob_channel, alice_channel)
871 self.assertEqual(len(alice_channel.get_next_commitment(LOCAL).outputs()), 2)
872 self.assertEqual(alice_channel.total_msat(SENT) // 1000, htlcAmt)
873
874 def force_state_transition(chanA, chanB):
875 chanB.receive_new_commitment(*chanA.sign_next_commitment())
876 rev = chanB.revoke_current_commitment()
877 bob_sig, bob_htlc_sigs = chanB.sign_next_commitment()
878 chanA.receive_revocation(rev)
879 chanA.receive_new_commitment(bob_sig, bob_htlc_sigs)
880 chanB.receive_revocation(chanA.revoke_current_commitment())
881
882 # calcStaticFee calculates appropriate fees for commitment transactions. This
883 # function provides a simple way to allow test balance assertions to take fee
884 # calculations into account.
885 def calc_static_fee(numHTLCs):
886 commitWeight = 724
887 htlcWeight = 172
888 feePerKw = 24//4 * 1000
889 return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000