unit tests: tests for both anchors and old ctx types
* in test_lnutil, patch htlc weight to pass original anchor commitment test vectors * activate tests for both commitment types
This commit is contained in:
@@ -28,6 +28,7 @@ class ElectrumTestCase(unittest.IsolatedAsyncioTestCase, Logger):
|
||||
"""Base class for our unit tests."""
|
||||
|
||||
TESTNET = False
|
||||
TEST_ANCHOR_CHANNELS = False
|
||||
# maxDiff = None # for debugging
|
||||
|
||||
# some unit tests are modifying globals... so we run sequentially:
|
||||
|
||||
@@ -43,17 +43,16 @@ from electrum.coinchooser import PRNG
|
||||
|
||||
from . import ElectrumTestCase
|
||||
|
||||
TEST_ANCHOR_CHANNELS = True
|
||||
|
||||
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=TEST_ANCHOR_CHANNELS):
|
||||
r_csv, anchor_outputs):
|
||||
#assert local_amount > 0
|
||||
#assert remote_amount > 0
|
||||
|
||||
channel_id, _ = lnpeer.channel_id_from_funding_tx(funding_txid, funding_index)
|
||||
channel_type = lnutil.ChannelType.OPTION_STATIC_REMOTEKEY
|
||||
if anchor_outputs:
|
||||
@@ -130,7 +129,7 @@ def bip32(sequence):
|
||||
def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None,
|
||||
alice_name="alice", bob_name="bob",
|
||||
alice_pubkey=b"\x01"*33, bob_pubkey=b"\x02"*33, random_seed=None,
|
||||
anchor_outputs=TEST_ANCHOR_CHANNELS):
|
||||
anchor_outputs=False):
|
||||
if random_seed is None: # needed for deterministic randomness
|
||||
random_seed = os.urandom(32)
|
||||
random_gen = PRNG(random_seed)
|
||||
@@ -214,10 +213,12 @@ class TestFee(ElectrumTestCase):
|
||||
https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2
|
||||
"""
|
||||
def test_fee(self):
|
||||
alice_channel, bob_channel = create_test_channels(feerate=253,
|
||||
local_msat=10000000000,
|
||||
remote_msat=5000000000, anchor_outputs=TEST_ANCHOR_CHANNELS)
|
||||
expected_value = 9999056 if TEST_ANCHOR_CHANNELS else 9999817
|
||||
alice_channel, bob_channel = create_test_channels(
|
||||
feerate=253,
|
||||
local_msat=10000000000,
|
||||
remote_msat=5000000000,
|
||||
anchor_outputs=self.TEST_ANCHOR_CHANNELS)
|
||||
expected_value = 9999056 if self.TEST_ANCHOR_CHANNELS else 9999817
|
||||
self.assertIn(expected_value, [x.value for x in alice_channel.get_latest_commitment(LOCAL).outputs()])
|
||||
|
||||
class TestChannel(ElectrumTestCase):
|
||||
@@ -231,7 +232,7 @@ class TestChannel(ElectrumTestCase):
|
||||
self.assertFalse()
|
||||
|
||||
def assertNumberNonAnchorOutputs(self, number, tx):
|
||||
self.assertEqual(number, len(tx.outputs()) - (2 if TEST_ANCHOR_CHANNELS else 0))
|
||||
self.assertEqual(number, len(tx.outputs()) - (2 if self.TEST_ANCHOR_CHANNELS else 0))
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -243,7 +244,7 @@ class TestChannel(ElectrumTestCase):
|
||||
# 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(anchor_outputs=TEST_ANCHOR_CHANNELS)
|
||||
self.alice_channel, self.bob_channel = create_test_channels(anchor_outputs=self.TEST_ANCHOR_CHANNELS)
|
||||
|
||||
self.paymentPreimage = b"\x01" * 32
|
||||
paymentHash = bitcoin.sha256(self.paymentPreimage)
|
||||
@@ -566,7 +567,6 @@ class TestChannel(ElectrumTestCase):
|
||||
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")
|
||||
|
||||
|
||||
def alice_to_bob_fee_update(self, fee=1111):
|
||||
aoldctx = self.alice_channel.get_next_commitment(REMOTE).outputs()
|
||||
self.alice_channel.update_fee(fee, True)
|
||||
@@ -670,9 +670,13 @@ class TestChannel(ElectrumTestCase):
|
||||
self.assertIn('Not enough local balance', cm.exception.args[0])
|
||||
|
||||
|
||||
class TestChannelAnchors(TestChannel):
|
||||
TEST_ANCHOR_CHANNELS = True
|
||||
|
||||
|
||||
class TestAvailableToSpend(ElectrumTestCase):
|
||||
def test_DesyncHTLCs(self):
|
||||
alice_channel, bob_channel = create_test_channels()
|
||||
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))
|
||||
|
||||
@@ -718,9 +722,13 @@ class TestAvailableToSpend(ElectrumTestCase):
|
||||
alice_channel.add_htlc(htlc)
|
||||
|
||||
|
||||
class TestAvailableToSpendAnchors(TestAvailableToSpend):
|
||||
TEST_ANCHOR_CHANNELS = True
|
||||
|
||||
|
||||
class TestChanReserve(ElectrumTestCase):
|
||||
def setUp(self):
|
||||
alice_channel, bob_channel = create_test_channels()
|
||||
alice_channel, bob_channel = create_test_channels(anchor_outputs=False)
|
||||
alice_min_reserve = int(.5 * one_bitcoin_in_msat // 1000)
|
||||
# We set Bob's channel reserve to a value that is larger than
|
||||
# his current balance in the channel. This will ensure that
|
||||
@@ -847,10 +855,15 @@ class TestChanReserve(ElectrumTestCase):
|
||||
self.assertEqual(self.alice_channel.available_to_spend(REMOTE), amt2)
|
||||
self.assertEqual(self.bob_channel.available_to_spend(LOCAL), amt2)
|
||||
|
||||
|
||||
class TestChanReserveAnchors(TestChanReserve):
|
||||
TEST_ANCHOR_CHANNELS = True
|
||||
|
||||
|
||||
class TestDust(ElectrumTestCase):
|
||||
def test_DustLimit(self):
|
||||
"""Test that addition of an HTLC below the dust limit changes the balances."""
|
||||
alice_channel, bob_channel = create_test_channels()
|
||||
alice_channel, bob_channel = create_test_channels(anchor_outputs=self.TEST_ANCHOR_CHANNELS)
|
||||
dust_limit_alice = alice_channel.config[LOCAL].dust_limit_sat
|
||||
dust_limit_bob = bob_channel.config[LOCAL].dust_limit_sat
|
||||
self.assertLess(dust_limit_alice, dust_limit_bob)
|
||||
@@ -860,7 +873,7 @@ class TestDust(ElectrumTestCase):
|
||||
paymentPreimage = b"\x01" * 32
|
||||
paymentHash = bitcoin.sha256(paymentPreimage)
|
||||
fee_per_kw = alice_channel.get_next_feerate(LOCAL)
|
||||
success_weight = effective_htlc_tx_weight(success=True, has_anchors=TEST_ANCHOR_CHANNELS)
|
||||
success_weight = effective_htlc_tx_weight(success=True, has_anchors=self.TEST_ANCHOR_CHANNELS)
|
||||
# we put a single sat less into the htlc than bob can afford
|
||||
# to pay for his htlc success transaction
|
||||
below_dust_for_bob = dust_limit_bob - 1
|
||||
@@ -882,13 +895,13 @@ class TestDust(ElectrumTestCase):
|
||||
self.assertNotEqual(bobs_original_outputs, bobs_second_outputs)
|
||||
# the htlc appears as an output in alice's ctx, as she has a lower
|
||||
# dust limit (also because her timeout tx costs less)
|
||||
self.assertEqual(3, len(alice_ctx.outputs()) - (2 if TEST_ANCHOR_CHANNELS else 0))
|
||||
self.assertEqual(3, len(alice_ctx.outputs()) - (2 if self.TEST_ANCHOR_CHANNELS else 0))
|
||||
# htlc in bob's case goes to miner fees
|
||||
self.assertEqual(2, len(bob_ctx.outputs()) - (2 if TEST_ANCHOR_CHANNELS else 0))
|
||||
self.assertEqual(2, len(bob_ctx.outputs()) - (2 if self.TEST_ANCHOR_CHANNELS else 0))
|
||||
self.assertEqual(htlc_amt, sum(bobs_original_outputs) - sum(bobs_second_outputs))
|
||||
empty_ctx_fee = lnutil.calc_fees_for_commitment_tx(
|
||||
num_htlcs=0, feerate=fee_per_kw, is_local_initiator=True,
|
||||
round_to_sat=True, has_anchors=TEST_ANCHOR_CHANNELS)[LOCAL] // 1000
|
||||
round_to_sat=True, has_anchors=self.TEST_ANCHOR_CHANNELS)[LOCAL] // 1000
|
||||
self.assertEqual(empty_ctx_fee + htlc_amt, bob_channel.get_next_fee(LOCAL))
|
||||
|
||||
bob_channel.settle_htlc(paymentPreimage, bob_htlc_id)
|
||||
@@ -899,12 +912,16 @@ class TestDust(ElectrumTestCase):
|
||||
# htlc is added back into the balance
|
||||
self.assertEqual(sum(bobs_original_outputs), sum(bobs_third_outputs))
|
||||
# balance shifts in bob's direction after settlement
|
||||
self.assertEqual(htlc_amt, bobs_third_outputs[1 + (2 if TEST_ANCHOR_CHANNELS else 0)] - bobs_original_outputs[1 + (2 if TEST_ANCHOR_CHANNELS else 0)])
|
||||
self.assertEqual(2, len(alice_channel.get_next_commitment(LOCAL).outputs()) - (2 if TEST_ANCHOR_CHANNELS else 0))
|
||||
self.assertEqual(2, len(bob_channel.get_next_commitment(LOCAL).outputs()) - (2 if TEST_ANCHOR_CHANNELS else 0))
|
||||
self.assertEqual(htlc_amt, bobs_third_outputs[1 + (2 if self.TEST_ANCHOR_CHANNELS else 0)] - bobs_original_outputs[1 + (2 if self.TEST_ANCHOR_CHANNELS else 0)])
|
||||
self.assertEqual(2, len(alice_channel.get_next_commitment(LOCAL).outputs()) - (2 if self.TEST_ANCHOR_CHANNELS else 0))
|
||||
self.assertEqual(2, len(bob_channel.get_next_commitment(LOCAL).outputs()) - (2 if self.TEST_ANCHOR_CHANNELS else 0))
|
||||
self.assertEqual(htlc_amt, alice_channel.total_msat(SENT) // 1000)
|
||||
|
||||
|
||||
class TestDustAnchors(TestDust):
|
||||
TEST_ANCHOR_CHANNELS = True
|
||||
|
||||
|
||||
def force_state_transition(chanA, chanB):
|
||||
chanB.receive_new_commitment(*chanA.sign_next_commitment())
|
||||
rev = chanB.revoke_current_commitment()
|
||||
|
||||
@@ -45,15 +45,12 @@ from electrum.invoices import PR_PAID, PR_UNPAID
|
||||
from electrum.interface import GracefulDisconnect
|
||||
from electrum.simple_config import SimpleConfig
|
||||
|
||||
from .test_lnchannel import create_test_channels as create_test_channels_anchors
|
||||
|
||||
|
||||
from .test_lnchannel import create_test_channels
|
||||
from .test_bitcoin import needs_test_with_all_chacha20_implementations
|
||||
from . import ElectrumTestCase
|
||||
|
||||
TEST_ANCHOR_CHANNELS = True
|
||||
|
||||
|
||||
def create_test_channels(*args, **kwargs):
|
||||
return create_test_channels_anchors(*args, **kwargs, anchor_outputs=TEST_ANCHOR_CHANNELS)
|
||||
|
||||
|
||||
def keypair():
|
||||
priv = ECPrivkey.generate_random_key().get_secret_bytes()
|
||||
@@ -155,7 +152,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
MPP_SPLIT_PART_FRACTION = 1 # this disables the forced splitting
|
||||
MPP_SPLIT_PART_MINAMT_MSAT = 5_000_000
|
||||
|
||||
def __init__(self, *, local_keypair: Keypair, chans: Iterable['Channel'], tx_queue, name):
|
||||
def __init__(self, *, local_keypair: Keypair, chans: Iterable['Channel'], tx_queue, name, has_anchors):
|
||||
self.name = name
|
||||
Logger.__init__(self)
|
||||
NetworkRetryManager.__init__(self, max_retry_delay_normal=1, init_retry_delay_normal=1)
|
||||
@@ -181,7 +178,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
self.features |= LnFeatures.OPTION_CHANNEL_TYPE_OPT
|
||||
self.features |= LnFeatures.OPTION_SCID_ALIAS_OPT
|
||||
self.features |= LnFeatures.OPTION_STATIC_REMOTEKEY_OPT
|
||||
self.config.ENABLE_ANCHOR_CHANNELS = TEST_ANCHOR_CHANNELS
|
||||
self.config.ENABLE_ANCHOR_CHANNELS = has_anchors
|
||||
self.pending_payments = defaultdict(asyncio.Future)
|
||||
for chan in chans:
|
||||
chan.lnworker = self
|
||||
@@ -559,8 +556,8 @@ class TestPeerDirect(TestPeer):
|
||||
bob_channel.storage['node_id'] = bob_channel.node_id
|
||||
t1, t2 = transport_pair(k1, k2, alice_channel.name, bob_channel.name)
|
||||
q1, q2 = asyncio.Queue(), asyncio.Queue()
|
||||
w1 = MockLNWallet(local_keypair=k1, chans=[alice_channel], tx_queue=q1, name=bob_channel.name)
|
||||
w2 = MockLNWallet(local_keypair=k2, chans=[bob_channel], tx_queue=q2, name=alice_channel.name)
|
||||
w1 = MockLNWallet(local_keypair=k1, chans=[alice_channel], tx_queue=q1, name=bob_channel.name, has_anchors=self.TEST_ANCHOR_CHANNELS)
|
||||
w2 = MockLNWallet(local_keypair=k2, chans=[bob_channel], tx_queue=q2, name=alice_channel.name, has_anchors=self.TEST_ANCHOR_CHANNELS)
|
||||
self._lnworkers_created.extend([w1, w2])
|
||||
p1 = PeerInTests(w1, k2.pubkey, t1)
|
||||
p2 = PeerInTests(w2, k1.pubkey, t2)
|
||||
@@ -1439,7 +1436,6 @@ class TestPeerForwarding(TestPeer):
|
||||
transports = {}
|
||||
workers = {} # type: Dict[str, MockLNWallet]
|
||||
peers = {}
|
||||
|
||||
# create channels
|
||||
for a, definition in graph_definition.items():
|
||||
for b, channel_def in definition.get('channels', {}).items():
|
||||
@@ -1450,6 +1446,7 @@ class TestPeerForwarding(TestPeer):
|
||||
bob_pubkey=keys[b].pubkey,
|
||||
local_msat=channel_def['local_balance_msat'],
|
||||
remote_msat=channel_def['remote_balance_msat'],
|
||||
anchor_outputs=self.TEST_ANCHOR_CHANNELS
|
||||
)
|
||||
channels[(a, b)], channels[(b, a)] = channel_ab, channel_ba
|
||||
transport_ab, transport_ba = transport_pair(keys[a], keys[b], channel_ab.name, channel_ba.name)
|
||||
@@ -1463,7 +1460,7 @@ class TestPeerForwarding(TestPeer):
|
||||
# create workers and peers
|
||||
for a, definition in graph_definition.items():
|
||||
channels_of_node = [c for k, c in channels.items() if k[0] == a]
|
||||
workers[a] = MockLNWallet(local_keypair=keys[a], chans=channels_of_node, tx_queue=txs_queues[a], name=a)
|
||||
workers[a] = MockLNWallet(local_keypair=keys[a], chans=channels_of_node, tx_queue=txs_queues[a], name=a, has_anchors=self.TEST_ANCHOR_CHANNELS)
|
||||
self._lnworkers_created.extend(list(workers.values()))
|
||||
|
||||
# create peers
|
||||
@@ -1500,6 +1497,9 @@ class TestPeerForwarding(TestPeer):
|
||||
print(f" {keys[a].pubkey.hex()}")
|
||||
return graph
|
||||
|
||||
async def test_payment_multihop(self):
|
||||
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['square_graph'])
|
||||
|
||||
async def test_payment_multihop(self):
|
||||
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['square_graph'])
|
||||
peers = graph.peers.values()
|
||||
@@ -1989,3 +1989,13 @@ class TestPeerForwarding(TestPeer):
|
||||
with self.assertRaises(PaymentDone):
|
||||
graph = self.create_square_graph(direct=False, is_legacy=False)
|
||||
await self._run_trampoline_payment(graph)
|
||||
|
||||
class TestPeerDirectAnchors(TestPeerDirect):
|
||||
TEST_ANCHOR_CHANNELS = True
|
||||
|
||||
class TestPeerForwardingAnchors(TestPeerForwarding):
|
||||
TEST_ANCHOR_CHANNELS = True
|
||||
|
||||
|
||||
def run(coro):
|
||||
return asyncio.run_coroutine_threadsafe(coro, loop=util.get_asyncio_loop()).result()
|
||||
|
||||
@@ -797,11 +797,21 @@ class TestLNUtil(ElectrumTestCase):
|
||||
ref_commit_tx_str = '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220'
|
||||
self.assertEqual(str(our_commit_tx), ref_commit_tx_str)
|
||||
|
||||
@unittest.skip("only valid for original anchor ouputs, "
|
||||
"but invalid due to different fee estimation "
|
||||
"with anchors-zero-fee-htlcs")
|
||||
@disable_ecdsa_r_value_grinding
|
||||
def test_commitment_tx_anchors_test_vectors(self):
|
||||
# this test is only valid for the original anchor output test vectors (not anchors-zero-fee-htlcs),
|
||||
# therefore we patch the effective htlc tx weight to result in a finite weight
|
||||
from electrum import lnutil
|
||||
effective_htlc_tx_weight_original = lnutil.effective_htlc_tx_weight
|
||||
def effective_htlc_tx_weight_patched(success: bool, has_anchors: bool):
|
||||
return lnutil.HTLC_SUCCESS_WEIGHT_ANCHORS if success else lnutil.HTLC_TIMEOUT_WEIGHT_ANCHORS
|
||||
lnutil.effective_htlc_tx_weight = effective_htlc_tx_weight_patched
|
||||
try:
|
||||
self._test_commitment_tx_anchors_test_vectors()
|
||||
finally:
|
||||
lnutil.effective_htlc_tx_weight = effective_htlc_tx_weight_original
|
||||
|
||||
def _test_commitment_tx_anchors_test_vectors(self):
|
||||
for test_vector in ANCHOR_TEST_VECTORS:
|
||||
with self.subTest(test_vector['Name']):
|
||||
to_local_msat = test_vector['LocalBalance']
|
||||
|
||||
Reference in New Issue
Block a user