diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index f4d19dd9c..e1cb43dbb 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -1022,8 +1022,8 @@ class Channel(AbstractChannel): elif self.is_static_remotekey_enabled(): our_payment_pubkey = self.config[LOCAL].payment_basepoint.pubkey addr = make_commitment_output_to_remote_address(our_payment_pubkey, has_anchors=self.has_anchors()) - #if self.lnworker: - # assert self.lnworker.wallet.is_mine(addr) # FIXME xxxxx chan should be deterministic. NEEDS to be fixed before merge + if self.lnworker: + assert self.lnworker.wallet.is_mine(addr) return addr def has_anchors(self) -> bool: diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 6f17ebc38..259b4e83f 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -1611,8 +1611,10 @@ class LNWallet(Logger): channel_type: ChannelType, multisig_funding_keypair: Optional[Keypair], # if None, will get derived from channel_seed peer_features: LnFeatures, + channel_seed: bytes = None, ) -> LocalConfig: - channel_seed = os.urandom(32) + if channel_seed is None: + channel_seed = os.urandom(32) initial_msat = funding_sat * 1000 - push_msat if initiator == LOCAL else push_msat # sending empty bytes as the upfront_shutdown_script will give us the diff --git a/tests/test_lnchannel.py b/tests/test_lnchannel.py index 0b3683345..7a81dcd77 100644 --- a/tests/test_lnchannel.py +++ b/tests/test_lnchannel.py @@ -37,10 +37,11 @@ from electrum import bitcoin from electrum import lnpeer from electrum import lnchannel from electrum import lnutil -from electrum import bip32 as bip32_utils from electrum.crypto import privkey_to_pubkey -from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED, UpdateAddHtlc -from electrum.lnutil import effective_htlc_tx_weight +from electrum.lnutil import ( + SENT, LOCAL, REMOTE, RECEIVED, UpdateAddHtlc, LnFeatures, secret_to_pubkey, ChannelType, + effective_htlc_tx_weight, LocalConfig, RemoteConfig, OnlyPubkeyKeypair, +) from electrum.logging import console_stderr_handler from electrum.lnchannel import ChannelState, Channel from electrum.json_db import StoredDict @@ -55,63 +56,53 @@ if TYPE_CHECKING: one_bitcoin_in_msat = bitcoin.COIN * 1000 -def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator, - local_amount, remote_amount, privkeys, other_pubkeys, - seed, cur, nex, other_node_id, l_dust, r_dust, l_csv, - r_csv, anchor_outputs, local_max_inflight, remote_max_inflight, - max_accepted_htlcs): - #assert local_amount > 0 - #assert remote_amount > 0 +def _convert_to_rconfig_from_lconfig(lconfig: LocalConfig) -> RemoteConfig: + """converts Alice's local config to Bob's remote config (neutering private keys, etc)""" + ctn = 0 + pcp_secret = lnutil.get_per_commitment_secret_from_seed( + lconfig.per_commitment_secret_seed, + lnutil.RevocationStore.START_INDEX - ctn) + pcp_point = secret_to_pubkey(int.from_bytes(pcp_secret, 'big')) + rconfig = RemoteConfig( + payment_basepoint=OnlyPubkeyKeypair(pubkey=lconfig.payment_basepoint.pubkey), + multisig_key=OnlyPubkeyKeypair(pubkey=lconfig.multisig_key.pubkey), + htlc_basepoint=OnlyPubkeyKeypair(pubkey=lconfig.htlc_basepoint.pubkey), + delayed_basepoint=OnlyPubkeyKeypair(pubkey=lconfig.delayed_basepoint.pubkey), + revocation_basepoint=OnlyPubkeyKeypair(pubkey=lconfig.revocation_basepoint.pubkey), + to_self_delay=lconfig.to_self_delay, + dust_limit_sat=lconfig.dust_limit_sat, + max_htlc_value_in_flight_msat=lconfig.max_htlc_value_in_flight_msat, + max_accepted_htlcs=lconfig.max_accepted_htlcs, + initial_msat=lconfig.initial_msat, + reserve_sat=lconfig.reserve_sat, + htlc_minimum_msat=lconfig.htlc_minimum_msat, + upfront_shutdown_script=lconfig.upfront_shutdown_script, + announcement_node_sig=lconfig.announcement_node_sig, + announcement_bitcoin_sig=lconfig.announcement_bitcoin_sig, + next_per_commitment_point=pcp_point, + current_per_commitment_point=None, + ) + return rconfig + +def create_channel_state( + *, + funding_txid: str, + funding_index: int, + funding_sat: int, + is_initiator: bool, + other_node_id: bytes, + channel_type: ChannelType, + local_config: LocalConfig, + remote_config: RemoteConfig, +): channel_id, _ = lnpeer.channel_id_from_funding_tx(funding_txid, funding_index) - channel_type = lnutil.ChannelType.OPTION_STATIC_REMOTEKEY - if anchor_outputs: - channel_type |= lnutil.ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX state = { "channel_id":channel_id.hex(), "short_channel_id":channel_id[:8], "funding_outpoint":lnpeer.Outpoint(funding_txid, funding_index), - "remote_config":lnpeer.RemoteConfig( - payment_basepoint=other_pubkeys[0], - multisig_key=other_pubkeys[1], - htlc_basepoint=other_pubkeys[2], - delayed_basepoint=other_pubkeys[3], - revocation_basepoint=other_pubkeys[4], - to_self_delay=r_csv, - dust_limit_sat=r_dust, - max_htlc_value_in_flight_msat=remote_max_inflight, - max_accepted_htlcs=max_accepted_htlcs, - initial_msat=remote_amount, - reserve_sat=0, - htlc_minimum_msat=1, - next_per_commitment_point=nex, - current_per_commitment_point=cur, - upfront_shutdown_script=b'', - announcement_node_sig=b'', - announcement_bitcoin_sig=b'', - ), - "local_config":lnpeer.LocalConfig( - channel_seed = None, - payment_basepoint=privkeys[0], - multisig_key=privkeys[1], - htlc_basepoint=privkeys[2], - delayed_basepoint=privkeys[3], - revocation_basepoint=privkeys[4], - to_self_delay=l_csv, - dust_limit_sat=l_dust, - max_htlc_value_in_flight_msat=local_max_inflight, - max_accepted_htlcs=max_accepted_htlcs, - initial_msat=local_amount, - reserve_sat=0, - per_commitment_secret_seed=seed, - funding_locked_received=True, - current_commitment_signature=None, - current_htlc_signatures=None, - htlc_minimum_msat=1, - upfront_shutdown_script=b'', - announcement_node_sig=b'', - announcement_bitcoin_sig=b'', - ), + "remote_config": remote_config, + "local_config": local_config, "constraints":lnpeer.ChannelConstraints( flags=0, capacity=funding_sat, @@ -130,15 +121,6 @@ def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator, return StoredDict(state, None) -@lru_cache() -def bip32(sequence): - node = bip32_utils.BIP32Node.from_rootseed(b"9dk", xtype='standard').subkey_at_private_derivation(sequence) - k = node.eckey.get_secret_bytes() - assert len(k) == 32 - assert type(k) is bytes - return k - - def create_test_channels( *, alice_lnwallet: 'MockLNWallet' = None, @@ -147,7 +129,7 @@ def create_test_channels( local_msat=None, remote_msat=None, random_seed=None, - anchor_outputs=False, + anchor_outputs: bool = False, local_max_inflight=None, remote_max_inflight=None, max_accepted_htlcs=5, @@ -155,50 +137,75 @@ def create_test_channels( if random_seed is None: # needed for deterministic randomness random_seed = os.urandom(32) random_gen = PRNG(random_seed) - if alice_lnwallet or bob_lnwallet: - assert alice_lnwallet and bob_lnwallet, "either both or neither lnwallet must be set" - alice_name = alice_lnwallet.name - bob_name = bob_lnwallet.name - alice_pubkey = alice_lnwallet.node_keypair.pubkey - bob_pubkey = bob_lnwallet.node_keypair.pubkey - else: - alice_name = "alice" - bob_name = "bob", - alice_pubkey = b"\x01" * 33 - bob_pubkey = b"\x02" * 33 - funding_txid = binascii.hexlify(random_gen.get_bytes(32)).decode("ascii") + if alice_lnwallet is None: + from .test_lnpeer import create_mock_lnwallet + alice_lnwallet = create_mock_lnwallet(name="alice", has_anchors=anchor_outputs) + if bob_lnwallet is None: + from .test_lnpeer import create_mock_lnwallet + bob_lnwallet = create_mock_lnwallet(name="bob", has_anchors=anchor_outputs) + alice_name = alice_lnwallet.name + bob_name = bob_lnwallet.name + alice_pubkey = alice_lnwallet.node_keypair.pubkey + bob_pubkey = bob_lnwallet.node_keypair.pubkey + funding_txid = random_gen.get_bytes(32).hex() funding_index = 0 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) + local_msat = local_msat if local_msat is not None else (funding_sat * 1000 // 2) + remote_msat = remote_msat if remote_msat is not None else (funding_sat * 1000 // 2) local_max_inflight = funding_sat * 1000 if local_max_inflight is None else local_max_inflight remote_max_inflight = funding_sat * 1000 if remote_max_inflight is None else remote_max_inflight - 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(privkey_to_pubkey(x), x) for x in alice_raw] # TODO make it depend on alice_lnwallet - bob_privkeys = [lnutil.Keypair(privkey_to_pubkey(x), x) for x in bob_raw] - alice_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys] - bob_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys] - alice_seed = random_gen.get_bytes(32) # TODO make it depend on alice_lnwallet - bob_seed = random_gen.get_bytes(32) + for config in [alice_lnwallet.config, bob_lnwallet.config]: + config.LIGHTNING_MAX_FUNDING_SAT = max(config.LIGHTNING_MAX_FUNDING_SAT, funding_sat) - alice_first = lnutil.secret_to_pubkey( - int.from_bytes(lnutil.get_per_commitment_secret_from_seed( - alice_seed, lnutil.RevocationStore.START_INDEX), "big")) - bob_first = lnutil.secret_to_pubkey( - int.from_bytes(lnutil.get_per_commitment_secret_from_seed( - bob_seed, lnutil.RevocationStore.START_INDEX), "big")) + peer_features = alice_lnwallet.features | LnFeatures.OPTION_SUPPORT_LARGE_CHANNEL_OPT + channel_type = ChannelType.OPTION_STATIC_REMOTEKEY + if anchor_outputs: + channel_type |= ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX + # create alice's local config + alice_lconfig = alice_lnwallet.make_local_config_for_new_channel( + funding_sat=funding_sat, + push_msat=remote_msat, + initiator=LOCAL, + channel_type=channel_type, + multisig_funding_keypair=None, + peer_features=peer_features, + channel_seed=random_gen.get_bytes(32), + ) + alice_lconfig.funding_locked_received = True + alice_lconfig.dust_limit_sat = 200 + alice_lconfig.to_self_delay = 5 + alice_lconfig.reserve_sat = 0 + alice_lconfig.max_accepted_htlcs = max_accepted_htlcs + alice_lconfig.max_htlc_value_in_flight_msat = local_max_inflight + # create bob's local config + bob_lconfig = bob_lnwallet.make_local_config_for_new_channel( + funding_sat=funding_sat, + push_msat=remote_msat, + initiator=REMOTE, + channel_type=channel_type, + multisig_funding_keypair=None, + peer_features=peer_features, + channel_seed=random_gen.get_bytes(32), + ) + bob_lconfig.funding_locked_received = True + bob_lconfig.dust_limit_sat = 1300 + bob_lconfig.to_self_delay = 4 + bob_lconfig.reserve_sat = 0 + bob_lconfig.max_accepted_htlcs = max_accepted_htlcs + bob_lconfig.max_htlc_value_in_flight_msat = remote_max_inflight alice, bob = ( lnchannel.Channel( create_channel_state( - funding_txid, funding_index, funding_sat, True, local_amount, - remote_amount, alice_privkeys, bob_pubkeys, alice_seed, None, - bob_first, other_node_id=bob_pubkey, l_dust=200, r_dust=1300, - l_csv=5, r_csv=4, anchor_outputs=anchor_outputs, - local_max_inflight=local_max_inflight, remote_max_inflight=remote_max_inflight, - max_accepted_htlcs=max_accepted_htlcs, + funding_txid=funding_txid, + funding_index=funding_index, + funding_sat=funding_sat, + is_initiator=True, + other_node_id=bob_pubkey, + channel_type=channel_type, + local_config=alice_lconfig, + remote_config=_convert_to_rconfig_from_lconfig(bob_lconfig), ), name=f"{alice_name}->{bob_name}", initial_feerate=feerate, @@ -206,12 +213,14 @@ def create_test_channels( ), lnchannel.Channel( create_channel_state( - funding_txid, funding_index, funding_sat, False, remote_amount, - local_amount, bob_privkeys, alice_pubkeys, bob_seed, None, - alice_first, other_node_id=alice_pubkey, l_dust=1300, r_dust=200, - l_csv=4, r_csv=5, anchor_outputs=anchor_outputs, - local_max_inflight=remote_max_inflight, remote_max_inflight=local_max_inflight, - max_accepted_htlcs=max_accepted_htlcs, + funding_txid=funding_txid, + funding_index=funding_index, + funding_sat=funding_sat, + is_initiator=False, + other_node_id=alice_pubkey, + channel_type=channel_type, + local_config=bob_lconfig, + remote_config=_convert_to_rconfig_from_lconfig(alice_lconfig), ), name=f"{bob_name}->{alice_name}", initial_feerate=feerate, @@ -235,11 +244,13 @@ def create_test_channels( assert len(a_htlc_sigs) == 0 assert len(b_htlc_sigs) == 0 - alice.open_with_first_pcp(bob_first, sig_from_bob) - bob.open_with_first_pcp(alice_first, sig_from_alice) + alice.open_with_first_pcp(alice.config[REMOTE].next_per_commitment_point, sig_from_bob) + bob.open_with_first_pcp(bob.config[REMOTE].next_per_commitment_point, sig_from_alice) - alice_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX - 1), "big")) - bob_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big")) + alice_second = lnutil.secret_to_pubkey(int.from_bytes( + lnutil.get_per_commitment_secret_from_seed(alice.config[LOCAL].per_commitment_secret_seed, lnutil.RevocationStore.START_INDEX - 1), "big")) + bob_second = lnutil.secret_to_pubkey(int.from_bytes( + lnutil.get_per_commitment_secret_from_seed(bob.config[LOCAL].per_commitment_secret_seed, lnutil.RevocationStore.START_INDEX - 1), "big")) # from funding_locked: alice.config[REMOTE].next_per_commitment_point = bob_second @@ -256,13 +267,13 @@ class TestFee(ElectrumTestCase): test https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2 """ - def test_fee(self): + async def test_fee(self): alice_channel, bob_channel = create_test_channels( feerate=253, - local_msat=10000000000, - remote_msat=5000000000, + local_msat=10_000_000_000, + remote_msat=5_000_000_000, anchor_outputs=self.TEST_ANCHOR_CHANNELS) - expected_value = 9999056 if self.TEST_ANCHOR_CHANNELS else 9999817 + expected_value = 9_999_056 if self.TEST_ANCHOR_CHANNELS else 9_999_817 self.assertIn(expected_value, [x.value for x in alice_channel.get_latest_commitment(LOCAL).outputs()]) @@ -284,8 +295,8 @@ class TestChannel(ElectrumTestCase): super().setUpClass() console_stderr_handler.setLevel(logging.DEBUG) - def setUp(self): - super().setUp() + async def asyncSetUp(self): + await super().asyncSetUp() # 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. @@ -341,7 +352,7 @@ class TestChannel(ElectrumTestCase): self.assertNumberNonAnchorOutputs(2, self.alice_channel.get_latest_commitment(REMOTE)) self.assertNumberNonAnchorOutputs(4, self.alice_channel.get_next_commitment(REMOTE)) - def test_SimpleAddSettleWorkflow(self): + async def test_SimpleAddSettleWorkflow(self): alice_channel, bob_channel = self.alice_channel, self.bob_channel htlc = self.htlc @@ -774,7 +785,7 @@ class TestChannelAnchors(TestChannel): class TestAvailableToSpend(ElectrumTestCase): - def test_DesyncHTLCs(self): + async def test_DesyncHTLCs(self): alice_channel, bob_channel = create_test_channels(anchor_outputs=self.TEST_ANCHOR_CHANNELS) self.assertEqual(499986152000 if not alice_channel.has_anchors() else 499980692000, alice_channel.available_to_spend(LOCAL)) self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) @@ -820,7 +831,7 @@ class TestAvailableToSpend(ElectrumTestCase): self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) alice_channel.add_htlc(htlc) - def test_single_payment(self): + async def test_single_payment(self): alice_channel, bob_channel = create_test_channels( anchor_outputs=self.TEST_ANCHOR_CHANNELS, local_msat=4000000000, @@ -1016,7 +1027,7 @@ class TestChanReserveAnchors(TestChanReserve): class TestDust(ElectrumTestCase): - def test_DustLimit(self): + async def test_DustLimit(self): """Test that addition of an HTLC below the dust limit changes the balances.""" alice_channel, bob_channel = create_test_channels(anchor_outputs=self.TEST_ANCHOR_CHANNELS) dust_limit_alice = alice_channel.config[LOCAL].dust_limit_sat diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index 134c6a803..0b14a6a29 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -138,7 +138,7 @@ class MockStandardWallet(Standard_Wallet): return passphrase # lol, super secure name def create_mock_lnwallet(*, name, has_anchors) -> 'MockLNWallet': - _user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-") + _user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-") # TODO clean-up after?? config = SimpleConfig({}, read_user_dir_function=lambda: _user_dir) config.ENABLE_ANCHOR_CHANNELS = has_anchors config.INITIAL_TRAMPOLINE_FEE_LEVEL = 0