From 146276c12a96639b5e7715ff4ead40d0890ffa6a Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Fri, 29 Oct 2021 21:46:02 +0200 Subject: [PATCH] tests: Add CPFP tests --- tests/test_misc.py | 154 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_rpc.py | 19 ++++++ 2 files changed, 173 insertions(+) diff --git a/tests/test_misc.py b/tests/test_misc.py index 67e03498..e89fd315 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -4,6 +4,7 @@ from bitcoin.core import COIN from fixtures import * +from test_framework import serializations from test_framework.utils import ( TailableProc, POSTGRES_IS_SETUP, @@ -226,3 +227,156 @@ def test_no_cosig_server(revault_network): rn.spend_vaults_anyhow(vaults[:2]) rn.unvault_vaults_anyhow([vaults[-1]]) rn.cancel_vault(vaults[-1]) + + +@pytest.mark.skipif(not POSTGRES_IS_SETUP, reason="Needs Postgres for servers db") +def test_cpfp_transaction(revault_network, bitcoind): + CSV = 12 + revault_network.deploy(2, 1, csv=CSV, with_watchtowers=False) + man = revault_network.mans()[0] + stks = revault_network.stks() + amount = 0.24 + vault = revault_network.fund(amount) + deposit = f"{vault['txid']}:{vault['vout']}" + + revault_network.secure_vault(vault) + revault_network.activate_vault(vault) + spend_psbt = revault_network.broadcast_unvaults_anyhow([vault], priority=True) + + unvault_psbt = serializations.PSBT() + unvault_b64 = stks[0].rpc.getunvaulttx(deposit)["unvault_tx"] + unvault_psbt.deserialize(unvault_b64) + unvault_psbt.tx.calc_sha256() + unvault_txid = unvault_psbt.tx.hash + spend_txid = spend_psbt.tx.hash + + # Let's test that the revaultd poller behaves correctly + # Case 1: the unvault is broadcasted and inserted in the CPFP table + for w in revault_network.participants(): + wait_for( + lambda: len(w.rpc.listvaults(["unvaulting"], [deposit])["vaults"]) == 1, + ) + man.wait_for_log( + f"Inserted freshly-broadcasted Unvault transaction {unvault_txid} in CPFPable table", + ) + + # Uh oh! The feerate is too low, miners aren't including our transaction... + # 1 block is fine, maybe miners didn't see the tx... + bitcoind.generate_blocks_censor(1, [unvault_txid]) + man.wait_for_log("Checking if transactions need CPFP...") + assert bitcoind.rpc.getmempoolentry(unvault_txid)["descendantcount"] == 1 + + # 2 blocks and yet not included? We start being aggressive. + bitcoind.generate_blocks_censor(1, [unvault_txid]) + man.wait_for_log( + f"CPFPed transaction with id '{unvault_txid}'", + ) + assert bitcoind.rpc.getmempoolentry(unvault_txid)["descendantcount"] == 2 + + # Case 2: the unvault is confirmed and removed from the CPFP table + bitcoind.generate_block(1) + for w in revault_network.participants(): + wait_for( + lambda: len(w.rpc.listvaults(["unvaulted"], [deposit])["vaults"]) == 1, + ) + man.wait_for_log( + f"Removed Unvault transaction {unvault_txid} from CPFPable table as it's confirmed", + ) + + # Case 3: the unvault got unconfirmed, insert it again in the CPFP table + bitcoind.simple_reorg(bitcoind.rpc.getblockcount() - 1, -1) + for w in revault_network.participants(): + w.wait_for_log("Detected reorg") + wait_for( + lambda: len(w.rpc.listvaults(["unvaulting"], [deposit])["vaults"]) == 1, + ) + man.wait_for_log( + f"Inserted Unvault transaction {unvault_txid} in CPFPable table as it got unconfirmed", + ) + + # TODO: Case 4: the unvault got evicted from the mempool, remove it from the CPFP table + # This is not implemented yet + + # Alright, now let's do everything again for the spend :tada: + + # Confirming the unvault + bitcoind.generate_block(1) + for w in revault_network.participants(): + wait_for( + lambda: len(w.rpc.listvaults(["unvaulted"], [deposit])["vaults"]) == 1, + ) + + bitcoind.generate_block(CSV - 1) + man.wait_for_logs( + [ + f"Succesfully broadcasted Spend tx '{spend_txid}'", + f"Inserted freshly-broadcasted Spend transaction {spend_txid} in CPFPable table", + ] + ) + + for w in revault_network.participants(): + wait_for( + lambda: len(w.rpc.listvaults(["spending"], [deposit])["vaults"]) == 1, + ) + + # Uh oh! The feerate is too low, miners aren't including our transaction... + # 1 block is fine, maybe miners didn't see the tx... + bitcoind.generate_blocks_censor(1, [spend_txid]) + assert bitcoind.rpc.getmempoolentry(spend_txid)["descendantcount"] == 1 + + # 2 blocks and yet not included? We start being aggressive. + bitcoind.generate_blocks_censor(1, [spend_txid]) + man.wait_for_log( + f"CPFPed transaction with id '{spend_txid}'", + ) + assert bitcoind.rpc.getmempoolentry(spend_txid)["descendantcount"] == 2 + + # Case 2: the spend is confirmed and removed from the CPFP table + bitcoind.generate_block(1) + for w in revault_network.participants(): + wait_for( + lambda: len(w.rpc.listvaults(["spent"], [deposit])["vaults"]) == 1, + ) + man.wait_for_log( + f"Removed Spend transaction {spend_txid} from CPFPable table as it's confirmed", + ) + + # Case 3: the spend got unconfirmed, insert it again in the CPFP table + bitcoind.simple_reorg(bitcoind.rpc.getblockcount() - 1, -1) + for w in revault_network.participants(): + w.wait_for_log("Detected reorg") + wait_for( + lambda: len(w.rpc.listvaults(["spending"], [deposit])["vaults"]) == 1, + ) + man.wait_for_log( + f"Inserted Spend transaction {spend_txid} in CPFPable table as it got unconfirmed", + ) + + # TODO: Case 4: the spend got evicted from the mempool, remove it from the CPFP table + # This case is (hopefully <3) handled in the code, it just needs to be tested + + # Let's test that non priority txs don't get cpfped + amount = 0.24 + vault = revault_network.fund(amount) + deposit = f"{vault['txid']}:{vault['vout']}" + + revault_network.secure_vault(vault) + revault_network.activate_vault(vault) + spend_psbt = revault_network.unvault_vaults_anyhow([vault], priority=False) + spend_txid = spend_psbt.tx.hash + + bitcoind.generate_block(CSV - 1) + man.wait_for_log( + f"Succesfully broadcasted Spend tx '{spend_txid}'", + ) + + # Uh oh! The feerate is too low, miners aren't including our transaction... + # 1 block is fine, maybe miners didn't see the tx... + bitcoind.generate_blocks_censor(1, [spend_txid]) + assert bitcoind.rpc.getmempoolentry(spend_txid)["descendantcount"] == 1 + + # 2 blocks and yet not included? We are still calm, this tx has no priority... + bitcoind.generate_blocks_censor(1, [spend_txid]) + man.wait_for_log("Checking if transactions need CPFP...") + # Nah, they don't + assert bitcoind.rpc.getmempoolentry(spend_txid)["descendantcount"] == 1 diff --git a/tests/test_rpc.py b/tests/test_rpc.py index 9074cc3b..295bcc09 100644 --- a/tests/test_rpc.py +++ b/tests/test_rpc.py @@ -1143,3 +1143,22 @@ def test_getserverstatus(revault_network, bitcoind): res = w.rpc.call("getserverstatus") for watchtower in res["watchtowers"]: assert not watchtower["reachable"] + + +@pytest.mark.skipif(not POSTGRES_IS_SETUP, reason="Needs Postgres for servers db") +def test_setspendtx_cpfp_not_enabled(revault_network, bitcoind): + CSV = 12 + revault_network.deploy(2, 1, n_stkmanagers=1, csv=CSV, with_cpfp=False) + man = revault_network.mans()[1] + stks = revault_network.stks() + amount = 0.24 + vault = revault_network.fund(amount) + deposit = f"{vault['txid']}:{vault['vout']}" + + revault_network.secure_vault(vault) + revault_network.activate_vault(vault) + with pytest.raises( + RpcError, + match="Can't read the cpfp key", + ): + revault_network.broadcast_unvaults_anyhow([vault], priority=True)