def part2(self): paymentPreimage = b"\x01" * 32 paymentHash = bitcoin.sha256(paymentPreimage) # Now we'll add HTLC of 3.5 BTC to Alice's commitment, this should put # Alice's balance at 1.5 BTC. # # Resulting balances: # Alice: 1.5 # Bob: 9.5 htlc_dict = { 'payment_hash': paymentHash, 'amount_msat': int(3.5 * one_bitcoin_in_msat), 'cltv_expiry': 5, } self.alice_channel.add_htlc(htlc_dict) self.bob_channel.receive_htlc(htlc_dict) # Add a second HTLC of 1 BTC. This should fail because it will take # Alice's balance all the way down to her channel reserve, but since # she is the initiator the additional transaction fee makes her # balance dip below. htlc_dict['amount_msat'] = one_bitcoin_in_msat with self.assertRaises(lnutil.PaymentFailure): self.alice_channel.add_htlc(htlc_dict) with self.assertRaises(lnutil.RemoteMisbehaving): self.bob_channel.receive_htlc(htlc_dict)
def part3(self): # Add a HTLC of 2 BTC to Alice, and the settle it. # Resulting balances: # Alice: 3.0 # Bob: 7.0 paymentPreimage = b"\x01" * 32 paymentHash = bitcoin.sha256(paymentPreimage) htlc_dict = { 'payment_hash': paymentHash, 'amount_msat': int(2 * one_bitcoin_in_msat), 'cltv_expiry': 5, 'timestamp': 0, } alice_idx = self.alice_channel.add_htlc(htlc_dict).htlc_id bob_idx = self.bob_channel.receive_htlc(htlc_dict).htlc_id force_state_transition(self.alice_channel, self.bob_channel) self.check_bals( one_bitcoin_in_msat * 3 - self.alice_channel.get_next_fee(LOCAL), one_bitcoin_in_msat * 5) self.bob_channel.settle_htlc(paymentPreimage, bob_idx) self.alice_channel.receive_htlc_settle(paymentPreimage, alice_idx) force_state_transition(self.alice_channel, self.bob_channel) self.check_bals( one_bitcoin_in_msat * 3 - self.alice_channel.get_next_fee(LOCAL), one_bitcoin_in_msat * 7) # And now let Bob add an HTLC of 1 BTC. This will take Bob's balance # all the way down to his channel reserve, but since he is not paying # the fee this is okay. htlc_dict['amount_msat'] = one_bitcoin_in_msat self.bob_channel.add_htlc(htlc_dict) self.alice_channel.receive_htlc(htlc_dict) force_state_transition(self.alice_channel, self.bob_channel) self.check_bals(one_bitcoin_in_msat * 3 \ - self.alice_channel.get_next_fee(LOCAL), one_bitcoin_in_msat * 6)
def setUp(self): super().setUp() # Create a test channel which will be used for the duration of this # unittest. The channel will be funded evenly with Alice having 5 BTC, # and Bob having 5 BTC. self.alice_channel, self.bob_channel = create_test_channels() self.paymentPreimage = b"\x01" * 32 paymentHash = bitcoin.sha256(self.paymentPreimage) self.htlc_dict = { 'payment_hash': paymentHash, 'amount_msat': one_bitcoin_in_msat, 'cltv_expiry': 5, 'timestamp': 0, } # First Alice adds the outgoing HTLC to her local channel's state # update log. Then Alice sends this wire message over to Bob who adds # this htlc to his remote state update log. self.aliceHtlcIndex = self.alice_channel.add_htlc( self.htlc_dict).htlc_id self.assertNotEqual( list( self.alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED, 1).values()), []) before = self.bob_channel.balance_minus_outgoing_htlcs(REMOTE) beforeLocal = self.bob_channel.balance_minus_outgoing_htlcs(LOCAL) self.bobHtlcIndex = self.bob_channel.receive_htlc( self.htlc_dict).htlc_id self.htlc = self.bob_channel.hm.log[REMOTE]['adds'][0]
class TestPeer(ElectrumTestCase): @classmethod def setUpClass(cls): super().setUpClass() console_stderr_handler.setLevel(logging.DEBUG) def setUp(self): super().setUp() self.asyncio_loop, self._stop_loop, self._loop_thread = create_and_start_event_loop( ) def tearDown(self): super().tearDown() self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1) self._loop_thread.join(timeout=1) def prepare_peers(self, alice_channel, bob_channel): k1, k2 = keypair(), keypair() t1, t2 = transport_pair(k2, k1, alice_channel.name, bob_channel.name) q1, q2 = asyncio.Queue(), asyncio.Queue() w1 = MockLNWallet(k1, k2, alice_channel, tx_queue=q1) w2 = MockLNWallet(k2, k1, bob_channel, tx_queue=q2) p1 = Peer(w1, k1.pubkey, t1) p2 = Peer(w2, k2.pubkey, t2) w1.peer = p1 w2.peer = p2 # mark_open won't work if state is already OPEN. # so set it to FUNDED alice_channel._state = ChannelState.FUNDED bob_channel._state = ChannelState.FUNDED # this populates the channel graph: p1.mark_open(alice_channel) p2.mark_open(bob_channel) return p1, p2, w1, w2, q1, q2 @staticmethod def prepare_invoice( w2, # receiver *, amount_sat=100_000, ): amount_btc = amount_sat / Decimal(COIN) payment_preimage = os.urandom(32) RHASH = sha256(payment_preimage) info = PaymentInfo(RHASH, amount_sat, RECEIVED, PR_UNPAID) w2.save_preimage(RHASH, payment_preimage) w2.save_payment_info(info) lnaddr = LnAddr(paymenthash=RHASH, amount=amount_btc, tags=[('c', lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE), ('d', 'coffee')]) return lnencode(lnaddr, w2.node_keypair.privkey)
def test_part1(self): # Add an HTLC that will increase Bob's balance. This should succeed, # since Alice stays above her channel reserve, and Bob increases his # balance (while still being below his channel reserve). # # Resulting balances: # Alice: 4.5 # Bob: 5.0 paymentPreimage = b"\x01" * 32 paymentHash = bitcoin.sha256(paymentPreimage) htlc_dict = { 'payment_hash': paymentHash, 'amount_msat': int(.5 * one_bitcoin_in_msat), 'cltv_expiry': 5, 'timestamp': 0, } self.alice_channel.add_htlc(htlc_dict) self.bob_channel.receive_htlc(htlc_dict) # Force a state transition, making sure this HTLC is considered valid # even though the channel reserves are not met. force_state_transition(self.alice_channel, self.bob_channel) aliceSelfBalance = self.alice_channel.balance(LOCAL)\ - lnchannel.htlcsum(self.alice_channel.hm.htlcs_by_direction(LOCAL, SENT).values()) bobBalance = self.bob_channel.balance(REMOTE)\ - lnchannel.htlcsum(self.alice_channel.hm.htlcs_by_direction(REMOTE, SENT).values()) self.assertEqual(aliceSelfBalance, one_bitcoin_in_msat * 4.5) self.assertEqual(bobBalance, one_bitcoin_in_msat * 5) # Now let Bob try to add an HTLC. This should fail, since it will # decrease his balance, which is already below the channel reserve. # # Resulting balances: # Alice: 4.5 # Bob: 5.0 with self.assertRaises(lnutil.PaymentFailure): htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02') self.bob_channel.add_htlc(htlc_dict) with self.assertRaises(lnutil.RemoteMisbehaving): self.alice_channel.receive_htlc(htlc_dict)
def test_AddHTLCNegativeBalance(self): # the test in lnd doesn't set the fee to zero. # probably lnd subtracts commitment fee after deciding weather # an htlc can be added. so we set the fee to zero so that # the test can work. self.alice_to_bob_fee_update(0) force_state_transition(self.alice_channel, self.bob_channel) self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02') self.alice_channel.add_htlc(self.htlc_dict) self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x03') self.alice_channel.add_htlc(self.htlc_dict) # now there are three htlcs (one was in setUp) # Alice now has an available balance of 2 BTC. We'll add a new HTLC of # value 2 BTC, which should make Alice's balance negative (since she # has to pay a commitment fee). new = dict(self.htlc_dict) new['amount_msat'] *= 2.5 new['payment_hash'] = bitcoin.sha256(32 * b'\x04') with self.assertRaises(lnutil.PaymentFailure) as cm: self.alice_channel.add_htlc(new) self.assertIn('Not enough local balance', cm.exception.args[0])
def test_DesyncHTLCs(self): alice_channel, bob_channel = create_test_channels() self.assertEqual(499994624000, alice_channel.available_to_spend(LOCAL)) self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) 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_idx = alice_channel.add_htlc(htlc_dict).htlc_id bob_idx = bob_channel.receive_htlc(htlc_dict).htlc_id self.assertEqual(89993592000, alice_channel.available_to_spend(LOCAL)) self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) force_state_transition(alice_channel, bob_channel) bob_channel.fail_htlc(bob_idx) alice_channel.receive_fail_htlc(alice_idx, error_bytes=None) self.assertEqual(89993592000, alice_channel.available_to_spend(LOCAL)) self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) # Alice now has gotten all her original balance (5 BTC) back, however, # adding a new HTLC at this point SHOULD fail, since if she adds the # HTLC and signs the next state, Bob cannot assume she received the # FailHTLC, and must assume she doesn't have the necessary balance # available. # We try adding an HTLC of value 1 BTC, which should fail because the # balance is unavailable. htlc_dict = { 'payment_hash': paymentHash, 'amount_msat': one_bitcoin_in_msat, 'cltv_expiry': 5, 'timestamp': 0, } with self.assertRaises(lnutil.PaymentFailure): alice_channel.add_htlc(htlc_dict) # Now do a state transition, which will ACK the FailHTLC, making Alice # able to add the new HTLC. force_state_transition(alice_channel, bob_channel) self.assertEqual(499994624000, alice_channel.available_to_spend(LOCAL)) 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.RemoteMisbehaving): 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)
def test_DustLimit(self): alice_channel, bob_channel = create_test_channels() paymentPreimage = b"\x01" * 32 paymentHash = bitcoin.sha256(paymentPreimage) fee_per_kw = alice_channel.get_next_feerate(LOCAL) self.assertEqual(fee_per_kw, 6000) htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000) self.assertEqual(htlcAmt, 4478) htlc = { 'payment_hash': paymentHash, 'amount_msat': 1000 * htlcAmt, 'cltv_expiry': 5, # also in create_test_channels 'timestamp': 0, } old_values = [ x.value for x in bob_channel.get_latest_commitment(LOCAL).outputs() ] aliceHtlcIndex = alice_channel.add_htlc(htlc).htlc_id bobHtlcIndex = bob_channel.receive_htlc(htlc).htlc_id force_state_transition(alice_channel, bob_channel) alice_ctx = alice_channel.get_latest_commitment(LOCAL) bob_ctx = bob_channel.get_latest_commitment(LOCAL) new_values = [x.value for x in bob_ctx.outputs()] self.assertNotEqual(old_values, new_values) self.assertEqual(len(alice_ctx.outputs()), 3) self.assertEqual(len(bob_ctx.outputs()), 2) default_fee = calc_static_fee(0) self.assertEqual(bob_channel.get_next_fee(LOCAL), default_fee + htlcAmt) bob_channel.settle_htlc(paymentPreimage, bobHtlcIndex) alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex) force_state_transition(bob_channel, alice_channel) self.assertEqual( len(alice_channel.get_next_commitment(LOCAL).outputs()), 2) self.assertEqual(alice_channel.total_msat(SENT) // 1000, htlcAmt)
def test_concurrent_reversed_payment(self): self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02') self.htlc_dict['amount_msat'] += 1000 self.bob_channel.add_htlc(self.htlc_dict) self.alice_channel.receive_htlc(self.htlc_dict) self.assertEqual( len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 2) self.assertEqual( len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3) self.assertEqual( len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2) self.assertEqual( len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 3) self.alice_channel.receive_new_commitment( *self.bob_channel.sign_next_commitment()) self.assertEqual( len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 3) self.assertEqual( len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3) self.assertEqual( len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2) self.assertEqual( len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 3) self.alice_channel.revoke_current_commitment() self.assertEqual( len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 3) self.assertEqual( len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3) self.assertEqual( len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2) self.assertEqual( len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 4)