1
0

tests: lnchannel: rewrite create_test_channels to use LNWallet

This commit is contained in:
SomberNight
2025-12-20 16:43:10 +00:00
parent 17f41044d5
commit dfeb9918d8
4 changed files with 135 additions and 122 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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