From 7f1ba788dca02189e4d1959ee53e25f9cfff6fdd Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Thu, 24 Mar 2022 11:26:55 +0100 Subject: [PATCH] lnprototest: refactoring the way to write a test Changelog-Fixed: pytest consider a exception an event that don't need to happens, and this keep alive all the subdeamon that lnprotetotest ran.We fixed with a try-catch- Signed-off-by: Vincenzo Palazzo --- lnprototest/funding.py | 4 + lnprototest/runner.py | 9 - tests/helpers.py | 27 ++- tests/spec_helper.py | 47 +++++ tests/test_bolt2-01-close_channel.py | 286 ++------------------------- 5 files changed, 96 insertions(+), 277 deletions(-) create mode 100644 tests/spec_helper.py diff --git a/lnprototest/funding.py b/lnprototest/funding.py index 6f2a94c..6734825 100644 --- a/lnprototest/funding.py +++ b/lnprototest/funding.py @@ -358,6 +358,10 @@ def from_utxo( tx.wit = CTxWitness([CTxInWitness(CScriptWitness([sig, inkey_pub.format()]))]) return funding, tx.serialize().hex() + def script_pub_key(self) -> str: + """Return the script pub key""" + return CScript([script.OP_0, sha256(self.redeemscript()).digest()]).hex() + def channel_id(self) -> str: # BOLT #2: This message introduces the `channel_id` to identify the # channel. It's derived from the funding transaction by combining the diff --git a/lnprototest/runner.py b/lnprototest/runner.py index 162e03f..b0cccc8 100644 --- a/lnprototest/runner.py +++ b/lnprototest/runner.py @@ -58,15 +58,6 @@ def __init__(self, config: Any): else: self.logger.setLevel(logging.INFO) - def __enter__(self) -> "Runner": - """Call the method when enter inside the class the first time""" - self.start() - return self - - def __del__(self): - """Call the method when the class is out of scope""" - self.stop() - def _is_dummy(self) -> bool: """The DummyRunner returns True here, as it can't do some things""" return False diff --git a/tests/helpers.py b/tests/helpers.py index 46f5d5c..77d127e 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,7 +1,7 @@ import bitcoin.core import coincurve -from typing import Tuple -from lnprototest import privkey_expand, KeySet +from typing import Tuple, Union, Sequence, List +from lnprototest import privkey_expand, KeySet, Runner, Event # Here are the keys to spend funds, derived from BIP32 seed # `0000000000000000000000000000000000000000000000000000000000000001`: @@ -145,3 +145,26 @@ def gen_random_keyset(counter: int = 20) -> KeySet: delayed_payment_base_secret=f"{counter + 4}", shachain_seed="00" * 32, ) + + +def run_runner(runner: Runner, test: Union[Sequence, List[Event], Event]) -> None: + """ + The pytest using the assertion as safe failure, and the exception it is only + an event that must not happen. + + From design, lnprototest fails with an exception, and for this reason, if the + lnprototest throws an exception, we catch it, and we fail with an assent. + """ + try: + runner.run(test) + except Exception as ex: + runner.stop() + assert False, ex + + +def merge_events_sequences( + pre: Union[Sequence, List[Event], Event], post: Union[Sequence, List[Event], Event] +) -> Union[Sequence, List[Event], Event]: + """Merge the two list in the pre-post order""" + pre.extend(post) + return pre diff --git a/tests/spec_helper.py b/tests/spec_helper.py new file mode 100644 index 0000000..e8befc2 --- /dev/null +++ b/tests/spec_helper.py @@ -0,0 +1,47 @@ +""" +Spec helper is a collection of functions to help to speed up the +operation of interoperability testing. + +It contains a method to generate the correct sequence of channel opening +and, and it feels a dictionary with all the propriety that needs to +be used after this sequence of steps. + +author: https://github.com/vincenzopalazzo +""" +from typing import List, Union + +from lnprototest import ( + TryAll, + Connect, + Block, + ExpectMsg, + RawMsg, + Msg, + Runner, + Funding, +) +from helpers import ( + utxo, +) + + +def open_and_announce_channel_helper( + runner: Runner, tx_spendable: str, opts: dict +) -> List[Union[Block, Connect, ExpectMsg, TryAll]]: + funding, funding_tx = Funding.from_utxo( + *utxo(0), + local_node_privkey="02", + local_funding_privkey="10", + remote_node_privkey="03", + remote_funding_privkey="20" + ) + opts["funding"] = funding + opts["funding_tx"] = funding_tx + return [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="03"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=""), + Block(blockheight=103, number=6, txs=[funding_tx]), + RawMsg(funding.channel_announcement("103x1x0", "")), + ] diff --git a/tests/test_bolt2-01-close_channel.py b/tests/test_bolt2-01-close_channel.py index b06d763..4e00e52 100644 --- a/tests/test_bolt2-01-close_channel.py +++ b/tests/test_bolt2-01-close_channel.py @@ -24,283 +24,37 @@ """ from lnprototest import ( - TryAll, - Connect, - Block, - FundChannel, ExpectMsg, - ExpectTx, Msg, - RawMsg, - AcceptFunding, - CreateFunding, - Commit, Runner, - CloseChannel, - remote_funding_pubkey, - remote_revocation_basepoint, - remote_payment_basepoint, - remote_htlc_basepoint, - remote_per_commitment_point, - remote_delayed_payment_basepoint, - Side, - CheckEq, - msat, - remote_funding_privkey, - regtest_hash, - bitfield, -) -from lnprototest.stash import ( - sent, - rcvd, - commitsig_to_send, - commitsig_to_recv, - channel_id, - funding_txid, - funding_tx, - funding, -) -from helpers import ( - utxo, - tx_spendable, - funding_amount_for_utxo, - pubkey_of, - gen_random_keyset, ) +from helpers import run_runner, tx_spendable, merge_events_sequences +from spec_helper import open_and_announce_channel_helper def test_close_channel_shutdown_msg(runner: Runner) -> None: """Close the channel with the other peer, and check if the shutdown message works in the expected way.""" - local_funding_privkey = "20" - local_keyset = gen_random_keyset() + # test preconditions. + # the option that the helper method feel for us + test_opts = {} + pre_events = open_and_announce_channel_helper( + runner=runner, tx_spendable=tx_spendable, opts=test_opts + ) + funding = test_opts["funding"] test = [ - Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey="02"), - ExpectMsg("init"), - TryAll( - # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #9: - # | 20/21 | `option_anchor_outputs` | Anchor outputs - Msg("init", globalfeatures="", features=bitfield(13, 21)), - # BOLT #9: - # | 12/13 | `option_static_remotekey` | Static key for remote output - Msg("init", globalfeatures="", features=bitfield(13)), - # And not. - Msg("init", globalfeatures="", features=""), - ), - TryAll( - # Accepter side: we initiate a new channel. - [ - Msg( - "open_channel", - chain_hash=regtest_hash, - temporary_channel_id="00" * 32, - funding_satoshis=funding_amount_for_utxo(0), - push_msat=0, - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - channel_reserve_satoshis=9998, - htlc_minimum_msat=0, - feerate_per_kw=253, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - max_accepted_htlcs=483, - funding_pubkey=pubkey_of(local_funding_privkey), - revocation_basepoint=local_keyset.revocation_basepoint(), - payment_basepoint=local_keyset.payment_basepoint(), - delayed_payment_basepoint=local_keyset.delayed_payment_basepoint(), - htlc_basepoint=local_keyset.htlc_basepoint(), - first_per_commitment_point=local_keyset.per_commit_point(0), - channel_flags=1, - ), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex("270F"))), - ExpectMsg( - "accept_channel", - temporary_channel_id=sent(), - funding_pubkey=remote_funding_pubkey(), - revocation_basepoint=remote_revocation_basepoint(), - payment_basepoint=remote_payment_basepoint(), - delayed_payment_basepoint=remote_delayed_payment_basepoint(), - htlc_basepoint=remote_htlc_basepoint(), - first_per_commitment_point=remote_per_commitment_point(0), - minimum_depth=3, - channel_reserve_satoshis=9998, - ), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex("270F"))), - # Create and stash Funding object and FundingTx - CreateFunding( - *utxo(0), - local_node_privkey="02", - local_funding_privkey=local_funding_privkey, - remote_node_privkey=runner.get_node_privkey(), - remote_funding_privkey=remote_funding_privkey() - ), - Commit( - funding=funding(), - opener=Side.local, - local_keyset=local_keyset, - local_to_self_delay=rcvd("to_self_delay", int), - remote_to_self_delay=sent("to_self_delay", int), - local_amount=msat(sent("funding_satoshis", int)), - remote_amount=0, - local_dust_limit=546, - remote_dust_limit=546, - feerate=253, - local_features=sent("init.features"), - remote_features=rcvd("init.features"), - ), - Msg( - "funding_created", - temporary_channel_id=rcvd(), - funding_txid=funding_txid(), - funding_output_index=0, - signature=commitsig_to_send(), - ), - ExpectMsg( - "funding_signed", - channel_id=channel_id(), - signature=commitsig_to_recv(), - ), - # Mine it and get it deep enough to confirm channel. - Block(blockheight=103, number=3, txs=[funding_tx()]), - ExpectMsg( - "funding_locked", - channel_id=channel_id(), - next_per_commitment_point="032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206", - ), - Msg( - "funding_locked", - channel_id=channel_id(), - next_per_commitment_point="027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d", - ), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex("270F"))), - ], - # Now we test the 'opener' side of an open_channel (node initiates) - [ - FundChannel(amount=999877), - # This gives a channel of 999877sat - ExpectMsg( - "open_channel", - chain_hash=regtest_hash, - funding_satoshis=999877, - push_msat=0, - dust_limit_satoshis=546, - htlc_minimum_msat=0, - channel_reserve_satoshis=9998, - to_self_delay=6, - funding_pubkey=remote_funding_pubkey(), - revocation_basepoint=remote_revocation_basepoint(), - payment_basepoint=remote_payment_basepoint(), - delayed_payment_basepoint=remote_delayed_payment_basepoint(), - htlc_basepoint=remote_htlc_basepoint(), - first_per_commitment_point=remote_per_commitment_point(0), - # FIXME: Check more fields! - channel_flags="01", - ), - Msg( - "accept_channel", - temporary_channel_id=rcvd(), - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - channel_reserve_satoshis=9998, - htlc_minimum_msat=0, - minimum_depth=3, - max_accepted_htlcs=483, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - funding_pubkey=pubkey_of(local_funding_privkey), - revocation_basepoint=local_keyset.revocation_basepoint(), - payment_basepoint=local_keyset.payment_basepoint(), - delayed_payment_basepoint=local_keyset.delayed_payment_basepoint(), - htlc_basepoint=local_keyset.htlc_basepoint(), - first_per_commitment_point=local_keyset.per_commit_point(0), - ), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex("270F"))), - ExpectMsg( - "funding_created", temporary_channel_id=rcvd("temporary_channel_id") - ), - # Now we can finally stash the funding information. - AcceptFunding( - rcvd("funding_created.funding_txid"), - funding_output_index=rcvd( - "funding_created.funding_output_index", int - ), - funding_amount=rcvd("open_channel.funding_satoshis", int), - local_node_privkey="02", - local_funding_privkey=local_funding_privkey, - remote_node_privkey=runner.get_node_privkey(), - remote_funding_privkey=remote_funding_privkey(), - ), - Commit( - funding=funding(), - opener=Side.remote, - local_keyset=local_keyset, - local_to_self_delay=rcvd("open_channel.to_self_delay", int), - remote_to_self_delay=sent("accept_channel.to_self_delay", int), - local_amount=0, - remote_amount=msat(rcvd("open_channel.funding_satoshis", int)), - local_dust_limit=sent("accept_channel.dust_limit_satoshis", int), - remote_dust_limit=rcvd("open_channel.dust_limit_satoshis", int), - feerate=rcvd("open_channel.feerate_per_kw", int), - local_features=sent("init.features"), - remote_features=rcvd("init.features"), - ), - # Now we've created commit, we can check sig is valid! - CheckEq(rcvd("funding_created.signature"), commitsig_to_recv()), - Msg( - "funding_signed", - channel_id=channel_id(), - signature=commitsig_to_send(), - ), - # It will broadcast tx - ExpectTx(rcvd("funding_created.funding_txid")), - # Mine three blocks to confirm channel. - Block(blockheight=103, number=3), - Msg( - "funding_locked", - channel_id=sent(), - next_per_commitment_point=local_keyset.per_commit_point(1), - ), - ExpectMsg( - "funding_locked", - channel_id=sent(), - next_per_commitment_point=remote_per_commitment_point(1), - ), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex("270F"))), - # After the funding locked the node update with the - # channel data on the other side with a channel update. - ExpectMsg("channel_update"), - Block(blockheight=106, number=3), - # From Spec - # Once both nodes have exchanged funding_locked (and optionally announcement_signatures), - # the channel can be used to make payments via Hashed Time Locked Contracts. - ExpectMsg("announcement_signatures"), - # Start closing the channel - Msg( - "shutdown", - channel_id=sent(), - scriptpubkey="51210253cdf835e328346a4f19de099cf3d42d4a7041e073cd4057a1c4fd7cdbb1228f2103ae903722f21f85e651b8f9b18fc854084fb90eeb76452bdcfd0cb43a16a382a221036c264d68a9727afdc75949f7d7fa71910ae9ae8001a1fbffa6f7ce000976597c21036429fa8a4ef0b2b1d5cb553e34eeb90a32ab19fae1f0024f332ab4f74283a7282103d4232f19ea85051e7b76bf5f01d03e17eea8751463dee36d71413a739de1a92755ae", - ), - CloseChannel(channel_id=sent()), - ExpectMsg( - "shutdown", - # TODO: injects the scriptpubkey here - channel_id=sent(), - ), - ExpectMsg("channel_update"), - Msg( - "shutdown", - channel_id=sent(), - ), - ], + Msg( + "shutdown", + channel_id=funding.channel_id(), + # TODO: The scriptpubkey should be a new one + # this should cause the failure + scriptpubkey="001473daa75958d5b2ddca87a6c279bb7cb307167037", ), + # FIXME: I received the message type 1, it should be a warning message, + # this should happen when there is a bad scriptpubkey + # BOLT2: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#channel-close + ExpectMsg("shutdown", channel_id=funding.channel_id()), ] - - runner.run(test) + run_runner(runner, merge_events_sequences(pre=pre_events, post=test))