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_SimpleAddSettleWorkflow(self): alice_channel, bob_channel = self.alice_channel, self.bob_channel htlc = self.htlc alice_out = alice_channel.get_latest_commitment(LOCAL).outputs() short_idx, = [ idx for idx, x in enumerate(alice_out) if len(x.address) == 43 ] long_idx, = [ idx for idx, x in enumerate(alice_out) if len(x.address) == 63 ] self.assertLess(alice_out[long_idx].value, 5 * 10**8, alice_out) self.assertEqual(alice_out[short_idx].value, 5 * 10**8, alice_out) alice_out = alice_channel.get_latest_commitment(REMOTE).outputs() short_idx, = [ idx for idx, x in enumerate(alice_out) if len(x.address) == 43 ] long_idx, = [ idx for idx, x in enumerate(alice_out) if len(x.address) == 63 ] self.assertLess(alice_out[short_idx].value, 5 * 10**8) self.assertEqual(alice_out[long_idx].value, 5 * 10**8) self.assertTrue( alice_channel.signature_fits( alice_channel.get_latest_commitment(LOCAL))) self.assertNotEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), []) self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 0), []) self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc]) self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 0), []) self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 1), []) self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 0), []) self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 1), []) self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 0), []) self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 1), []) from electrum_ltc.lnutil import extract_ctn_from_tx_and_chan tx0 = str(alice_channel.force_close_tx()) self.assertEqual(alice_channel.get_oldest_unrevoked_ctn(LOCAL), 0) self.assertEqual( extract_ctn_from_tx_and_chan(alice_channel.force_close_tx(), alice_channel), 0) self.assertTrue( alice_channel.signature_fits( alice_channel.get_latest_commitment(LOCAL))) # Next alice commits this change by sending a signature message. Since # we expect the messages to be ordered, Bob will receive the HTLC we # just sent before he receives this signature, so the signature will # cover the HTLC. aliceSig, aliceHtlcSigs = alice_channel.sign_next_commitment() self.assertEqual(len(aliceHtlcSigs), 1, "alice should generate one htlc signature") self.assertTrue( alice_channel.signature_fits( alice_channel.get_latest_commitment(LOCAL))) self.assertEqual( next(iter(alice_channel.hm.get_htlcs_in_next_ctx(REMOTE)))[0], RECEIVED) self.assertEqual(alice_channel.hm.get_htlcs_in_next_ctx(REMOTE), bob_channel.hm.get_htlcs_in_next_ctx(LOCAL)) self.assertEqual( alice_channel.get_latest_commitment(REMOTE).outputs(), bob_channel.get_next_commitment(LOCAL).outputs()) # Bob receives this signature message, and checks that this covers the # state he has in his remote log. This includes the HTLC just sent # from Alice. self.assertTrue( bob_channel.signature_fits( bob_channel.get_latest_commitment(LOCAL))) bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs) self.assertTrue( bob_channel.signature_fits( bob_channel.get_latest_commitment(LOCAL))) self.assertEqual(bob_channel.get_oldest_unrevoked_ctn(REMOTE), 0) self.assertEqual(bob_channel.included_htlcs(LOCAL, RECEIVED, 1), [htlc]) # self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 0), []) self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc]) self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 0), []) self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 1), []) self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 0), []) self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 1), []) # Bob revokes his prior commitment given to him by Alice, since he now # has a valid signature for a newer commitment. bobRevocation = bob_channel.revoke_current_commitment() self.assertTrue( bob_channel.signature_fits( bob_channel.get_latest_commitment(LOCAL))) # Bob finally sends a signature for Alice's commitment transaction. # This signature will cover the HTLC, since Bob will first send the # revocation just created. The revocation also acks every received # HTLC up to the point where Alice sent her signature. bobSig, bobHtlcSigs = bob_channel.sign_next_commitment() self.assertTrue( bob_channel.signature_fits( bob_channel.get_latest_commitment(LOCAL))) self.assertEqual(len(bobHtlcSigs), 1) self.assertTrue( alice_channel.signature_fits( alice_channel.get_latest_commitment(LOCAL))) # so far: Alice added htlc, Alice signed. self.assertEqual( len(alice_channel.get_latest_commitment(LOCAL).outputs()), 2) self.assertEqual( len(alice_channel.get_next_commitment(LOCAL).outputs()), 2) self.assertEqual( len( alice_channel.get_oldest_unrevoked_commitment( REMOTE).outputs()), 2) self.assertEqual( len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3) # Alice then processes this revocation, sending her own revocation for # her prior commitment transaction. Alice shouldn't have any HTLCs to # forward since she's sending an outgoing HTLC. alice_channel.receive_revocation(bobRevocation) self.assertTrue( alice_channel.signature_fits( alice_channel.get_latest_commitment(LOCAL))) self.assertEqual( len(alice_channel.get_latest_commitment(LOCAL).outputs()), 2) self.assertEqual( len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3) self.assertEqual(len(alice_channel.force_close_tx().outputs()), 2) self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1) self.assertEqual( alice_channel.get_next_commitment(LOCAL).outputs(), bob_channel.get_latest_commitment(REMOTE).outputs()) # Alice then processes bob's signature, and since she just received # the revocation, she expect this signature to cover everything up to # the point where she sent her signature, including the HTLC. alice_channel.receive_new_commitment(bobSig, bobHtlcSigs) self.assertEqual( len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3) self.assertEqual(len(alice_channel.force_close_tx().outputs()), 3) self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1) tx1 = str(alice_channel.force_close_tx()) self.assertNotEqual(tx0, tx1) # Alice then generates a revocation for bob. aliceRevocation = alice_channel.revoke_current_commitment() tx2 = str(alice_channel.force_close_tx()) # since alice already has the signature for the next one, it doesn't change her force close tx (it was already the newer one) self.assertEqual(tx1, tx2) # Finally Bob processes Alice's revocation, at this point the new HTLC # is fully locked in within both commitment transactions. Bob should # also be able to forward an HTLC now that the HTLC has been locked # into both commitment transactions. self.assertTrue( bob_channel.signature_fits( bob_channel.get_latest_commitment(LOCAL))) bob_channel.receive_revocation(aliceRevocation) # At this point, both sides should have the proper number of satoshis # sent, and commitment height updated within their local channel # state. aliceSent = 0 bobSent = 0 self.assertEqual(alice_channel.total_msat(SENT), aliceSent, "alice has incorrect milli-satoshis sent") self.assertEqual(alice_channel.total_msat(RECEIVED), bobSent, "alice has incorrect milli-satoshis received") self.assertEqual(bob_channel.total_msat(SENT), bobSent, "bob has incorrect milli-satoshis sent") self.assertEqual(bob_channel.total_msat(RECEIVED), aliceSent, "bob has incorrect milli-satoshis received") self.assertEqual(bob_channel.get_oldest_unrevoked_ctn(LOCAL), 1, "bob has incorrect commitment height") self.assertEqual(alice_channel.get_oldest_unrevoked_ctn(LOCAL), 1, "alice has incorrect commitment height") # Both commitment transactions should have three outputs, and one of # them should be exactly the amount of the HTLC. alice_ctx = alice_channel.get_next_commitment(LOCAL) bob_ctx = bob_channel.get_next_commitment(LOCAL) self.assertEqual( len(alice_ctx.outputs()), 3, "alice should have three commitment outputs, instead have %s" % len(alice_ctx.outputs())) self.assertEqual( len(bob_ctx.outputs()), 3, "bob should have three commitment outputs, instead have %s" % len(bob_ctx.outputs())) self.assertOutputExistsByValue(alice_ctx, htlc.amount_msat // 1000) self.assertOutputExistsByValue(bob_ctx, htlc.amount_msat // 1000) # Now we'll repeat a similar exchange, this time with Bob settling the # HTLC once he learns of the preimage. preimage = self.paymentPreimage bob_channel.settle_htlc(preimage, self.bobHtlcIndex) alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex) tx3 = str(alice_channel.force_close_tx()) # just settling a htlc does not change her force close tx self.assertEqual(tx2, tx3) bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment() self.assertEqual(len(bobHtlcSigs2), 0) self.assertEqual( list( alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED).values()), [htlc]) self.assertEqual( alice_channel.included_htlcs( REMOTE, RECEIVED, alice_channel.get_oldest_unrevoked_ctn(REMOTE)), [htlc]) self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc]) self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 2), [htlc]) self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 1), [htlc]) self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 2), []) self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 1), []) self.assertEqual(alice_channel.included_htlcs(REMOTE, SENT, 2), []) self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 1), []) self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 2), []) alice_ctx_bob_version = bob_channel.get_latest_commitment( REMOTE).outputs() alice_ctx_alice_version = alice_channel.get_next_commitment( LOCAL).outputs() self.assertEqual(alice_ctx_alice_version, alice_ctx_bob_version) alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2) tx4 = str(alice_channel.force_close_tx()) self.assertNotEqual(tx3, tx4) self.assertEqual(alice_channel.balance(LOCAL), 500000000000) self.assertEqual(1, alice_channel.get_oldest_unrevoked_ctn(LOCAL)) self.assertEqual( len(alice_channel.included_htlcs(LOCAL, RECEIVED, ctn=2)), 0) aliceRevocation2 = alice_channel.revoke_current_commitment() aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment() self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures") self.assertEqual( len(bob_channel.get_latest_commitment(LOCAL).outputs()), 3) bob_channel.receive_revocation(aliceRevocation2) bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2) bobRevocation2 = bob_channel.revoke_current_commitment() received = lnchannel.htlcsum( bob_channel.hm.received_in_ctn(bob_channel.get_latest_ctn(LOCAL))) self.assertEqual(one_bitcoin_in_msat, received) alice_channel.receive_revocation(bobRevocation2) # At this point, Bob should have 6 BTC settled, with Alice still having # 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel # should show 1 BTC received. They should also be at commitment height # two, with the revocation window extended by 1 (5). mSatTransferred = one_bitcoin_in_msat self.assertEqual(alice_channel.total_msat(SENT), mSatTransferred, "alice satoshis sent incorrect") self.assertEqual(alice_channel.total_msat(RECEIVED), 0, "alice satoshis received incorrect") self.assertEqual(bob_channel.total_msat(RECEIVED), mSatTransferred, "bob satoshis received incorrect") self.assertEqual(bob_channel.total_msat(SENT), 0, "bob satoshis sent incorrect") self.assertEqual(bob_channel.get_latest_ctn(LOCAL), 2, "bob has incorrect commitment height") self.assertEqual(alice_channel.get_latest_ctn(LOCAL), 2, "alice has incorrect commitment height") alice_channel.update_fee(100000, True) alice_outputs = alice_channel.get_next_commitment(REMOTE).outputs() old_outputs = bob_channel.get_next_commitment(LOCAL).outputs() bob_channel.update_fee(100000, False) new_outputs = bob_channel.get_next_commitment(LOCAL).outputs() self.assertNotEqual(old_outputs, new_outputs) self.assertEqual(alice_outputs, new_outputs) tx5 = str(alice_channel.force_close_tx()) # sending a fee update does not change her force close tx self.assertEqual(tx4, tx5) force_state_transition(alice_channel, bob_channel) tx6 = str(alice_channel.force_close_tx()) self.assertNotEqual(tx5, tx6) self.htlc_dict['amount_msat'] *= 5 bob_index = bob_channel.add_htlc(self.htlc_dict).htlc_id alice_index = alice_channel.receive_htlc(self.htlc_dict).htlc_id force_state_transition(bob_channel, alice_channel) alice_channel.settle_htlc(self.paymentPreimage, alice_index) bob_channel.receive_htlc_settle(self.paymentPreimage, bob_index) force_state_transition(alice_channel, bob_channel) self.assertEqual(alice_channel.total_msat(SENT), one_bitcoin_in_msat, "alice satoshis sent incorrect") self.assertEqual(alice_channel.total_msat(RECEIVED), 5 * one_bitcoin_in_msat, "alice satoshis received incorrect") self.assertEqual(bob_channel.total_msat(RECEIVED), one_bitcoin_in_msat, "bob satoshis received incorrect") self.assertEqual(bob_channel.total_msat(SENT), 5 * one_bitcoin_in_msat, "bob satoshis sent incorrect")