lnpeer: add unit test for upfront shutdown script
This commit is contained in:
@@ -34,8 +34,6 @@ from electrum import lnchannel
|
|||||||
from electrum import lnutil
|
from electrum import lnutil
|
||||||
from electrum import bip32 as bip32_utils
|
from electrum import bip32 as bip32_utils
|
||||||
from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
|
from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
|
||||||
from electrum.lnutil import FeeUpdate
|
|
||||||
from electrum.ecc import sig_string_from_der_sig
|
|
||||||
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
|
||||||
@@ -46,7 +44,11 @@ from . import ElectrumTestCase
|
|||||||
|
|
||||||
one_bitcoin_in_msat = bitcoin.COIN * 1000
|
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):
|
|
||||||
|
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):
|
||||||
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)
|
||||||
@@ -134,16 +136,30 @@ def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None,
|
|||||||
alice_seed = random_gen.get_bytes(32)
|
alice_seed = random_gen.get_bytes(32)
|
||||||
bob_seed = random_gen.get_bytes(32)
|
bob_seed = random_gen.get_bytes(32)
|
||||||
|
|
||||||
alice_first = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX), "big"))
|
alice_first = lnutil.secret_to_pubkey(
|
||||||
bob_first = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX), "big"))
|
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"))
|
||||||
|
|
||||||
alice, bob = (
|
alice, bob = (
|
||||||
lnchannel.Channel(
|
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),
|
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
|
||||||
|
),
|
||||||
name=bob_name,
|
name=bob_name,
|
||||||
initial_feerate=feerate),
|
initial_feerate=feerate),
|
||||||
lnchannel.Channel(
|
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),
|
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
|
||||||
|
),
|
||||||
name=alice_name,
|
name=alice_name,
|
||||||
initial_feerate=feerate)
|
initial_feerate=feerate)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,14 +12,15 @@ from typing import Iterable, NamedTuple
|
|||||||
|
|
||||||
from aiorpcx import TaskGroup
|
from aiorpcx import TaskGroup
|
||||||
|
|
||||||
|
from electrum import bitcoin
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
from electrum.network import Network
|
from electrum.network import Network
|
||||||
from electrum.ecc import ECPrivkey
|
from electrum.ecc import ECPrivkey
|
||||||
from electrum import simple_config, lnutil
|
from electrum import simple_config, lnutil
|
||||||
from electrum.lnaddr import lnencode, LnAddr, lndecode
|
from electrum.lnaddr import lnencode, LnAddr, lndecode
|
||||||
from electrum.bitcoin import COIN, sha256
|
from electrum.bitcoin import COIN, sha256
|
||||||
from electrum.util import bh2u, create_and_start_event_loop, NetworkRetryManager
|
from electrum.util import bh2u, create_and_start_event_loop, NetworkRetryManager, bfh
|
||||||
from electrum.lnpeer import Peer
|
from electrum.lnpeer import Peer, UpfrontShutdownScriptViolation
|
||||||
from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey
|
from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey
|
||||||
from electrum.lnutil import LightningPeerConnectionClosed, RemoteMisbehaving
|
from electrum.lnutil import LightningPeerConnectionClosed, RemoteMisbehaving
|
||||||
from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner
|
from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner
|
||||||
@@ -121,6 +122,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
|
|||||||
self.wallet = MockWallet()
|
self.wallet = MockWallet()
|
||||||
self.features = LnFeatures(0)
|
self.features = LnFeatures(0)
|
||||||
self.features |= LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT
|
self.features |= LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT
|
||||||
|
self.features |= LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_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
|
||||||
@@ -605,6 +607,76 @@ class TestPeer(ElectrumTestCase):
|
|||||||
with self.assertRaises(concurrent.futures.CancelledError):
|
with self.assertRaises(concurrent.futures.CancelledError):
|
||||||
run(f())
|
run(f())
|
||||||
|
|
||||||
|
@needs_test_with_all_chacha20_implementations
|
||||||
|
def test_close_upfront_shutdown_script(self):
|
||||||
|
alice_channel, bob_channel = create_test_channels()
|
||||||
|
|
||||||
|
# create upfront shutdown script for bob, alice doesn't use upfront
|
||||||
|
# shutdown script
|
||||||
|
bob_uss_pub = lnutil.privkey_to_pubkey(os.urandom(32))
|
||||||
|
bob_uss_addr = bitcoin.pubkey_to_address('p2wpkh', bh2u(bob_uss_pub))
|
||||||
|
bob_uss = bfh(bitcoin.address_to_script(bob_uss_addr))
|
||||||
|
|
||||||
|
# bob commits to close to bob_uss
|
||||||
|
alice_channel.config[HTLCOwner.REMOTE].upfront_shutdown_script = bob_uss
|
||||||
|
# but bob closes to some receiving address, which we achieve by not
|
||||||
|
# setting the upfront shutdown script in the channel config
|
||||||
|
bob_channel.config[HTLCOwner.LOCAL].upfront_shutdown_script = b''
|
||||||
|
|
||||||
|
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
|
||||||
|
w1.network.config.set_key('dynamic_fees', False)
|
||||||
|
w2.network.config.set_key('dynamic_fees', False)
|
||||||
|
w1.network.config.set_key('fee_per_kb', 5000)
|
||||||
|
w2.network.config.set_key('fee_per_kb', 1000)
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
async def close():
|
||||||
|
await asyncio.wait_for(p1.initialized, 1)
|
||||||
|
await asyncio.wait_for(p2.initialized, 1)
|
||||||
|
# bob closes channel with different shutdown script
|
||||||
|
await p1.close_channel(alice_channel.channel_id)
|
||||||
|
gath.cancel()
|
||||||
|
|
||||||
|
async def main_loop(peer):
|
||||||
|
async with peer.taskgroup as group:
|
||||||
|
await group.spawn(peer._message_loop())
|
||||||
|
await group.spawn(peer.htlc_switch())
|
||||||
|
|
||||||
|
coros = [close(), main_loop(p1), main_loop(p2)]
|
||||||
|
gath = asyncio.gather(*coros)
|
||||||
|
await gath
|
||||||
|
|
||||||
|
with self.assertRaises(UpfrontShutdownScriptViolation):
|
||||||
|
run(test())
|
||||||
|
|
||||||
|
# bob sends the same upfront_shutdown_script has he announced
|
||||||
|
alice_channel.config[HTLCOwner.REMOTE].upfront_shutdown_script = bob_uss
|
||||||
|
bob_channel.config[HTLCOwner.LOCAL].upfront_shutdown_script = bob_uss
|
||||||
|
|
||||||
|
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
|
||||||
|
w1.network.config.set_key('dynamic_fees', False)
|
||||||
|
w2.network.config.set_key('dynamic_fees', False)
|
||||||
|
w1.network.config.set_key('fee_per_kb', 5000)
|
||||||
|
w2.network.config.set_key('fee_per_kb', 1000)
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
async def close():
|
||||||
|
await asyncio.wait_for(p1.initialized, 1)
|
||||||
|
await asyncio.wait_for(p2.initialized, 1)
|
||||||
|
await p1.close_channel(alice_channel.channel_id)
|
||||||
|
gath.cancel()
|
||||||
|
|
||||||
|
async def main_loop(peer):
|
||||||
|
async with peer.taskgroup as group:
|
||||||
|
await group.spawn(peer._message_loop())
|
||||||
|
await group.spawn(peer.htlc_switch())
|
||||||
|
|
||||||
|
coros = [close(), main_loop(p1), main_loop(p2)]
|
||||||
|
gath = asyncio.gather(*coros)
|
||||||
|
await gath
|
||||||
|
with self.assertRaises(asyncio.CancelledError):
|
||||||
|
run(test())
|
||||||
|
|
||||||
def test_channel_usage_after_closing(self):
|
def test_channel_usage_after_closing(self):
|
||||||
alice_channel, bob_channel = create_test_channels()
|
alice_channel, bob_channel = create_test_channels()
|
||||||
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
|
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
|
||||||
|
|||||||
Reference in New Issue
Block a user