package validation: relax the package-not-child-with-unconfirmed-parents rule #31385

pull glozow wants to merge 2 commits into bitcoin:master from glozow:2024-11-relax-unconf changing 7 files +86 −83
  1. glozow commented at 1:33 am on November 28, 2024: member

    Broadens the package validation interface, see #27463 for wider context.

    On master, package rules include that (1) the package topology must be child-wth-parents (2) all of the child’s unconfirmed parents must be present. This PR relaxes the second rule, leaving the first rule untouched (there are plans to change the second rule, but not here).

    Original motivation for this rule was based on the idea that we would have a child-with-unconfirmed-parents package relay protocol, and this would verify that the peer provided the “correct” package. For various reasons, we’re not planning on doing this. We could potentially do this for ancestor packages (with a similar definition that all UTXOs to make the tx valid are available in this package), but it’s also questionable whether it’s useful to enforce this.

    This rule gets in the way of certain usage of 1p1c package relay currently. If a transaction has multiple parents, of which only 1 requires a package CPFP, this rule blocks the package from relaying. Even if all the non-low-feerate parents are already in mempool, when the p2p logic submits the 1p1c package, it gets rejected for not meeting this rule.

  2. glozow added the label TX fees and policy on Nov 28, 2024
  3. DrahtBot commented at 1:33 am on November 28, 2024: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31385.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Stale ACK instagibbs

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    No conflicts as of last run.

  4. DrahtBot added the label CI failed on Nov 28, 2024
  5. DrahtBot commented at 2:08 am on November 28, 2024: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/33632131707

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  6. relax child-with-unconfirmed-parents rule
    This rule was originally introduced along with a very early proposal for
    package relay as a way to verify that the "correct"
    child-with-unconfirmed-parents package was provided for a transaction,
    where correctness was defined as all of the transactions unconfirmed
    parents. However, we are not planning to introduce a protocol where
    peers would be asked to send these packages.
    
    This rule has downsides: if a transaction has multiple parents but only
    1 that requires package CPFP to be accepted, the current rule prevents
    us from accepting that package. Even if the other parents are already in
    mempool, the p2p logic will only submit the 1p1c package, which fails
    this check. See the test in p2p_1p1c_network.py
    0f0f979c06
  7. in test/functional/p2p_opportunistic_1p1c.py:379 in dad28726c9 outdated
    375-        # Same error if submitted through submitpackage without parent_high
    376-        package_hex_missing_parent = [parent_low["hex"], child["hex"]]
    377-        result_missing_parent = node.submitpackage(package_hex_missing_parent)
    378-        assert_equal(result_missing_parent["package_msg"], "package-not-child-with-unconfirmed-parents")
    379+        assert parent_low["txid"] in node_mempool
    380+        assert child["txid"] in node_mempool
    


    instagibbs commented at 4:10 pm on November 29, 2024:

    can expand this test case to be a little more interesting with a grandparent as well:

     0diff --git a/test/functional/p2p_opportunistic_1p1c.py b/test/functional/p2p_opportunistic_1p1c.py
     1index 00b94d1a71..8aa5b43a53 100755
     2--- a/test/functional/p2p_opportunistic_1p1c.py
     3+++ b/test/functional/p2p_opportunistic_1p1c.py
     4@@ -62,18 +62,18 @@ class PackageRelayTest(BitcoinTestFramework):
     5             "-datacarriersize=100000",
     6             "-maxmempool=5",
     7         ]]
     8         self.supports_cli = False
     9 
    10-    def create_tx_below_mempoolminfee(self, wallet):
    11+    def create_tx_below_mempoolminfee(self, wallet, utxo_to_spend=None):
    12         """Create a 1-input 1sat/vB transaction using a confirmed UTXO. Decrement and use
    13         self.sequence so that subsequent calls to this function result in unique transactions."""
    14 
    15         self.sequence -= 1
    16         assert_greater_than(self.nodes[0].getmempoolinfo()["mempoolminfee"], FEERATE_1SAT_VB)
    17 
    18-        return wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, sequence=self.sequence, confirmed_only=True)
    19+        return wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, sequence=self.sequence, utxo_to_spend=utxo_to_spend, confirmed_only=True) [@cleanup](/bitcoin-bitcoin/contributor/cleanup/)
    20     def test_basic_child_then_parent(self):
    21         node = self.nodes[0]
    22         self.log.info("Check that opportunistic 1p1c logic works when child is received before parent")
    23@@ -341,34 +341,42 @@ class PackageRelayTest(BitcoinTestFramework): [@cleanup](/bitcoin-bitcoin/contributor/cleanup/)
    24     def test_other_parent_in_mempool(self):
    25         self.log.info("Check opportunistic 1p1c works even if child already has another parent in mempool")
    26         node = self.nodes[0]
    27 
    28+        # Grandparent will enter mempool by itself
    29+        grandparent_high = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
    30+
    31         # This parent needs CPFP
    32-        parent_low = self.create_tx_below_mempoolminfee(self.wallet)
    33+        parent_low = self.create_tx_below_mempoolminfee(self.wallet, utxo_to_spend=grandparent_high["new_utxo"])
    34         # This parent does not need CPFP and can be submitted alone ahead of time
    35         parent_high = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
    36         child = self.wallet.create_self_transfer_multi(
    37             utxos_to_spend=[parent_high["new_utxo"], parent_low["new_utxo"]],
    38             fee_per_output=999*parent_low["tx"].get_vsize(),
    39         )
    40 
    41         peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
    42 
    43-        # 1. Send first parent which will be accepted.
    44+        # 1. Send grandparent which will be accepted
    45+        peer_sender.send_and_ping(msg_tx(grandparent_high["tx"]))
    46+        assert grandparent_high["txid"] in node.getrawmempool()
    47+
    48+        # 2. Send first parent which will be accepted.
    49         peer_sender.send_and_ping(msg_tx(parent_high["tx"]))
    50         assert parent_high["txid"] in node.getrawmempool()
    51 
    52-        # 2. Send child.
    53+        # 3. Send child.
    54         peer_sender.send_and_ping(msg_tx(child["tx"]))
    55 
    56-        # 3. Node requests parent_low.
    57+        # 4. Node requests parent_low.
    58         parent_low_txid_int = int(parent_low["txid"], 16)
    59         peer_sender.wait_for_getdata([parent_low_txid_int])
    60         peer_sender.send_and_ping(msg_tx(parent_low["tx"]))
    61 
    62         node_mempool = node.getrawmempool()
    63+        assert grandparent_high["txid"] in node_mempool
    64         assert parent_high["txid"] in node_mempool
    65         assert parent_low["txid"] in node_mempool
    66         assert child["txid"] in node_mempool
    67 
    68     def run_test(self):
    

    glozow commented at 1:55 pm on December 4, 2024:
    added
  8. glozow force-pushed on Dec 4, 2024
  9. glozow commented at 2:12 pm on December 4, 2024: member
    Rebased after #31096 and took @instagibbs’ comment, thank you
  10. glozow marked this as ready for review on Dec 4, 2024
  11. instagibbs commented at 2:55 pm on December 4, 2024: member

    Original motivation for this rule was based on the idea that we would have a child-with-unconfirmed-parents package relay protocol, and this would verify that the peer provided the “correct” package

    Was the idea that the full ancestor package was to be “pulled” into the package validation logic, including in-mempool things?

  12. in src/test/txpackage_tests.cpp:444 in 0f0f979c06 outdated
    440@@ -441,20 +441,6 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
    441         BOOST_CHECK_EQUAL(result_quit_early.m_state.GetResult(), PackageValidationResult::PCKG_TX);
    442     }
    443 
    444-    // Child with missing parent.
    


    instagibbs commented at 3:27 pm on December 4, 2024:

    Might still be an interesting case to keep around, shoving it to the end after parent is already accepted:

     0diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp
     1index 09e719ff4c..be0ebac834 100644
     2--- a/src/test/txpackage_tests.cpp
     3+++ b/src/test/txpackage_tests.cpp
     4@@ -476,10 +476,30 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
     5             BOOST_CHECK(it_child_deduped->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
     6         }
     7 
     8         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     9     }
    10+
    11+    // In-mempool parent and child with missing parent.
    12+    mtx_child.vin.emplace_back(COutPoint(package_unrelated[0]->GetHash(), 0));
    13+    CTransactionRef tx_child_missing_parent = MakeTransactionRef(mtx_child);
    14+    Package package_missing_parent;
    15+    package_missing_parent.push_back(tx_parent);
    16+    package_missing_parent.push_back(tx_child_missing_parent);
    17+    {
    18+        const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
    19+                                                             package_missing_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
    20+        auto it_parent = result_missing_parent.m_tx_results.find(tx_parent->GetWitnessHash());
    21+        auto it_child = result_missing_parent.m_tx_results.find(tx_child_missing_parent->GetWitnessHash());
    22+        BOOST_CHECK(result_missing_parent.m_state.IsInvalid());
    23+        BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
    24+        BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "transaction failed");
    25+        BOOST_CHECK(it_parent->second.m_state.IsValid());
    26+        BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MISSING_INPUTS);
    27+        BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "bad-txns-inputs-missingorspent");
    28+        BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
    29+    }
    30 }
    31 
    32 // Tests for packages containing a single transaction
    33 BOOST_AUTO_TEST_CASE(package_single_tx)
    34 {
    

    glozow commented at 12:44 pm on December 10, 2024:

    I’ve added a different test, creating a new set of transactions instead of borrowing stuff from earlier tests. Testing

    1. Missing parent: gives you TX_RECONSIDERABLE and TX_MISSING_INPUTS
    2. Submit the missing parent
    3. Try again, should work just fine
  13. instagibbs commented at 3:55 pm on December 4, 2024: member

    approach ACK 0f0f979c062055c28383bd4b06f69be90380fe53

    changes seem correct, and I like less code

    I have to think a bit about the UX that we currently have:

    1. submitpackage requires child-with-parents-tree (no other ancestors, unless single tx)
    2. AcceptPackage requires child-with-parents (no other ancestors)
    3. 1P1C relay allows relay of child-with-parent (no other ancestors)

    and after this PR:

    1. submitpackage requires child-with-parents-tree (but can have arbitrary in-mempool ancestors)
    2. AcceptPackage requires child-with-parents (but can have arbitrary in-mempool ancestors)
    3. 1P1C relays a child-with-parent (but can have arbitrary in-mempool ancestors)
  14. DrahtBot removed the label CI failed on Dec 4, 2024
  15. glozow commented at 4:23 pm on December 4, 2024: member

    Was the idea that the full ancestor package was to be “pulled” into the package validation logic, including in-mempool things?

    Yes, I remember thinking “this is restrictive but easy to work around because you can just fetch the parents from mempool and add them to the package if you need to” (and we could code that up) but that’s a pain. And I’m no longer convinced it’s useful at all to require all parents be present.

  16. [unit test] package submission 2p1c with 1 parent missing b14e1a8688

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-01-21 06:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me