unit tests: test anchors in lnpeer and lnchannel
* testing of anchor channels is controlled via TEST_ANCHOR_CHANNELS * rewrite tests in test_lnchannel.py
This commit is contained in:
@@ -35,6 +35,7 @@ from electrum import lnutil
|
|||||||
from electrum import bip32 as bip32_utils
|
from electrum import bip32 as bip32_utils
|
||||||
from electrum.crypto import privkey_to_pubkey
|
from electrum.crypto import privkey_to_pubkey
|
||||||
from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED, UpdateAddHtlc
|
from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED, UpdateAddHtlc
|
||||||
|
from electrum.lnutil import effective_htlc_tx_weight
|
||||||
from electrum.logging import console_stderr_handler
|
from electrum.logging import console_stderr_handler
|
||||||
from electrum.lnchannel import ChannelState
|
from electrum.lnchannel import ChannelState
|
||||||
from electrum.json_db import StoredDict
|
from electrum.json_db import StoredDict
|
||||||
@@ -42,6 +43,7 @@ from electrum.coinchooser import PRNG
|
|||||||
|
|
||||||
from . import ElectrumTestCase
|
from . import ElectrumTestCase
|
||||||
|
|
||||||
|
TEST_ANCHOR_CHANNELS = True
|
||||||
|
|
||||||
one_bitcoin_in_msat = bitcoin.COIN * 1000
|
one_bitcoin_in_msat = bitcoin.COIN * 1000
|
||||||
|
|
||||||
@@ -49,10 +51,13 @@ one_bitcoin_in_msat = bitcoin.COIN * 1000
|
|||||||
def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator,
|
def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator,
|
||||||
local_amount, remote_amount, privkeys, other_pubkeys,
|
local_amount, remote_amount, privkeys, other_pubkeys,
|
||||||
seed, cur, nex, other_node_id, l_dust, r_dust, l_csv,
|
seed, cur, nex, other_node_id, l_dust, r_dust, l_csv,
|
||||||
r_csv):
|
r_csv, anchor_outputs=TEST_ANCHOR_CHANNELS):
|
||||||
#assert local_amount > 0
|
#assert local_amount > 0
|
||||||
#assert remote_amount > 0
|
#assert remote_amount > 0
|
||||||
channel_id, _ = lnpeer.channel_id_from_funding_tx(funding_txid, funding_index)
|
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_ANCHOR_OUTPUTS
|
||||||
state = {
|
state = {
|
||||||
"channel_id":channel_id.hex(),
|
"channel_id":channel_id.hex(),
|
||||||
"short_channel_id":channel_id[:8],
|
"short_channel_id":channel_id[:8],
|
||||||
@@ -111,7 +116,7 @@ def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator,
|
|||||||
'log': {},
|
'log': {},
|
||||||
'unfulfilled_htlcs': {},
|
'unfulfilled_htlcs': {},
|
||||||
'revocation_store': {},
|
'revocation_store': {},
|
||||||
'channel_type': lnutil.ChannelType.OPTION_STATIC_REMOTEKEY
|
'channel_type': channel_type,
|
||||||
}
|
}
|
||||||
return StoredDict(state, None, [])
|
return StoredDict(state, None, [])
|
||||||
|
|
||||||
@@ -124,7 +129,8 @@ def bip32(sequence):
|
|||||||
|
|
||||||
def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None,
|
def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None,
|
||||||
alice_name="alice", bob_name="bob",
|
alice_name="alice", bob_name="bob",
|
||||||
alice_pubkey=b"\x01"*33, bob_pubkey=b"\x02"*33, random_seed=None):
|
alice_pubkey=b"\x01"*33, bob_pubkey=b"\x02"*33, random_seed=None,
|
||||||
|
anchor_outputs=TEST_ANCHOR_CHANNELS):
|
||||||
if random_seed is None: # needed for deterministic randomness
|
if random_seed is None: # needed for deterministic randomness
|
||||||
random_seed = os.urandom(32)
|
random_seed = os.urandom(32)
|
||||||
random_gen = PRNG(random_seed)
|
random_gen = PRNG(random_seed)
|
||||||
@@ -156,7 +162,7 @@ def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None,
|
|||||||
funding_txid, funding_index, funding_sat, True, local_amount,
|
funding_txid, funding_index, funding_sat, True, local_amount,
|
||||||
remote_amount, alice_privkeys, bob_pubkeys, alice_seed, None,
|
remote_amount, alice_privkeys, bob_pubkeys, alice_seed, None,
|
||||||
bob_first, other_node_id=bob_pubkey, l_dust=200, r_dust=1300,
|
bob_first, other_node_id=bob_pubkey, l_dust=200, r_dust=1300,
|
||||||
l_csv=5, r_csv=4
|
l_csv=5, r_csv=4, anchor_outputs=anchor_outputs
|
||||||
),
|
),
|
||||||
name=f"{alice_name}->{bob_name}",
|
name=f"{alice_name}->{bob_name}",
|
||||||
initial_feerate=feerate),
|
initial_feerate=feerate),
|
||||||
@@ -165,7 +171,7 @@ def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None,
|
|||||||
funding_txid, funding_index, funding_sat, False, remote_amount,
|
funding_txid, funding_index, funding_sat, False, remote_amount,
|
||||||
local_amount, bob_privkeys, alice_pubkeys, bob_seed, None,
|
local_amount, bob_privkeys, alice_pubkeys, bob_seed, None,
|
||||||
alice_first, other_node_id=alice_pubkey, l_dust=1300, r_dust=200,
|
alice_first, other_node_id=alice_pubkey, l_dust=1300, r_dust=200,
|
||||||
l_csv=4, r_csv=5
|
l_csv=4, r_csv=5, anchor_outputs=anchor_outputs
|
||||||
),
|
),
|
||||||
name=f"{bob_name}->{alice_name}",
|
name=f"{bob_name}->{alice_name}",
|
||||||
initial_feerate=feerate)
|
initial_feerate=feerate)
|
||||||
@@ -210,8 +216,9 @@ class TestFee(ElectrumTestCase):
|
|||||||
def test_fee(self):
|
def test_fee(self):
|
||||||
alice_channel, bob_channel = create_test_channels(feerate=253,
|
alice_channel, bob_channel = create_test_channels(feerate=253,
|
||||||
local_msat=10000000000,
|
local_msat=10000000000,
|
||||||
remote_msat=5000000000)
|
remote_msat=5000000000, anchor_outputs=TEST_ANCHOR_CHANNELS)
|
||||||
self.assertIn(9999817, [x.value for x in alice_channel.get_latest_commitment(LOCAL).outputs()])
|
expected_value = 9999056 if TEST_ANCHOR_CHANNELS else 9999817
|
||||||
|
self.assertIn(expected_value, [x.value for x in alice_channel.get_latest_commitment(LOCAL).outputs()])
|
||||||
|
|
||||||
class TestChannel(ElectrumTestCase):
|
class TestChannel(ElectrumTestCase):
|
||||||
maxDiff = 999
|
maxDiff = 999
|
||||||
@@ -223,6 +230,9 @@ class TestChannel(ElectrumTestCase):
|
|||||||
else:
|
else:
|
||||||
self.assertFalse()
|
self.assertFalse()
|
||||||
|
|
||||||
|
def assertNumberNonAnchorOutputs(self, number, tx):
|
||||||
|
self.assertEqual(number, len(tx.outputs()) - (2 if TEST_ANCHOR_CHANNELS else 0))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
@@ -233,15 +243,15 @@ class TestChannel(ElectrumTestCase):
|
|||||||
# Create a test channel which will be used for the duration of this
|
# 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,
|
# unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
# and Bob having 5 BTC.
|
# and Bob having 5 BTC.
|
||||||
self.alice_channel, self.bob_channel = create_test_channels()
|
self.alice_channel, self.bob_channel = create_test_channels(anchor_outputs=TEST_ANCHOR_CHANNELS)
|
||||||
|
|
||||||
self.paymentPreimage = b"\x01" * 32
|
self.paymentPreimage = b"\x01" * 32
|
||||||
paymentHash = bitcoin.sha256(self.paymentPreimage)
|
paymentHash = bitcoin.sha256(self.paymentPreimage)
|
||||||
self.htlc_dict = {
|
self.htlc_dict = {
|
||||||
'payment_hash' : paymentHash,
|
'payment_hash': paymentHash,
|
||||||
'amount_msat' : one_bitcoin_in_msat,
|
'amount_msat': one_bitcoin_in_msat,
|
||||||
'cltv_abs' : 5,
|
'cltv_abs': 5,
|
||||||
'timestamp' : 0,
|
'timestamp': 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
# First Alice adds the outgoing HTLC to her local channel's state
|
# First Alice adds the outgoing HTLC to her local channel's state
|
||||||
@@ -263,40 +273,60 @@ class TestChannel(ElectrumTestCase):
|
|||||||
self.bob_channel.add_htlc(self.htlc_dict)
|
self.bob_channel.add_htlc(self.htlc_dict)
|
||||||
self.alice_channel.receive_htlc(self.htlc_dict)
|
self.alice_channel.receive_htlc(self.htlc_dict)
|
||||||
|
|
||||||
self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, self.alice_channel.get_latest_commitment(LOCAL))
|
||||||
self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, self.alice_channel.get_next_commitment(LOCAL))
|
||||||
self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, self.alice_channel.get_latest_commitment(REMOTE))
|
||||||
self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, self.alice_channel.get_next_commitment(REMOTE))
|
||||||
|
|
||||||
self.alice_channel.receive_new_commitment(*self.bob_channel.sign_next_commitment())
|
self.alice_channel.receive_new_commitment(*self.bob_channel.sign_next_commitment())
|
||||||
|
|
||||||
self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, self.alice_channel.get_latest_commitment(LOCAL))
|
||||||
self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, self.alice_channel.get_next_commitment(LOCAL))
|
||||||
self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, self.alice_channel.get_latest_commitment(REMOTE))
|
||||||
self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, self.alice_channel.get_next_commitment(REMOTE))
|
||||||
|
|
||||||
self.alice_channel.revoke_current_commitment()
|
self.alice_channel.revoke_current_commitment()
|
||||||
|
|
||||||
self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, self.alice_channel.get_latest_commitment(LOCAL))
|
||||||
self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, self.alice_channel.get_next_commitment(LOCAL))
|
||||||
self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, self.alice_channel.get_latest_commitment(REMOTE))
|
||||||
self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 4)
|
self.assertNumberNonAnchorOutputs(4, self.alice_channel.get_next_commitment(REMOTE))
|
||||||
|
|
||||||
def test_SimpleAddSettleWorkflow(self):
|
def test_SimpleAddSettleWorkflow(self):
|
||||||
alice_channel, bob_channel = self.alice_channel, self.bob_channel
|
alice_channel, bob_channel = self.alice_channel, self.bob_channel
|
||||||
htlc = self.htlc
|
htlc = self.htlc
|
||||||
|
|
||||||
|
# Starting point: alice has sent an update_add_htlc message to bob
|
||||||
|
# but the htlc is not yet committed to
|
||||||
alice_out = alice_channel.get_latest_commitment(LOCAL).outputs()
|
alice_out = alice_channel.get_latest_commitment(LOCAL).outputs()
|
||||||
short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
|
if not alice_channel.has_anchors():
|
||||||
long_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
|
# ctx outputs are ordered by increasing amounts
|
||||||
self.assertLess(alice_out[long_idx].value, 5 * 10**8, alice_out)
|
low_amt_idx = 0
|
||||||
self.assertEqual(alice_out[short_idx].value, 5 * 10**8, alice_out)
|
assert len(alice_out[low_amt_idx].address) == 62 # p2wsh
|
||||||
|
high_amt_idx = 1
|
||||||
|
assert len(alice_out[high_amt_idx].address) == 42 # p2wpkh
|
||||||
|
else:
|
||||||
|
# using anchor outputs, all outputs are p2wsh
|
||||||
|
low_amt_idx = 2
|
||||||
|
assert len(alice_out[low_amt_idx].address) == 62
|
||||||
|
high_amt_idx = 3
|
||||||
|
assert len(alice_out[high_amt_idx].address) == 62
|
||||||
|
self.assertLess(alice_out[low_amt_idx].value, 5 * 10**8, alice_out)
|
||||||
|
self.assertEqual(alice_out[high_amt_idx].value, 5 * 10**8, alice_out)
|
||||||
|
|
||||||
alice_out = alice_channel.get_latest_commitment(REMOTE).outputs()
|
alice_out = alice_channel.get_latest_commitment(REMOTE).outputs()
|
||||||
short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
|
if not alice_channel.has_anchors():
|
||||||
long_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
|
low_amt_idx = 0
|
||||||
self.assertLess(alice_out[short_idx].value, 5 * 10**8)
|
assert len(alice_out[low_amt_idx].address) == 42
|
||||||
self.assertEqual(alice_out[long_idx].value, 5 * 10**8)
|
high_amt_idx = 1
|
||||||
|
assert len(alice_out[high_amt_idx].address) == 62
|
||||||
|
else:
|
||||||
|
low_amt_idx = 2
|
||||||
|
assert len(alice_out[low_amt_idx].address) == 62
|
||||||
|
high_amt_idx = 3
|
||||||
|
assert len(alice_out[high_amt_idx].address) == 62
|
||||||
|
self.assertLess(alice_out[low_amt_idx].value, 5 * 10**8)
|
||||||
|
self.assertEqual(alice_out[high_amt_idx].value, 5 * 10**8)
|
||||||
|
|
||||||
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
|
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
|
||||||
|
|
||||||
@@ -341,7 +371,7 @@ class TestChannel(ElectrumTestCase):
|
|||||||
self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
|
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.get_oldest_unrevoked_ctn(REMOTE), 0)
|
||||||
self.assertEqual(bob_channel.included_htlcs(LOCAL, RECEIVED, 1), [htlc])#
|
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, 0), [])
|
||||||
self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc])
|
self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc])
|
||||||
@@ -369,10 +399,10 @@ class TestChannel(ElectrumTestCase):
|
|||||||
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
|
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
|
||||||
|
|
||||||
# so far: Alice added htlc, Alice signed.
|
# so far: Alice added htlc, Alice signed.
|
||||||
self.assertEqual(len(alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, alice_channel.get_latest_commitment(LOCAL))
|
||||||
self.assertEqual(len(alice_channel.get_next_commitment(LOCAL).outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, alice_channel.get_next_commitment(LOCAL))
|
||||||
self.assertEqual(len(alice_channel.get_oldest_unrevoked_commitment(REMOTE).outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, alice_channel.get_oldest_unrevoked_commitment(REMOTE))
|
||||||
self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, alice_channel.get_latest_commitment(REMOTE))
|
||||||
|
|
||||||
# Alice then processes this revocation, sending her own revocation for
|
# Alice then processes this revocation, sending her own revocation for
|
||||||
# her prior commitment transaction. Alice shouldn't have any HTLCs to
|
# her prior commitment transaction. Alice shouldn't have any HTLCs to
|
||||||
@@ -381,21 +411,21 @@ class TestChannel(ElectrumTestCase):
|
|||||||
|
|
||||||
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
|
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
|
||||||
|
|
||||||
self.assertEqual(len(alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, alice_channel.get_latest_commitment(LOCAL))
|
||||||
self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, alice_channel.get_latest_commitment(REMOTE))
|
||||||
self.assertEqual(len(alice_channel.force_close_tx().outputs()), 2)
|
self.assertNumberNonAnchorOutputs(2, alice_channel.force_close_tx())
|
||||||
|
|
||||||
self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
|
self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
|
||||||
self.assertEqual(alice_channel.get_next_commitment(LOCAL).outputs(),
|
self.assertEqual(alice_channel.get_next_commitment(LOCAL).outputs(),
|
||||||
bob_channel.get_latest_commitment(REMOTE).outputs())
|
bob_channel.get_latest_commitment(REMOTE).outputs())
|
||||||
|
|
||||||
# Alice then processes bob's signature, and since she just received
|
# Alice then processes bob's signature, and since she just received
|
||||||
# the revocation, she expect this signature to cover everything up to
|
# the revocation, she expects this signature to cover everything up to
|
||||||
# the point where she sent her signature, including the HTLC.
|
# the point where she sent her signature, including the HTLC.
|
||||||
alice_channel.receive_new_commitment(bobSig, bobHtlcSigs)
|
alice_channel.receive_new_commitment(bobSig, bobHtlcSigs)
|
||||||
|
|
||||||
self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, alice_channel.get_latest_commitment(REMOTE))
|
||||||
self.assertEqual(len(alice_channel.force_close_tx().outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, alice_channel.force_close_tx())
|
||||||
|
|
||||||
self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
|
self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
|
||||||
|
|
||||||
@@ -433,8 +463,8 @@ class TestChannel(ElectrumTestCase):
|
|||||||
# them should be exactly the amount of the HTLC.
|
# them should be exactly the amount of the HTLC.
|
||||||
alice_ctx = alice_channel.get_next_commitment(LOCAL)
|
alice_ctx = alice_channel.get_next_commitment(LOCAL)
|
||||||
bob_ctx = bob_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.assertNumberNonAnchorOutputs(3, alice_ctx)
|
||||||
self.assertEqual(len(bob_ctx.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_ctx.outputs()))
|
self.assertNumberNonAnchorOutputs(3, bob_ctx)
|
||||||
self.assertOutputExistsByValue(alice_ctx, htlc.amount_msat // 1000)
|
self.assertOutputExistsByValue(alice_ctx, htlc.amount_msat // 1000)
|
||||||
self.assertOutputExistsByValue(bob_ctx, htlc.amount_msat // 1000)
|
self.assertOutputExistsByValue(bob_ctx, htlc.amount_msat // 1000)
|
||||||
|
|
||||||
@@ -482,7 +512,7 @@ class TestChannel(ElectrumTestCase):
|
|||||||
aliceRevocation2 = alice_channel.revoke_current_commitment()
|
aliceRevocation2 = alice_channel.revoke_current_commitment()
|
||||||
aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
|
aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
|
||||||
self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
|
self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
|
||||||
self.assertEqual(len(bob_channel.get_latest_commitment(LOCAL).outputs()), 3)
|
self.assertNumberNonAnchorOutputs(3, bob_channel.get_latest_commitment(LOCAL))
|
||||||
bob_channel.receive_revocation(aliceRevocation2)
|
bob_channel.receive_revocation(aliceRevocation2)
|
||||||
|
|
||||||
bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2)
|
bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2)
|
||||||
@@ -643,7 +673,7 @@ class TestChannel(ElectrumTestCase):
|
|||||||
class TestAvailableToSpend(ElectrumTestCase):
|
class TestAvailableToSpend(ElectrumTestCase):
|
||||||
def test_DesyncHTLCs(self):
|
def test_DesyncHTLCs(self):
|
||||||
alice_channel, bob_channel = create_test_channels()
|
alice_channel, bob_channel = create_test_channels()
|
||||||
self.assertEqual(499986152000, alice_channel.available_to_spend(LOCAL))
|
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))
|
self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
|
||||||
|
|
||||||
paymentPreimage = b"\x01" * 32
|
paymentPreimage = b"\x01" * 32
|
||||||
@@ -657,13 +687,13 @@ class TestAvailableToSpend(ElectrumTestCase):
|
|||||||
|
|
||||||
alice_idx = alice_channel.add_htlc(htlc).htlc_id
|
alice_idx = alice_channel.add_htlc(htlc).htlc_id
|
||||||
bob_idx = bob_channel.receive_htlc(htlc).htlc_id
|
bob_idx = bob_channel.receive_htlc(htlc).htlc_id
|
||||||
self.assertEqual(89984088000, alice_channel.available_to_spend(LOCAL))
|
self.assertEqual(89984088000 if not alice_channel.has_anchors() else 89978628000, alice_channel.available_to_spend(LOCAL))
|
||||||
self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
|
self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
|
||||||
|
|
||||||
force_state_transition(alice_channel, bob_channel)
|
force_state_transition(alice_channel, bob_channel)
|
||||||
bob_channel.fail_htlc(bob_idx)
|
bob_channel.fail_htlc(bob_idx)
|
||||||
alice_channel.receive_fail_htlc(alice_idx, error_bytes=None)
|
alice_channel.receive_fail_htlc(alice_idx, error_bytes=None)
|
||||||
self.assertEqual(89984088000, alice_channel.available_to_spend(LOCAL))
|
self.assertEqual(89984088000 if not alice_channel.has_anchors() else 89978628000, alice_channel.available_to_spend(LOCAL))
|
||||||
self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
|
self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
|
||||||
# Alice now has gotten all her original balance (5 BTC) back, however,
|
# Alice now has gotten all her original balance (5 BTC) back, however,
|
||||||
# adding a new HTLC at this point SHOULD fail, since if she adds the
|
# adding a new HTLC at this point SHOULD fail, since if she adds the
|
||||||
@@ -683,7 +713,7 @@ class TestAvailableToSpend(ElectrumTestCase):
|
|||||||
# Now do a state transition, which will ACK the FailHTLC, making Alice
|
# Now do a state transition, which will ACK the FailHTLC, making Alice
|
||||||
# able to add the new HTLC.
|
# able to add the new HTLC.
|
||||||
force_state_transition(alice_channel, bob_channel)
|
force_state_transition(alice_channel, bob_channel)
|
||||||
self.assertEqual(499986152000, alice_channel.available_to_spend(LOCAL))
|
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))
|
self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
|
||||||
alice_channel.add_htlc(htlc)
|
alice_channel.add_htlc(htlc)
|
||||||
|
|
||||||
@@ -720,10 +750,10 @@ class TestChanReserve(ElectrumTestCase):
|
|||||||
paymentPreimage = b"\x01" * 32
|
paymentPreimage = b"\x01" * 32
|
||||||
paymentHash = bitcoin.sha256(paymentPreimage)
|
paymentHash = bitcoin.sha256(paymentPreimage)
|
||||||
htlc_dict = {
|
htlc_dict = {
|
||||||
'payment_hash' : paymentHash,
|
'payment_hash': paymentHash,
|
||||||
'amount_msat' : int(.5 * one_bitcoin_in_msat),
|
'amount_msat': int(.5 * one_bitcoin_in_msat),
|
||||||
'cltv_abs' : 5,
|
'cltv_abs': 5,
|
||||||
'timestamp' : 0,
|
'timestamp': 0,
|
||||||
}
|
}
|
||||||
self.alice_channel.add_htlc(htlc_dict)
|
self.alice_channel.add_htlc(htlc_dict)
|
||||||
self.bob_channel.receive_htlc(htlc_dict)
|
self.bob_channel.receive_htlc(htlc_dict)
|
||||||
@@ -759,9 +789,9 @@ class TestChanReserve(ElectrumTestCase):
|
|||||||
# Alice: 1.5
|
# Alice: 1.5
|
||||||
# Bob: 9.5
|
# Bob: 9.5
|
||||||
htlc_dict = {
|
htlc_dict = {
|
||||||
'payment_hash' : paymentHash,
|
'payment_hash': paymentHash,
|
||||||
'amount_msat' : int(3.5 * one_bitcoin_in_msat),
|
'amount_msat': int(3.5 * one_bitcoin_in_msat),
|
||||||
'cltv_abs' : 5,
|
'cltv_abs': 5,
|
||||||
}
|
}
|
||||||
self.alice_channel.add_htlc(htlc_dict)
|
self.alice_channel.add_htlc(htlc_dict)
|
||||||
self.bob_channel.receive_htlc(htlc_dict)
|
self.bob_channel.receive_htlc(htlc_dict)
|
||||||
@@ -783,10 +813,10 @@ class TestChanReserve(ElectrumTestCase):
|
|||||||
paymentPreimage = b"\x01" * 32
|
paymentPreimage = b"\x01" * 32
|
||||||
paymentHash = bitcoin.sha256(paymentPreimage)
|
paymentHash = bitcoin.sha256(paymentPreimage)
|
||||||
htlc_dict = {
|
htlc_dict = {
|
||||||
'payment_hash' : paymentHash,
|
'payment_hash': paymentHash,
|
||||||
'amount_msat' : int(2 * one_bitcoin_in_msat),
|
'amount_msat': int(2 * one_bitcoin_in_msat),
|
||||||
'cltv_abs' : 5,
|
'cltv_abs': 5,
|
||||||
'timestamp' : 0,
|
'timestamp': 0,
|
||||||
}
|
}
|
||||||
alice_idx = self.alice_channel.add_htlc(htlc_dict).htlc_id
|
alice_idx = self.alice_channel.add_htlc(htlc_dict).htlc_id
|
||||||
bob_idx = self.bob_channel.receive_htlc(htlc_dict).htlc_id
|
bob_idx = self.bob_channel.receive_htlc(htlc_dict).htlc_id
|
||||||
@@ -819,38 +849,61 @@ class TestChanReserve(ElectrumTestCase):
|
|||||||
|
|
||||||
class TestDust(ElectrumTestCase):
|
class TestDust(ElectrumTestCase):
|
||||||
def test_DustLimit(self):
|
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()
|
||||||
|
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)
|
||||||
|
|
||||||
|
bob_ctx = bob_channel.get_latest_commitment(LOCAL)
|
||||||
|
bobs_original_outputs = [x.value for x in bob_ctx.outputs()]
|
||||||
paymentPreimage = b"\x01" * 32
|
paymentPreimage = b"\x01" * 32
|
||||||
paymentHash = bitcoin.sha256(paymentPreimage)
|
paymentHash = bitcoin.sha256(paymentPreimage)
|
||||||
fee_per_kw = alice_channel.get_next_feerate(LOCAL)
|
fee_per_kw = alice_channel.get_next_feerate(LOCAL)
|
||||||
self.assertEqual(fee_per_kw, 6000)
|
success_weight = effective_htlc_tx_weight(success=True, has_anchors=TEST_ANCHOR_CHANNELS)
|
||||||
htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
|
# we put a single sat less into the htlc than bob can afford
|
||||||
self.assertEqual(htlcAmt, 4478)
|
# to pay for his htlc success transaction
|
||||||
|
below_dust_for_bob = dust_limit_bob - 1
|
||||||
|
htlc_amt = below_dust_for_bob + success_weight * (fee_per_kw // 1000)
|
||||||
htlc = {
|
htlc = {
|
||||||
'payment_hash' : paymentHash,
|
'payment_hash': paymentHash,
|
||||||
'amount_msat' : 1000 * htlcAmt,
|
'amount_msat': 1000 * htlc_amt,
|
||||||
'cltv_abs' : 5, # also in create_test_channels
|
'cltv_abs': 5, # consistent with channel policy
|
||||||
'timestamp' : 0,
|
'timestamp': 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
old_values = [x.value for x in bob_channel.get_latest_commitment(LOCAL).outputs()]
|
# add the htlc
|
||||||
aliceHtlcIndex = alice_channel.add_htlc(htlc).htlc_id
|
alice_htlc_id = alice_channel.add_htlc(htlc).htlc_id
|
||||||
bobHtlcIndex = bob_channel.receive_htlc(htlc).htlc_id
|
bob_htlc_id = bob_channel.receive_htlc(htlc).htlc_id
|
||||||
force_state_transition(alice_channel, bob_channel)
|
force_state_transition(alice_channel, bob_channel)
|
||||||
alice_ctx = alice_channel.get_latest_commitment(LOCAL)
|
alice_ctx = alice_channel.get_latest_commitment(LOCAL)
|
||||||
bob_ctx = bob_channel.get_latest_commitment(LOCAL)
|
bob_ctx = bob_channel.get_latest_commitment(LOCAL)
|
||||||
new_values = [x.value for x in bob_ctx.outputs()]
|
bobs_second_outputs = [x.value for x in bob_ctx.outputs()]
|
||||||
self.assertNotEqual(old_values, new_values)
|
self.assertNotEqual(bobs_original_outputs, bobs_second_outputs)
|
||||||
self.assertEqual(len(alice_ctx.outputs()), 3)
|
# the htlc appears as an output in alice's ctx, as she has a lower
|
||||||
self.assertEqual(len(bob_ctx.outputs()), 2)
|
# dust limit (also because her timeout tx costs less)
|
||||||
default_fee = calc_static_fee(0)
|
self.assertEqual(3, len(alice_ctx.outputs()) - (2 if TEST_ANCHOR_CHANNELS else 0))
|
||||||
self.assertEqual(bob_channel.get_next_fee(LOCAL), default_fee + htlcAmt)
|
# htlc in bob's case goes to miner fees
|
||||||
bob_channel.settle_htlc(paymentPreimage, bobHtlcIndex)
|
self.assertEqual(2, len(bob_ctx.outputs()) - (2 if TEST_ANCHOR_CHANNELS else 0))
|
||||||
alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex)
|
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
|
||||||
|
self.assertEqual(empty_ctx_fee + htlc_amt, bob_channel.get_next_fee(LOCAL))
|
||||||
|
|
||||||
|
bob_channel.settle_htlc(paymentPreimage, bob_htlc_id)
|
||||||
|
alice_channel.receive_htlc_settle(paymentPreimage, alice_htlc_id)
|
||||||
force_state_transition(bob_channel, alice_channel)
|
force_state_transition(bob_channel, alice_channel)
|
||||||
self.assertEqual(len(alice_channel.get_next_commitment(LOCAL).outputs()), 2)
|
bob_ctx = bob_channel.get_latest_commitment(LOCAL)
|
||||||
self.assertEqual(alice_channel.total_msat(SENT) // 1000, htlcAmt)
|
bobs_third_outputs = [x.value for x in bob_ctx.outputs()]
|
||||||
|
# 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, alice_channel.total_msat(SENT) // 1000)
|
||||||
|
|
||||||
|
|
||||||
def force_state_transition(chanA, chanB):
|
def force_state_transition(chanA, chanB):
|
||||||
chanB.receive_new_commitment(*chanA.sign_next_commitment())
|
chanB.receive_new_commitment(*chanA.sign_next_commitment())
|
||||||
@@ -859,12 +912,3 @@ def force_state_transition(chanA, chanB):
|
|||||||
chanA.receive_revocation(rev)
|
chanA.receive_revocation(rev)
|
||||||
chanA.receive_new_commitment(bob_sig, bob_htlc_sigs)
|
chanA.receive_new_commitment(bob_sig, bob_htlc_sigs)
|
||||||
chanB.receive_revocation(chanA.revoke_current_commitment())
|
chanB.receive_revocation(chanA.revoke_current_commitment())
|
||||||
|
|
||||||
# calcStaticFee calculates appropriate fees for commitment transactions. This
|
|
||||||
# function provides a simple way to allow test balance assertions to take fee
|
|
||||||
# calculations into account.
|
|
||||||
def calc_static_fee(numHTLCs):
|
|
||||||
commitWeight = 724
|
|
||||||
htlcWeight = 172
|
|
||||||
feePerKw = 24//4 * 1000
|
|
||||||
return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000
|
|
||||||
|
|||||||
@@ -45,9 +45,16 @@ from electrum.invoices import PR_PAID, PR_UNPAID
|
|||||||
from electrum.interface import GracefulDisconnect
|
from electrum.interface import GracefulDisconnect
|
||||||
from electrum.simple_config import SimpleConfig
|
from electrum.simple_config import SimpleConfig
|
||||||
|
|
||||||
from .test_lnchannel import create_test_channels
|
from .test_lnchannel import create_test_channels as create_test_channels_anchors
|
||||||
from . import ElectrumTestCase
|
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():
|
def keypair():
|
||||||
priv = ECPrivkey.generate_random_key().get_secret_bytes()
|
priv = ECPrivkey.generate_random_key().get_secret_bytes()
|
||||||
k1 = Keypair(
|
k1 = Keypair(
|
||||||
@@ -169,6 +176,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
|||||||
self.features |= LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM
|
self.features |= LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM
|
||||||
self.features |= LnFeatures.OPTION_CHANNEL_TYPE_OPT
|
self.features |= LnFeatures.OPTION_CHANNEL_TYPE_OPT
|
||||||
self.features |= LnFeatures.OPTION_SCID_ALIAS_OPT
|
self.features |= LnFeatures.OPTION_SCID_ALIAS_OPT
|
||||||
|
self.features |= LnFeatures.OPTION_STATIC_REMOTEKEY_OPT
|
||||||
self.pending_payments = defaultdict(asyncio.Future)
|
self.pending_payments = defaultdict(asyncio.Future)
|
||||||
for chan in chans:
|
for chan in chans:
|
||||||
chan.lnworker = self
|
chan.lnworker = self
|
||||||
@@ -387,7 +395,7 @@ low_fee_channel = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
depleted_channel = {
|
depleted_channel = {
|
||||||
'local_balance_msat': 0,
|
'local_balance_msat': 330 * 1000, # local pays anchors
|
||||||
'remote_balance_msat': 10 * bitcoin.COIN * 1000,
|
'remote_balance_msat': 10 * bitcoin.COIN * 1000,
|
||||||
'local_base_fee_msat': 1_000,
|
'local_base_fee_msat': 1_000,
|
||||||
'local_fee_rate_millionths': 1,
|
'local_fee_rate_millionths': 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user