1
0

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:
bitromortac
2021-11-08 15:26:41 +01:00
committed by ThomasV
parent 19e993f39b
commit de9fee706d
4 changed files with 75 additions and 37 deletions

View File

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

View File

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

View File

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

View File

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