validate package transactions with their in-package ancestor sets #26711

pull glozow wants to merge 7 commits into bitcoin:master from glozow:2022-12-subpackages changing 9 files +1754 −62
  1. glozow commented at 11:27 am on December 16, 2022: member

    This contains everything to make mempool/validation logic ready for package relay (see #27463).

    Design goals:

    • Be able to gracefully deal with any arbitrary list of transactions (these come from p2p)
    • Validate any ancestor package so that the incentive-compatible transactions end up in our mempool.
      • If the transactions would be accepted individually, they should also be accepted through AcceptPackage. We don’t want to accidentally reject things just because we happened to download them as a package.
      • Bug prior to these changes: we required IsChildWithParents but if there were dependencies between the parents, we could end up (1) accepting a low-feerate child or (2) rejecting a high-feerate parent CPFPing another parent. See the “interdependent parents” test case for a specific example.
    • Be DoS-resistant.
      • Avoid quadratic validation costs.
      • Avoid loading a lot of stuff from disk, or loading repeatedly.

    There are 2 main improvements to package evaluation here: (1) We submit transactions with their in-package ancestor sets. - See unit test package_ppfp: without this change, we would reject everything. - See unit test package_ppfc: shows that this doesn’t let us do “parent pays for child;” we only do this when the individual and ancestor feerates meet mempool minimum feerate (2) We linearize the package transactions based on ancestor set scoring. - See unit test package_needs_reorder: without this change, if we use the original order of transactions, we would only accept 1 grandparent, even if we submit subpackages. - See unit test package_desc_limits: without this change, we accept one of the lower-feerate transactions (a bit more of a “nice to have” than a “must have” example).

    A description of the package validation logic (originally #26711 (comment)):

    • Basic sanitization. Quit if it’s too big, inconsistent, etc.
    • Linearize (Topological sort only)
    • PreChecks loop For each tx, grab UTXOs to calculate fees and filter out anything we should skip:
      • If already in mempool (or same txid in mempool), mark as skip
      • If missing inputs or conflict, record this failure and mark this and all descendants as skip.
      • If no failures or TX_SINGLE_FAILURE, continue
      • For some failures that we expect due to differing chainstates, skip these transactions and their descendants, but continue.
      • Otherwise, record this failure and mark that we will quit_early (i.e. not do the Subpackage validation loop).
    • Refine our linearization using the fee information.
    • Subpackage validation loop For each transaction in the new linearized order:
      • Get the transaction’s ancestor subpackage.
      • If the feerate of this transaction is insufficient, continue;
      • If the feerate of this subpackage is insufficient, continue;
      • Otherwise, try to submit the subpackage, using AcceptSingleTransaction() if it’s just 1 tx
      • if at any point we get a non-fee-related error, abort all.
    • Call LimitMempoolSize
    • Backfill results:
      • If the transaction was in mempool, check to see if it’s still there in case it was trimmed in LimitMempoolSize.
      • Try to use results from the subpackage validation loop.
      • If that doesn’t exist (i.e. we quit early), use results from prechecks loop.
      • If that doesn’t exist (i.e. we quit early and we hadn’t found a failure with it yet), fill with TX_UNKNOWN.

    This means we will call PreChecks for each transaction 2 times (fewer if we quit early), and run all other validation checks at most 1 time. A transaction shouldn’t be validated in the subpackage validation loop more than once.

  2. DrahtBot commented at 11:27 am on December 16, 2022: contributor

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

    Code Coverage

    For detailed information about the code coverage, see the test coverage report.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Stale ACK instagibbs, naumenkogs

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

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #28813 (rpc: permit any ancestor package through submitpackage by glozow)
    • #28690 (build: Introduce internal kernel library by TheCharlatan)
    • #28455 (refactor: share and use GenerateRandomKey helper by theStack)
    • #28391 (refactor: Simplify CTxMempool/BlockAssembler fields, remove some external mapTx access by TheCharlatan)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  3. glozow force-pushed on Dec 19, 2022
  4. glozow force-pushed on Dec 20, 2022
  5. DrahtBot added the label Needs rebase on Jan 11, 2023
  6. glozow force-pushed on Jan 11, 2023
  7. DrahtBot removed the label Needs rebase on Jan 11, 2023
  8. glozow force-pushed on Jan 11, 2023
  9. glozow force-pushed on Jan 12, 2023
  10. glozow added the label Validation on Jan 12, 2023
  11. glozow added the label Mempool on Jan 12, 2023
  12. glozow force-pushed on Jan 12, 2023
  13. glozow marked this as ready for review on Jan 13, 2023
  14. in src/validation.cpp:1398 in cb065422e5 outdated
    1393@@ -1394,6 +1394,11 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
    1394             // Provide the wtxid of the mempool tx so that the caller can look it up in the mempool.
    1395             results_final.emplace(wtxid, MempoolAcceptResult::MempoolTxDifferentWitness(iter.value()->GetTx().GetWitnessHash()));
    1396         } else {
    1397+            if (wtxid == child->GetWitnessHash() && !quit_early) {
    1398+                txns_package_eval.push_back(tx);
    


    naumenkogs commented at 12:20 pm on January 17, 2023:
    cb065422e58be7558e6c2ad9eadfa885bb48c708 Let’s add an Assume checking tx == package.end()?

    naumenkogs commented at 1:25 pm on January 17, 2023:

    cb065422e58be7558e6c2ad9eadfa885bb48c708

    After this change it’s impossible to hit quit_early=false and txns_package_eval.empty(), which was possible before. It means that even if everything was either (valid, already in the mempool, or invalid due to a subset of failures) — you will force the checks again? (before this change, it could have terminated early)

    Maybe related to the other comment i left.


    glozow commented at 11:53 am on January 18, 2023:

    After this change it’s impossible to hit quit_early=false and txns_package_eval.empty(), which was possible before.

    Right, quit_early=false && txns_package_eval.empty() means everything so far has been valid or txid already in mempool.

    It means that even if everything was either (valid, already in the mempool, or invalid due to a subset of failures) — you will force the checks again? (before this change, it could have terminated early)

    Ok I will try to break down all the possibilities in this scenario. If we get to the child and quit_early=false && txns_package_eval.empty(), everything else has been valid, and we should validate the child. It doesn’t matter if we do it inside or outside this loop, but it’s better to only do it 1 time. The child’s result can be: valid, policy/missing inputs, or other failure. If txid already in mempool we would have already exited the loop.

    Before the commit:

    • If valid: the tx was validated 1 time, inside the loop.
    • If policy/missing inputs: the tx was validated 2 times, both inside the loop and with AcceptMultipleTransactions(txns_package_eval) outside the loop.
    • If other failure: the tx was validated 1 time, inside the loop.

    Right after the loop, quit_early || txns_package_eval.empty() hits, but none of them terminated without validating the child.

    After the commit:

    • If valid: the tx is validated 1 time, outside the loop.
    • If policy/missing inputs: the tx is validated 1 time, outside the loop.
    • If other failure: the tx is validated 1 time, outside the loop.

    Right after the loop, quit_early || txns_package_eval.empty() does not hit. The tx is validated, and then the results returned. The function returns at a later line of code, but that doesn’t mean more work was done.


    instagibbs commented at 9:14 pm on January 18, 2023:
    would help for reading clarity if nothing else, agreed

    naumenkogs commented at 11:51 am on January 19, 2023:

    Thank you for this elaborate analysis!

    So in if valid and if other failure cases, it’s not just inside vs outside the loop, right? Because “outside the loop” also means re-validating other transactions, not just the child (AcceptMultipleTransactions call). (Note that I’m look only at the first commit so far)


    naumenkogs commented at 12:41 pm on January 19, 2023:
    What I’m talking about seems to be fixed in the next commit 879b55fa9da3688fb6b85e7f40f9778753a9102f. If my understanding is correct, I’d suggest mentioning this in the commit message.

    glozow commented at 5:55 pm on January 19, 2023:
    Added

    glozow commented at 5:56 pm on January 19, 2023:
    Thank you yes, it makes more sense to switch the commits around. Switched their order and made the commit messages more descriptive
  15. in src/validation.cpp:1455 in cb065422e5 outdated
    1393@@ -1394,6 +1394,11 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
    1394             // Provide the wtxid of the mempool tx so that the caller can look it up in the mempool.
    1395             results_final.emplace(wtxid, MempoolAcceptResult::MempoolTxDifferentWitness(iter.value()->GetTx().GetWitnessHash()));
    1396         } else {
    1397+            if (wtxid == child->GetWitnessHash() && !quit_early) {
    1398+                txns_package_eval.push_back(tx);
    1399+                // Unless we're quitting early, validate the child outside of this loop.
    


    naumenkogs commented at 12:49 pm on January 17, 2023:

    cb065422e58be7558e6c2ad9eadfa885bb48c708

    What you’re saying in the commit message is true only for TxValidationResult::TX_MEMPOOL_POLICY or TxValidationResult::TX_MISSING_INPUTS? In other failures, we won’t validate the child for the second time (because it would trigger quit_early)

    What’s even worse than a mistake in the commit message, you won’t be able to catch other kinds of failures here (because you don’t set quit_early here), so you might do some extra work which should have been avoided otherwise, no?


    glozow commented at 5:54 pm on January 19, 2023:
    Added in the commit message that, specifically if the failure is policy or missing inputs, it was validated twice. Thanks
  16. glozow renamed this:
    [WIP] validate package transactions with their in-package ancestor sets
    validate package transactions with their in-package ancestor sets
    on Jan 17, 2023
  17. in src/policy/packages.cpp:21 in 67f7b2c84a outdated
    30-    if (package_count > 1 && total_size > MAX_PACKAGE_SIZE * 1000) {
    31-        return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-large");
    32-    }
    33-
    34     // Require the package to be sorted in order of dependency, i.e. parents appear before children.
    35     // An unsorted package will fail anyway on missing-inputs, but it's better to quit earlier and
    


    instagibbs commented at 9:37 pm on January 18, 2023:
    Rest of this comment seems appropriate elsewhere now that its put in its own subroutine.

    glozow commented at 5:56 pm on January 19, 2023:
    Moved back to IsPackageWellFormed()
  18. in src/policy/packages.cpp:59 in 67f7b2c84a outdated
    55@@ -65,6 +56,26 @@ bool CheckPackage(const Package& txns, PackageValidationState& state)
    56     return true;
    57 }
    58 
    59+bool CheckPackage(const Package& txns, PackageValidationState& state)
    


    instagibbs commented at 9:54 pm on January 18, 2023:
    While we’re here: CheckPackageWellFormed

    glozow commented at 5:49 pm on January 19, 2023:
    Going with IsPackageWellFormed to follow the Is* pattern, if that’s ok
  19. in src/policy/packages.h:76 in ed4272c85e outdated
    71+    Package txns;
    72+    /** Caches the transactions by txid for quick lookup. */
    73+    std::map<uint256, CTransactionRef> txid_to_tx;
    74+    /** Caches the in-package ancestors for each transaction. */
    75+    std::map<uint256, std::set<uint256>> ancestor_subsets;
    76+    /** Transactions so exclude when returning ancestor subsets.*/
    


    instagibbs commented at 9:58 pm on January 18, 2023:
    s/so/to/

    glozow commented at 5:56 pm on January 19, 2023:
    Done
  20. in src/policy/packages.h:98 in ed4272c85e outdated
    93+    /** Get the ancestor subpackage for a transaction. */
    94+    std::vector<CTransactionRef> GetAncestorSet(const CTransactionRef& tx);
    95+    /** From now on, exclude these transactions from GetAncestorSet(). */
    96+    void Exclude(const CTransactionRef& transaction);
    97+    /** Mark a transaction as "banned." From now on, if this transaction is present in the ancestor
    98+     * set, return an empty vector instead. */
    


    instagibbs commented at 10:00 pm on January 18, 2023:
    when calling Txns()

    glozow commented at 5:55 pm on January 19, 2023:
    Only when calling GetAncestorSet()*, added comment that Txns() is unchanged.
  21. in src/validation.cpp:1463 in 879b55fa9d outdated
    1460     // Include already-in-mempool transaction results in the final result.
    1461     for (const auto& [wtxid, mempoolaccept_res] : results_final) {
    1462         Assume(submission_result.m_tx_results.emplace(wtxid, mempoolaccept_res).second);
    1463-        Assume(mempoolaccept_res.m_result_type != MempoolAcceptResult::ResultType::INVALID);
    1464+        Assume(mempoolaccept_res.m_result_type != MempoolAcceptResult::ResultType::INVALID ||
    1465+               mempoolaccept_res.m_state.GetResult() == TxValidationResult::TX_MISSING_INPUTS);
    


    naumenkogs commented at 12:24 pm on January 19, 2023:

    879b55fa9da3688fb6b85e7f40f9778753a9102f

    According to this change, it should now be possible to hit mempoolaccept_res.m_result_type == INVALID, but only when […].

    However, I don’t see how the AcceptPackageWrappingSingle code change touches mempoolaccept_res at all. So it’s unclear why the Assume is changed here.


    glozow commented at 5:56 pm on January 19, 2023:
    Oops yes, I’ve removed this
  22. in src/policy/packages.h:86 in 45029ee878 outdated
    81+    /** Helper function for recursively constructing ancestor caches in ctor. */
    82+    void visit(const CTransactionRef&);
    83+public:
    84+    /** Constructs ancestor package, sorting the transactions topologically and constructing the
    85+     * txid_to_tx and ancestor_subsets maps. It is ok if the input txns is not sorted.
    86+     * Expects that basic sanitization checks have passed:
    


    instagibbs commented at 4:51 pm on January 19, 2023:
    0     * Expects that:
    

    glozow commented at 5:57 pm on January 19, 2023:
    Done
  23. in src/validation.cpp:1511 in 45029ee878 outdated
    1390     // Results from individual validation. "Nonfinal" because if a transaction fails by itself but
    1391     // succeeds later (i.e. when evaluated with a fee-bumping child), the result changes (though not
    1392     // reflected in this map). If a transaction fails more than once, we want to return the first
    1393     // result, when it was considered on its own. So changes will only be from invalid -> valid.
    1394     std::map<uint256, MempoolAcceptResult> individual_results_nonfinal;
    1395     bool quit_early{false};
    


    instagibbs commented at 5:12 pm on January 19, 2023:
    Maybe we can get rid of quit_early and have Packagifier contain a HasBan function?

    glozow commented at 6:00 pm on January 19, 2023:
    (was able to get rid of quit_early without HasBan())
  24. in src/validation.cpp:1487 in 45029ee878 outdated
    1507-    // already been submitted.
    1508-    if (quit_early || txns_package_eval.empty()) {
    1509+    // already been submitted. Since this is an ancestor package, if the child is in, that means all
    1510+    // the other transactions in the package are as well. We check for the child by txid because
    1511+    // same-txid-different-witness is an acceptable case for deduplication in the loop above.
    1512+    if (quit_early || m_pool.exists(GenTxid::Txid(child->GetHash()))) {
    


    instagibbs commented at 5:20 pm on January 19, 2023:

    If we change GetAncestorSet to an optional and store banned-ness, then we can use it to check if there is nothing left to be submitted directly by calling txns_package_eval.empty().

    0    if (packageified.HasBan() || txns_package_eval.empty()) {
    

    Or, if we keep GetAncestorSet as is, just do txns_package_eval.empty() only? It will be empty in both the cases where it has a banned input, or where all transactions have been excluded?

    Then we can just get rid of quit_early and don’t need to track if we’ve banned anything?


    glozow commented at 5:57 pm on January 19, 2023:
    Good idea, got rid of quit_early and exists(). Using txns_package_eval std::nullopt or empty
  25. instagibbs commented at 5:25 pm on January 19, 2023: member
    dropping some initial comments
  26. glozow force-pushed on Jan 19, 2023
  27. in src/validation.cpp:1471 in 036d2b07fb outdated
    1437@@ -1422,15 +1438,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
    1438     }
    1439     // Validate the (deduplicated) transactions as a package. Note that submission_result has its
    1440     // own PackageValidationState; package_state_quit_early is unused past this point.
    1441-    auto submission_result = AcceptMultipleTransactions(txns_package_eval, args);
    1442+    auto submission_result = AcceptPackageWrappingSingle(txns_package_eval);
    1443     // Include already-in-mempool transaction results in the final result.
    1444     for (const auto& [wtxid, mempoolaccept_res] : results_final) {
    1445         Assume(submission_result.m_tx_results.emplace(wtxid, mempoolaccept_res).second);
    1446-        Assume(mempoolaccept_res.m_result_type != MempoolAcceptResult::ResultType::INVALID);
    


    naumenkogs commented at 11:57 am on January 20, 2023:
    036d2b07fb3ce404dd6787d8d0fe4e80c168fdf4 This Assume is orthogonal to the rest of the code changes (these changes don’t touch mempoolaccept_res), so it’s unclear why you remove it here.

    glozow commented at 2:44 pm on January 30, 2023:
    Oops, removed
  28. in src/policy/packages.cpp:75 in 4c4cea3856 outdated
    70+    // Require the package to be sorted in order of dependency, i.e. parents appear before children.
    71+    // An unsorted package will fail anyway on missing-inputs, but it's better to quit earlier and
    72+    // fail on something less ambiguous (missing-inputs could also be an orphan or trying to
    73+    // spend nonexistent coins).
    74+    if (!IsSorted(txns)) return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-sorted");
    75+    if (!HasNoConflicts(txns)) return state.Invalid(PackageValidationResult::PCKG_POLICY, "conflict-in-package");
    


    naumenkogs commented at 12:03 pm on January 20, 2023:
    4c4cea3856b1fa2a50de25847b19ae90c4263916 nit: maybe it’s better to use HasConflicts. Double-negation is harder to understand :)

    glozow commented at 2:53 pm on January 30, 2023:
    I’ve changed it to IsConsistent(). I didn’t want to change to HasConflicts() because that would require changing the function implementation to be opposite, and the Is* pattern seems to be good for readability
  29. in src/policy/packages.cpp:109 in 8764ecc566 outdated
    100+    std::set<uint256> my_ancestors;
    101+    my_ancestors.insert(curr_txid);
    102+    for (const auto& input : curr_tx->vin) {
    103+        auto parent_tx = txid_to_tx.find(input.prevout.hash);
    104+        if (parent_tx == txid_to_tx.end()) continue;
    105+        if (ancestor_subsets.count(parent_tx->first) == 0) {
    


    naumenkogs commented at 12:37 pm on January 20, 2023:
    8764ecc566413b4974e030c596193e75ff39f746 Can this result in an endless recursion, if txA spends txB and txB spends txA at the same time? Or possibly through txC. (similar in IsAncestorPackage)

    glozow commented at 1:04 pm on January 20, 2023:
    If we have a cycle of transactions, SHA256 is broken, no? 😅

    naumenkogs commented at 2:10 pm on January 20, 2023:
    You are right. Not sure if it’s worth commenting. It’s unlikely someone will violate this assumption (it would require big changes to Bitcoin), but it could save time to someone like me who forget about this property.

    glozow commented at 2:51 pm on January 30, 2023:
    I’ve added a comment to visit()
  30. in src/policy/packages.h:78 in 8764ecc566 outdated
    73+    std::map<uint256, CTransactionRef> txid_to_tx;
    74+    /** Caches the in-package ancestors for each transaction. */
    75+    std::map<uint256, std::set<uint256>> ancestor_subsets;
    76+    /** Transactions to exclude when returning ancestor subsets.*/
    77+    std::unordered_set<uint256, SaltedTxidHasher> excluded_txns;
    78+    /** Transactions that are banned. Return empty vector if any ancestor subset contains these transactions.*/
    


    naumenkogs commented at 12:54 pm on January 20, 2023:
    8764ecc566413b4974e030c596193e75ff39f746 The comment is incorrect: nullopt is returned, not empty vector.

    glozow commented at 2:51 pm on January 30, 2023:
    Thanks, fixed
  31. in src/test/util/setup_common.h:176 in cea7dcf438 outdated
    187@@ -171,7 +188,7 @@ struct TestChain100Setup : public TestingSetup {
    188      * @param submit             Whether or not to submit to mempool
    189      */
    190     CMutableTransaction CreateValidMempoolTransaction(CTransactionRef input_transaction,
    191-                                                      int input_vout,
    192+                                                      uint32_t input_vout,
    193                                                       int input_height,
    


    naumenkogs commented at 7:43 am on January 24, 2023:
    cea7dcf4388ad65436fcb98c7025b607708e09ab nit: worth changing input_height to uint too, maybe even with const?

    glozow commented at 2:26 pm on January 30, 2023:
    • It will need to be cast to int anyway when we call AddCoins(). Maybe one day if we change AddCoins() ?
    • I don’t think const is very meaningful since it’s passed by value
  32. in src/validation.cpp:1403 in db6eed0cdf outdated
    1397@@ -1398,6 +1398,12 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
    1398             // Provide the wtxid of the mempool tx so that the caller can look it up in the mempool.
    1399             results_final.emplace(wtxid, MempoolAcceptResult::MempoolTxDifferentWitness(iter.value()->GetTx().GetWitnessHash()));
    1400         } else {
    1401+            if (wtxid == child->GetWitnessHash() && !quit_early) {
    1402+                Assume(tx == package.back());
    1403+                txns_package_eval.push_back(tx);
    


    naumenkogs commented at 8:23 am on January 24, 2023:
    db6eed0cdfb8a90b54fee4955fd9b07d5fc1817b Consider everything, including the child, to be valid. Previously, the execution would terminate at txns_package_eval.empty() gate after the loop. After this commit, this won’t pass — and everything (other transactions) would be validated once again.

    glozow commented at 3:06 pm on January 30, 2023:

    I think it’s the opposite - we avoid re-validating things now.

    Consider everything, including the child, to be valid.

    Ok, we can break this down. Let’s split it into 2 types of scenarios: non-CPFP case and CPFP case. In the CPFP case, the parent needs the child in order to pass.

    (1) Non-CPFP case (1a) Before this commit Inside this loop, validate parent. The parent passes and is submitted to the mempool. Then, validate child. The child passes and is submitted. After the loop, we quit because txns_package_eval.empty(). How many times we validated something: 2

    (1b) After this commit Inside this loop, validate parent. The parent passes and is submitted to the mempool. Inside this loop, at this condition, txns_package_eval.push_back(child) and break. After the loop, we validate using AcceptPackageWrappingSingle({child}). How many times we validated something: 2

    (2) CPFP case (2a) Before this commit Inside loop, validate parent. The parent fails due to too low fee. txns_package_eval.push_back(parent). Inside this loop, validate child. The child fails due to missing inputs. txns_package_eval.push_back(child). After the loop, we validate using AcceptPackageWrappingSingle({parent, child}). How many times we validated something: 3

    (2b) After this commit Inside loop, validate parent. The parent fails due to too low fee. txns_package_eval.push_back(parent). Inside this loop, at this condition, txns_package_eval.push_back(child) and break. After the loop, we validate using AcceptPackageWrappingSingle({parent, child}). How many times we validated something: 2


    naumenkogs commented at 9:41 am on January 31, 2023:
    Thank you for helping me understand this. I understand you are right now.
  33. in src/validation.cpp:1444 in db6eed0cdf outdated
    1402+                Assume(tx == package.back());
    1403+                txns_package_eval.push_back(tx);
    1404+                // Unless we're quitting early, validate the child outside of this loop.
    1405+                break;
    1406+            }
    1407             // Transaction does not already exist in the mempool.
    


    naumenkogs commented at 9:32 am on January 24, 2023:

    glozow commented at 2:51 pm on January 30, 2023:
    Added
  34. in src/validation.cpp:1508 in 936c7f170b outdated
    1493-    // same-txid-different-witness is an acceptable case for deduplication in the loop above.
    1494-    if (quit_early || m_pool.exists(GenTxid::Txid(child->GetHash()))) {
    1495+    const auto txns_package_eval{packageified.GetAncestorSet(child)};
    1496+    // If txns_package_eval is std::nullopt, the last tx's result was pre-filled with missing
    1497+    // inputs. If txns_package_eval is empty, all transactions have already passed.
    1498+    if (!txns_package_eval || txns_package_eval->empty()) {
    


    naumenkogs commented at 10:03 am on January 24, 2023:
    936c7f170bc68ec583ae026ddbaf197ca6cd0792 Could you help me understand how the latter condition can be possible for this transaction?

    glozow commented at 4:21 pm on January 24, 2023:
    I assume you’re asking “how can txns_package_eval be empty?” If all of the transactions were already in the mempool, they have each been Exclude’d. So GetAncestorSet(child) returns a vector of all the non-excluded ancestors, which is an empty vector.
  35. glozow force-pushed on Jan 30, 2023
  36. in src/policy/packages.h:117 in 132159cc6a outdated
     96+    /** Constructs ancestor package, sorting the transactions topologically and constructing the
     97+     * txid_to_tx and ancestor_subsets maps. It is ok if the input txns is not sorted.
     98+     * Expects:
     99+     * - No duplicate transactions.
    100+     * - No conflicts between transactions.
    101+     * - txns is of reasonable size (e.g. below MAX_PACKAGE_COUNT) to limit recursion depth
    


    naumenkogs commented at 8:58 am on January 31, 2023:
    132159cc6ae36da7697fcbb90bd5914e02a41386 This limit is never checked?

    glozow commented at 11:15 am on January 31, 2023:
    meant to be a precondition, so the caller should check it beforehand
  37. in src/validation.cpp:1442 in 93e976d987 outdated
    1443+                // should be skipped.
    1444                 quit_early = true;
    1445                 package_state_quit_early.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
    1446                 individual_results_nonfinal.emplace(wtxid, single_res);
    1447+                packageified.Ban(tx);
    1448             } else {
    


    naumenkogs commented at 9:56 am on January 31, 2023:
    93e976d98726040edf4070175c46fdc504aabff5 why not ban here?

    glozow commented at 11:18 am on January 31, 2023:
    Here, the tx failed for missing inputs or policy (i.e. possibly too low fee), so there is a chance of it becoming valid if later validated as a package.
  38. in src/validation.cpp:1507 in 93e976d987 outdated
    1447@@ -1432,6 +1448,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
    1448 
    1449     // Quit early because package validation won't change the result or the entire package has
    1450     // already been submitted.
    1451+    // If txns_package_eval is empty, all transactions have already passed.
    


    naumenkogs commented at 10:08 am on January 31, 2023:
    93e976d98726040edf4070175c46fdc504aabff5 Does “passed” mean “valid” in this context?

    glozow commented at 11:00 am on January 31, 2023:
    Yes

    naumenkogs commented at 11:37 am on January 31, 2023:
    Can’t it be empty if the child is invalid? (if (!subpackage) { etc)

    glozow commented at 11:56 am on January 31, 2023:
    subpackage has type std::optional<std::vector<CTransactionRef>>, so !subpackage means it is std::nullopt. empty vector means all transactions have been excluded.

    naumenkogs commented at 12:26 pm on January 31, 2023:

    I’m thinking about the following:

    • two transactions
    • Ban(parent)
    • if (!subpackage) { on the child iteration
    • txns_package_eval.empty() but nothing is “valid” (if that’s what you mean by passed)

    Maybe you meant “when quit_early is false, then […]” but I’m not sure.


    glozow commented at 10:47 am on February 1, 2023:
    In that case, if (!subpackage) { hits on the child iteration, and its result is prefilled with the “missing inputs” failure. And then we have !txns_package_eval, not txns_package_eval.empty().

    naumenkogs commented at 11:28 am on February 1, 2023:

    And then we have !txns_package_eval,

    See the commit I’m referring to, !txns_package_eval is impossible there. I know that this inaccuracy goes away in the next commit, but w.r.t. 93e976d98726040edf4070175c46fdc504aabff5 this comment seems incorrect.


    glozow commented at 12:08 pm on February 1, 2023:
    Ah I see. I can move this comment to the last commit.
  39. in src/validation.cpp:1489 in 8ff2655342 outdated
    1467+
    1468+            const auto single_res_it = subpackage_result.m_tx_results.find(wtxid);
    1469+            if (single_res_it != subpackage_result.m_tx_results.end()) {
    1470+                const auto single_res = single_res_it->second;
    1471+                if (single_res.m_state.GetResult() != TxValidationResult::TX_MEMPOOL_POLICY &&
    1472+                    single_res.m_state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) {
    


    glozow commented at 10:48 am on February 1, 2023:
    Noting that we can remove the missing inputs condition from here, since we are validating subpackages now and nobody should have missing inputs unless there is a transaction missing

    instagibbs commented at 2:45 pm on June 12, 2023:

    I don’t think this is true?

    We’re only returning TxValidationResult::TX_MEMPOOL_POLICY for package size of 1(which becomes a Single Accept), so and subsequent subpackage relying on this prior tx will infer a result of TX_MISSING_INPUTS, invalid-tx-dependency in AcceptPackageWrappingSingle.

    Wondering if it might be worth it to have a specific TxValidationResult that is precisely for the reasons that we may allow re-evaluation(early-ish abort due to low (package) feerate), and no others. Would this make reasoning about DoS easier?


    instagibbs commented at 7:41 pm on June 22, 2023:

    https://github.com/instagibbs/bitcoin/commit/30eaa61160cf63cc79d1bc3232d33de091f94391

    This is what I was thinking. As a reviewer I can pretty quickly tell what errors will be allowed retries later(and be included in following sub-packages), and which will not. e.g., bip125-replacement-disallowed will never work later, nor will txn-mempool-conflict, min relay fee not met, too-long-mempool-chain(?), too-long-mempool-chain(?) and nothing currently in ReplacementChecks.

    Could also just bubble up the individual result to avoid the TX_MISSING_INPUTS case, but I really like being able to very quickly find all instances… thoughts?

  40. in src/policy/packages.cpp:70 in 4a4ae2f128 outdated
    58+
    59+    if (package_count > MAX_PACKAGE_COUNT) {
    60+        return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-many-transactions");
    61+    }
    62+
    63+    const int64_t total_size = std::accumulate(txns.cbegin(), txns.cend(), 0,
    


    mzumsande commented at 9:11 pm on March 14, 2023:
    This is just being moved here and I don’t think this can overflow in practice, but the init value should be int64_t{0}, so maybe it would make sense to fix this somewhere within this PR. (searched the rest of the codebase after #27021 (review)).

    glozow commented at 2:44 pm on March 20, 2023:
    Ah thanks!
  41. glozow commented at 10:42 am on April 5, 2023: member
    Marking as draft for now, will rebase on top of #26933
  42. glozow marked this as a draft on Apr 5, 2023
  43. glozow force-pushed on Apr 12, 2023
  44. glozow force-pushed on Apr 17, 2023
  45. glozow force-pushed on May 1, 2023
  46. glozow marked this as ready for review on May 1, 2023
  47. DrahtBot added the label CI failed on May 1, 2023
  48. in src/test/txpackage_tests.cpp:59 in aa16a46345 outdated
    38@@ -39,6 +39,30 @@ inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outpu
    39     return MakeTransactionRef(mtx);
    40 }
    41 
    42+// Context-free check that a package only contains a tx (the last tx in the package) with its
    43+// ancestors. Not all of the tx's ancestors need to be present.
    44+bool IsAncestorPackage(const Package& package)
    


    instagibbs commented at 4:10 pm on May 2, 2023:
    this should assert that package is non-empty

    glozow commented at 10:13 pm on May 10, 2023:
    Aha, thanks!
  49. in src/test/txpackage_tests.cpp:137 in aa16a46345 outdated
    133+    }
    134+    // 99 Parents and 1 Child
    135+    {
    136+        Package package;
    137+        CMutableTransaction child;
    138+        for (int i{0}; i < 99; ++i) {
    


    instagibbs commented at 4:17 pm on May 2, 2023:
    0        for (int parent_idx{0}; parent_idx < 99; ++parent_idx) {
    
  50. in src/test/txpackage_tests.cpp:230 in aa16a46345 outdated
    146+
    147+        Package package_copy(package);
    148+        Shuffle(package_copy.begin(), package_copy.end(), det_rand);
    149+        AncestorPackage packageified(package_copy);
    150+        for (auto i{0}; i < 99; ++i) {
    151+            BOOST_CHECK_EQUAL(packageified.GetAncestorSet(package[i])->size(), 1);
    


    instagibbs commented at 4:45 pm on May 2, 2023:
    ordering between package[i] and packageified.Txns()[i] doesn’t seem to be stable? The above shuffle is only “undone” by sorting by number of in-package ancestors when constructing the AncestorPackage, but of course all parents have the same value.

    glozow commented at 10:13 pm on May 10, 2023:
    Changed to always use package[i], and added a comment about this unstable sort
  51. instagibbs commented at 4:46 pm on May 2, 2023: member
    test issue causing failure
  52. in src/validation.cpp:568 in 08a07c348d outdated
    563+     * If only 1 transaction exists in subpackage, calls AcceptSingleTransaction() with adjusted
    564+     * ATMPArgs to avoid additional package policy restrictions like PackageMempoolChecks() and
    565+     * disabled RBF. Also creates a PackageMempoolAcceptResult wrapping the result.
    566+     * If multiple transactions exist in subpackage, calls AcceptMultipleTransactions() with the
    567+     * provided ATMPArgs. Additionally, if the "representative tx" is not present in the result,
    568+     * fills it in with TX_MISSING_INPUTS.
    


    instagibbs commented at 7:30 pm on May 2, 2023:
    under what situations does the “representative tx” not get filled, and why is TX_MISSING_INPUTS the right value to set?

    glozow commented at 10:13 pm on May 10, 2023:
    Elaborated

    instagibbs commented at 5:14 pm on May 15, 2023:
    perfect
  53. in src/test/fuzz/ancestorpackage.cpp:20 in 7fd7cc3502 outdated
    15+    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
    16+    std::vector<CTransactionRef> txns_in;
    17+    // Avoid repeat coins, as they may cause transactions to conflict
    18+    std::set<COutPoint> available_coins;
    19+    for (auto i{0}; i < 100; ++i) {
    20+        if (auto outpoint{ConsumeDeserializable<COutPoint>(fuzzed_data_provider)}) available_coins.insert(*outpoint);
    


    instagibbs commented at 2:46 pm on May 3, 2023:

    hmm I think the fuzzer was smart enough to give a coin with the same hash as the txid of a constructed transaction below, built on a different set of ancestors…

    I think hashing these bytes would defeat this since it can’t generate a valid tx with that txid?


    instagibbs commented at 5:00 pm on May 4, 2023:
    or to be lazier, just set the initial utxos’ prevout.n index to some impossibly high numbers to ever be generated

    glozow commented at 10:12 pm on May 10, 2023:

    I think hashing these bytes would defeat this since it can’t generate a valid tx with that txid?

    Did this one, thanks 👍

  54. in src/validation.cpp:1487 in dd9d4f6fe8 outdated
    1481@@ -1478,42 +1482,55 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
    1482                 // parent, and should still be considered.
    1483                 continue;
    1484             }
    1485-            if (wtxid == child->GetWitnessHash() && !quit_early) {
    1486+            if (wtxid == child->GetWitnessHash()) {
    1487                 Assume(tx == package.back());
    1488-                txns_package_eval.push_back(tx);
    1489                 // Unless we're quitting early, validate the child outside of this loop.
    


    instagibbs commented at 6:04 pm on May 8, 2023:
    this comment becomes a bit disconnected from the logic itself.

    glozow commented at 10:12 pm on May 10, 2023:
    Elaborated on the comment, hopefully it’s better now
  55. in src/validation.cpp:1395 in aa16a46345 outdated
    1419-    const auto package_or_confirmed = [this, &unconfirmed_parent_txids](const auto& input) {
    1420-         return unconfirmed_parent_txids.count(input.prevout.hash) > 0 || m_view.HaveCoin(input.prevout);
    1421-    };
    1422-    if (!std::all_of(child->vin.cbegin(), child->vin.cend(), package_or_confirmed)) {
    1423-        package_state_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-unconfirmed-parents");
    1424+    if (!packageified.IsAncestorPackage()) {
    


    instagibbs commented at 8:09 pm on May 8, 2023:
    I know what this means, but kinda… odd that AncestorPackage can be !IsAncestorPackage

    glozow commented at 10:12 pm on May 10, 2023:
    Yeah. Added some docs to the class (also mentioned BIP331) so maybe it’s more clear? I used to call it Packageifier because it can potentially build a package out of any random list of transactions. But then it’s weird because we packageify a Package. Open to naming improvements 😅
  56. in test/functional/rpc_packages.py:335 in aa16a46345 outdated
    329         self.log.info("Submitpackage valid packages with 1 child and some number of parents")
    330         for num_parents in [1, 2, 24]:
    331             self.test_submit_child_with_parents(num_parents, False)
    332             self.test_submit_child_with_parents(num_parents, True)
    333 
    334-        self.log.info("Submitpackage only allows packages of 1 child with its parents")
    


    instagibbs commented at 8:16 pm on May 8, 2023:
    should just leave this example in in the positive sense of it being accepted?

    glozow commented at 10:12 pm on May 10, 2023:
    Re-added
  57. instagibbs commented at 8:32 pm on May 8, 2023: member

    Last bit of nitting:

    AcceptPackage -> AcceptAncestorPackage AcceptMultipleTransactions -> AcceptSubPackage

    With BIP331 I think maybe knowing what an ancestor package is and how it fits into this code may become clearer?

  58. glozow force-pushed on May 10, 2023
  59. DrahtBot removed the label CI failed on May 10, 2023
  60. glozow force-pushed on May 11, 2023
  61. DrahtBot added the label CI failed on May 11, 2023
  62. in src/test/txpackage_tests.cpp:622 in f4e449254e outdated
    617+        package_ppfc.push_back(tx_child);
    618+
    619+        // Magic Number Sanity Checks
    620+        BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_grandparent), 5649);
    621+        BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_parent1), 191);
    622+        BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_parent2), 191);
    


    achow101 commented at 7:56 pm on May 11, 2023:

    CI Failure here:

    0C:/Users/ContainerAdministrator/AppData/Local/Temp/cirrus-ci-build/src/test/txpackage_tests.cpp(622): error: in "txpackage_tests/package_submission_tests": check GetVirtualTransactionSize(*tx_parent2) == 191 has failed [190 != 191]
    

    I’m going to guess that this is because real signatures are being created, which can occasionally be smaller than 71 bytes, thus resulting in a tx that is one byte smaller. If the script type doesn’t matter, I would suggest using taproot keypath spends since that has fixed size signatures. Otherwise these checks will need to account for possibly varying transaction sizes.


    instagibbs commented at 7:57 pm on May 11, 2023:
    I’m fine if the magic number checks have a bit of wiggle room tbh, just leave a comment?

    glozow commented at 11:30 am on May 24, 2023:
    Changed to use T-T-T-Taproot for fixed size (thanks @achow101)

    glozow commented at 5:59 pm on May 24, 2023:
    grandparent still not fixed size :facepalm: edited the assertion to be on the multiple of 10. I think it worked this time.

    instagibbs commented at 8:42 pm on May 24, 2023:

    taproot coinbase outputs wen

    LGTM

  63. in src/test/fuzz/ancestorpackage.cpp:20 in 35da289d97 outdated
    15+    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
    16+    std::vector<CTransactionRef> txns_in;
    17+    // Avoid repeat coins, as they may cause transactions to conflict
    18+    std::set<COutPoint> available_coins;
    19+    for (auto i{0}; i < 100; ++i) {
    20+        if (auto mtx{ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider)}) {
    


    instagibbs commented at 5:30 pm on May 15, 2023:
    even more sensible than my suggestion :+1: no issues running so far
  64. in src/test/txpackage_tests.cpp:559 in f4e449254e outdated
    554+        //     grandparent
    555+        //       5649sat
    556+        //        5649vB
    557+        //     ^         ^
    558+        //  parent1     parent2
    559+        // 13223sat     13223sat
    


    instagibbs commented at 5:35 pm on May 15, 2023:
    ASCII :gem: needs connection between grandparent and child

    glozow commented at 5:59 pm on May 24, 2023:
    Fixed
  65. instagibbs commented at 6:01 pm on May 15, 2023: member
    looking good
  66. joostjager commented at 8:02 pm on May 16, 2023: none
    What are the current use cases / applications for transaction packages enabled by this PR that are more complicated than child-with-parents-tree-only?
  67. instagibbs commented at 8:08 pm on May 16, 2023: member
    @joostjager could be as simple as any wallet that does CPFP bumping in their coin selection algorithm(chaining unconfirmed spends). The ideal is to support as many usage patters exist in practice, ancestor packages is just a large-ish subset of that from a CPFP point of view.
  68. joostjager commented at 8:28 pm on May 16, 2023: none

    I am trying to get a feel for how big of a problem it is that this PR solves. In Lightning the problem is very real and potentially exposing lots of users to coin loss because of the combination of pre-signed transactions and time sensitivity, but the topology is of the simplest kind. I am not sure if the wallet example that you give is of the same order because RBF is also an option and timing may not be as critical?

    Of course it is great to support as many patterns as possible, but it also makes the system more complex to reason about and maintain. Maybe #27609 takes away 90% of the pain at 10% of the cost?

  69. glozow force-pushed on May 24, 2023
  70. glozow force-pushed on May 24, 2023
  71. glozow force-pushed on May 24, 2023
  72. in src/test/txpackage_tests.cpp:688 in 7e25e00210 outdated
    429+                                                       /*output_amount=*/parent_value, /*submit=*/false)};
    430+        CTransactionRef tx_parent2 = MakeTransactionRef(mtx_parent2);
    431+        package_ppfc.push_back(tx_parent2);
    432+
    433+
    434+        const CAmount child_fee{minfeerate.GetFee(5676 + 121 + 121 + 227) - grandparent_fee - parent_fee - parent_fee};
    


    instagibbs commented at 8:30 pm on May 24, 2023:
    s/121/112/ ?

    ismaelsadeeq commented at 1:03 pm on June 15, 2023:
    0const CAmount child_fee{minfeerate.GetFee(5676 + 121 + 121 + 227) - grandparent_fee - parent_fee - parent_fee};
    

    What does 5676 + 121 + 121 + 227 numbers represent, 5676 corresponds to the grandparent size does 121 121 mean the sizes of parent1 and parent2 (which should be 112) ? Also is 227 the child size? maybe use a variable with a descriptive name for the magic numbers before usage since I see they are used in multiple places.

  73. in src/test/txpackage_tests.cpp:635 in b234f993da outdated
    550+    {
    551+        Package package_ppfc;
    552+        // Diamond shape:
    553+        //
    554+        //     grandparent
    555+        //       5679sat
    


    instagibbs commented at 8:45 pm on May 24, 2023:
    diamond keeps going “out of sync”, maybe just have variable names that people can find?
  74. instagibbs approved
  75. instagibbs commented at 8:46 pm on May 24, 2023: member

    ACK https://github.com/bitcoin/bitcoin/pull/26711/commits/b234f993da3caa16f7716948fbcd16ab1a500180

    modulo magic numbers in tests (sorry). Ran the fuzzer for a while again, no issues this time.

  76. DrahtBot removed the label CI failed on May 25, 2023
  77. in test/functional/rpc_packages.py:333 in c44df9b0bf outdated
    334-        self.log.info("Submitpackage only allows packages of 1 child with its parents")
    335-        # Chain of 3 transactions has too many generations
    336+        self.log.info("Submitpackage with a 25-generation chain")
    337         chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=25)]
    338-        assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex)
    339+        self.nodes[0].submitpackage(chain_hex)
    


    fjahr commented at 3:15 pm on June 6, 2023:
    How about actually checking the result here?

    ismaelsadeeq commented at 8:02 pm on June 15, 2023:

    Test the result and ensure that all the package transactions are accepted?

    0        submitpackage_result = self.nodes[0].submitpackage(chain_hex)
    1        assert 'tx-results' in submitpackage_result
    2        result = submitpackage_result['tx-results']
    3        assert_equal(len(result), len(chain_hex))
    4        assert all([(tx_from_hex(tx_hex).getwtxid() in result) for tx_hex in chain_hex])
    
  78. in src/policy/packages.cpp:72 in c9bf37de2e outdated
    60+        return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-many-transactions");
    61+    }
    62+
    63+    const int64_t total_size = std::accumulate(txns.cbegin(), txns.cend(), 0,
    64+                               [](int64_t sum, const auto& tx) { return sum + GetVirtualTransactionSize(*tx); });
    65+    // If the package only contains 1 tx, it's better to report the policy violation on individual tx size.
    


    fjahr commented at 5:07 pm on June 11, 2023:
    I understand the rationale, but it seems dangerous to skip validation steps in special cases because of expected checks happening later in another scope (that’s how I understand it). Such things can lead to nasty errors down the line.
  79. in src/test/txpackage_tests.cpp:59 in 005581b5ea outdated
    38@@ -39,6 +39,31 @@ inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outpu
    39     return MakeTransactionRef(mtx);
    40 }
    41 
    42+// Context-free check that a package only contains a tx (the last tx in the package) with its
    43+// ancestors. Not all of the tx's ancestors need to be present.
    44+bool IsAncestorPackage(const Package& package)
    


    fjahr commented at 5:17 pm on June 11, 2023:
    Kind of confusing and grep unfriendly that this test helper has the exact same name as that Package method. Maybe call it TestAncestorPackage or so?
  80. in src/policy/packages.h:89 in 005581b5ea outdated
    84+ * ancestor set within the package. Exclude() should be called when a transaction is in
    85+ * the mempool so that it can be excluded from other transactions' subpackages. Ban() should be
    86+ * called when a transaction is invalid and all of its descendants should be considered invalid as
    87+ * well; GetAncestorSet() will then return std::nullopt for those descendants.
    88+ * */
    89+class AncestorPackage
    


    fjahr commented at 5:20 pm on June 11, 2023:
    nit: Developer notes say “Class member variables have a m_ prefix.”.

    glozow commented at 3:34 pm on July 21, 2023:
    Done
  81. in src/validation.cpp:1308 in b234f993da outdated
    1296@@ -1283,6 +1297,12 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
    1297         !CheckFeeRate(m_total_vsize, m_total_modified_fees, placeholder_state)) {
    1298         package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-fee-too-low");
    1299         return PackageMempoolAcceptResult(package_state, {});
    1300+    } else if (args.m_package_feerates &&
    1301+               workspaces.back().m_modified_fees * m_total_vsize < m_total_modified_fees * workspaces.back().m_vsize) {
    1302+        // The package feerate is high enough, but the child's feerate is lower than the package
    1303+        // feerate. This should fail, otherwise we're allowing "parent pay for child."
    


    fjahr commented at 7:02 pm on June 11, 2023:
    Just checking the last tx doesn’t guarantee this alone. Maybe mention here that this works because subpackages are being evaluated by themselves as well?

    ajtowns commented at 4:11 am on June 12, 2023:
    This check doesn’t feel like it makes sense to me – it should either be unnecessary (because dealing with subpackages first takes care of it), or else it seems insufficient (because a grandparent might be paying for parent and child, and child paying for parent; but grandparents alone are good enough, but after they’re accepted parent and child combined aren’t good enough).

    instagibbs commented at 1:55 pm on June 12, 2023:

    IIRC circles back to the diamond problem: #27609 (comment)

    Both the parents should contribute to the grandparent being included, but in ancestor packages we can’t take that into account(without a common descendant). Instead the whole ancestor package is included, even though the child is likely to be immediately evicted.


    ajtowns commented at 3:07 am on June 13, 2023:

    I don’t think this needs a diamond, even.

    For example: mempool is evicting below 10sat/vb, grandparent is A at 3300sat, 100vb (33sat/vb); parent is B spending A, at 700sat, 200vb (3.5sat/vb), child is C, spending B at 2000 sat, 100vb (20sat/vb). If you accept A first, then B alone is still below the eviction threshold, but so is B+C (2700sat, 300vb 9sat/vb), even though C is doing cpfp here.


    fjahr commented at 11:13 am on June 13, 2023:
    @ajtowns Right, this is the example I thought of as well. But I don’t understand why you think the check is unnecessary. From my understanding, this is the point where the subpackage A+B would fail then so I think it is necessary. It just depends on the fact that the function is called with each subpackage.

    instagibbs commented at 1:28 pm on June 13, 2023:
    I don’t understand what this scenario has to do with this case. A will be accepted, then B rejected, then B+C rejected, all for having package feerates lower than 10 sat/vbyte.

    mzumsande commented at 4:21 pm on June 13, 2023:
    I don’t think it’s unnecessary - the diamond package would be accepted if we remove this check, the subpackage algorithm doesn’t help with this. However, I’m also not sure if it’s sufficient - e.g. what if we’d add another “grandchild” to the child of the diamond package, with a feerate slightly above the previous package feerate (e.g. 5.1 sat/vB). Then this check wouldn’t fail anymore, but the descendant feerate of the child (i.e. the combined fees of the child and the grandchild) could be below the minfeerate, so the child/grandchild would get evicted right after submission?

    glozow commented at 11:17 am on June 14, 2023:

    The A <- B <- C example is handled without the diamond check, yes. Accept A, reject B, reject B+C. I’m pretty sure the code on master would do this if you removed the child-with-parents requirement.

    The case in the unit test isn’t handled by validating subpackages, no. I agree the “no parent pay for child” rule is not enough in some cases, and is too restrictive in others.

    For example, let’s say the minfeerate is 2sat/vB:

    0A1      A2                 (both 1sat/vB)
    1  \    /  \
    2    B1     B2              (both 3.5sat/vB)
    3      \   /
    4        C                  (1sat/vB)
    

    Ideally we take all except C. We reject A1, reject A2, reject A1+A2+B1, accept A2+B2, reject A1+B1+C. It would be nice if, while considering A1+B1+C, we are again willing to take a subset. Or if we were to try another order or operations, we could reject A1, reject A2, accept A2+B2, accept A1+B1, reject C. But the logic for these two strategies is essentially “try every possible subset.” For this example, C actually can be any tx we shouldn’t accept, e.g. one with an invalid signature.

    Perhaps one way to generalize this is that, within an ancestor package, we should be able to accept a subset. But unless we try every subset, it seems there’s always a way to rearrange a package or add a tx to make us reject part of it. V3 world has fewer possibilities but still feels a bit intractable.

    Maybe we can brainstorm a way to rearrange which checks we do when. Like #26711 (review). Perhaps try everything without feerate checks, eliminate invalid ones, then do feerate analysis. But one transaction could replace transactions in the mempool, then affecting the ancestor set of another transaction in the package…

    We can do our best to have the sender-side logic give a good sort. And if a sender is deliberately crafting packages that we’ll reject, we address that by taking care not to cache rejections too aggressively. e.g. here we’d want to cache the A1+A2+B1 rejection, but be willing to retry A1+B1.


    ajtowns commented at 2:19 pm on June 15, 2023:

    So my idea for that would be to have net_processing do retries:

    [EDIT: fix logic for skipping B1 on first pass. Probably doesn’t quite make sense for B1 to be sorted before B2 in a feerate topo sort, if B1’s ancestor fee rate is lower than B2’s though…]

    • precheck each
    • topo sort by feerate: A1, A2, B1, B2, C
    • skip A1, A2 as too low fee rate
    • skip [A1, A2, B1] as too low fee rate
    • accept B2 with ancestor A2; recalculate fee rates
    • skip C as too low fee
    • return [A1,B1,C] as skipped
    • on the next ProcessMessages call for that peer, retry [A1,B1,C]
    • topo sort again; skip A1, accept B1, recalculate C, skip C
    • return [C] as skipped
    • on the next ProcessMessages call for that peer, retry [C]
    • no successes, so return [C] as soft-rejected

    instagibbs commented at 8:04 pm on June 22, 2023:
    If these retries aren’t sufficient to capture CPFPs, they are also unnecessary from the standpoint of getting MVP deployed perhaps. If there is no way to avoid missing things without going full cluster mempool, I’d suggest keeping things very simple to start. I’ll let Gloria whiteboard some alternatives before exploring further.

    glozow commented at 11:12 am on June 29, 2023:

    So my #26711 (review) for that would be to have net_processing do retries:

    This seems like a good option to consider for computational complexity, but I think could be even slower if we are blocked by UTXO fetching - they are cached when fetched in PreChecks, uncached at the end of ProcessNewPackage, and then reloaded again when we retry. So just thinking about UTXO fetching, it seems better to do retries while we have them loaded and cached.

    I feel like this also requires us making a tradeoff between “good enough result” and potentially a very long queue of things to retry. We would need to put the txns in an orphanage/memory-bounded data structure between retries. We’d want to slow down package relay requests with this peer to avoid getting backed up, be prepared to drop things, etc. In situations where somebody is specially crafting packages, imo we aren’t necessarily solving the problem and we’d end up doing more work.

  82. fjahr commented at 7:07 pm on June 11, 2023: contributor
    Still gathering more context but leaving some first comments.
  83. in doc/release-notes-27609.md:14 in f6b928b124 outdated
     6+
     7+    - Not all features are available. For example, RBF is not supported and the package is limited
     8+      to a child with its unconfirmed parents. Refer to doc/policy/packages.md for more details on
     9+      package policies and limitations.
    10+
    11+    - This RPC is experimental. Its interface may change.
    


    ajtowns commented at 2:52 am on June 12, 2023:

    Should probably mark it as EXPERIMENTAL in the rpc docs – see “sendall” eg in wallet/rpc/spend.cpp

    Not really sure this should be advertised in the release notes at this stage at all, though.

  84. in src/validation.cpp:1539 in 617fe7092e outdated
    1519-            } else {
    1520-                individual_results_nonfinal.emplace(wtxid, single_res);
    1521-                txns_package_eval.push_back(tx);
    1522+            // This transaction does not already exist in the mempool and is not the child.
    1523+            // Try submitting the transaction with its in-package ancestor set.
    1524+            const auto subpackage_result = AcceptPackageWrappingSingle(subpackage.value(), args);
    


    ajtowns commented at 4:21 am on June 12, 2023:

    I worry that this approach can cause O(n^2) validation costs in some way, if you’ve got n txs and end up doing work that results in multiple soft-rejects for each tx.

    I wonder if it wouldn’t be better to do something more like:

    • topologically sort the package
    • accept each tx in order into a mem-pond – a special temporary mini pool just for this package, that enforces consensus rules, but doesn’t care about minimum fees
    • any txs that weren’t valid obviously fail at this point and are dropped, along with any descendants
    • for everything that made it into the pond, consider moving it into the mempool proper, applying the cpfp logic and possibly rbf logic that the mempond didn’t enforce

    Ideally, I think we’d want to:

    1. lookup utxos (slow, but unavoidable)
    2. drop anything that can’t possibly pay sufficient fees
    3. run script validation in topological order (slow, don’t do it if parents failed, only do it once)
    4. do final fee rate analysis (can’t do this until we know what txs are actually valid)

    One way to preserve the current approach while reducing the risk of O(n^2) behaviour might be to move the “retry” behaviour out of validation – ie:

    • do PreChecks on all the package txs (then you can calculate fees)
    • topologically sort the txs
    • for each tx:
      • high enough fee rate? try accepting it.
        • failure: immediately return.
        • success: remove that tx and its ancestors from the package, marking them as succeeded; recalculate fee rates for descendants
    • release mempool.cs

    Then net_processing or rpc can still retry the remaining txs if there was a failure, but can decide to only do that after processing a tx from every other peer, eg.


    glozow commented at 11:13 am on June 29, 2023:
    After whiteboarding this idea + chunking, ended up with #26711 (comment)
  85. in src/validation.cpp:1400 in b234f993da outdated
    1437     }
    1438-    // Protect against bugs where we pull more inputs from disk that miss being added to
    1439-    // coins_to_uncache. The backend will be connected again when needed in PreChecks.
    1440-    m_view.SetBackend(m_dummy);
    1441+    const auto& child = package.back();
    1442+    const auto child_subpackage = packageified.GetAncestorSet(child);
    


    instagibbs commented at 2:20 pm on June 12, 2023:
    unused
  86. in src/validation.cpp:1442 in c44df9b0bf outdated
    1391     // Context-free package checks.
    1392-    if (!IsPackageWellFormed(package, package_state_quit_early)) return PackageMempoolAcceptResult(package_state_quit_early, {});
    1393-
    1394-    // All transactions in the package must be a parent of the last transaction. This is just an
    1395-    // opportunity for us to fail fast on a context-free check without taking the mempool lock.
    1396-    if (!IsChildWithParents(package)) {
    


    mzumsande commented at 2:50 am on June 13, 2023:
    IsChildWithParents() is unused after this outside of tests - can we remove it?

    glozow commented at 12:32 pm on September 28, 2023:
    Removed
  87. in src/validation.cpp:1392 in c44df9b0bf outdated
    1386@@ -1387,49 +1387,17 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
    1387 
    1388     // Check that the package is well-formed. If it isn't, we won't try to validate any of the
    1389     // transactions and thus won't return any MempoolAcceptResults, just a package-wide error.
    1390-
    1391     // Context-free package checks.
    1392-    if (!IsPackageWellFormed(package, package_state_quit_early)) return PackageMempoolAcceptResult(package_state_quit_early, {});
    


    mzumsande commented at 3:02 pm on June 13, 2023:
    after c44df9b0bfa66c77e5cbbff6688ea73cf39ec1d3, the packages.md explanation “Packages must be child-with-unconfirmed-parents packages.” is out of date. It would be good to document the definition of an “ancestor package” there instead.

    glozow commented at 1:42 pm on October 2, 2023:
    Fixed

    naumenkogs commented at 8:15 am on October 24, 2023:

    9962efedb813e02034f2d1634efe79562c5adc19

    Still no definition of ancestor package it seems?

  88. in src/test/txpackage_tests.cpp:652 in 7e25e00210 outdated
    393+        // {grandparent + parent1} and {grandparent + parent2} are both below minfeerate
    394+        // {grandparent + parent1 + parent2} is above minfeerate
    395+        // child has a feerate just below minfeerate
    396+        // {grandparent + parent1 + parent2 + child} is above minfeerate
    397+        // All transactions should be rejected.
    398+        const CAmount grandparent_fee{5679};
    


    achow101 commented at 9:10 pm on June 14, 2023:

    In 7e25e002100f57d69a967f475eb484a8637d9dcb “[unit test] parent pay for child is not allowed”

    The magic numbers in this test were bothering me, so here’s a diff that drops most of them. It refactors CreateValidMempoolTransaction again to have a function CreateValidTransaction that just creates a transaction. This can take a feerate and it will deduct the fee from one of the outputs. This test is then changed to have target feerates for each of the transactions that are all relative to minfeerate.

      0diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp
      1index 1e00a75a79..1d480eedad 100644
      2--- a/src/test/txpackage_tests.cpp
      3+++ b/src/test/txpackage_tests.cpp
      4@@ -378,16 +378,13 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
      5         // Diamond shape:
      6         //
      7         //     grandparent
      8-        //       5679sat
      9-        //        5676vB
     10+        //       minfr/5
     11         //     ^    ^    ^
     12         //  parent1 |   parent2
     13-        // 12000sat |  12000sat
     14-        //  112vB   |   112vB
     15+        // minfr*25 |  minfr*25
     16         //       ^  |  ^
     17         //        child
     18-        //       2424sat
     19-        //        959vB
     20+        //       minfr - 0.05 s/vB
     21         //
     22         // grandparent is below minfeerate
     23         // {grandparent + parent1} and {grandparent + parent2} are both below minfeerate
     24@@ -395,71 +392,72 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
     25         // child has a feerate just below minfeerate
     26         // {grandparent + parent1 + parent2 + child} is above minfeerate
     27         // All transactions should be rejected.
     28-        const CAmount grandparent_fee{5679};
     29+        const CFeeRate grandparent_feerate{minfeerate.GetFeePerK() / 5};
     30         std::vector<CTransactionRef> grandparent_input_txns;
     31         std::vector<COutPoint> grandparent_inputs;
     32         for (auto i{1}; i < 50; ++i) {
     33             grandparent_input_txns.push_back(m_coinbase_txns[i]);
     34             grandparent_inputs.push_back(COutPoint{m_coinbase_txns[i]->GetHash(), 0});
     35         }
     36-        CAmount last_value = grandparent_inputs.size()*50*COIN - 10*COIN - 10*COIN - grandparent_fee;
     37-        auto mtx_grandparent{CreateValidMempoolTransaction(/*input_transactions=*/grandparent_input_txns,
     38-                                                           /*inputs=*/grandparent_inputs,
     39-                                                           /*input_height=*/102,
     40-                                                           /*input_signing_keys=*/{coinbaseKey},
     41-                                                           /*outputs=*/{CTxOut{10*COIN, parent_locking_script}, CTxOut{10*COIN, parent_locking_script},
     42-                                                                        CTxOut{last_value, parent_locking_script}},
     43-                                                           /*submit=*/false)};
     44+        CAmount last_value = grandparent_inputs.size()*50*COIN - 10*COIN - 10*COIN;
     45+        auto [mtx_grandparent, grandparent_fee] = CreateValidTransaction(/*input_transactions=*/grandparent_input_txns,
     46+                                                    /*inputs=*/grandparent_inputs,
     47+                                                    /*input_height=*/102,
     48+                                                    /*input_signing_keys=*/{coinbaseKey},
     49+                                                    /*outputs=*/{CTxOut{10*COIN, parent_locking_script}, CTxOut{10*COIN, parent_locking_script},
     50+                                                                 CTxOut{last_value, parent_locking_script}},
     51+                                                    /*feerate=*/grandparent_feerate,
     52+                                                    /*fee_output=*/2);
     53         CTransactionRef tx_grandparent = MakeTransactionRef(mtx_grandparent);
     54         package_ppfc.push_back(tx_grandparent);
     55 
     56-        const CAmount parent_fee{12000};
     57-        const CAmount parent_value{10*COIN - parent_fee};
     58-        auto mtx_parent1{CreateValidMempoolTransaction(/*input_transaction=*/tx_grandparent, /*input_vout=*/0,
     59-                                                       /*input_height=*/102,
     60-                                                       /*input_signing_key=*/parent_key,
     61-                                                       /*output_destination=*/child_locking_script,
     62-                                                       /*output_amount=*/parent_value, /*submit=*/false)};
     63+        const CFeeRate parent_feerate{minfeerate.GetFeePerK() * 25};
     64+        const CAmount parent_value{10*COIN};
     65+        auto [mtx_parent1, parent_fee1] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent},
     66+                                                /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 0}},
     67+                                                /*input_height=*/102,
     68+                                                /*input_signing_keys=*/{parent_key},
     69+                                                /*outputs=*/{CTxOut{parent_value, child_locking_script}},
     70+                                                /*feerate=*/parent_feerate,
     71+                                                /*fee_output=*/0);
     72         CTransactionRef tx_parent1 = MakeTransactionRef(mtx_parent1);
     73         package_ppfc.push_back(tx_parent1);
     74-        auto mtx_parent2{CreateValidMempoolTransaction(/*input_transaction=*/tx_grandparent, /*input_vout=*/1,
     75-                                                       /*input_height=*/102,
     76-                                                       /*input_signing_key=*/parent_key,
     77-                                                       /*output_destination=*/child_locking_script,
     78-                                                       /*output_amount=*/parent_value, /*submit=*/false)};
     79+        auto [mtx_parent2, parent_fee2] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent},
     80+                                                /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 1}},
     81+                                                /*input_height=*/102,
     82+                                                /*input_signing_keys=*/{parent_key},
     83+                                                /*outputs=*/{CTxOut{parent_value, child_locking_script}},
     84+                                                /*feerate=*/parent_feerate,
     85+                                                /*fee_output=*/0);
     86         CTransactionRef tx_parent2 = MakeTransactionRef(mtx_parent2);
     87         package_ppfc.push_back(tx_parent2);
     88 
     89 
     90-        const CAmount child_fee{minfeerate.GetFee(5676 + 121 + 121 + 227) - grandparent_fee - parent_fee - parent_fee};
     91-        const CAmount child_value{last_value + 2*parent_value - child_fee};
     92-        auto mtx_child{CreateValidMempoolTransaction(/*input_transactions=*/{tx_grandparent, tx_parent1, tx_parent2},
     93-                                                     /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 2}, COutPoint{tx_parent1->GetHash(), 0}, COutPoint{tx_parent2->GetHash(), 0}},
     94-                                                     /*input_height=*/102,
     95-                                                     /*input_signing_keys=*/{parent_key, child_key, grandchild_key},
     96-                                                     /*outputs=*/{CTxOut{child_value, grandchild_locking_script}},
     97-                                                     /*submit=*/false)};
     98+        const CFeeRate child_feerate{minfeerate.GetFeePerK() - 50};
     99+        const CAmount child_value{last_value + 2*parent_value};
    100+        auto [mtx_child, child_fee] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent, tx_parent1, tx_parent2},
    101+                                              /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 2}, COutPoint{tx_parent1->GetHash(), 0}, COutPoint{tx_parent2->GetHash(), 0}},
    102+                                              /*input_height=*/102,
    103+                                              /*input_signing_keys=*/{parent_key, child_key, grandchild_key},
    104+                                              /*outputs=*/{CTxOut{child_value, grandchild_locking_script}},
    105+                                              /*feerate=*/child_feerate,
    106+                                              /*fee_output=*/0);
    107         CTransactionRef tx_child = MakeTransactionRef(mtx_child);
    108         package_ppfc.push_back(tx_child);
    109 
    110-        // Magic Number Sanity Checks
    111-        // A little bit of wiggle room because the signature spending the coinbase is not fixed size.
    112-        BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_grandparent) / 10, 567);
    113-        BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_parent1), 112);
    114-        BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_parent2), 112);
    115-        BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_child), 227);
    116         // Neither parent can pay for the grandparent by itself
    117-        BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1)) > grandparent_fee + parent_fee);
    118-        BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent2)) > grandparent_fee + parent_fee);
    119+        BOOST_CHECK_EQUAL(parent_fee1, parent_fee2);
    120+        BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1)) > grandparent_fee + parent_fee1);
    121+        BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent2)) > grandparent_fee + parent_fee1);
    122         const auto parents_vsize = GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1) + GetVirtualTransactionSize(*tx_parent2);
    123         // Combined, they can pay for the grandparent
    124-        BOOST_CHECK(minfeerate.GetFee(parents_vsize) <= grandparent_fee + 2 * parent_fee);
    125+        BOOST_CHECK(minfeerate.GetFee(parents_vsize) <= grandparent_fee + 2 * parent_fee1);
    126         const auto total_vsize = parents_vsize + GetVirtualTransactionSize(*tx_child);
    127         BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_child)) > child_fee);
    128         // The total package is above feerate, but mostly because of the 2 parents
    129-        BOOST_CHECK(minfeerate.GetFee(total_vsize) <= grandparent_fee + 2 * parent_fee + child_fee);
    130+        BOOST_CHECK(minfeerate.GetFee(total_vsize) <= grandparent_fee + 2 * parent_fee1 + child_fee);
    131         // Child feerate is less than the package feerate
    132-        BOOST_CHECK(CFeeRate(child_fee, GetVirtualTransactionSize(*tx_child)) < CFeeRate(grandparent_fee + 2 * parent_fee + child_fee, total_vsize));
    133+        BOOST_CHECK(CFeeRate(child_fee, GetVirtualTransactionSize(*tx_child)) < CFeeRate(grandparent_fee + 2 * parent_fee1 + child_fee, total_vsize));
    134 
    135         const auto result_ppfc = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_ppfc, /*test_accept=*/false);
    136         BOOST_CHECK(result_ppfc.m_state.IsInvalid());
    137diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
    138index 1ca69371b3..11b98cda49 100644
    139--- a/src/test/util/setup_common.cpp
    140+++ b/src/test/util/setup_common.cpp
    141@@ -343,12 +343,13 @@ CBlock TestChain100Setup::CreateAndProcessBlock(
    142 }
    143 
    144 
    145-CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::vector<CTransactionRef>& input_transactions,
    146-                                                                     const std::vector<COutPoint>& inputs,
    147-                                                                     int input_height,
    148-                                                                     const std::vector<CKey>& input_signing_keys,
    149-                                                                     const std::vector<CTxOut>& outputs,
    150-                                                                     bool submit)
    151+std::pair<CMutableTransaction, CAmount> TestChain100Setup::CreateValidTransaction(const std::vector<CTransactionRef>& input_transactions,
    152+                                                                                  const std::vector<COutPoint>& inputs,
    153+                                                                                  int input_height,
    154+                                                                                  const std::vector<CKey>& input_signing_keys,
    155+                                                                                  const std::vector<CTxOut>& outputs,
    156+                                                                                  const std::optional<CFeeRate>& feerate,
    157+                                                                                  const std::optional<uint32_t>& fee_output)
    158 {
    159     CMutableTransaction mempool_txn;
    160     mempool_txn.vin.reserve(inputs.size());
    161@@ -372,17 +373,57 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::
    162     }
    163     // Build Outpoint to Coin map for SignTransaction
    164     std::map<COutPoint, Coin> input_coins;
    165+    CAmount inputs_amount{0};
    166     for (const auto& outpoint_to_spend : inputs) {
    167         // - Use GetCoin to properly populate utxo_to_spend,
    168         Coin utxo_to_spend;
    169         assert(coins_cache.GetCoin(outpoint_to_spend, utxo_to_spend));
    170         input_coins.insert({outpoint_to_spend, utxo_to_spend});
    171+        inputs_amount += utxo_to_spend.out.nValue;
    172     }
    173     // - Default signature hashing type
    174     int nHashType = SIGHASH_ALL;
    175     std::map<int, bilingual_str> input_errors;
    176     assert(SignTransaction(mempool_txn, &keystore, input_coins, nHashType, input_errors));
    177 
    178+    // Calculate fees paid
    179+    CAmount current_fee = inputs_amount - std::accumulate(outputs.begin(), outputs.end(), CAmount(0),
    180+        [](const CAmount& acc, const CTxOut& out) {
    181+            return acc + out.nValue;
    182+        });
    183+
    184+    // Deduct fees from fee_output to meet feerate if set
    185+    if (feerate.has_value()) {
    186+        assert(fee_output.has_value());
    187+        assert(fee_output.value() < mempool_txn.vout.size());
    188+
    189+        CAmount target_fee = feerate.value().GetFee(GetVirtualTransactionSize(CTransaction{mempool_txn}));
    190+        CAmount deduction = target_fee - current_fee;
    191+
    192+        if (deduction > 0) {
    193+            // Only deduct fee if there's anything to deduct.
    194+            // If the caller has put more fees than the target feerate, don't change the fee.
    195+            mempool_txn.vout[fee_output.value()].nValue -= deduction;
    196+
    197+            // Re-sign since an output has changed
    198+            input_errors.clear();
    199+            assert(SignTransaction(mempool_txn, &keystore, input_coins, nHashType, input_errors));
    200+            current_fee = target_fee;
    201+        }
    202+    }
    203+
    204+    return {mempool_txn, current_fee};
    205+}
    206+
    207+CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::vector<CTransactionRef>& input_transactions,
    208+                                                                     const std::vector<COutPoint>& inputs,
    209+                                                                     int input_height,
    210+                                                                     const std::vector<CKey>& input_signing_keys,
    211+                                                                     const std::vector<CTxOut>& outputs,
    212+                                                                     bool submit)
    213+{
    214+    CMutableTransaction mempool_txn = CreateValidTransaction(input_transactions, inputs, input_height, input_signing_keys, outputs).first;
    215+
    216     // If submit=true, add transaction to the mempool.
    217     if (submit) {
    218         LOCK(cs_main);
    219diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
    220index 106bee6b4b..6c15140ffa 100644
    221--- a/src/test/util/setup_common.h
    222+++ b/src/test/util/setup_common.h
    223@@ -9,6 +9,7 @@
    224 #include <key.h>
    225 #include <node/caches.h>
    226 #include <node/context.h>
    227+#include <policy/feerate.h>
    228 #include <primitives/transaction.h>
    229 #include <pubkey.h>
    230 #include <random.h>
    231@@ -20,10 +21,10 @@
    232 #include <util/vector.h>
    233 
    234 #include <functional>
    235+#include <optional>
    236 #include <type_traits>
    237 #include <vector>
    238 
    239-class CFeeRate;
    240 class Chainstate;
    241 
    242 /** This is connected to the logger. Can be used to redirect logs to any other log */
    243@@ -154,6 +155,27 @@ struct TestChain100Setup : public TestingSetup {
    244     //! Mine a series of new blocks on the active chain.
    245     void mineBlocks(int num_blocks);
    246 
    247+    /**
    248+     * Create a transaction, optionally setting the fee based on the feerate.
    249+     * Note: The feerate may not be met exactly depending on whether the signatures can have different sizes.
    250+     *
    251+     * [@param](/bitcoin-bitcoin/contributor/param/) input_transactions   The transactions to spend
    252+     * [@param](/bitcoin-bitcoin/contributor/param/) input_height         The height of the block that included the input transactions.
    253+     * [@param](/bitcoin-bitcoin/contributor/param/) inputs               Outpoints with which to construct transaction vin.
    254+     * [@param](/bitcoin-bitcoin/contributor/param/) input_signing_keys   The keys to spend the input transactions.
    255+     * [@param](/bitcoin-bitcoin/contributor/param/) outputs              Transaction vout.
    256+     * [@param](/bitcoin-bitcoin/contributor/param/) feerate              The feerate the transaction should pay.
    257+     * [@param](/bitcoin-bitcoin/contributor/param/) fee_output           The index of the output to take the fee from.
    258+     * [@return](/bitcoin-bitcoin/contributor/return/) The transaction and the fee it pays
    259+     */
    260+    std::pair<CMutableTransaction, CAmount> CreateValidTransaction(const std::vector<CTransactionRef>& input_transactions,
    261+                                                                   const std::vector<COutPoint>& inputs,
    262+                                                                   int input_height,
    263+                                                                   const std::vector<CKey>& input_signing_keys,
    264+                                                                   const std::vector<CTxOut>& outputs,
    265+                                                                   const std::optional<CFeeRate>& feerate = std::nullopt,
    266+                                                                   const std::optional<uint32_t>& fee_output = std::nullopt);
    267+
    268     /**
    269      * Create a transaction and submit to the mempool.
    270      *
    

    glozow commented at 3:33 pm on July 21, 2023:
    Taken, thanks
  89. in src/policy/packages.cpp:115 in 005581b5ea outdated
    159+    for (const auto& txid : ancestor_set->second) {
    160+        auto it = txid_to_tx.find(txid);
    161+        if (excluded_txns.find(txid) == excluded_txns.end()) {
    162+            result.push_back(it->second);
    163+        }
    164+    }
    


    achow101 commented at 9:32 pm on June 14, 2023:

    In 005581b5ea61edd444439900109c527c7112a650 “[packages] AncestorPackage sorts and builds ancestor subsets”

    nit: These two loops could be combined

  90. in src/test/txpackage_tests.cpp:167 in 005581b5ea outdated
    139+            auto parent = MakeTransactionRef(CreateValidMempoolTransaction(m_coinbase_txns[parent_idx + 1],
    140+                                             0, 0, coinbaseKey, spk, CAmount(49 * COIN), false));
    141+            package.emplace_back(parent);
    142+            child.vin.push_back(CTxIn(COutPoint(parent->GetHash(), 0)));
    143+        }
    144+        child.vout.push_back(CTxOut(4900 * COIN, spk));
    


    achow101 commented at 9:39 pm on June 14, 2023:

    In 005581b5ea61edd444439900109c527c7112a650 “[packages] AncestorPackage sorts and builds ancestor subsets”

    nit: The inputs add up to 4851, not 4900.

  91. in src/policy/packages.h:109 in 005581b5ea outdated
    104+    std::unordered_set<uint256, SaltedTxidHasher> excluded_txns;
    105+    /** Txids of transactions that are banned. Return nullopt from GetAncestorSet() if it contains
    106+     * any of these transactions.*/
    107+    std::unordered_set<uint256, SaltedTxidHasher> banned_txns;
    108+
    109+    /** Helper function for recursively constructing ancestor caches in ctor. */
    


    ismaelsadeeq commented at 5:22 pm on June 15, 2023:
    what is cto please Saw it in policy/packages.cpp AncestorPackage::visit description comment?

    fjahr commented at 9:22 pm on June 15, 2023:
    ctor is short for constructor.
  92. glozow marked this as a draft on Jun 19, 2023
  93. sipa commented at 4:11 pm on June 27, 2023: member

    I’ve discussed some of the concerns here with @instagibbs, though haven’t reviewed the code or read all the discussion.

    As I understand it, there are (at least) these two issues to be addressed:

    • If a package is received, but its overall feerate is too low, it may still be the case that there are acceptable subpackages (at the top of the graph) we would have liked to accept (and this could happen when e.g. we missed the initial announcement of those subpackages). This is a regression compared to the current non-package relay, as orphan handling may resolve such cases today, though it’s not clear to me how serious this is.
    • It’s also possible that the feerate of the overall package is sufficient to be accepted, but it contains a low-feerate subset (at the “bottom” of the graph) that is actually too low, and we would ideally avoid accepting (possibly while still accepting the rest), to avoid cases where low feerate stuff makes it into our mempool (accidentally or maliciously) through the package relay mechanism.

    My belief is that the second problem, deciding whether a low-feerate subset exists in a package, is actually equivalent to the cluster linearization/chunking problem (see #27677). A special case is a single leaf transaction in the package that’s low-fee, but the more general question is much harder.

    If the “accepting low-fee stuff” is considered a problem (as it appears to be, as you’re trying to detect it for the last transaction in the package), then my thinking is that we actually may want to address it through a cluster linearization lens (independent of actually making the mempool cluster-based):

    • Run some linearization algorithm on the received package (it may be a very dumb one, like just sorting topologically, or the ancestor set feerate based one that the current mempool mining logic is using, or possibly later a much more intelligent one). Given that you’re inherently limited to (I think) 25 transaction packages anyway, this ought to be very fast, even for ancestor sets). I’m happy to provide code to do this (I’ve been working on this problem the past few weeks anyway). The smarter the linearization is, the more capable it’ll be at detecting sneaking-in, but even dumb topological sort is sufficient to detect the “single last bad transaction” case.
    • Run chunking on the linearization. This is a very simple O(n) algorithm that partitions the linearization into chunks, with the property that the combined feerate of the transactions in each consecutive chunk goes down.
    • Consider feerate checks of mempool insertion for every chunk separately, and abort as soon as one fails (possibly keeping the previous chunks, if that’s easily doable). Given that chunk feerates are monotonously decreasing, if one fails for feerate reasons, the later ones will fail too.

    This approach does not deal well with (transient) failures for non-feerate reasons, because if some transactions fails checks, if you’ve already linearized, everything after it may possibly depend on the failing one. It’s possible in that case to go back and relinearize, but perhaps it’s okay to just give up then?

  94. glozow commented at 11:08 am on June 29, 2023: member

    As I understand it, there are (at least) these two issues to be addressed:

    Yes, essentially “the package contains valid transactions, but we should only accept a subset of it due to fees.” The first one can happen normally if we just came out of IBD or our peer has a lower minfeerate than we do - this PR was initially to address that. It was also to address “the package contains some invalid transactions, and we should take the subset that is valid” but I now think of that as a distinct concern.

    For the second, I believe the worst concerns are due to problems with eviction (i.e. fixed with cluster mempool). But it’s also such a waste to validate, accept, and potentially relay them, so I think we should try to reject it as soon as possible.

    Most problems with this PR boil down to how it groups transactions together (each time, just take the not-yet-submitted ancestor set of each tx). It’s not smart enough to, for example, find a parent+2children group since every grouping is ancestor set-shaped. It also seems that its retry attempts (specifically the O(n^2) PreChecks) are a dealbreaker, so being able to decide on groups earlier is important.

    My belief is that the second problem, deciding whether a low-feerate subset exists in a package, is actually equivalent to the cluster linearization/chunking problem

    Yes! I think using ancestor feerate linearization + chunking method would be a much better way to divide the package into subgroups before validation. I also spent some time whiteboarding whether package RBF could be feasible for non-v3 this way. I haven’t found anything I’m happy with yet, but I think incorporating “cost to RBF conflicts” into the chunking algorithm could be a start.

    One thing I want to preserve from the current approach is minimizing groups when we can. For example, when the “top” of the package is above minimum feerate, we’ll try it by itself before grouping. One goal of that is to prevent any potential regression from legacy orphan handling. Another problem is around granularity of checking ancestor/descendant limits: imagine a diamond shape (grandparent, 2 parents, 1 child) where grandparent has 23 in-mempool ancestors. The child won’t make it regardless of fee, since it would have 26 ancestors. So if it’s not necessary to chunk all 4 together (i.e. no need to CPFP), we shouldn’t. [1]

    So I think we’d want to use the linearization/chunking algo, but favor smaller chunks whenever possible. And preserve the behavior where, when every transaction’s feerate is above our minimums, we just call AcceptSingleTransaction on each one. This also allows RBF.

    This approach does not deal well with (transient) failures for non-feerate reasons, because if some transactions fails checks, if you’ve already linearized, everything after it may possibly depend on the failing one. It’s possible in that case to go back and relinearize, but perhaps it’s okay to just give up then?

    I still believe the answer to this is to give up and ensure we do not block an honest peer from relaying it to us later. If a peer is stuffing invalid transactions into a package, we don’t waste any time and imo it’s reasonable to wait for an honest peer to send the real package to us.

    I’ve been wondering if we should still try the transactions that are not descendants of the transaction(s) that failed. It seems a shame to just skip validation for a transaction we downloaded and has no dependencies on things we deemed invalid. But this does leave room for weird behavior… for example, we can only take 1 of 2 transactions before hitting descendant limits, so stuffing the package with high-feerate invalid txns can make us accept the lower-feerate one first if we don’t re-linearize after we see the invalid tx. One idea is, when something is invalid for non-fee reasons, we skip its descendants but do a re-linearization that’s topo-only and randomly tie-broken for everything else? But maybe this is unrealistic and not worth thinking about.

    Anyway, to summarize, the approach I’m going with now is:

    1. Basic sanitization. Topological sort.
    2. Grab UTXOs and calculate fees. Deduplicate txns already in mempool or seemingly just-confirmed. Drop things with missing inputs.
    3. Refine our linearization using the fee information.
    4. If each transaction passes CheckFeeRate by themselves, just call AcceptSingleTransaction(tx) for each tx -> return
    5. Chunkify, favoring smaller chunks (i.e. don’t chunk together if possible)
    6. For each chunk, AcceptPackageWrappingSingle(chunk) -> return

    Will see what can be borrowed from the cluster mempool code - thanks!!

    [1] Similarly, since PackageMempoolChecks would overestimate each parent’s ancestor count the way it’s currently implemented (it was written with the assumption that inputs would be tx + ancestors), we should be willing to take one parent and not the other.

  95. instagibbs commented at 2:25 pm on June 29, 2023: member

    One goal of that is to prevent any potential regression from legacy orphan handling

    Sorry can you motivate the issue here explicitly? How would this issue not arise in cluster mempool world?

    favoring smaller chunks over larger ones.

    I think it’d be good to be explicit what “favoring” means here. Is this just individual submissions, then the full chunk on the rest of the chunk on an individual failure?

    Refine our linearization using the fee information.

    Any thoughts on what algo here? IIUC Pieter should have ancestor-set rate code ready to go so maybe that could be used as good enough for mvp.

  96. sipa commented at 8:12 pm on June 30, 2023: member

    I also spent some time whiteboarding whether package RBF could be feasible for non-v3 this way. I haven’t found anything I’m happy with yet, but I think incorporating “cost to RBF conflicts” into the chunking algorithm could be a start.

    I haven’t thought hard about this, but package RBF sounds a whole lot more complex - and that’s probably something that actually becomes easier to reason about in a post-clustermempool world.

    One thing I want to preserve from the current approach is minimizing groups when we can. For example, when the “top” of the package is above minimum feerate, we’ll try it by itself before grouping. One goal of that is to prevent any potential regression from legacy orphan handling. Another problem is around granularity of checking ancestor/descendant limits: imagine a diamond shape (grandparent, 2 parents, 1 child) where grandparent has 23 in-mempool ancestors. The child won’t make it regardless of fee, since it would have 26 ancestors. So if it’s not necessary to chunk all 4 together (i.e. no need to CPFP), we shouldn’t. [1]

    Interesting; ancestor set size limits do complicate things, and do factor into this. Ideally, I think the linearization and chunking just take these limits (including those impacted by existing in-mempool transactions) into account, but that’s a lot less trivial. However, for specifically this concern a possibility is just weeding out transactions from the package whose ancestor set size would exceed the limit, before even invoking linearization, because ancestor set size of a transaction is a static property that does not depend on whatever else may be included. They can be ignored, as they’re never going to be acceptable, so they shouldn’t affect the order of other things that can be. It may be an independently useful optimization.

    So I think we’d want to use the linearization/chunking algo, but favor smaller chunks whenever possible. And preserve the behavior where, when every transaction’s feerate is above our minimums, we just call AcceptSingleTransaction on each one. This also allows RBF.

    About minimizing groupings in general, you’re right. Chunking (as I had in mind) is actually too aggressive, and it’s better to only group things as necessary. I don’t think you need to special case individual transactions even, actually. Instead, use this, instead of chunking:

    • Start with an empty group of transactions to process.
    • Go through the transactions in linearized order, one by one:
      • Add transaction to the group of transactions to process.
      • Compute the combined feerate of that group (total fees divided by total size).
        • If that feerate is sufficiently high, process the transactions in the group, and clear the group.
        • If that feerate is not high enough, just leave the transaction in the group and continue with the next one.
    • If the final group is above the acceptable rate, process it. If not, discard it.

    This will, I think, in most cases just process the transactions one by one, once. Only situations like a CPFP of which the parent alone isn’t sufficient will trigger a grouped validation.

    I still believe the answer to this is to give up and ensure we do not block an honest peer from relaying it to us later. If a peer is stuffing invalid transactions into a package, we don’t waste any time and imo it’s reasonable to wait for an honest peer to send the real package to us.

    I agree. The goal isn’t to make sure that all imaginable unacceptable packages with some acceptable subpackage get considered. If the peer is sending bad things, we’re allowed to just give up, as long as we make sure that in honest situations it’s recoverable.

    Anyway, to summarize, the approach I’m going with now is:

    I’d modify step 4-6 to instead use the lazy grouping I described above, and use single transaction processing whenever the group happens to be a single transaction.

  97. instagibbs commented at 4:44 pm on July 3, 2023: member
    Without going full cluster mempool, I think whatever linearization is given out, we should probably ensure that each remaining prefix of the ancestor package is an ancestor package itself, before attempting submission to the mempool. Skip the entry if it’s not. Maybe with ancestor set scoring linearizer this is redundant? I can’t tell. Ancestor set scoring is probably going to get a lot closer to the ordering you want regardless, as it catches things like #26711 (review)
  98. glozow commented at 2:39 pm on July 4, 2023: member

    I don’t think you need to special case individual transactions even, actually. Instead, use this, instead of chunking:

    I’m not sure about using a group’s aggregate feerate without checking their spending relationships, as it may allow unrelated transactions to pay for each other. For example:

    0A(1)  B(3)
    1   ^   ^ 
    2  C(100)
    

    Where minfeerate is 2sat/vB. Imagine C is invalid (e.g. a fake child created to connect A and B). The ancestor score-based linearization I was imagining (i.e. the current BlockAssembler algorithm) would be selecting them as part of C’s ancestor set, so the result could be ABC or BAC. If the linearization is ABC: A is below feerate, but A+B is ok. We’ve allowed B to “pay for” A but they’re unrelated. If the linearization is BAC we’re fine.

    I think whatever linearization is given out, we should probably ensure that each remaining prefix of the ancestor package is an ancestor package itself, before attempting submission to the mempool. Skip the entry if it’s not.

    We could do lazy grouping but only try groups that are connected (e.g. n-parents-1-child or 1-parent-2-children, etc)? Requiring them to be ancestor set-shaped would miss e.g. the 1-parent-2-children case.

    Maybe we can linearize the selected ancestor sets themselves to guarantee that, in this kind of situation, we’d always look at B before A? I think that would address the “only one can fit in package limits” problem as well.

  99. instagibbs commented at 2:50 pm on July 4, 2023: member

    Nothing is perfect, but a linearizer would ideally pick B before A, yes. So you might get:

    B, A, C or B, C, A

    depending on if the strategy is greedy. “topo” sort may miss this, which is why we should probably be smarter than that?

  100. sipa commented at 3:03 pm on July 4, 2023: member

    @glozow You’re right; I didn’t think this through. @instagibbs To a limited extent better linearization can help here (though within-chunk optimization isn’t something I’ve been looking at, as it doesn’t matter for mining/eviction) but I think you can construct more complex examples where even a “perfect” linearization results in grouping of things that should not pay for each other.

    I’m starting to think that something closer to your idea here is right: trying ancestor sets of every transaction in the linearization in order, if the ancestor set feerate is suffiicently high. This indeed won’t deal with multiple-children-pay-for-parent cases perfectly, but including everything connected may be too much as well. I’ll try to think about this more.

  101. ajtowns commented at 4:56 pm on July 4, 2023: contributor

    I’m starting to think that something closer to your idea here is right: trying ancestor sets of every transaction in the linearization in order, if the ancestor set feerate is suffiicently high. This indeed won’t deal with multiple-children-pay-for-parent cases perfectly, but including everything connected may be too much as well. I’ll try to think about this more.

    I don’t think handling multiple-children-pay-for-parent cases perfectly should be a goal here – we’re not fixing eviction (or changing the mining algo) at this point, so we’ll still have imperfect corner cases anyway. Just getting “high feerate child pays for below minfee ancestors” mostly working, without introducing a DoS or breaking existing functional tx patterns, should be enough of a win IMO.

  102. sipa commented at 5:59 pm on July 5, 2023: member

    @ajtowns Yes, I don’t think we have any requirement or even strong desire to go beyond ancestor-set-based linearization quality (= sufficient for single child pay for parent) in general, so anything beyond that would be “extra”. Just trying ancestor sets of consecutive linearization elements (whenever their joint feerate is high enough) sounds sufficient to me, but I’m still wondering about:

    • whether something simpler is possible that’s equally good.
    • whether there is a more general way to think about this - imagine we have access to an optimal linearization algorithm, is there are tractable optimal solution to this problem too? If so, that may inform how to think about this.
  103. glozow force-pushed on Jul 19, 2023
  104. DrahtBot added the label CI failed on Jul 19, 2023
  105. glozow force-pushed on Jul 21, 2023
  106. glozow force-pushed on Jul 21, 2023
  107. glozow commented at 9:12 am on July 24, 2023: member

    I’m starting to think that something closer to your idea here is right: trying ancestor sets of every transaction in the linearization in order, if the ancestor set feerate is suffiicently high. This indeed won’t deal with multiple-children-pay-for-parent cases perfectly, but including everything connected may be too much as well.

    I don’t think handling multiple-children-pay-for-parent cases perfectly should be a goal here

    I agree that the non-ancestor-set-shaped submission is not something we should try to do right now.

    Outline of approach I’ve just pushed (lmk what y’all think?):

    • Basic sanitization. Linearize (Topological sort only)
    • PreChecks loop For each tx, grab UTXOs to calculate fees and filter out anything we should skip:
      • If already in mempool (or same txid in mempool), mark as skip
      • If missing inputs or conflict [2], record this failure and mark this and all descendants as skip.
      • If no failures or TX_SINGLE_FAILURE [3], continue
      • Otherwise, record this failure and mark that we will quit_early.
    • Refine our linearization using the fee information [4]
    • Subpackage validation loop For each transaction in the new linearized order:
      • Get the transaction’s ancestor subpackage.
      • If the feerate of this transaction is insufficient, continue; [5]
      • If the feerate of this subpackage is insufficient, continue;
      • Otherwise, try to submit the subpackage, using AcceptSingleTransaction() if it’s just 1 tx [6]
      • if at any point we get a non-fee-related error, abort all.
    • Backfill results:
      • Try to use results from the subpackage validation loop.
      • If that doesn’t exist (i.e. we quit early), use results from prechecks loop.
      • If that doesn’t exist (i.e. it depends on something missing inputs), fill with TX_UNKNOWN.

    This means we will call PreChecks for each transaction 2 times (fewer if we quit early), and run all other validation checks at most 1 time. A transaction shouldn’t be validated in the subpackage validation loop more than once. Note that UTXOs are cached in m_view so there is not repeated loading the second time we call PreChecks.

    [1] I’m using PreChecks for this in order to keep the anti-DoS fail-fast checks. These checks are very cheap and I don’t think we should skip them before loading UTXOs. For example, something larger than max standard size could cause us to load a lot of UTXOs. But we could discuss using something more minimal than PreChecks. I also think the things done in PreChecks() like calculating ancestors and in-mempool conflicts can be used to refine some of the logic. For example, we could quickly see that a tx conflicts with something it can’t replace and skip it. [2] i.e. TX_MISSING_INPUTS or TX_CONFLICT. This includes missing inputs and txn-already-known (something we guess is already confirmed). I special case’d these as something we should be more tolerant of (i.e. skip instead of abort) because I can imagine them happening in honest cases when we have a different chainstate from our peer, and it’s fairly easy to skip over them. [3] This includes fee-related errors, but only the ones that can be bypassed by package validation. Unless we remove the rule from #26933, something below min relay feerate would not fall in this category. And since we have no package RBF, not meeting fee-related RBF requirements is not in this category. I’ve documented these in the code. [4] I’m using a slightly modified MiniMiner here to get the order in which these transactions would be mined. It does not pull the mempool ancestors/cluster since there is no limit to how large that cluster might be. [5] This naturally resolves the “package feerate is ok but the bottom tx isn’t” issue. [6] Rationale #26711 (comment)

  108. in src/consensus/validation.h:57 in f73efcd171 outdated
    53@@ -54,6 +54,8 @@ enum class TxValidationResult {
    54     TX_CONFLICT,
    55     TX_MEMPOOL_POLICY,        //!< violated mempool's fee/size/descendant/RBF/etc limits
    56     TX_NO_MEMPOOL,            //!< this node does not have a mempool so can't validate the transaction
    57+    TX_SINGLE_FAILURE,        //!< fee was insufficient to meet some policy (minimum/RBF/etc)
    


    instagibbs commented at 1:59 pm on July 24, 2023:

    yay! much easier to think about now

    needs a reasonable comment to distinguish from TX_MEMPOOL_POLICY

    my suggestion was adding something like “but result may differ when in different package”


    glozow commented at 10:36 pm on August 14, 2023:
    Added that to the comment :+1:
  109. in src/validation.cpp:1342 in 86e556add9 outdated
    1344@@ -1334,6 +1345,23 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
    1345     return PackageMempoolAcceptResult(package_state, std::move(results));
    1346 }
    1347 
    1348+PackageMempoolAcceptResult MemPoolAccept::AcceptPackageWrappingSingle(const std::vector<CTransactionRef>& subpackage, ATMPArgs& args)
    1349+{
    1350+    ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
    1351+    AssertLockHeld(::cs_main);
    1352+    AssertLockHeld(m_pool.cs);
    1353+    if (subpackage.size() > 1) {
    


    instagibbs commented at 2:21 pm on July 24, 2023:
    very glad to see the special casing of this to MISSING_INPUTS gone
  110. in src/validation.cpp:1503 in 6fc2bd3c69 outdated
    1522+        if (submission_result.m_tx_results.count(wtxid) > 0) {
    1523+            // Already in submission result.
    1524+            Assume(results_final.count(wtxid) == 0);
    1525+            continue;
    1526+        } else if (const auto it{results_final.find(wtxid)}; it != results_final.end()) {
    1527+            // Already-in-mempool transaction.
    


    instagibbs commented at 2:35 pm on July 24, 2023:

    “[refactor] fill in results for every tx in AcceptPackage”

    It could have just been submitted in the above loop, maybe if this goes away later it won’t be worth being more verbose…


    glozow commented at 11:00 pm on August 14, 2023:
    comment has been removed
  111. in src/node/mini_miner.cpp:135 in 8a7087b0e5 outdated
    123@@ -124,6 +124,34 @@ MiniMiner::MiniMiner(const CTxMemPool& mempool, const std::vector<COutPoint>& ou
    124     SanityCheck();
    125 }
    126 
    127+MiniMiner::MiniMiner(const std::vector<MiniMinerMempoolEntry>& manual_entries,
    


    instagibbs commented at 2:48 pm on July 24, 2023:

    We’ll need some miniminer Experts(TM) to review this, but I’m just going to assume for now that this will be used as a topological “tie breaker” for linearization that is currently in the branch to this point

    so if there’s a bug in it, it’s no worse than topo-sort, and can be improved later if dependencies need to be cut for whatever reason


    glozow commented at 10:37 pm on August 14, 2023:
    Yeah agreed, makes it much more important that there isn’t e.g. a crash bug in there somewhere, and if anything goes wrong we should quit gracefully and default to topo sort.

    instagibbs commented at 7:10 pm on September 28, 2023:
    cc @murchandamus I think this is something you could review without too much additional background
  112. in src/policy/ancestor_packages.h:82 in e82f4e7d7b outdated
    77+                if (mining_sequence.value() == rhs.mining_sequence.value()) {
    78+                    // Identical mining sequence means they would be included in the same ancestor
    79+                    // set. The one with fewer ancestors comes first.
    80+                    if (ancestor_subset.size() == rhs.ancestor_subset.size()) {
    81+                        // Individual feerate. This is not necessarily fee-optimal, but helps in some situations.
    82+                        // (a.fee * a.vsize > b.fee * a.vsize) is a shortcut for (a.fee / a.vsize > b.fee / b.vsize)
    


    instagibbs commented at 3:24 pm on July 24, 2023:

    (a.fee * a.vsize > b.fee * a.vsize)

    fix comment


    achow101 commented at 8:27 pm on July 31, 2023:

    In e82f4e7d7b41592b8891a2c6f9b961b2fc2ec51e “[txpackages] add AncestorPackage for linearizing packages”

    typo

    0                        // (a.fee * b.vsize > b.fee * a.vsize) is a shortcut for (a.fee / a.vsize > b.fee / b.vsize)
    

    glozow commented at 10:40 pm on August 14, 2023:
    Fixed
  113. in src/policy/ancestor_packages.h:118 in e82f4e7d7b outdated
    113+     * include skipped ancestors. If this transaction dangles, returns std::nullopt. */
    114+    std::optional<std::vector<CTransactionRef>> GetAncestorSet(const CTransactionRef& tx);
    115+    /** Get the total fee and vsize of the ancestor subpackage for a tx. Includes the tx. Does not
    116+     * include skipped ancestors. If this transaction dangles or fee and vsize are
    117+     * unavailable, returns std::nullopt. This result is always consistent with GetAncestorSet(). */
    118+    std::optional<std::pair<CAmount, int64_t>> GetAncestorFeeAndVsize(const CTransactionRef& tx);
    


    instagibbs commented at 3:32 pm on July 24, 2023:
    would be nice to put “Filtered” in the function names of whatever is doing filtering to reduce mental load

    glozow commented at 10:49 pm on August 14, 2023:
    Renamed to FilteredAncestorSet and FilteredAncestorFeeAndVsize
  114. in src/validation.cpp:1511 in 68c29044bb outdated
    1512+                    break;
    1513+                }
    1514+                case TxValidationResult::TX_RESULT_UNSET: case TxValidationResult::TX_UNKNOWN: case TxValidationResult::TX_NO_MEMPOOL:
    1515+                {
    1516+                    // These results shouldn't be possible.
    1517+                    Assume(false);
    


    instagibbs commented at 4:16 pm on July 24, 2023:
    if this somehow happens in prod, do we want to keep stumbling along?

    glozow commented at 10:50 pm on August 14, 2023:
    Imo it’s fine, since we don’t really do anything with it.
  115. in src/validation.cpp:1459 in 68c29044bb outdated
    1517+                    Assume(false);
    1518+                    break;
    1519+                }
    1520+                default:
    1521+                {
    1522+                    // If a transaction fails for any other reason, abort validation for the whole
    


    instagibbs commented at 4:25 pm on July 24, 2023:
    Seems like this will continue evaluating individual txns that have no in-package ancestors(that aren’t accepted individually before!). Seems safe, but wasn’t immediately obvious to me. It will abort any package evaluation later.

    glozow commented at 10:50 pm on August 14, 2023:
    Refined the comment
  116. in src/validation.cpp:1626 in 0ea0149c67 outdated
    1550             const auto individual_fee_vsize = linearized_package.GetFeeAndVsize(tx);
    1551             TxValidationState placeholder_state;
    1552             if (individual_fee_vsize.has_value() &&
    1553                 !CheckFeeRate(individual_fee_vsize->second, individual_fee_vsize->first, placeholder_state)) {
    1554                 // No need to validate if we know this transaction wouldn't meet feerate requirements.
    1555+                // Even if package feerate is sufficient, we don't allow "parent pay for child."
    


    instagibbs commented at 5:19 pm on July 24, 2023:

    I think we decided pre-cluster mempool we can’t really nail all these cases due to inherent symmetry.

    With this rule we can see cases where the penultimate child is paid for by parents, the penultimate child and final child being immediately trimmed next at below-minfee rates. It’s not very pathological since the evicted package is below minfee and if the mempool isn’t too large it is worthwhile to mine. I think we agreed these can still exist, but just noting.

  117. in src/validation.cpp:1733 in 0ea0149c67 outdated
    1614+                // We detect that a transaction was successful by looking for it in mempool.
    1615+                if (m_pool.exists(GenTxid::Wtxid(subpackage_wtxid))) {
    1616+                    linearized_package.Skip(subpackage_tx);
    1617+                }
    1618+            }
    1619+            if (subpackage_result.m_state.IsInvalid()) {
    


    instagibbs commented at 5:33 pm on July 24, 2023:
    might want to note that we do this final check after filling out results_final to give more meaningful tx results

    glozow commented at 10:50 pm on August 14, 2023:
    Added comment

    instagibbs commented at 5:38 pm on September 29, 2023:
    just noting even though I think it’s reasonable: we may bail on other subpackages(that would fit in mempool) if we hit chain limits on any particular subpackage?
  118. in src/validation.cpp:1546 in 2be70a6d26 outdated
    1516+            m_viewmempool.PackageAddTransaction(tx);
    1517+        }
    1518+    }
    1519+    // Clear the temporary coins in CCoinsViewMemPool and copies in CCoinsViewCache.
    1520+    for (const auto& outpoint : m_viewmempool.ClearPackageCoins()) {
    1521+        m_view.Uncache(outpoint);
    


    instagibbs commented at 5:53 pm on July 24, 2023:

    Note that UTXOs are cached in m_view so there is not repeated loading the second time we call PreChecks

    im a utxo cache newb but it looks like the the utxo caches are being wiped before the actual submission which has its own PreChecks?


    glozow commented at 8:31 am on August 9, 2023:
    These are just uncaching the temporary coins (i.e. outputs of the package transactions), so they are not part of the UTXO set and definitely weren’t pulled from disk.

    instagibbs commented at 1:05 pm on August 9, 2023:
    Ah! Might be worth being overly explanatory here.

    glozow commented at 10:51 pm on August 14, 2023:
    Added to the comment
  119. instagibbs commented at 4:45 pm on July 25, 2023: member

    new approach seems in line with what we’ve discussed previously. Basically #26711 (review) AJ’s suggestion, minus retries, plus using explicit linearization step.

    Obvious point but including miniminer dependency means we’ll likely have increased review surface, even if it ends up making a lot of sense in this case. From my basic experimentation in #28152 it looks like the performance hit is negligible, with 15k ops/s with cluster sizes of 25, and maybe that’s an underestimate since it’s constructing a more general cluster instead of a ancestor package.

    I noticed at least two nice cleanups based on this strategy, but I’m going to let more approach feedback come back before leaving more in-depth feedback.

  120. in src/policy/ancestor_packages.h:15 in 2be70a6d26 outdated
    10+
    11+#include <vector>
    12+
    13+/** A potential BIP331 Ancestor Package, i.e. one transaction with its set of ancestors.
    14+ * This class does not have any knowledge of chainstate, so it cannot determine whether all
    15+ * unconfirmed ancestors are present. Its constructor accepts any list of transactions that
    


    ariard commented at 2:46 am on July 29, 2023:

    So if my understanding of the API is correct, you receive a package from AcceptPackage(), and there is a call to the constructor AncestorPackage(), then we verify each transaction being component of the package is in the mempool by wtxid or txid or PreChecks() them.

    If the transaction is PreChecks() valid, then we call AddFeeAndVsize(). If we have a TX_SINGLE_FAILURE, we add AddFeeAndVsize() or if we have a TX_MISSING_INPUTS, we SkipWithDescendants() the transaction. All other cases are treaded as impossible or failure.

    So when we call LinearizeWithFees() in AcceptPackage and therefore start to rely on MiniMiner calculations. we have asserted than all packages component linearize are either spending a UTXO in the chainstate set, or spending an in-package component, so the worst-case scenario from a DoS viewpoint will be the limits as set in BIP331 / nversion=3 ?


    glozow commented at 8:52 am on July 31, 2023:

    So if my understanding of the API is correct, you receive a package from AcceptPackage() …

    Correct

    we have asserted than all packages component linearize are either spending a UTXO in the chainstate set, or spending an in-package component, so the worst-case scenario from a DoS viewpoint will be the limits as set in BIP331 / nversion=3 ?

    The first check in AcceptPackage is IsPackageWellFormed which requires the package to be 25 transactions at most. The linearization with MiniMiner also does not fetch in-mempool transactions. So you can assume we never construct the miniminer with more than 25 transactions.

    This is independent of v3 so those limits aren’t enforced.

    BIP331 doesn’t impose a maximum size other than 100 for pkgtxns, but I expect the p2p logic to observe that an ancpkginfo with more than 25 transactions wouldn’t be submitted and just exit there without downloading the transactions. So we wouldn’t send a getpkgtxns request for more than 25, and thus would immediately reject a (unsolicited) pkgtxns with more than 25. See logic for handling ancpkginfo, we will give up on downloading an ancestor package from a peer if it has too many transactions: https://github.com/bitcoin/bitcoin/pull/27742/files#diff-6875de769e90cec84d2e8a9c1b962cdbcda44d870d42e4215827e599e11e90e3R4398.


    ariard commented at 2:48 am on August 2, 2023:

    So you can assume we never construct the miniminer with more than 25 transactions.

    Thanks, yes I think MAX_PACKAGE_COUNT as enforced by IsPackageWellFormed is the upper bound that one can assume in the calculation of MiniMiner. This can ease the DoS reasoning to link the max ancpkginfo issued at the p2p level with MAX_PACKAGE_COUNT as requested by the mempool logic to save on bandwidth both by the local node (the list of wtxids announced) and its peer (pkgtnxs’s txn), I think.

  121. in src/policy/ancestor_packages.cpp:102 in e82f4e7d7b outdated
    83+}
    84+
    85+std::optional<std::vector<CTransactionRef>> AncestorPackage::GetAncestorSet(const CTransactionRef& tx)
    86+{
    87+    if (m_txid_to_entry.count(tx->GetHash()) == 0) return std::nullopt;
    88+    const auto& entry = m_txid_to_entry.find(tx->GetHash())->second;
    


    achow101 commented at 8:36 pm on July 31, 2023:

    In e82f4e7d7b41592b8891a2c6f9b961b2fc2ec51e “[txpackages] add AncestorPackage for linearizing packages”

    nit: avoid looking up twice

    0    const auto& entry_it = m_txid_to_entry.find(tx->GetHash());
    1    if (entry_it == m_txid_to_entry.end()) return std::nullopt;
    2    const auto& entry = entry_it->second;
    

    glozow commented at 10:39 pm on August 14, 2023:
    Done
  122. in src/policy/ancestor_packages.h:36 in e82f4e7d7b outdated
    31+     * */
    32+    bool m_is_ancestor_package{false};
    33+
    34+    /** Linearized transactions. Topological (IsSorted()) or, if fee information is provided through
    35+     * LinearizeWithFees(), using ancestor set scores. */
    36+    Package m_txns;
    


    achow101 commented at 8:44 pm on July 31, 2023:

    In e82f4e7d7b41592b8891a2c6f9b961b2fc2ec51e “[txpackages] add AncestorPackage for linearizing packages”

    Since all of the transactions in the package are already stored in PackageEntrys in m_txid_to_entry, could m_txns just be a vector of PackageEntry&? This would reduce the number of lookups to m_txid_entry that are needed just to sort the transactions when we are returning them.

    Additionally, if functions like GetAncestorSet used a std::set<PackageEntry&>, then std::sort at the end would not be necessary.


    glozow commented at 5:43 pm on August 8, 2023:
    std::vector<PackageEntry&> isn’t possible, but will do reference wrappers
  123. in doc/policy/packages.md:55 in cc756bcfa2 outdated
    47@@ -48,8 +48,8 @@ The following rules are enforced for all packages:
    48 The following rules are only enforced for packages to be submitted to the mempool (not enforced for
    49 test accepts):
    50 
    51-* Packages must be child-with-unconfirmed-parents packages. This also means packages must contain at
    52-  least 2 transactions. (#22674)
    53+* Packages must be ancestor packages, i.e. a transaction with its unconfirmed ancestors. This also
    54+  means packages must contain at least 2 transactions. (#26711)
    


    ariard commented at 1:41 am on August 2, 2023:

    From my understanding we have submitpackage which is the current interface under which child-with-unconfirmed-parents can be submitted. The definition is “a topologically sorted package that consists of exactly one child and all of its unconfirmed parents (no other transactions may be present)”.

    The definition given by AncestorPackage class is “A potential BIP331 Ancestor Package, i.e one transaction with its set of ancestors”, which sounds similar.

    If child-with-unconfirmed-parents and AncestorPackage definitions are identical, I think the child-with-unconfirmed-parents definition in packages.md can be updated to fusion them, and maybe ProcessNewPackage can be updated.


    glozow commented at 1:39 pm on October 2, 2023:
    Oh true, removed the 2+ thing
  124. in src/policy/ancestor_packages.h:156 in cc756bcfa2 outdated
    122+     * Should be called when a transaction is accepted to mempool or already found in it. */
    123+    void Skip(const CTransactionRef& transaction);
    124+    /** Skip a transaction and all of its descendants. From now on, if this transaction is present
    125+     * in the ancestor set, GetAncestorSet() returns std::nullopt for that tx. Does not affect Txns().
    126+     * Should be called when a transaction is missing inputs. */
    127+    void SkipWithDescendants(const CTransactionRef& transaction);
    


    ariard commented at 2:23 am on August 2, 2023:

    I think you thought about it in term of AncestorPackage API design, though what is the purpose of SkipWithDescendants as the “dangle” state of a transaction could be determined as soon as all the transactions are received and IsPackageWellFormed ?

    Therefore checking than we have available UTXOs in mempool (or in-package) to spend could be done before to call to AncestorPackage ctor and visit to obtain the ancestor_subset, sounds it could be a small perf again assuming AcceptPackage is re-worked a bit.


    glozow commented at 3:12 pm on August 4, 2023:

    Therefore checking than we have available UTXOs in mempool (or in-package) to spend could be done before to call to AncestorPackage ctor

    I don’t think this is true. We should sort the transactions before we look up UTXOs.


    ariard commented at 0:10 am on August 8, 2023:
    Fair, I still think it’s slightly less DoSy though depends benchmark and UTXO layout on disk of course.
  125. in src/policy/ancestor_packages.h:32 in cc756bcfa2 outdated
    27+    /** Whether m_txns contains a connected package in which all transactions are ancestors of the
    28+     * last transaction. This object is not aware of chainstate. So if m_txns only includes a
    29+     * grandparent and not the "connecting" parent, this will (incorrectly) determine that the
    30+     * grandparent is not an ancestor.
    31+     * */
    32+    bool m_is_ancestor_package{false};
    


    ariard commented at 2:34 am on August 2, 2023:
    The name could be more verbose to reflect the notion of “connection” it lays on, e.g m_is_ancestor_package.
  126. in src/policy/ancestor_packages.h:63 in cc756bcfa2 outdated
    46+        bool dangles{false};
    47+        /** This value starts as std::nullopt when we don't have any fee information yet. It can be
    48+         * updated by calling LinearizeWithFees() if this entry isn't being skipped. */
    49+        std::optional<uint32_t> mining_sequence;
    50+        /** (Modified) fees of this transaction. Starts as std::nullopt, can be updated using AddFeeAndVsize(). */
    51+        std::optional<CAmount> fee;
    


    ariard commented at 2:38 am on August 2, 2023:
    Unclear if “(Modified) fees” means GetModifiedFee()

    glozow commented at 9:43 am on August 9, 2023:
    Removed mention of “modified,” as it’s not really relevant whether it’s real fees or not
  127. glozow force-pushed on Aug 3, 2023
  128. DrahtBot removed the label CI failed on Aug 3, 2023
  129. glozow force-pushed on Aug 8, 2023
  130. instagibbs commented at 5:48 pm on August 8, 2023: member
    dropping the comments since I haven’t seen any approach pushback yet, and I don’t want to lose these comments accidentally
  131. DrahtBot added the label CI failed on Aug 8, 2023
  132. glozow force-pushed on Aug 14, 2023
  133. glozow force-pushed on Aug 14, 2023
  134. glozow commented at 10:59 pm on August 14, 2023: member
    Rebased on top of #28251 (which also knocked out 2 commits) and addressed comments
  135. DrahtBot removed the label CI failed on Aug 15, 2023
  136. DrahtBot added the label CI failed on Aug 29, 2023
  137. DrahtBot commented at 10:21 am on September 3, 2023: contributor

    Some iwyu issue?

     0/bin/bash ../libtool  --tag=CXX --preserve-dup-deps  --mode=link /usr/bin/ccache g++-9 -std=c++17 -O0 -g3 -ftrapv -fdebug-prefix-map=/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu=. -fstack-reuse=none -Wstack-protector -fstack-protector-all -fcf-protection=full -fstack-clash-protection       -fno-extended-identifiers -fPIE -pipe -std=c++17 -O1 -g0 -O2 -funsigned-char   -Wl,-z,relro -Wl,-z,now -Wl,-z,separate-code -pie      -pthread -lpthread -static -L/ci_container_base/depends/x86_64-pc-linux-gnu/lib  -o test/test_bitcoin test/test_bitcoin-main.o  wallet/test/test_test_bitcoin-wallet_test_fixture.o wallet/test/test_test_bitcoin-init_test_fixture.o test/test_bitcoin-addrman_tests.o test/test_bitcoin-allocator_tests.o test/test_bitcoin-amount_tests.o test/test_bitcoin-argsman_tests.o test/test_bitcoin-arith_uint256_tests.o test/test_bitcoin-banman_tests.o test/test_bitcoin-base32_tests.o test/test_bitcoin-base58_tests.o test/test_bitcoin-base64_tests.o test/test_bitcoin-bech32_tests.o test/test_bitcoin-bip32_tests.o test/test_bitcoin-bip324_tests.o test/test_bitcoin-blockchain_tests.o test/test_bitcoin-blockencodings_tests.o test/test_bitcoin-blockfilter_index_tests.o test/test_bitcoin-blockfilter_tests.o test/test_bitcoin-blockmanager_tests.o test/test_bitcoin-bloom_tests.o test/test_bitcoin-bswap_tests.o test/test_bitcoin-checkqueue_tests.o test/test_bitcoin-coins_tests.o test/test_bitcoin-coinstatsindex_tests.o test/test_bitcoin-compilerbug_tests.o test/test_bitcoin-compress_tests.o test/test_bitcoin-crypto_tests.o test/test_bitcoin-cuckoocache_tests.o test/test_bitcoin-dbwrapper_tests.o test/test_bitcoin-denialofservice_tests.o test/test_bitcoin-descriptor_tests.o test/test_bitcoin-flatfile_tests.o test/test_bitcoin-fs_tests.o test/test_bitcoin-getarg_tests.o test/test_bitcoin-hash_tests.o test/test_bitcoin-headers_sync_chainwork_tests.o test/test_bitcoin-httpserver_tests.o test/test_bitcoin-i2p_tests.o test/test_bitcoin-interfaces_tests.o test/test_bitcoin-key_io_tests.o test/test_bitcoin-key_tests.o test/test_bitcoin-logging_tests.o test/test_bitcoin-mempool_tests.o test/test_bitcoin-merkle_tests.o test/test_bitcoin-merkleblock_tests.o test/test_bitcoin-miner_tests.o test/test_bitcoin-miniminer_tests.o test/test_bitcoin-miniscript_tests.o test/test_bitcoin-minisketch_tests.o test/test_bitcoin-multisig_tests.o test/test_bitcoin-net_peer_eviction_tests.o test/test_bitcoin-net_tests.o test/test_bitcoin-netbase_tests.o test/test_bitcoin-orphanage_tests.o test/test_bitcoin-pmt_tests.o test/test_bitcoin-policy_fee_tests.o test/test_bitcoin-policyestimator_tests.o test/test_bitcoin-pool_tests.o test/test_bitcoin-pow_tests.o test/test_bitcoin-prevector_tests.o test/test_bitcoin-raii_event_tests.o test/test_bitcoin-random_tests.o test/test_bitcoin-rbf_tests.o test/test_bitcoin-rest_tests.o test/test_bitcoin-result_tests.o test/test_bitcoin-reverselock_tests.o test/test_bitcoin-rpc_tests.o test/test_bitcoin-sanity_tests.o test/test_bitcoin-scheduler_tests.o test/test_bitcoin-script_p2sh_tests.o test/test_bitcoin-script_parse_tests.o test/test_bitcoin-script_segwit_tests.o test/test_bitcoin-script_standard_tests.o test/test_bitcoin-script_tests.o test/test_bitcoin-scriptnum_tests.o test/test_bitcoin-serfloat_tests.o test/test_bitcoin-serialize_tests.o test/test_bitcoin-settings_tests.o test/test_bitcoin-sighash_tests.o test/test_bitcoin-sigopcount_tests.o test/test_bitcoin-skiplist_tests.o test/test_bitcoin-sock_tests.o test/test_bitcoin-streams_tests.o test/test_bitcoin-sync_tests.o test/test_bitcoin-system_tests.o test/test_bitcoin-timedata_tests.o test/test_bitcoin-torcontrol_tests.o test/test_bitcoin-transaction_tests.o test/test_bitcoin-translation_tests.o test/test_bitcoin-txindex_tests.o test/test_bitcoin-txpackage_tests.o test/test_bitcoin-txreconciliation_tests.o test/test_bitcoin-txrequest_tests.o test/test_bitcoin-txvalidation_tests.o test/test_bitcoin-txvalidationcache_tests.o test/test_bitcoin-uint256_tests.o test/test_bitcoin-util_tests.o test/test_bitcoin-util_threadnames_tests.o test/test_bitcoin-validation_block_tests.o test/test_bitcoin-validation_chainstate_tests.o test/test_bitcoin-validation_chainstatemanager_tests.o test/test_bitcoin-validation_flush_tests.o test/test_bitcoin-validation_tests.o test/test_bitcoin-validationinterface_tests.o test/test_bitcoin-versionbits_tests.o test/test_bitcoin-xoroshiro128plusplus_tests.o wallet/test/test_test_bitcoin-feebumper_tests.o wallet/test/test_test_bitcoin-psbt_wallet_tests.o wallet/test/test_test_bitcoin-spend_tests.o wallet/test/test_test_bitcoin-wallet_tests.o wallet/test/test_test_bitcoin-walletdb_tests.o wallet/test/test_test_bitcoin-wallet_crypto_tests.o wallet/test/test_test_bitcoin-wallet_transaction_tests.o wallet/test/test_test_bitcoin-coinselector_tests.o wallet/test/test_test_bitcoin-init_tests.o wallet/test/test_test_bitcoin-ismine_tests.o wallet/test/test_test_bitcoin-rpc_util_tests.o wallet/test/test_test_bitcoin-scriptpubkeyman_tests.o wallet/test/test_test_bitcoin-walletload_tests.o wallet/test/test_test_bitcoin-group_outputs_tests.o wallet/test/test_test_bitcoin-db_tests.o     libtest_util.a libbitcoin_wallet.a libbitcoin_node.a libbitcoin_cli.a libbitcoin_common.a libbitcoin_util.a libbitcoin_consensus.a crypto/libbitcoin_crypto_base.la crypto/libbitcoin_crypto_sse41.la crypto/libbitcoin_crypto_avx2.la crypto/libbitcoin_crypto_x86_shani.la  libunivalue.la leveldb/libleveldb.la crc32c/libcrc32c.la crc32c/libcrc32c_sse42.la  leveldb/libmemenv.la secp256k1/libsecp256k1.la -L/ci_container_base/depends/x86_64-pc-linux-gnu/lib -levent -L/ci_container_base/depends/x86_64-pc-linux-gnu/lib -levent_pthreads -levent minisketch/libminisketch.a minisketch/libminisketch_clmul.a -ldb_cxx-4.8   -L/ci_container_base/depends/x86_64-pc-linux-gnu/lib -lsqlite3 -lz -lm -lpthread libbitcoin_zmq.a -L/ci_container_base/depends/x86_64-pc-linux-gnu/lib -lzmq -lpthread -lrt 
     1libtool: link: /usr/bin/ccache g++-9 -std=c++17 -O0 -g3 -ftrapv -fdebug-prefix-map=/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu=. -fstack-reuse=none -Wstack-protector -fstack-protector-all -fcf-protection=full -fstack-clash-protection -fno-extended-identifiers -fPIE -pipe -std=c++17 -O1 -g0 -O2 -funsigned-char -Wl,-z -Wl,relro -Wl,-z -Wl,now -Wl,-z -Wl,separate-code -pie -pthread -o test/test_bitcoin test/test_bitcoin-main.o wallet/test/test_test_bitcoin-wallet_test_fixture.o wallet/test/test_test_bitcoin-init_test_fixture.o test/test_bitcoin-addrman_tests.o test/test_bitcoin-allocator_tests.o test/test_bitcoin-amount_tests.o test/test_bitcoin-argsman_tests.o test/test_bitcoin-arith_uint256_tests.o test/test_bitcoin-banman_tests.o test/test_bitcoin-base32_tests.o test/test_bitcoin-base58_tests.o test/test_bitcoin-base64_tests.o test/test_bitcoin-bech32_tests.o test/test_bitcoin-bip32_tests.o test/test_bitcoin-bip324_tests.o test/test_bitcoin-blockchain_tests.o test/test_bitcoin-blockencodings_tests.o test/test_bitcoin-blockfilter_index_tests.o test/test_bitcoin-blockfilter_tests.o test/test_bitcoin-blockmanager_tests.o test/test_bitcoin-bloom_tests.o test/test_bitcoin-bswap_tests.o test/test_bitcoin-checkqueue_tests.o test/test_bitcoin-coins_tests.o test/test_bitcoin-coinstatsindex_tests.o test/test_bitcoin-compilerbug_tests.o test/test_bitcoin-compress_tests.o test/test_bitcoin-crypto_tests.o test/test_bitcoin-cuckoocache_tests.o test/test_bitcoin-dbwrapper_tests.o test/test_bitcoin-denialofservice_tests.o test/test_bitcoin-descriptor_tests.o test/test_bitcoin-flatfile_tests.o test/test_bitcoin-fs_tests.o test/test_bitcoin-getarg_tests.o test/test_bitcoin-hash_tests.o test/test_bitcoin-headers_sync_chainwork_tests.o test/test_bitcoin-httpserver_tests.o test/test_bitcoin-i2p_tests.o test/test_bitcoin-interfaces_tests.o test/test_bitcoin-key_io_tests.o test/test_bitcoin-key_tests.o test/test_bitcoin-logging_tests.o test/test_bitcoin-mempool_tests.o test/test_bitcoin-merkle_tests.o test/test_bitcoin-merkleblock_tests.o test/test_bitcoin-miner_tests.o test/test_bitcoin-miniminer_tests.o test/test_bitcoin-miniscript_tests.o test/test_bitcoin-minisketch_tests.o test/test_bitcoin-multisig_tests.o test/test_bitcoin-net_peer_eviction_tests.o test/test_bitcoin-net_tests.o test/test_bitcoin-netbase_tests.o test/test_bitcoin-orphanage_tests.o test/test_bitcoin-pmt_tests.o test/test_bitcoin-policy_fee_tests.o test/test_bitcoin-policyestimator_tests.o test/test_bitcoin-pool_tests.o test/test_bitcoin-pow_tests.o test/test_bitcoin-prevector_tests.o test/test_bitcoin-raii_event_tests.o test/test_bitcoin-random_tests.o test/test_bitcoin-rbf_tests.o test/test_bitcoin-rest_tests.o test/test_bitcoin-result_tests.o test/test_bitcoin-reverselock_tests.o test/test_bitcoin-rpc_tests.o test/test_bitcoin-sanity_tests.o test/test_bitcoin-scheduler_tests.o test/test_bitcoin-script_p2sh_tests.o test/test_bitcoin-script_parse_tests.o test/test_bitcoin-script_segwit_tests.o test/test_bitcoin-script_standard_tests.o test/test_bitcoin-script_tests.o test/test_bitcoin-scriptnum_tests.o test/test_bitcoin-serfloat_tests.o test/test_bitcoin-serialize_tests.o test/test_bitcoin-settings_tests.o test/test_bitcoin-sighash_tests.o test/test_bitcoin-sigopcount_tests.o test/test_bitcoin-skiplist_tests.o test/test_bitcoin-sock_tests.o test/test_bitcoin-streams_tests.o test/test_bitcoin-sync_tests.o test/test_bitcoin-system_tests.o test/test_bitcoin-timedata_tests.o test/test_bitcoin-torcontrol_tests.o test/test_bitcoin-transaction_tests.o test/test_bitcoin-translation_tests.o test/test_bitcoin-txindex_tests.o test/test_bitcoin-txpackage_tests.o test/test_bitcoin-txreconciliation_tests.o test/test_bitcoin-txrequest_tests.o test/test_bitcoin-txvalidation_tests.o test/test_bitcoin-txvalidationcache_tests.o test/test_bitcoin-uint256_tests.o test/test_bitcoin-util_tests.o test/test_bitcoin-util_threadnames_tests.o test/test_bitcoin-validation_block_tests.o test/test_bitcoin-validation_chainstate_tests.o test/test_bitcoin-validation_chainstatemanager_tests.o test/test_bitcoin-validation_flush_tests.o test/test_bitcoin-validation_tests.o test/test_bitcoin-validationinterface_tests.o test/test_bitcoin-versionbits_tests.o test/test_bitcoin-xoroshiro128plusplus_tests.o wallet/test/test_test_bitcoin-feebumper_tests.o wallet/test/test_test_bitcoin-psbt_wallet_tests.o wallet/test/test_test_bitcoin-spend_tests.o wallet/test/test_test_bitcoin-wallet_tests.o wallet/test/test_test_bitcoin-walletdb_tests.o wallet/test/test_test_bitcoin-wallet_crypto_tests.o wallet/test/test_test_bitcoin-wallet_transaction_tests.o wallet/test/test_test_bitcoin-coinselector_tests.o wallet/test/test_test_bitcoin-init_tests.o wallet/test/test_test_bitcoin-ismine_tests.o wallet/test/test_test_bitcoin-rpc_util_tests.o wallet/test/test_test_bitcoin-scriptpubkeyman_tests.o wallet/test/test_test_bitcoin-walletload_tests.o wallet/test/test_test_bitcoin-group_outputs_tests.o wallet/test/test_test_bitcoin-db_tests.o  -lpthread -L/ci_container_base/depends/x86_64-pc-linux-gnu/lib libtest_util.a libbitcoin_wallet.a libbitcoin_node.a libbitcoin_cli.a libbitcoin_common.a libbitcoin_util.a libbitcoin_consensus.a crypto/.libs/libbitcoin_crypto_base.a crypto/.libs/libbitcoin_crypto_sse41.a crypto/.libs/libbitcoin_crypto_avx2.a crypto/.libs/libbitcoin_crypto_x86_shani.a ./.libs/libunivalue.a leveldb/.libs/libleveldb.a crc32c/.libs/libcrc32c.a crc32c/.libs/libcrc32c_sse42.a leveldb/.libs/libmemenv.a secp256k1/.libs/libsecp256k1.a -levent -levent_pthreads -levent minisketch/libminisketch.a minisketch/libminisketch_clmul.a -ldb_cxx-4.8 -lsqlite3 -lz -lm -lpthread libbitcoin_zmq.a -lzmq -lpthread -lrt -pthread
     2/usr/bin/ccache g++-9 -std=c++17 -DHAVE_CONFIG_H -I. -I../src/config  -DDEBUG -DDEBUG_LOCKORDER -DDEBUG_LOCKCONTENTION -DRPC_DOC_CHECK -DABORT_ON_FAILED_ASSUME -fmacro-prefix-map=/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu=.  -DHAVE_BUILD_INFO -DPROVIDE_FUZZ_MAIN_FUNCTION -I. -I./minisketch/include -I./secp256k1/include -I./univalue/include -isystem /ci_container_base/depends/x86_64-pc-linux-gnu/include -DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION -DBOOST_NO_CXX98_FUNCTION_BASE -isystem /ci_container_base/depends/x86_64-pc-linux-gnu/include -pthread -I/ci_container_base/depends/x86_64-pc-linux-gnu/include -I./bench/ -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC -D_LIBCPP_ENABLE_ASSERTIONS=1 -I/ci_container_base/depends/x86_64-pc-linux-gnu/include/ -DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE -O0 -g3 -ftrapv -fdebug-prefix-map=/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu=. -fstack-reuse=none -Wstack-protector -fstack-protector-all -fcf-protection=full -fstack-clash-protection       -fno-extended-identifiers -fPIE -pipe -std=c++17 -O1 -g0 -O2 -funsigned-char -c -o bench/bench_bitcoin-ancestorpackage.o `test -f 'bench/ancestorpackage.cpp' || echo './'`bench/ancestorpackage.cpp
     3bench/ancestorpackage.cpp: In function void AncestorPackageRandom(ankerl::nanobench::Bench&):
     4bench/ancestorpackage.cpp:16:23: error: variable FastRandomContext rand has initializer but incomplete type
     5   16 |     FastRandomContext rand{false};
     6      |                       ^~~~
     7make[2]: *** [Makefile:15575: bench/bench_bitcoin-ancestorpackage.o] Error 1
     8make[2]: Leaving directory '/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/src'
     9make[1]: Leaving directory '/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/src'
    10make[1]: *** [Makefile:20166: install-recursive] Error 1
    11make: *** [Makefile:811: install-recursive] Error 1
    
  138. in src/policy/ancestor_packages.cpp:191 in b8cd561b13 outdated
    182+    for (auto& [_, entry] : m_txid_to_entry) entry.mining_sequence = std::nullopt;
    183+    std::vector<node::MiniMinerMempoolEntry> miniminer_info;
    184+    std::map<uint256, std::set<uint256>> descendant_caches;
    185+    // For each non-skipped transaction, calculate their ancestor fee and vsize.
    186+    for (const auto& [txid, entry] : m_txid_to_entry) {
    187+        if (entry.skip) continue;
    


    instagibbs commented at 8:49 pm on September 11, 2023:

    if an entry is skip‘ed, but also included in another entry’s descendant_subset, this can cause an Assume to fail here https://github.com/bitcoin/bitcoin/pull/26711/files#diff-a8d952408e5a47db5e09d0b13117088fef66805cf706f6df83dfa0dc5814874eR142

    I think this is as simple as a descendant missing inputs, right? I’m uncertain to the implications of this.

    I hit this in fuzz testing, and commented out the assertion to continue fuzzing


    glozow commented at 8:40 am on September 13, 2023:
    ~Hm, it doesn’t seem possible to skip a tx that is a descendant of a non-skipped tx (that means we submitted a tx to mempool without submitting its ancestor). I haven’t seen the fuzzer but perhaps the issue is that the fuzzer is selecting transactions to skip a little too randomly?~ Edit: Ignore this, was only thinking about Skip() and not SkipWithDescendants

    glozow commented at 8:48 am on September 13, 2023:
    Jk I understand what you mean now by descendant missing inputs. I think the solution is to delete dangling transactions from descendant_subsets

    glozow commented at 10:49 am on September 14, 2023:
    I’ve fixed this by filtering the descendant_caches before passing it to MiniMiner (seemed cleaner than deleting from descendant_subset).

    instagibbs commented at 3:20 pm on September 29, 2023:
    looks fixed to me :+1:
  139. glozow force-pushed on Sep 13, 2023
  140. glozow force-pushed on Sep 13, 2023
  141. DrahtBot added the label Needs rebase on Sep 13, 2023
  142. glozow force-pushed on Sep 14, 2023
  143. glozow commented at 10:50 am on September 14, 2023: member
    Rebased this on top of master +#28471 +#28450 +#28472
  144. DrahtBot removed the label Needs rebase on Sep 14, 2023
  145. DrahtBot removed the label CI failed on Sep 14, 2023
  146. in src/policy/ancestor_packages.cpp:77 in 0b98391134 outdated
    65+        if (m_txid_to_entry.at(tx->GetHash()).ancestor_subset.empty()) visit(tx);
    66+        Assume(!m_txid_to_entry.at(tx->GetHash()).ancestor_subset.empty());
    67+        ++i;
    68+    }
    69+    // Sort by the number of in-package ancestors.
    70+    std::sort(m_txns.begin(), m_txns.end(), CompareEntry());
    


    instagibbs commented at 2:37 pm on September 14, 2023:
    0    const auto txns_copy = m_txns;
    1    std::sort(m_txns.begin(), m_txns.end(), CompareEntry());
    2        if (!IsSorted(Txns())) {
    3        m_txns = txns_copy;
    4    }  
    

    glozow commented at 9:11 am on September 27, 2023:
    Taken
  147. in src/policy/ancestor_packages.cpp:254 in 0b98391134 outdated
    215+    // Sort again, this time using mining score.
    216+    std::sort(m_txns.begin(), m_txns.end(), CompareEntry());
    217+    Assume(std::all_of(m_txid_to_entry.cbegin(), m_txid_to_entry.cend(), [](const auto& entry) {
    218+        bool should_have_sequence = !entry.second.skip && !entry.second.dangles;
    219+        return entry.second.mining_sequence.has_value() == should_have_sequence;
    220+    }));
    


    instagibbs commented at 2:37 pm on September 14, 2023:
    0    }));
    1    // If we somehow break topological ordering, revert to what we already had without
    2    // ancestor set scoring
    3    if (!IsSorted(Txns())) {
    4        m_txns = txns_copy;
    5        Assume(IsSorted(Txns())); // Not a lot we can do here
    6    }
    

    glozow commented at 9:03 am on September 27, 2023:
    Taken
  148. in src/policy/ancestor_packages.cpp:250 in 0b98391134 outdated
    211+    if (!miniminer.IsReadyToCalculate()) return false;
    212+    for (const auto& [txid, mining_sequence] : miniminer.Linearize()) {
    213+        m_txid_to_entry.at(txid).mining_sequence = mining_sequence;
    214+    }
    215+    // Sort again, this time using mining score.
    216+    std::sort(m_txns.begin(), m_txns.end(), CompareEntry());
    


    instagibbs commented at 2:38 pm on September 14, 2023:
    0    const auto txns_copy = m_txns;
    1    std::sort(m_txns.begin(), m_txns.end(), CompareEntry());
    

    glozow commented at 9:11 am on September 27, 2023:
    Taken
  149. instagibbs commented at 2:39 pm on September 14, 2023: member
    dropping a thought I had about “protecting” ourselves against a bug in ancestor set scoring, or similar so we can revert to the simpler topo-sort ordering we achieved at the beginning.
  150. DrahtBot added the label Needs rebase on Sep 20, 2023
  151. glozow commented at 1:41 pm on September 20, 2023: member
    Conflict is due to #28472 getting merged. I’m going to hold off on rebasing until the other bug fix + fuzzer are in as well. Hopefully that’s soon.
  152. glozow force-pushed on Sep 27, 2023
  153. DrahtBot removed the label Needs rebase on Sep 27, 2023
  154. glozow force-pushed on Sep 28, 2023
  155. glozow force-pushed on Sep 28, 2023
  156. glozow marked this as ready for review on Sep 28, 2023
  157. in src/policy/packages.cpp:70 in 2835a7b49e outdated
    50+            if (inputs_seen.find(input.prevout) != inputs_seen.end()) {
    51+                // This input is also present in another tx in the package.
    52+                return false;
    53+            }
    54+        }
    55+        // Batch-add all the inputs for a tx at a time. If we added them 1 at a time, we could
    


    instagibbs commented at 6:18 pm on September 28, 2023:

    This should be noted in the declaration. I’m also not sure how this is a “more severe” case. Is it really worth it to punt this until later when we have mempool lock?

    Also should have a unit/functional test to capture this allowed behavior.


    glozow commented at 10:05 am on October 2, 2023:
    Added note and a test
  158. in src/test/txpackage_tests.cpp:1182 in 5a647bbf72 outdated
    1185+                                                                                       /*outputs=*/{CTxOut{coinbase_value * 2 - low_fee - med_fee - high_fee, child_spk}},
    1186+                                                                                       /*submit=*/false));
    1187+        package_with_rbf.push_back(txC_package);
    1188+        const auto result_rbf = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_with_rbf, /*test_accept=*/false);
    1189+        BOOST_CHECK_MESSAGE(CheckPackageMempoolAcceptResult(package_with_rbf, result_rbf, /*expect_valid=*/true, m_node.mempool.get(), str), str);
    1190+        expected_pool_size += 3 - 1;
    


    instagibbs commented at 6:31 pm on September 28, 2023:

    nit

    0        expected_pool_size += package_with_rbf.size() - 1;
    

    glozow commented at 1:43 pm on October 2, 2023:
    taken
  159. in src/validation.cpp:1151 in 080bab4069 outdated
    1143@@ -1136,7 +1144,7 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
    1144     if (!args.m_package_submission && !bypass_limits) {
    1145         LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());
    1146         if (!m_pool.exists(GenTxid::Txid(hash)))
    1147-            return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full");
    1148+            return state.Invalid(TxValidationResult::TX_SINGLE_FAILURE, "mempool full");
    


    instagibbs commented at 6:38 pm on September 28, 2023:
    We’re not trimming anymore during package evaluation; does this make sense to change?

    glozow commented at 10:07 am on October 2, 2023:
    Yes, as it’s a type of fee failure that can be “overcome” by package submission. If there’s a CPFP package and we received the parent first, and rejected it for this reason, we should put it in the m_recent_rejects_reconsiderable cache instead of the m_recent_rejects cache so we can still cpfp it in a package.

    instagibbs commented at 3:30 pm on October 2, 2023:
    Ok so to be clear this is a future-facing p2p issue. Makes sense.
  160. in src/node/mini_miner.cpp:76 in 7361707ee4 outdated
    71@@ -72,7 +72,9 @@ MiniMiner::MiniMiner(const CTxMemPool& mempool, const std::vector<COutPoint>& ou
    72     // Add every entry to m_entries_by_txid and m_entries, except the ones that will be replaced.
    73     for (const auto& txiter : cluster) {
    74         if (!m_to_be_replaced.count(txiter->GetTx().GetHash())) {
    75-            auto [mapiter, success] = m_entries_by_txid.emplace(txiter->GetTx().GetHash(), MiniMinerMempoolEntry(txiter));
    76+            auto [mapiter, success] = m_entries_by_txid.emplace(txiter->GetTx().GetHash(),
    77+                MiniMinerMempoolEntry(txiter->GetModifiedFee(), txiter->GetModFeesWithAncestors(),
    


    instagibbs commented at 6:47 pm on September 28, 2023:
    annotate args; maybe clang tidy would catch the wrong values in wrong slots?

    glozow commented at 10:08 am on October 2, 2023:
    Added
  161. in src/node/mini_miner.cpp:153 in 7361707ee4 outdated
    139+        m_entries.push_back(mapiter);
    140+    }
    141+    // Descendant cache is already built, but we need to translate them to m_entries_by_txid iters.
    142+    for (const auto& [txid, desc_txids] : descendant_caches) {
    143+        // Descendant cache should include at least the tx itself.
    144+        if (!Assume(!desc_txids.empty())) {
    


    instagibbs commented at 6:51 pm on September 28, 2023:
    gripe: these !Assume() things are throwing me for a loop, especially !!empty

    glozow commented at 10:13 am on October 2, 2023:
    (that’s why they have comments above them) if (!Assume(assumption)) exit_early_because_assumption_doesnt_hold();
  162. in src/node/mini_miner.cpp:156 in 7361707ee4 outdated
    151+            // Descendants should only include transactions with corresponding entries.
    152+            if (!Assume(desc_it != m_entries_by_txid.end())) {
    153+                m_ready_to_calculate = false;
    154+                return;
    155+            }
    156+            if (desc_it != m_entries_by_txid.end()) cached_descendants.emplace_back(desc_it);
    


    instagibbs commented at 6:54 pm on September 28, 2023:
    I think this conditional is already handled by the previous conditional? Could make it an else block to make it clearer

    glozow commented at 10:14 am on October 2, 2023:
    removed conditional, made else block
  163. in src/node/mini_miner.cpp:146 in 7361707ee4 outdated
    133+        if (!Assume(descendant_caches.count(txid) > 0)) {
    134+            m_ready_to_calculate = false;
    135+            return;
    136+        }
    137+        // Just forward these args onto MiniMinerMempoolEntry
    138+        auto [mapiter, success] = m_entries_by_txid.emplace(txid, entry);
    


    instagibbs commented at 6:57 pm on September 28, 2023:
    anything we can Assume on success?

    glozow commented at 2:48 pm on October 2, 2023:
    added a if (Assume(success)) if that’s what you mean
  164. in src/policy/ancestor_packages.h:38 in e9752bf711 outdated
    33+
    34+    struct PackageEntry {
    35+        /** Whether this transaction should be skipped in FilteredAncestorSet() and linearization by
    36+         * fees, i.e. because it is already in the mempool.
    37+         * This value can be set to true by calling Skip(). */
    38+        bool skip{false};
    


    instagibbs commented at 2:40 pm on September 29, 2023:
    this value can also be set by SkipWithDescendants() if its the descendant, so not just because it’s already in mempool

    glozow commented at 12:06 pm on October 2, 2023:
    Yes, clarified docs.
  165. in src/policy/ancestor_packages.h:48 in e9752bf711 outdated
    43+        /** This value starts as std::nullopt when we don't have any fee information yet. It can be
    44+         * updated by calling LinearizeWithFees() if this entry isn't being skipped. */
    45+        std::optional<uint32_t> mining_sequence;
    46+        /** Fees of this transaction. Starts as std::nullopt, can be updated using AddFeeAndVsize(). */
    47+        std::optional<CAmount> fee;
    48+        /** Virtual size of this transaction. Starts as std::nullopt, can be updated using AddFeeAndVsize(). */
    


    instagibbs commented at 2:42 pm on September 29, 2023:
    sigops-adjusted I presume, better to be explicit

    glozow commented at 12:06 pm on October 2, 2023:
    added “depends on policy, calculated externally” which hopefully gets the point across.
  166. in src/policy/ancestor_packages.h:52 in e9752bf711 outdated
    47+        std::optional<CAmount> fee;
    48+        /** Virtual size of this transaction. Starts as std::nullopt, can be updated using AddFeeAndVsize(). */
    49+        std::optional<int64_t> vsize;
    50+
    51+        CTransactionRef tx;
    52+        /** Txids of all in-package ancestors. Populated in ctor and does not change.
    


    instagibbs commented at 2:43 pm on September 29, 2023:
    in AncestorPackage ctor?

    glozow commented at 12:05 pm on October 2, 2023:
    yes, added
  167. in src/policy/ancestor_packages.h:85 in e9752bf711 outdated
    80+                    if (ancestor_subset.size() == rhs.ancestor_subset.size()) {
    81+                        // Individual feerate. This is not necessarily fee-optimal, but helps in some situations.
    82+                        // (a.fee * b.vsize > b.fee * a.vsize) is a shortcut for (a.fee / a.vsize > b.fee / b.vsize)
    83+                        return *fee * *rhs.vsize  > *rhs.fee * *vsize;
    84+                    }
    85+                    if (ancestor_subset.size() == rhs.ancestor_subset.size()) {
    


    instagibbs commented at 2:50 pm on September 29, 2023:
    isn’t this a duplicate of the condition just above?

    glozow commented at 12:44 pm on October 2, 2023:
    Oof yes removed
  168. in src/policy/ancestor_packages.h:73 in e9752bf711 outdated
    68+        // Otherwise, the transaction with fewer in-package ancestors comes first (topological sort).
    69+        bool operator<(const PackageEntry& rhs) const {
    70+            if (mining_sequence == std::nullopt || rhs.mining_sequence == std::nullopt) {
    71+                // If mining sequence is missing for either entry, default to topological order.
    72+                if (ancestor_subset.size() == rhs.ancestor_subset.size()) {
    73+                    return descendant_subset.size() > rhs.descendant_subset.size();
    


    instagibbs commented at 2:52 pm on September 29, 2023:
    is this just an arbitrary tie-breaker?

    glozow commented at 12:07 pm on October 2, 2023:
    Yeah. I’ve deleted it.
  169. in src/policy/ancestor_packages.h:98 in e9752bf711 outdated
    93+        }
    94+    };
    95+    /** Map from each txid to PackageEntry */
    96+    std::map<uint256, PackageEntry> m_txid_to_entry;
    97+
    98+    /** Linearized transactions. Topological (IsSorted()) or, if fee information is provided through
    


    instagibbs commented at 2:54 pm on September 29, 2023:
    the latter condition is also IsSorted(), might want to make that extra clear

    glozow commented at 12:49 pm on October 2, 2023:
    Clarified
  170. in src/policy/ancestor_packages.cpp:219 in e9752bf711 outdated
    214+    node::MiniMiner miniminer(miniminer_info, descendant_caches);
    215+    if (!miniminer.IsReadyToCalculate()) return false;
    216+    for (const auto& [txid, mining_sequence] : miniminer.Linearize()) {
    217+        m_txid_to_entry.at(txid).mining_sequence = mining_sequence;
    218+    }
    219+    // Sort again, this time using mining score.
    


    instagibbs commented at 3:15 pm on September 29, 2023:
    0    // Sort again, this time including mining scores and individual feerates.
    

    glozow commented at 10:15 am on October 2, 2023:
    Taken
  171. in src/validation.cpp:1631 in b2779c7bc8 outdated
    1565+                    //   maliciously stuffed with invalid transactions, trying to find the optimal
    1566+                    //   subset of transactions to accept is too resource-intensive.
    1567+                    //
    1568+                    // This error may happen if we have a different policy from our peer or we
    1569+                    // have a conflicting transaction they didn't have.
    1570+                    quit_early = true;
    


    instagibbs commented at 3:44 pm on September 29, 2023:

    Commit message for “[validation] fully abort package validation for non-TX_SINGLE_FAILURE”

    “for all other failures, abort the whole thing.” this should say something like “for all other failures, continue with single evaluation, but do not do package evaluation of remaining transactions in package.”?

    I know this commit is relatively temporary, but I’m wondering if we can have something like linearized_package.SkipPackage() to just allow linearized_package.FilteredTxns() to return empty, rather than quit_early and checking two ways later on if to bail.


    glozow commented at 1:05 pm on October 2, 2023:
    Took the suggested changes to the commit message

    glozow commented at 3:06 pm on October 2, 2023:
    Unsure if SkipPackage is cleaner than having an external bool tbh
  172. in src/validation.cpp:1499 in b2779c7bc8 outdated
    1492@@ -1491,39 +1493,74 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
    1493             assert(iter != std::nullopt);
    1494             // Provide the wtxid of the mempool tx so that the caller can look it up in the mempool.
    1495             results_final.emplace(wtxid, MempoolAcceptResult::MempoolTxDifferentWitness(iter.value()->GetTx().GetWitnessHash()));
    1496+            linearized_package.Skip(tx);
    1497         } else {
    1498             // Transaction does not already exist in the mempool.
    1499-            // Try submitting the transaction on its own.
    1500-            const auto single_package_res = AcceptSubPackage({tx}, args);
    1501+            // Try submitting the transaction on its own, unless it has dependencies (then we
    


    instagibbs commented at 3:57 pm on September 29, 2023:
    0            // Try submitting the transaction on its own, unless it has in-package dependencies (then we
    

    glozow commented at 1:04 pm on October 2, 2023:
    taken
  173. in src/validation.cpp:1278 in d582fa352b outdated
    1256@@ -1251,7 +1257,10 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
    1257                                             ws.m_base_fees, effective_feerate, single_wtxid);
    1258     }
    1259 
    1260-    if (!Finalize(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
    1261+    if (!Finalize(args, ws)) {
    


    instagibbs commented at 4:03 pm on September 29, 2023:
    0    if (!Finalize(args, ws)) {
    1        Assume(ws.m_state.GetResult() == TxValidationResult::TX_SINGLE_FAILURE);
    

    glozow commented at 1:12 pm on October 2, 2023:
    taken
  174. in src/validation.cpp:1261 in d582fa352b outdated
    1256@@ -1251,7 +1257,10 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
    1257                                             ws.m_base_fees, effective_feerate, single_wtxid);
    1258     }
    1259 
    1260-    if (!Finalize(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
    1261+    if (!Finalize(args, ws)) {
    1262+        // Failed for fee reasons. It's helpful to know what the fee and vsize were.
    


    instagibbs commented at 4:04 pm on September 29, 2023:
    0        // Failed for fee reasons (mempool full). It's helpful to know what the fee and vsize were.
    

    glozow commented at 1:13 pm on October 2, 2023:
    taken
  175. in src/validation.cpp:1567 in ff737bfde4 outdated
    1563                     package_state_quit_early.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
    1564                     break;
    1565                 }
    1566                 }
    1567             }
    1568+            linearized_package.AddFeeAndVsize(tx->GetHash(), ws.m_modified_fees, ws.m_vsize);
    


    instagibbs commented at 4:16 pm on September 29, 2023:

    this seems erroneous. This is unconditional, even if we deemed it missing inputs, so it can’t even know the fees. Also, it’s called twice above in more “logical” locations.

    edit: I also see it’s gone by the tip commit :shrug:


    glozow commented at 1:21 pm on October 2, 2023:
    you’re right, removed
  176. in src/validation.cpp:1572 in ff737bfde4 outdated
    1568+            linearized_package.AddFeeAndVsize(tx->GetHash(), ws.m_modified_fees, ws.m_vsize);
    1569+            m_viewmempool.PackageAddTransaction(tx);
    1570+        }
    1571+    }
    1572+    // Clear the temporary coins (outputs of the transactions in the package) in CCoinsViewMemPool
    1573+    // and copies in CCoinsViewCache.
    


    instagibbs commented at 4:21 pm on September 29, 2023:
    0    // and copies in CCoinsViewCache that were pulled in on `PreCheck` loop.
    

    glozow commented at 1:47 pm on October 2, 2023:
    added
  177. in src/validation.cpp:1540 in ff737bfde4 outdated
    1567             }
    1568+            linearized_package.AddFeeAndVsize(tx->GetHash(), ws.m_modified_fees, ws.m_vsize);
    1569+            m_viewmempool.PackageAddTransaction(tx);
    1570+        }
    1571+    }
    1572+    // Clear the temporary coins (outputs of the transactions in the package) in CCoinsViewMemPool
    


    instagibbs commented at 4:23 pm on September 29, 2023:
    Since we’re doing this within AcceptSubPackage as well now, let’s break it out into a helper function with the comments? Maybe an interesting fuzz target of its own?

    instagibbs commented at 0:41 am on October 4, 2023:
    still would like this done since it seems to be duplicated now

    glozow commented at 10:38 am on October 4, 2023:
    Done, see CleanupTemporaryCoins
  178. in src/validation.cpp:1665 in ff737bfde4 outdated
    1578+    }
    1579+    // This deletes the temporary and mempool coins.
    1580+    m_viewmempool.Reset();
    1581+
    1582+    // Now that we have determined which transactions to skip, do full validation for each transaction.
    1583+    if (!quit_early) {
    


    instagibbs commented at 4:27 pm on September 29, 2023:
    nit: if we SkipPackage as I suggested, this behavior also naturally falls out pretty much when FilteredTxns is called below
  179. in src/validation.cpp:1587 in ff737bfde4 outdated
    1583+    if (!quit_early) {
    1584+        linearized_package.LinearizeWithFees();
    1585+        for (const auto& tx : linearized_package.FilteredTxns()) {
    1586+            const auto individual_fee_vsize = linearized_package.GetFeeAndVsize(tx);
    1587+            TxValidationState placeholder_state;
    1588+            if (individual_fee_vsize.has_value() &&
    


    instagibbs commented at 4:30 pm on September 29, 2023:
    should this also continue if there is no fee/size set vs blunder forward?

    glozow commented at 2:11 pm on October 2, 2023:
    Blundering forward just means that we’ll submit the tx and potentially get a failure, and then quit. So I think that’s a safe thing to do. I’ve refined this condition a little bit so it’s only if LinearizeWithFees returned true
  180. in src/validation.cpp:1606 in ff737bfde4 outdated
    1602+                // The transaction succeeded on its own and is now in the mempool. Don't include it
    1603+                // in package validation, because its fees should only be "used" once.
    1604+                results_final.emplace(wtxid, single_res);
    1605+                linearized_package.Skip(tx);
    1606+            } else {
    1607+                // Fee-related errors and missing inputs should not occur since we skip those.
    


    instagibbs commented at 4:34 pm on September 29, 2023:

    drop some Assumes related to this? Would be good to catch fallacious reasoning in debug builds

    got a bit turned around because fee-related aren’t Skip()’d, just skipped above in the loop


    glozow commented at 2:00 pm on October 2, 2023:
    added Assumes that it’s not fee failure or missing inputs

    glozow commented at 4:04 pm on October 3, 2023:
    oof jk, cannot assume that it isn’t missing inputs. That crashes on the test_mid_package_replacement because the coin disappears mid evaluation. Removed that, kept the fee one.
  181. in src/validation.cpp:1608 in f101ad5cb2 outdated
    1608             const auto subpackage = linearized_package.FilteredAncestorSet(tx);
    1609-            if (!subpackage || subpackage->size() > 1) {
    1610-                // No need to validate if we know this transaction would have missing inputs.
    1611-                // TODO: try to submit the ancestor subpackage.
    1612+            if (!subpackage) continue;
    1613+            if (subpackage_fee_vsize.has_value() &&
    


    instagibbs commented at 4:43 pm on September 29, 2023:
    if !subpackage_fee_vsize.has_value(), probably should fail?

    glozow commented at 2:47 pm on October 2, 2023:
    Added a skip condition if linearization failed + subpackages are larger than 1, so effectively this.
  182. in src/validation.cpp:1676 in 5a647bbf72 outdated
    1631+                    Assume(results_final.count(subpackage_wtxid) == 0);
    1632+                    results_final.emplace(subpackage_wtxid, subpackage_it->second);
    1633+                }
    1634+                // We detect that a transaction was successful by looking for it in mempool.
    1635+                if (m_pool.exists(GenTxid::Wtxid(subpackage_wtxid))) {
    1636+                    linearized_package.Skip(subpackage_tx);
    


    instagibbs commented at 5:04 pm on September 29, 2023:

    ?

    0                    Assume(subpackage_it != subpackage_result.m_tx_results.end());
    1                    linearized_package.Skip(subpackage_tx);
    

    glozow commented at 2:42 pm on October 2, 2023:

    not necessarily the case, could e.g. hit “package-mempool-limits” and m_tx_results would be empty.

    documented a couple lines above (“there might not be one for every transaction”)

  183. in src/test/txpackage_tests.cpp:276 in 5a647bbf72 outdated
    228+    const auto& tx_20{sorted_transactions[20]};
    229+    CTxMemPool::setEntries descendants_20;
    230+    m_node.mempool->CalculateDescendants(m_node.mempool->GetIter(tx_20->GetHash()).value(), descendants_20);
    231+    packageified.Skip(tx_20);
    232+    for (const auto& desc_iter : descendants_20) {
    233+        BOOST_CHECK_EQUAL(packageified.FilteredAncestorSet(m_node.mempool->info(GenTxid::Txid(desc_iter->GetTx().GetHash())).tx)->size(),
    


    instagibbs commented at 7:29 pm on September 29, 2023:
    You should check if FilteredAncestorSet is giving back a nullopt or not, otherwise it’s UB to call ->Size() on it when it’s nullopt

    glozow commented at 12:55 pm on October 2, 2023:
    done
  184. instagibbs commented at 7:35 pm on September 29, 2023: member

    reviewed through 5a647bbf72624a3394b08bfaad628bf2f7031135

    I couldn’t figure out what dangles actually did, so I tried removing them here https://github.com/instagibbs/bitcoin/commit/0cf4c46fed552d7825e9b0b6e354b3b56c8a21b9 and tests seem to pass with one inconsequential modification.

    If it’s the same thing, we should just remove that additional state.

  185. glozow force-pushed on Oct 2, 2023
  186. glozow commented at 2:47 pm on October 2, 2023: member

    I couldn’t figure out what dangles actually did, so I tried removing them here https://github.com/instagibbs/bitcoin/commit/0cf4c46fed552d7825e9b0b6e354b3b56c8a21b9 and tests seem to pass with one inconsequential modification.

    If it’s the same thing, we should just remove that additional state.

    Changed these 2 bools to a State enum to better reflect the 3 possible states. Also, when someone calls Skip*, we now remove those txids from the transactions’ ancestor_subsets so there is never a skipped tx in anyone’s ancestor_subset. This makes the sorting handle skipped stuff transactions better, and I think it’s cleaner.

  187. in src/validation.cpp:1495 in ebcee2f52e outdated
    1515+                    linearized_package.AddFeeAndVsize(tx->GetHash(), ws.m_modified_fees, ws.m_vsize);
    1516+                    individual_results_nonfinal.emplace(wtxid,
    1517+                        MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {tx->GetWitnessHash()}));
    1518+                    break;
    1519+                }
    1520+                case TxValidationResult::TX_MISSING_INPUTS: case TxValidationResult::TX_CONFLICT:
    


    instagibbs commented at 4:09 pm on October 2, 2023:

    Chainstate mismatches can result in a few more PreCheck results which are safe to continue processing likely. TX_MEMPOOL_POLICY is included here because if we somehow ended up asking for a too-large ancestor package or disagree on RBF policy, we should still make progress on it rather than immediately giving up. Ideally we’ll only give up in the precheck loop if it’s “obviously” bad, like consensus-invalid transactions or a DoS risk.

    0                case TxValidationResult::TX_MISSING_INPUTS:
    1                case TxValidationResult::TX_CONFLICT:
    2                case TxValidationResult::TX_PREMATURE_SPEND:
    3                case TxValidationResult::TX_MEMPOOL_POLICY
    

    Ones that occur in PreCheck that I didn’t add: TX_CONSENSUS: obvious TX_NOT_STANDARD: DoS risk TX_INPUTS_NOT_STANDARD: DoS risk TX_WITNESS_MUTATED: DoS risk

    Ran a fuzzer a while seeing if single txns being accepted implied it being accepted in an ancestor package and ran into this with TX_PREMATURE_SPEND


    glozow commented at 7:38 am on October 4, 2023:

    Ran a fuzzer a while seeing if single txns being accepted implied it being accepted in an ancestor package and ran into this with TX_PREMATURE_SPEND

    To clarify, the 1 transaction was accepted, and was rejected when submitted in a package with another premature spend tx?


    glozow commented at 7:53 am on October 4, 2023:
    Wait, why put TX_PREMATURE_SPEND in this category? Is that not akin to TX_CONSENSUS?

    glozow commented at 10:37 am on October 4, 2023:
    Ok I added all of these except for premature spend (see question above).

    instagibbs commented at 4:35 pm on October 4, 2023:

    Wait, why put TX_PREMATURE_SPEND in this category?

    Alice’s block height is N Bob’s block height is N - 1

    Alice sends: TxA: nlocktime of N TxB: spends TxA nlocktime of N + 1

    Bob will reject all the transactions because of a simple block race instead of taking the valid TxA.


    glozow commented at 3:06 pm on October 5, 2023:
    Oh I see what you mean. I wonder how common this is 🤔 I was mostly trying to catch “my view of the unconfirmed ancestor set has +/- transactions diff.” Will think about this some more.

    instagibbs commented at 3:12 pm on October 5, 2023:
    I’m not sure it’s common, but Core wallet/others will run into this once in a while due to anti-sniping nlocktime being set; I’ve had it happen with deployed systems before, so it’s definitely possible.
  188. DrahtBot added the label CI failed on Oct 2, 2023
  189. in src/policy/packages.cpp:32 in c2150e8fd9 outdated
    29+            if (later_txids.find(input.prevout.hash) != later_txids.end()) {
    30+                // The parent is a subsequent transaction in the package.
    31+                return false;
    32+            }
    33+        }
    34+        later_txids.erase(tx->GetHash());
    


    naumenkogs commented at 7:55 am on October 3, 2023:

    c2150e8fd93172205c0ad4bb7c8fa0437e82e15f

    Assume erase returns 1?


    glozow commented at 10:37 am on October 10, 2023:
    Added
  190. in src/policy/packages.h:63 in c2150e8fd9 outdated
    58+ * @returns true if sorted. False if any tx spends the output of a tx that appears later in txns.
    59+ */
    60+bool IsSorted(const Package& txns);
    61+
    62+/** IsSorted where a set of txids has been pre-populated. The set is assumed to be correct and
    63+ * is mutated within this function. */
    


    naumenkogs commented at 7:58 am on October 3, 2023:

    c2150e8fd93172205c0ad4bb7c8fa0437e82e15f

    nit: mutated (even in case of false)


    glozow commented at 10:37 am on October 10, 2023:
    Added
  191. in src/test/txpackage_tests.cpp:132 in c2150e8fd9 outdated
    76@@ -76,6 +77,33 @@ BOOST_FIXTURE_TEST_CASE(package_sanitization_tests, TestChain100Setup)
    77     BOOST_CHECK(!CheckPackage(package_duplicate_txids_empty, state_duplicates));
    78     BOOST_CHECK_EQUAL(state_duplicates.GetResult(), PackageValidationResult::PCKG_POLICY);
    79     BOOST_CHECK_EQUAL(state_duplicates.GetRejectReason(), "package-contains-duplicates");
    80+    BOOST_CHECK(!IsConsistent(package_duplicate_txids_empty));
    81+
    82+    // Packages can't have transactions spending the same prevout
    83+    CMutableTransaction tx_zero_1;
    


    naumenkogs commented at 8:22 am on October 3, 2023:

    c2150e8fd93172205c0ad4bb7c8fa0437e82e15f

    Why no test for IsSorted here?


    glozow commented at 10:37 am on October 10, 2023:
    Added a test
  192. in src/test/util/setup_common.h:150 in 656a2cd929 outdated
    147+    /**
    148+     * Create a transaction and, optionally, submit to the mempool.
    149+     *
    150+     * @param input_transactions   The transactions to spend
    151+     * @param input_height         The height of the block that included the input transaction(s).
    152+     * @param inputs               Outpoints with which to construct transaction vin.
    


    naumenkogs commented at 8:25 am on October 3, 2023:

    656a2cd929e9680082f782f2e2842241ff3da87c

    nit: reordered compared to the actual function


    naumenkogs commented at 8:25 am on October 3, 2023:
    same above

    glozow commented at 10:37 am on October 10, 2023:
    Changed ordering on all the docs
  193. in src/consensus/validation.h:57 in e9e68a28f3 outdated
    53@@ -54,6 +54,8 @@ enum class TxValidationResult {
    54     TX_CONFLICT,
    55     TX_MEMPOOL_POLICY,        //!< violated mempool's fee/size/descendant/RBF/etc limits
    56     TX_NO_MEMPOOL,            //!< this node does not have a mempool so can't validate the transaction
    57+    TX_SINGLE_FAILURE,        //!< fee was insufficient to meet some policy, but can change if submitted in a (different) package
    


    naumenkogs commented at 8:31 am on October 3, 2023:

    e9e68a28f3e7eff8654c4eec97b75cf6e796ff51

    please add the (different) in the commit message too, it helps a lot to understand what’s going on.


    glozow commented at 10:37 am on October 10, 2023:
    added
  194. glozow force-pushed on Oct 3, 2023
  195. in src/validation.cpp:1587 in 6d1e2097e8 outdated
    1607+            if (!rely_on_fees && subpackage->size() > 1) continue;
    1608+
    1609+            // No need to validate if we know this subpackage won't meet feerate
    1610+            // requirements. If it's a CPFP'd transaction, presumably there is a subsequent
    1611+            // subpackage that will bump it.
    1612+            if (rely_on_fees && subpackage_fee_vsize.has_value() &&
    


    instagibbs commented at 11:21 pm on October 3, 2023:

    rely_on_fees implies this?

    0            if (rely_on_fees && Assume(subpackage_fee_vsize.has_value()) &&
    

    glozow commented at 7:39 am on October 4, 2023:
    Yep
  196. in src/validation.cpp:1596 in 6d1e2097e8 outdated
    1616+                std::transform(subpackage->cbegin(), subpackage->cend(), std::back_inserter(subpackage_wtxids),
    1617+                               [](const auto& tx) { return tx->GetWitnessHash(); });
    1618+                // Override previous result.
    1619+                individual_results_nonfinal.erase(tx->GetWitnessHash());
    1620+                individual_results_nonfinal.emplace(tx->GetWitnessHash(),
    1621+                    MempoolAcceptResult::FeeFailure(placeholder_state,
    


    instagibbs commented at 11:36 pm on October 3, 2023:
    weird indenting?

    glozow commented at 10:36 am on October 4, 2023:
    Fixed
  197. DrahtBot removed the label CI failed on Oct 4, 2023
  198. in src/node/mini_miner.h:61 in 5826d99601 outdated
    54@@ -55,8 +55,11 @@ struct IteratorComparator
    55     }
    56 };
    57 
    58-/** A minimal version of BlockAssembler. Allows us to run the mining algorithm on a subset of
    59- * mempool transactions, ignoring consensus rules, to calculate mining scores. */
    60+/** A minimal version of BlockAssembler. Allows us to run the mining algorithm on a limited set of
    61+ * transactions (e.g. subset of mempool or transactions that are not yet in mempool) instead of the entire
    62+ * mempool, ignoring consensus rules. Callers may use this to calculate mining scores, bump fees, or
    63+ * linearization order of a list of transactions.
    


    naumenkogs commented at 9:14 am on October 4, 2023:

    5826d9960161ed8b6d6fe4985ff7495c4456c737

    nit: missing a verb


    glozow commented at 10:39 am on October 10, 2023:
    Pretty sure this is correct. Changed it so it’s a bit easier to parse maybe

    instagibbs commented at 2:55 pm on October 10, 2023:
    ah, maybe actually too many verbs here depending on how “bump fees” is read. “fee bumps”?

    glozow commented at 9:02 am on October 11, 2023:

    image

    Not sure what to do, leaving the comment as is until there’s a concrete suggestion that people prefer.


    ajtowns commented at 9:25 pm on October 12, 2023:
    +1 to s/bump fees/fee bumps/. “calculate bump fees” already sounds weird anyway.

    glozow commented at 2:58 pm on October 13, 2023:
    Ok, I’ve changed this to a list of bullet points that each begin with a verb so that it’s easier to parse than a comma-separated list. I’ve also added quotes around “bump fees” - please note this term was chosen in #26152 and I’m just using it.
  199. in src/policy/ancestor_packages.cpp:38 in 347a060777 outdated
    33+    std::set<uint256> my_ancestors;
    34+    my_ancestors.insert(curr_txid);
    35+    for (const auto& input : curr_tx->vin) {
    36+        const auto& parent_txid = input.prevout.hash;
    37+        if (m_txid_to_entry.count(parent_txid) == 0) continue;
    38+        auto parent_tx = m_txid_to_entry.at(parent_txid).tx;
    


    naumenkogs commented at 9:43 am on October 4, 2023:

    347a060777b4b5be0384e5b813336618e59fbc56

    This line can be moved inside the following if.


    glozow commented at 10:14 am on October 10, 2023:
    refactored to reduce the number of accesses to m_txid_to_entry
  200. in src/policy/ancestor_packages.cpp:55 in 347a060777 outdated
    47+}
    48+
    49+AncestorPackage::AncestorPackage(const Package& txns_in)
    50+{
    51+    if (txns_in.empty()) return;
    52+    // Duplicate transactions are not allowed, as they will result in infinite visit() recursion.
    


    naumenkogs commented at 9:46 am on October 4, 2023:

    347a060777b4b5be0384e5b813336618e59fbc56

    This could be double-checked in the following way: Assume(entry.ancestor_subset.empty()) after the recursive visit call in the visit function


    glozow commented at 10:16 am on October 10, 2023:
    I think this is already there? Assuming not empty.

    naumenkogs commented at 9:47 am on October 18, 2023:

    7f8aaaa76e9bf324c6ea0a028d2cf817c137d66c It’s a slightly different assumption. You can literally apply my code at line 42.

    It is a way to catch an infinite recursion I think: a current entry gets an ancestor_subset populated not as expected at L46, but instead after visiting a parent.

    But maybe this makes the code too complicated…


    glozow commented at 1:35 pm on October 18, 2023:
    Ah, I see what you mean now. I thought you meant for the transaction we were calling visit on. I’ve added this as an assert.
  201. in src/policy/ancestor_packages.cpp:64 in 347a060777 outdated
    59+    }
    60+    // DFS-based algorithm to sort transactions by ancestor count and populate ancestor_subset.
    61+    // Best case runtime is if the package is already sorted and no recursive calls happen.
    62+    // An empty PackageEntry::ancestor_subset is equivalent to not yet being processed.
    63+    size_t i{0};
    64+    while (i < txns_in.size()) {
    


    naumenkogs commented at 9:53 am on October 4, 2023:

    347a060777b4b5be0384e5b813336618e59fbc56

    why not for (const auto& tx : txns_in) { to avoid i?


    glozow commented at 10:39 am on October 10, 2023:
    changed
  202. in src/policy/ancestor_packages.h:76 in 347a060777 outdated
    70+         * filtered ancestor sets. */
    71+        std::set<uint256> ancestor_subset;
    72+
    73+        /** Txids of all in-package descendant. Populated in AncestorPackage ctor and does not
    74+         * change within AncestorPackage lifetime. */
    75+        std::set<uint256> descendant_subset;
    


    naumenkogs commented at 9:58 am on October 4, 2023:

    347a060777b4b5be0384e5b813336618e59fbc56

    Does order matter? If not, maybe switch to unordered_set?


    glozow commented at 10:21 am on October 10, 2023:
    I’m thinking that set is actually better than unordered_set here, as it uses less memory and shouldn’t be any slower if we only expect 25 elements or fewer. I’ve actually been wondering if the other unordered_sets used in packages.cpp should also be sets instead…
  203. glozow force-pushed on Oct 4, 2023
  204. in src/policy/ancestor_packages.h:149 in 347a060777 outdated
    144+     * tx is DANGLING. */
    145+    std::optional<std::pair<CAmount, int64_t>> GetFeeAndVsize(const CTransactionRef& tx) const;
    146+
    147+    /** Should be called if a tx or same txid transaction is found in mempool or submitted to it.
    148+     * From now on, skip this tx from any result in FilteredAncestorSet(). Does not affect Txns().
    149+     * Should be called when a transaction is accepted to mempool or already found in it. */
    


    naumenkogs commented at 10:16 am on October 4, 2023:

    347a060777b4b5be0384e5b813336618e59fbc56

    Duplicate text


    glozow commented at 10:40 am on October 10, 2023:
    Removed
  205. in src/policy/ancestor_packages.h:33 in 347a060777 outdated
    27+    /** Whether m_txns contains a connected package in which all transactions are ancestors of the
    28+     * last transaction. This object is not aware of chainstate. So if m_txns only includes a
    29+     * grandparent and not the "connecting" parent, this will (incorrectly) determine that the
    30+     * grandparent is not an ancestor.
    31+     * */
    32+    bool m_ancestor_package_shaped{false};
    


    naumenkogs commented at 10:20 am on October 4, 2023:

    347a060777b4b5be0384e5b813336618e59fbc56

    If you drop (skip?) the last transaction, should this become false again? Or not even the last but some connecting transaction


    glozow commented at 10:40 am on October 10, 2023:
    I don’t think it should be changed to false (think of it as applying to Txns, not FilteredTxns), but good point, so I’ve added a sentence about this in the doc.
  206. glozow commented at 10:38 am on October 4, 2023: member
    Addressed @instagibbs comments, cleaned up the commits a little bit so they’re (hopefully) easier to review.
  207. in src/policy/packages.h:55 in c2150e8fd9 outdated
    50@@ -49,6 +51,29 @@ using Package = std::vector<CTransactionRef>;
    51 
    52 class PackageValidationState : public ValidationState<PackageValidationResult> {};
    53 
    54+/** If any direct dependencies exist between transactions (i.e. a child spending the output of a
    55+ * parent), checks that all parents appear somewhere in the list before their respective children.
    


    ariard commented at 5:42 pm on October 4, 2023:
    “There is no check of the parent ordering itself (i.e first parent spent coming first)”

    glozow commented at 12:30 pm on October 5, 2023:
    Sorry could you clarify what you mean by “first parent?” I’ve added a sentence “No other ordering is enforced” to the comment.
  208. in src/consensus/validation.h:58 in e9e68a28f3 outdated
    53@@ -54,6 +54,8 @@ enum class TxValidationResult {
    54     TX_CONFLICT,
    55     TX_MEMPOOL_POLICY,        //!< violated mempool's fee/size/descendant/RBF/etc limits
    56     TX_NO_MEMPOOL,            //!< this node does not have a mempool so can't validate the transaction
    57+    TX_SINGLE_FAILURE,        //!< fee was insufficient to meet some policy, but can change if submitted in a (different) package
    58+    TX_UNKNOWN,               //!< transaction was not validated
    


    ariard commented at 6:25 pm on October 4, 2023:
    As with this PR, the only code place where we fulfilled a TX_UNKNOWN as TxValidationResult is within AcceptPackage(), if we don’t find wtxid in individual_results_nonfinal. AFAICT, this can happen when we have caught errors in subpackage and we break early from linearized package validation. If so, I think comment can be extended “transaction was not validated, early error its in package was caught” (could be called TX_PACKAGE_FAILURE)

    glozow commented at 12:32 pm on October 5, 2023:
    Didn’t rename because I don’t like having the word “failure” in it (this tx didn’t fail!). But added a “because package failed” to the comment.
  209. in src/node/mini_miner.h:78 in 5826d99601 outdated
    74@@ -72,7 +75,10 @@ class MiniMiner
    75     // the same tx will have the same bumpfee. Excludes non-mempool transactions.
    76     std::map<uint256, std::vector<COutPoint>> m_requested_outpoints_by_txid;
    77 
    78-    // What we're trying to calculate.
    79+    // Txid to a sequence number representing the order in which this transaction was included.
    


    ariard commented at 6:39 pm on October 4, 2023:
    I think I might not the first one to make the comment, though using “sequence number” can be confusing as we have already the transaction nSequence. I guess order number or ancestor number could be used.

    glozow commented at 12:26 pm on October 5, 2023:
    Renamed to m_inclusion_order
  210. in src/node/mini_miner.h:123 in 5826d99601 outdated
    119+     * mempool. */
    120     MiniMiner(const CTxMemPool& mempool, const std::vector<COutPoint>& outpoints);
    121 
    122+    /** Constructor in which MiniMinerMempoolEntry entries have been constructed manually,
    123+     * presumably because these transactions are not in the mempool (yet). It is assumed that all
    124+     * values are correct, otherwise MiniMiner algorithms won't work. Callers should check
    


    ariard commented at 6:48 pm on October 4, 2023:
    I think it can precise what is yield by “MiniMiner algorithms won’t work” e.g calculate non-optimal mining scores.

    glozow commented at 12:29 pm on October 5, 2023:
    I’ve added “otherwise the results may be incorrect” if that works?
  211. in src/node/mini_miner.cpp:141 in 5826d99601 outdated
    128+                     const std::map<uint256, std::set<uint256>>& descendant_caches)
    129+{
    130+    for (const auto& entry : manual_entries) {
    131+        const auto& txid = entry.GetTx().GetHash();
    132+        // We need to know the descendant set of every transaction.
    133+        if (!Assume(descendant_caches.count(txid) > 0)) {
    


    ariard commented at 6:56 pm on October 4, 2023:
    Could check by wtxid rather than txid ? One txid can have different wtxids different witness weight and as such mining score, unless wtxid support is disregarded for simplicity as a design decision ?

    glozow commented at 8:12 am on October 5, 2023:
    An explicit assumption enforced in the MiniMiner and AncestorPackage constructors is that no transaction conflicts with each other, which includes not having the same txid. Given we’re constantly looking up parent transactions using prevouts, having a txid-based map is much more useful than a wtxid-based one.

    glozow commented at 12:26 pm on October 5, 2023:
    Added a comment above the Assume in MiniMiner and mentioned the comment above the constructor declaration, hopefully it’s clearer now.
  212. ariard commented at 6:57 pm on October 4, 2023: member
    Reviewed until 5826d996 - “[MiniMiner] manually construct MiniMinerMempoolEntry, Linearize()”. Minor comments so far.
  213. in src/test/txpackage_tests.cpp:186 in f2f0834c85 outdated
    181+            BOOST_CHECK_EQUAL(packageified.FilteredAncestorSet(package[i])->size(), i + 1);
    182+            BOOST_CHECK(TestIsAncestorPackage(*packageified.FilteredAncestorSet(package[i])));
    183+        }
    184+        for (auto i{0}; i < 10; ++i) packageified.Skip(package[i]);
    185+        packageified.SkipWithDescendants(package[20]);
    186+        for (auto i{11}; i < 20; ++i) {
    


    naumenkogs commented at 11:37 am on October 5, 2023:

    f2f0834c85af8ecca05d32c0016efe1620764f63

    could start with i{10}?


    glozow commented at 10:40 am on October 10, 2023:
    done
  214. in src/test/txpackage_tests.cpp:215 in f2f0834c85 outdated
    206+        package.push_back(MakeTransactionRef(child));
    207+
    208+        Package package_copy(package);
    209+        Shuffle(package_copy.begin(), package_copy.end(), det_rand);
    210+        AncestorPackage packageified(package_copy);
    211+        BOOST_CHECK(TestIsAncestorPackage(packageified.Txns()));
    


    naumenkogs commented at 11:38 am on October 5, 2023:

    f2f0834c85af8ecca05d32c0016efe1620764f63

    Test IsChildWithParents?


    glozow commented at 10:40 am on October 10, 2023:
    added, though it has to be removed later when that function is deleted
  215. in src/test/txpackage_tests.cpp:243 in f2f0834c85 outdated
    238+            }
    239+        }
    240+        BOOST_CHECK(!packageified.FilteredAncestorSet(package.back()));
    241+    }
    242+
    243+    // Heavily inter-connected set of 50 transactions
    


    naumenkogs commented at 11:41 am on October 5, 2023:

    f2f0834c85af8ecca05d32c0016efe1620764f63

    Could you summarize what is checked in this block, in a code comment? It looks like you basically compare package functioning to mempool functioning.


    glozow commented at 10:40 am on October 10, 2023:
    Added summary
  216. naumenkogs commented at 12:00 pm on October 5, 2023: member
    I reviewed the code up to 225e739e6400a87e65ffba373df3daa1b57337da. I’m pretty sure I don’t 100% understand all the stuff here at the high level (the discussion in the comments), but hopefully the review is still useful.
  217. glozow force-pushed on Oct 5, 2023
  218. glozow force-pushed on Oct 5, 2023
  219. DrahtBot added the label Needs rebase on Oct 5, 2023
  220. naumenkogs commented at 8:16 am on October 9, 2023: member

    Apparently my review was not submitted along with the comment

    Anyway, hopefully they are not too outdated after sitting offline for 4 days, and i will continue review shortly.

  221. glozow force-pushed on Oct 9, 2023
  222. glozow commented at 8:39 am on October 9, 2023: member
    Last push is just a rebase, will get to @naumenkogs’s review asap (thanks!)
  223. DrahtBot removed the label Needs rebase on Oct 9, 2023
  224. DrahtBot added the label CI failed on Oct 9, 2023
  225. glozow force-pushed on Oct 10, 2023
  226. DrahtBot removed the label CI failed on Oct 10, 2023
  227. in src/validation.cpp:1459 in ff27bc4203 outdated
    1495     // (i.e. when evaluated with a fee-bumping child), the result in this map may be discarded.
    1496     std::map<uint256, MempoolAcceptResult> individual_results_nonfinal;
    1497+    // Call PreChecks() for every transaction in topological order, de-duplicating and skipping
    1498+    // transactions we can't validate, making each transaction's inputs available for subsequent
    1499+    // transactions to spend. If we encounter a failure that isn't TX_SINGLE_FAILURE, we continue to
    1500+    // call PreChecks() but will quit_early and not do package validation.
    


    instagibbs commented at 5:38 pm on October 12, 2023:

    quit_early is only set for a few cases, not all failures sans TX_SINGLE_FAILURE, and if it’s set nothing is submitted.

    0    // call PreChecks() but may quit_early and not submit any transactions to the mempool.
    

    glozow commented at 2:59 pm on October 13, 2023:
    Taken
  228. in src/policy/ancestor_packages.h:151 in ff27bc4203 outdated
    146+    std::optional<std::pair<CAmount, int64_t>> GetFeeAndVsize(const CTransactionRef& tx) const;
    147+
    148+    /** Should be called if a tx or same txid transaction is found in mempool or submitted to it.
    149+     * From now on, skip this tx from any result in FilteredAncestorSet(). Does not affect Txns().
    150+     */
    151+    void Skip(const CTransactionRef& transaction);
    


    instagibbs commented at 5:42 pm on October 12, 2023:
    Now that we have clearer states via enums, I think this could be named something like IsInMempool since that’s what we’re asserting?

    glozow commented at 2:56 pm on October 13, 2023:
    Makes sense, renamed to IsInMempool
  229. in src/policy/ancestor_packages.h:156 in ff27bc4203 outdated
    151+    void Skip(const CTransactionRef& transaction);
    152+
    153+    /** Should be called if a tx will not be considered because it is missing inputs.
    154+     * Skip a transaction and all of its descendants. From now on, if this transaction is present
    155+     * in the ancestor set, FilteredAncestorSet() returns std::nullopt for that tx. Does not affect Txns(). */
    156+    void SkipWithDescendants(const CTransactionRef& transaction);
    


    instagibbs commented at 5:48 pm on October 12, 2023:
    Now that we have clearer states via enums, I think this could be named something like IsDanglingWithDescendants?

    glozow commented at 8:47 am on October 13, 2023:

    Agree with making these names hint at intended usage instead of internal details, changing

    Just realized that maybe TX_CONFLICT should be in the “skip” instead of “skip with descendants” category. Imagine parent+child package, parent already confirmed and child hasn’t been received yet. SkipWithDescendants means the child gets skipped too… assuming we can see a UTXO from the parent in the coinsview, we should treat it similarly to how we treat same-txid-different-witness-already-in-mempool right?


    glozow commented at 2:56 pm on October 13, 2023:
    Renamed to IsDanglingWithDescendants as suggested. Still chewing on the TX_CONFLICT case.

    instagibbs commented at 3:47 pm on October 13, 2023:

    TX_CONFLICT happens in three cases:

    1. wtxid in mempool already txn-already-in-mempool
    2. txid in mempool already txn-same-nonwitness-data-in-mempool
    3. already in chain txn-already-known

    Indeed the third case could slip through with current logic. We should probably treat this similarly to the first two cases.

    I’m still lobbying for TX_PREMATURE_SPEND to be moved to this block :)


    glozow commented at 5:05 pm on October 13, 2023:
    Ok makes sense. I will do this next week, need to add a different MempoolAcceptResult::ResultType I think, and will write some tests. Addressed the other comments though.

    glozow commented at 1:31 pm on October 16, 2023:
    Added, and moved TX_PREMATURE_SPEND into the dangle category. I decided not to make a new ResultType because the result itself isn’t that different from the INVALID case. Also added a table to MempoolAcceptResult to illustrate what fields/properties each of the result types should have.
  230. glozow force-pushed on Oct 13, 2023
  231. glozow force-pushed on Oct 13, 2023
  232. glozow force-pushed on Oct 16, 2023
  233. in src/validation.h:120 in daad63c687 outdated
    114@@ -115,6 +115,21 @@ void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight
    115 
    116 /**
    117 * Validation result for a single transaction mempool acceptance.
    118+*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+
    


    instagibbs commented at 2:02 pm on October 16, 2023:

    Made a fuzz test for ATMP to check this table:

    https://github.com/instagibbs/bitcoin/commit/f589f519c80bf823ba7044c5791d7f541f3a9e34

    As noted offline, m_vsize and m_base_fees are not actually set for FeeFailure, so the fuzz test differs. CheckPackageMempoolAcceptResult seems to actually match the code as is for package cases.


    glozow commented at 1:08 pm on October 17, 2023:
    Ran your test for a bit and it seems fine after the changes. Should I cherry-pick that into this PR or should we put it in a followup?

    instagibbs commented at 1:52 pm on October 17, 2023:
    I think it’s worthwhile having running as this PR develops, please cherry-pick it
  234. in src/validation.cpp:1267 in daad63c687 outdated
    1261@@ -1244,7 +1262,12 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
    1262                                             ws.m_base_fees, effective_feerate, single_wtxid);
    1263     }
    1264 
    1265-    if (!Finalize(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
    1266+    if (!Finalize(args, ws)) {
    1267+        // The only possible failure reason is fee-related (mempool full). It's helpful for the
    1268+        // caller to know what the fee and vsize were.
    


    instagibbs commented at 4:04 pm on October 16, 2023:
    this doesn’t actually return vsize or base fees, contra to the table indications or comment here

    glozow commented at 1:01 pm on October 17, 2023:
    I got the vsize/fee field mixed up with the effective feerate field, sorry! Fixed now.

    instagibbs commented at 2:10 pm on October 17, 2023:
    the chart was cleaned up, could these comments be fixed as well?

    glozow commented at 2:16 pm on October 17, 2023:
    Fixed the comment
  235. DrahtBot added the label Needs rebase on Oct 16, 2023
  236. glozow force-pushed on Oct 16, 2023
  237. glozow commented at 4:40 pm on October 16, 2023: member
    Rebased + fixed a typo in the MempoolAcceptResult table
  238. DrahtBot removed the label Needs rebase on Oct 16, 2023
  239. DrahtBot added the label CI failed on Oct 16, 2023
  240. glozow force-pushed on Oct 17, 2023
  241. glozow force-pushed on Oct 17, 2023
  242. in src/validation.h:117 in 63b2711240 outdated
    114@@ -115,6 +115,21 @@ void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight
    115 
    116 /**
    117 * Validation result for a single transaction mempool acceptance.
    


    instagibbs commented at 1:56 pm on October 17, 2023:
    ah, this text line is old; can it be updated to note that this table is for package contexts? otherwise the “txid in mempool” and “wtxid in mempool” differ a bit for the INVALID cases, since they cover the MEMPOOL_ENTRY and DIFFERENT_WITNESS cases for ATMP cases

    glozow commented at 1:39 pm on October 18, 2023:
    Thanks, changed/added comment in validation.h
  243. in src/validation.cpp:1243 in 63b2711240 outdated
    1237@@ -1226,7 +1238,13 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
    1238 
    1239     Workspace ws(ptx);
    1240 
    1241-    if (!PreChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
    1242+    if (!PreChecks(args, ws)) {
    1243+        if (ws.m_state.GetResult() == TxValidationResult::TX_SINGLE_FAILURE) {
    1244+            // Failed for fee reasons. It's helpful to know what the fee and vsize were.
    


    instagibbs commented at 2:11 pm on October 17, 2023:
    the chart was cleaned up, could these comments be fixed as well?

    glozow commented at 2:16 pm on October 17, 2023:
    Fixed the comment
  244. glozow force-pushed on Oct 17, 2023
  245. instagibbs commented at 2:24 pm on October 17, 2023: member
  246. in src/test/txpackage_tests.cpp:149 in ad0f2336b0 outdated
     95+    PackageValidationState state_conflicts;
     96+    BOOST_CHECK(!CheckPackage(package_conflicts, state_conflicts));
     97+    BOOST_CHECK_EQUAL(state_conflicts.GetResult(), PackageValidationResult::PCKG_POLICY);
     98+    BOOST_CHECK_EQUAL(state_conflicts.GetRejectReason(), "conflict-in-package");
     99+
    100+    // IsConsistent only cares about conflicts between transactions, not about a transaction
    


    naumenkogs commented at 8:39 am on October 18, 2023:

    ad0f2336b0ae5ab132373f52e713b94dd1365f03

    I’m still slightly confused to why you want to enable this so much :) In the IsConsistent code checking this looks very natural. I don’t think it affects anything, but it always distracts me from understanding with it’s oddness.


    glozow commented at 1:09 pm on October 18, 2023:
    I’m not sure what “this” you’re referring to, could you be more specific? Checking that a package IsConsistent is just a basic sanitization check before we try to build sets of txids of prevouts. We rely heavily on the assumption that the prevouts are unique, so it’s important to check that assumption.

    naumenkogs commented at 9:55 am on October 20, 2023:

    I meant this code.

    Adding once-at-a-time seemed more natural, no? And then you’d have an assume not in the list already, which would say “it’s very surprising we’re hitting it in IsConsistent because it should have been checked early”.


    glozow commented at 10:19 am on October 20, 2023:

    I’m still slightly confused to why you want to enable this so much

    This is not new code. It was added to #20833 to address this comment (screenshotting because github will struggle to load it): image

    tldr the rationale was to not change a TX_CONSENSUS error into a package policy error.

    I agree it’s an extra thing to think about when reading the code, but it’s not new to this PR. This PR adds a comment and a test for this behavior because it was requested by this comment.


    naumenkogs commented at 12:24 pm on October 20, 2023:
    Thank you for clarification, i totally missed it’s not new code. I agree with you now. Hopefully it doesn’t distract other reviewers like i was distracted.
  247. in src/policy/ancestor_packages.cpp:85 in 7f8aaaa76e outdated
    70+    if (!Assume(m_txns.size() == txns_in.size() && IsSorted(Txns()))) {
    71+        // If something went wrong with the sorting, just revert to the original order.
    72+        m_txns = txns_copy;
    73+    }
    74+    // This package is ancestor package-shaped if every transaction is an ancestor of the last tx.
    75+    m_ancestor_package_shaped = m_txns.back().get().ancestor_subset.size() == m_txns.size();
    


    naumenkogs commented at 9:51 am on October 18, 2023:

    7f8aaaa76e9bf324c6ea0a028d2cf817c137d66c

    nit: you could explain why it’s sufficient to just compare sizes here (this is a loose check compared to what the comment above says)


    glozow commented at 1:36 pm on October 18, 2023:
    Added a sentence to the comment.
  248. naumenkogs commented at 12:00 pm on October 18, 2023: member
    minor comments after re-review up to 7f8aaaa76e9bf324c6ea0a028d2cf817c137d66c
  249. glozow force-pushed on Oct 18, 2023
  250. glozow force-pushed on Oct 18, 2023
  251. glozow force-pushed on Oct 18, 2023
  252. in src/test/txpackage_tests.cpp:135 in 24490e71d1 outdated
    131+
    132+    // Packages can't have transactions spending the same prevout
    133+    CMutableTransaction tx_zero_1;
    134+    CMutableTransaction tx_zero_2;
    135+    COutPoint same_prevout{InsecureRand256(), 0};
    136+    tx_zero_1.vin.emplace_back(CTxIn{same_prevout});
    


    DrahtBot commented at 8:34 am on October 20, 2023:
    0_64-pc-linux-gnu/src/test/txpackage_tests.cpp
    1test/txpackage_tests.cpp:135:32: error: unnecessary temporary object created while calling emplace_back [modernize-use-emplace,-warnings-as-errors]
    2  135 |     tx_zero_1.vin.emplace_back(CTxIn{same_prevout});
    3      |                                ^~~~~~            ~
    

    glozow commented at 2:11 pm on October 20, 2023:
    thanks, fixed
  253. in src/policy/ancestor_packages.h:43 in 89913ee14e outdated
    38+             * If calling LinearizeWithFees(), this tx must have a fee and vsize. */
    39+            PACKAGE,
    40+            /** Excluded from FilteredAncestor{Set,FeeAndVsize}.
    41+             * Should not appear in any transaction's ancestor_subset.
    42+             * Excluded in LinearizeWithFees().
    43+             * Set by calling IsAlreadyHave() on this tx. */
    


    naumenkogs commented at 9:58 am on October 20, 2023:
    89913ee14e8e2a3247191966c66b3c3fcbeb45b8 IsInMempool is a new name i guess. IsAlreadyHave is outdated.

    glozow commented at 2:11 pm on October 20, 2023:
    thanks, removed all the old names now I think
  254. in src/policy/ancestor_packages.cpp:145 in 89913ee14e outdated
    143+    CAmount total_fee{0};
    144+    int64_t total_vsize{0};
    145+    for (const auto& txid : entry.ancestor_subset) {
    146+        const auto& anc_entry = m_txid_to_entry.at(txid);
    147+        // All non-PACKAGE ancestor_subset transactions should have been removed when the Skip* happened.
    148+        if (Assume(anc_entry.m_state == PackageEntry::State::PACKAGE)) {
    


    naumenkogs commented at 10:06 am on October 20, 2023:

    89913ee14e8e2a3247191966c66b3c3fcbeb45b8

    Is it safe to still return a non-null result if this fails?


    glozow commented at 2:14 pm on October 20, 2023:
    Yes, this is just asserting that we cleaned up in the skipping functions the way we were supposed to. I think as long as this is ancestor set-shaped there isn’t much danger.
  255. in src/test/fuzz/ancestorpackage.cpp:78 in 4dfd040bb4 outdated
    73+    if (packageified.IsAncestorPackage()) {
    74+        // Optionally Skip() (submit to mempool) the first n transactions. These must be at the
    75+        // beginning of the package as it doesn't make sense to submit a transaction without
    76+        // submitting all of its ancestors too. The ith transaction is not necessarily an ancestor
    77+        // of the i+1th transaction, but just skip 1...n to keep things simple.
    78+        // For the rest of the transactions, optionally call SkipWithDescendants() (missing inputs).
    


    naumenkogs commented at 11:54 am on October 20, 2023:

    4dfd040bb4504240caed4c2108fb7e85bd2c9eba

    outdated function names here and there


    glozow commented at 2:11 pm on October 20, 2023:
    removed
  256. in src/validation.cpp:1653 in a24fe5ea83 outdated
    1648+                // If we don't exit early here, we'll hit a low fee failure and quit early.
    1649+                continue;
    1650+            }
    1651+            const auto subpackage = linearized_package.FilteredAncestorSet(tx);
    1652+            if (!subpackage || subpackage->size() > 1) {
    1653+                // No need to validate if we know this transaction would have missing inputs.
    


    naumenkogs commented at 12:22 pm on October 20, 2023:
    Why does if (!subpackage || subpackage->size() > 1) { imply missing inputs?

    instagibbs commented at 12:34 pm on October 20, 2023:
    yeah this comment is a bit off I think; currently in this commit we’re attempting to only do one-by-one then “rest” package submission like before. This is clamping it to size 1 so it goes through single transaction submission here.

    glozow commented at 1:22 pm on October 20, 2023:
    Don’t think the comment is necessarily off - we aren’t clamping it to 1 on purpose, we’re just keeping the existing behavior which doesn’t validate subpackages yet. This is just a “quit early” optimization since we have access to the subpackage (which I can remove if is confusing).

    instagibbs commented at 1:26 pm on October 20, 2023:
    I mean, you are clamping it to 1 on purpose: to keep existing behavior.

    glozow commented at 1:28 pm on October 20, 2023:
    Would it be better to squash the “add a PreChecks loop to linearize by fee and find obvious errors” and the “submit ancestor subpackages” commits maybe? There isn’t a functional reason to do 1 without the other, I just thought maybe the diff would be smaller and easier to review. But maybe it’s just confusing?

    glozow commented at 1:34 pm on October 20, 2023:

    I mean, you are clamping it to 1 on purpose: to keep existing behavior.

    o yeah true


    glozow commented at 2:11 pm on October 20, 2023:
    I’ve squashed the 2 commits
  257. naumenkogs commented at 12:23 pm on October 20, 2023: member
    reviewed up to a24fe5ea83ec1001d8dcdaef1dcb127e304cd1e1
  258. glozow force-pushed on Oct 20, 2023
  259. glozow commented at 2:16 pm on October 20, 2023: member
    Squashed 2 commits together. The big commit “[validation] linearize using fees + submit ancestor subpackages” implements the algorithm described in the op.
  260. in src/validation.cpp:1663 in e63e371c09 outdated
    1647+    // transaction with its ancestor subpackage. If this is a single transaction, call
    1648+    // AcceptSingleTransaction() with a single transaction ATMPArgs to avoid unnecessary package
    1649+    // restrictions like disallowment of RBF. Skip when we know the feerate will not be sufficient.
    1650+    // If we encounter any errors at all, we quit immediately and do not validate anything else.
    1651+    // This means we may potentially skip validation for valid transactions, but we can expect that
    1652+    // some other peer will announce those transactions to us.
    


    naumenkogs commented at 11:33 am on October 23, 2023:

    e63e371c094ddf06f0b1b7ef427bb1ca47da640b

    What’s the threat model with some other peer will announce...? e.g. I’m thinking of blocking an in-package LN transaction propagation by submitting something conflicting to the victim’s peers


    glozow commented at 11:44 am on October 24, 2023:

    Not sure if I understand what you mean fully, but the point here is to try to avoid an attack where somebody tries to block a transaction by stuffing invalid transactions into a package with it. Nodes will still be inving transactions meeting the peer’s fee filter so, unless there is a CPFP or other missing inputs situation, the receiver won’t need to download this transaction as a package. The model here is we assume we have at least 1 peer who is honest / isn’t actively creating invalid packages.

    For a LN commitment transaction + CPFP child, they’ll announce the child, you’ll download it, request the ancestor package, and then validate commitment transaction + child. I don’t expect that there will be any other transactions in that package. Even if there is another descendant on the second anchor output or a grandchild, your node will still be told the existence of the CPFP child, and still be told the 2-transaction ancestor package if you request it.

    blocking an in-package LN transaction propagation by submitting something conflicting to the victim’s peers

    This seems equivalent to a single transaction scenario. If all your peers have tx A which conflicts with tx B, then you probably won’t hear about B. I don’t think there’s anything we can do about that here…

  261. glozow force-pushed on Oct 23, 2023
  262. in src/test/txpackage_tests.cpp:703 in ea770e9a63 outdated
    666@@ -658,6 +667,198 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
    667         BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash())));
    668         BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child->GetHash())));
    669     }
    670+
    671+    // do not allow parents to pay for children
    672+    {
    673+        Package package_ppfc;
    674+        // Diamond shape:
    


    naumenkogs commented at 9:05 am on October 24, 2023:

    ea770e9a63e68466d4f1c4f5ad5bb5519ecf3efe

    Where do we document why we don’t drop the child and accept grandparent+parent1+parent2?


    glozow commented at 12:03 pm on October 24, 2023:
    Generally we are not considering non-ancestor-set-shaped candidate subsets (see other comments supporting this decision #26711 (comment) and #26711 (comment)), as they wouldn’t be considered in our BlockAssembler algorithm anyway (and annoyingly they’d also resist eviction). Just doing ancestor-set-shaped subsets is also much simpler.
  263. naumenkogs commented at 10:10 am on October 24, 2023: member

    Reviewed everything at the low and mid level 1a3d5e4b828efe96038d71e838525a150f4ff204.

    I will spend some more time thinking high-level and then provide my ACK.

  264. in src/policy/ancestor_packages.cpp:46 in 1a3d5e4b82 outdated
    41+        }
    42+        auto parent_ancestor_set = m_txid_to_entry.at(parent_txid).ancestor_subset;
    43+        Assume(!parent_ancestor_set.empty());
    44+        // This recursive call should not have included ourselves; it should be impossible for this
    45+        // tx to be both an ancestor and a descendant of us.
    46+        assert(m_txid_to_entry.at(curr_txid).ancestor_subset.empty());
    


    instagibbs commented at 6:06 pm on October 24, 2023:
    only assert in the file, is this desired vs Assume?

    glozow commented at 12:46 pm on October 26, 2023:
    Felt a bit showstopper-y, but I can change it to an Assume next time I retouch

    glozow commented at 2:24 pm on November 7, 2023:
    This is an Assume now
  265. instagibbs commented at 6:09 pm on October 24, 2023: member

    ACK 1a3d5e4b828efe96038d71e838525a150f4ff204 modulo single question

    only minor changes since last review.

    I’ll rebase my additional fuzz target changes on top once there are more ACKs

  266. DrahtBot requested review from naumenkogs on Oct 24, 2023
  267. in src/node/mini_miner.cpp:288 in a0545f457c outdated
    282@@ -237,14 +283,28 @@ void MiniMiner::BuildMockTemplate(const CFeeRate& target_feerate)
    283                 to_process.erase(iter);
    284             }
    285         }
    286+        // Track the order in which transactions were selected.
    287+        for (const auto& ancestor : ancestors) {
    288+            m_inclusion_order.emplace(ancestor->first, sequence_num);
    


    naumenkogs commented at 10:10 am on October 25, 2023:

    a0545f457c26a5d1e7ac2c0fab46213f3d07c739

    nit: It seems like the order (DFS/BFS or whatever) of inclusion into ancestors here doesn’t matter at all. I wish the comment said that in the loop above.


    glozow commented at 2:29 pm on October 25, 2023:

    “Transactions included in an ancestor set together have the same sequence number” is part of the docstring for m_inclusion_order. Also, note how topology is a separate “score” used to break ties in AncestorPackage::PackageEntry::operator< (also documented).

    If I retouch, I can add a sentence that interdependency between the transactions in the ancestor set is not a concern when assigning the inclusion numbers in the comment here.


    naumenkogs commented at 7:18 am on October 26, 2023:
    Thank you. It’s no big deal, it’s mostly about reviewing the piece of code right above this one.
  268. in src/node/mini_miner.cpp:261 in a0545f457c outdated
    244@@ -201,8 +245,9 @@ void MiniMiner::SanityCheck() const
    245         [&](const auto& txid){return m_entries_by_txid.find(txid) == m_entries_by_txid.end();}));
    246 }
    247 
    248-void MiniMiner::BuildMockTemplate(const CFeeRate& target_feerate)
    249+void MiniMiner::BuildMockTemplate(std::optional<CFeeRate> target_feerate)
    250 {
    251+    uint32_t sequence_num{0};
    252     while (!m_entries_by_txid.empty()) {
    253         // Sort again, since transaction removal may change some m_entries' ancestor feerates.
    


    naumenkogs commented at 10:50 am on October 25, 2023:

    a0545f457c26a5d1e7ac2c0fab46213f3d07c739

    when would this be possible?


    glozow commented at 2:36 pm on October 25, 2023:

    When a transaction’s parent is mined and it isn’t, its parent is no longer part of its ancestor fees and size. This may change its ancestor feerate.

    The analogous concept in BlockAssembler is mapModifiedTx.

    Btw, there are review club notes that may be helpful for understanding #27021.

  269. DrahtBot requested review from naumenkogs on Oct 25, 2023
  270. glozow commented at 3:14 pm on October 25, 2023: member
  271. ariard commented at 3:45 am on October 26, 2023: member
    +2,344 diff sounds good to review on one branch, still have to answer past review comments.
  272. DrahtBot removed the label CI failed on Oct 26, 2023
  273. naumenkogs commented at 11:43 am on October 26, 2023: member
    ACK 1a3d5e4b828efe96038d71e838525a150f4ff204
  274. DrahtBot removed review request from naumenkogs on Oct 26, 2023
  275. ajtowns commented at 3:45 am on October 31, 2023: contributor

    Shouldn’t this PR be split up at this point? It’s been in development for almost a year, has +2000 lines changed, and the commit log alone is 230 lines, and it at least seems like it does a few different things, all of which seem like they’ll make it easy for review to miss bugs? Something like:

    • refactors + test additions that don’t change functionality changes (first 3 commits plus CleanupTemporaryCoins?)
    • add TX_SINGLE_FAILURE / TX_UNKNOWN + tests (+ package-fee-too-low?)
    • linearisation + miniminer + unit/fuzz test and bench
    • acceptpackage loop + subpackage linearization + unit/fuzz tests?
    • permit any ancestor package, functional tests

    seems like it might be a logical sequence of changes that might reasonably be reviewed individually?

    [edit: miniminer patch could probably be its own PR too, which would probably be easier to review for people familiar with that particular code but not everything else]

    The “linearize using fees” commit message seems to have an extra line at the end left over from a rebase? The algorithm description seems like it would be better as a code comment rather than a commit message.

    The “permit any ancestor package” commit seems like it should be annotated as an RPC change, rather than a “policy” change?

  276. in src/node/mini_miner.h:29 in a0545f457c outdated
    32-        fee_individual{entry->GetModifiedFee()},
    33-        tx{entry->GetSharedTx()},
    34-        vsize_individual(entry->GetTxSize()),
    35-        fee_with_ancestors{entry->GetModFeesWithAncestors()},
    36-        vsize_with_ancestors(entry->GetSizeWithAncestors())
    37+    explicit MiniMinerMempoolEntry(CAmount feei, CAmount feea, int64_t sizei, int64_t sizea, const CTransactionRef& tx_in):
    


    ajtowns commented at 3:47 am on October 31, 2023:
    feei, feea etc aren’t very clear names, and aren’t very self-documenting at the call site; just spelling them out in long form seems better?

    glozow commented at 2:46 pm on October 31, 2023:
    Changed in #28762 to {fee,vsize}_{self,ancestor}. Just trying to make them compact-ish
  277. in src/node/mini_miner.h:129 in a0545f457c outdated
    112@@ -103,13 +113,28 @@ class MiniMiner
    113     bool IsReadyToCalculate() const { return m_ready_to_calculate; }
    114 
    115     /** Build a block template until the target feerate is hit. */
    116-    void BuildMockTemplate(const CFeeRate& target_feerate);
    117+    void BuildMockTemplate(std::optional<CFeeRate> target_feerate);
    


    ajtowns commented at 3:50 am on October 31, 2023:
    What’s the point of making this optional? How does passing nullopt in differ from passing 0 in? (If it does differ, it’s probably worth adding a proper doxygen comment explaining how the parameter works in detail)

    glozow commented at 2:48 pm on October 31, 2023:
    Fees can be negative since we use modified fees, so 0 is not the same thing as turning it off. I’ve added a comment + test for this in #28762.
  278. in src/rpc/mempool.cpp:823 in 9962efedb8 outdated
    819@@ -820,7 +820,7 @@ static RPCHelpMan submitpackage()
    820 {
    821     return RPCHelpMan{"submitpackage",
    822         "Submit a package of raw transactions (serialized, hex-encoded) to local node.\n"
    823-        "The package must consist of a child with its parents, and none of the parents may depend on one another.\n"
    824+        "The package may only consist of a transaction with its unconfirmed ancestors.\n"
    


    ajtowns commented at 3:56 am on October 31, 2023:
    “may” seems out of place here – if it means “must”, should write must; if it means that it might be this but it also might not, and that’s fine, should write something more like “The package may consist of multiple unrelated transactions and their ancestors”.

    glozow commented at 4:32 pm on November 3, 2023:
    Changed to “must”
  279. in src/policy/ancestor_packages.h:110 in 9ffffd9a61 outdated
    105+                }
    106+            }
    107+        }
    108+    };
    109+    /** Map from each txid to PackageEntry */
    110+    std::map<uint256, PackageEntry> m_txid_to_entry;
    


    ajtowns commented at 3:59 am on October 31, 2023:
    Use Txid/Wtxid types here?

    glozow commented at 1:56 pm on November 7, 2023:
    Using Txid for this now
  280. in src/policy/ancestor_packages.h:151 in 9ffffd9a61 outdated
    146+    std::optional<std::pair<CAmount, int64_t>> GetFeeAndVsize(const CTransactionRef& tx) const;
    147+
    148+    /** Should be called if a tx or same txid transaction is found in mempool or submitted to it.
    149+     * From now on, skip this tx from any result in FilteredAncestorSet(). Does not affect Txns().
    150+     */
    151+    void IsInMempool(const CTransactionRef& transaction);
    


    ajtowns commented at 4:01 am on October 31, 2023:
    This sounds like a query, rather a property setter. Perhaps void SetFoundInMempool(transaction) to make it clear it’s a setter not a getter?

    glozow commented at 4:33 pm on November 3, 2023:
    Changed to MarkAsInMempool to also include the case where it has been found as well as the case where we have submitted it.
  281. in src/policy/ancestor_packages.h:164 in 9ffffd9a61 outdated
    159+    /** Add information about fee and vsize for a transaction. */
    160+    void AddFeeAndVsize(const uint256& txid, CAmount fee, int64_t vsize);
    161+
    162+    /** Re-linearize transactions using the fee and vsize information given. Updates Txns().
    163+     * Information must have been provided for all non-skipped transactions via AddFeeAndVsize().
    164+     * @returns true if successful, false if something went wrong. */
    


    ajtowns commented at 4:03 am on October 31, 2023:
    Would be helpful to explain what can go wrong? What are you meant to do if you get a false result? Punish the peer for giving you bad info? Do some sort of orphan resolution? assert because you’ve run out of memory or ended up with corrupt data structures?

    glozow commented at 1:58 pm on November 7, 2023:
    Added doc.
  282. in src/policy/ancestor_packages.cpp:196 in 9ffffd9a61 outdated
    191+
    192+void AncestorPackage::AddFeeAndVsize(const uint256& txid, CAmount fee, int64_t vsize)
    193+{
    194+    if (m_txid_to_entry.count(txid) == 0) return;
    195+    m_txid_to_entry.at(txid).fee = fee;
    196+    m_txid_to_entry.at(txid).vsize = vsize;
    


    ajtowns commented at 4:09 am on October 31, 2023:
    0if (auto e = m_txid_to_entry.find(txid); e != m_txid_to_entry.end()) {
    1    e->fee = fee;
    2    e->vsize = vsize;
    3}
    

    (search for txid once rather than three times)


    glozow commented at 4:33 pm on November 3, 2023:
    Taken
  283. ajtowns commented at 4:13 am on October 31, 2023: contributor
    Some not very substantive review
  284. glozow commented at 2:50 pm on October 31, 2023: member

    Shouldn’t this PR be split up at this point?

    Makes sense

    *refactors + test additions that don't change functionality changes (first 3 commits plus CleanupTemporaryCoins?)
    

    ^I’ve split this off into #28758

    [edit: miniminer patch could probably be its own PR too, which would probably be easier to review for people familiar with that particular code but not everything else]

    ^I’ve split this off into #28762. I also expanded it into smaller commits, added more tests, and addressed #26711 (review) and #26711 (review) there.

  285. glozow force-pushed on Oct 31, 2023
  286. in src/policy/ancestor_packages.h:65 in fb161b2c65 outdated
    60+        std::optional<uint32_t> mining_sequence;
    61+
    62+        /** Fees of this transaction. Starts as std::nullopt, can be updated using AddFeeAndVsize(). */
    63+        std::optional<CAmount> fee;
    64+
    65+        /** Virtual size of this transaction (depends on policy, calculated externally). Starts as
    


    ariard commented at 3:40 am on November 1, 2023:
    I think the usage of virtual size to build the ancestor score of a package might come with incentive suboptimal block template if you have a significant number of segwit spends constituting it (sounds 90% as of 2023). A 500 vbyte transaction A paying 100 sats with 100 vbytes of witnesses isn’t equivalent to a 500 vbyte transaction B paying 100 sats with 20 vbytes of witnesses considering block max size is checked against MAX_BLOCK_WEIGHT in CheckBlock().

    glozow commented at 9:26 am on November 2, 2023:
    Er, I’m pretty sure two transactions with the same virtual size but different amounts of witness data are treated exactly the same when checking against the maximum weight. These are both 1997-2000 weight units.
  287. in src/policy/ancestor_packages.h:20 in fb161b2c65 outdated
    15+ * unconfirmed ancestors are present. Its constructor accepts any list of transactions that
    16+ * IsConsistentPackage(), linearizes them topologically, and determines whether it IsAncestorPackage().
    17+ * If fee and vsizes are given for each transaction, it can also linearize the transactions using
    18+ * the ancestor score-based mining algorithm via MiniMiner.
    19+ *
    20+ * IsIsInMempool() and IsDanglingWithDescendants() can be used to omit transactions.
    


    ariard commented at 3:45 am on November 1, 2023:
    I think one limitation of the partial set of ancestors being used to evaluate the ancestor-score of a package could be the edge-case where unconfirmed ancestors are replaced in the mempool by better same-nonwitness-data-in-mempool candidates, with increased or diminished witness size. This is not an issue right now as we don’t allow wtxid-replacement in PreChecks().

    glozow commented at 9:42 am on November 2, 2023:
    This is also not an issue because we omit the already-in-mempool transactions in our linearization.
  288. in src/policy/ancestor_packages.h:80 in fb161b2c65 outdated
    75+         * change within AncestorPackage lifetime. */
    76+        std::set<Txid> descendant_subset;
    77+
    78+        explicit PackageEntry(CTransactionRef tx_in) : tx(tx_in) {}
    79+
    80+        // Used to sort the result of Txns, FilteredTxns, and FilteredAncestorSet. Always guarantees
    


    ariard commented at 4:06 am on November 1, 2023:
    If my understanding of the guarantees of the PackageEntry comparison operator is correct, a breakage of the guarantee would be a yield AncestorPackage that produce a consensus-invalid block template, e.g a more incentive-compatible subset of descendants where the parents are sorted after. This comment to understand how test coverage should be built for this PR.

    glozow commented at 9:58 am on November 2, 2023:

    If the sorting is broken, it means that FilteredTxns etc. might return transactions out of order, but this means validation will get a “missing inputs” error and nothing will be submitted. It can also mean that FilteredTxns doesn’t give the more incentive compatible transactions first, which can affect whether we accept or reject these transactions.

    This function being incorrect can’t make us produce a consensus-invalid block template. Transactions still need to pass validation to be submitted, CTxMemPool sorts its entries, and BlockAssembler does its own sanity checks.

  289. in src/policy/ancestor_packages.h:86 in fb161b2c65 outdated
    81+        // topological sort to the best of our knowledge (see IsSorted()), and puts the most
    82+        // incentive-compatible subset(s) first if that information is available.
    83+        //
    84+        // If ancestor score-based linearization sequence exists for both transactions, the
    85+        // transaction with the lower sequence number comes first.
    86+        //    If unavailable or there is a tie, the transaction with fewer in-package ancestors comes first (topological sort).
    


    ariard commented at 4:08 am on November 1, 2023:
    I don’t know if it has been discussed before in the review of this PR, though in case of a tie sounds the breaker should not be on the absolute number of in-package ancestor though on their accumulated vsize ? E.g package entry A might have 1 ancestor of 1 kvB and package entry B might have 3 ancestors of 200 vbytes each.

    glozow commented at 9:53 am on November 2, 2023:
    The idea here is just to make sure a descendant doesn’t come before its child. If A is an ancestor of B, then all of A’s ancestors are also B’s ancestors. It must be that A.ancestor_count < B.ancestor_count. Using total ancestor vsize would also work, but count is much easier to check.
  290. ariard commented at 4:10 am on November 1, 2023: member
    Review at commit “[txpackages] add AncestorPackage for linearizing packages”.
  291. glozow commented at 1:24 pm on November 2, 2023: member
    Some test commits have been split off into #28764.
  292. glozow referenced this in commit f23ac10ca5 on Nov 3, 2023
  293. DrahtBot added the label Needs rebase on Nov 3, 2023
  294. fanquake referenced this in commit 5d9f45082b on Nov 3, 2023
  295. achow101 referenced this in commit d9007f51a7 on Nov 3, 2023
  296. glozow force-pushed on Nov 3, 2023
  297. DrahtBot removed the label Needs rebase on Nov 3, 2023
  298. glozow commented at 4:31 pm on November 3, 2023: member

    add TX_SINGLE_FAILURE / TX_UNKNOWN + tests (+ package-fee-too-low?)

    This has been split off in #28785

  299. glozow force-pushed on Nov 3, 2023
  300. DrahtBot added the label CI failed on Nov 3, 2023
  301. glozow force-pushed on Nov 7, 2023
  302. DrahtBot removed the label CI failed on Nov 7, 2023
  303. glozow force-pushed on Nov 7, 2023
  304. glozow force-pushed on Nov 7, 2023
  305. glozow commented at 2:23 pm on November 7, 2023: member

    The “linearize using fees” commit message seems to have an extra line at the end left over from a rebase? The algorithm description seems like it would be better as a code comment rather than a commit message.

    Added the algo description to the documentation of the AcceptPackage function

    • linearisation + miniminer + unit/fuzz test and bench
    • acceptpackage loop + subpackage linearization + unit/fuzz tests?

    I’d prefer to keep the AncestorPackage changes (along with its unit/bench/fuzz) together with the validation changes in this PR since it’s part of the implementation of the algorithm and doesn’t have any other uses.

    • permit any ancestor package, functional tests

    I’ve moved this to #28813 which will follow this PR.

    The “permit any ancestor package” commit seems like it should be annotated as an RPC change, rather than a “policy” change?

    Done, in #28813.

    So to recap, I think we are down to 3 PRs to get this done: #28785, then #26711 (this PR), then #28813.

  306. fanquake referenced this in commit d690f89b57 on Nov 8, 2023
  307. [txpackages] add AncestorPackage for linearizing packages
    We cannot require that peers send topologically sorted lists, because we
    cannot check for this property without ensuring we have the same chain
    tip and ensuring we have the full ancestor set. Instead, add the ability
    to handle arbitrarily ordered transaction lists.
    The AncestorPackage ctor linearizes the transactions topologically. If
    fee and vsize information is added, it uses MiniMiner to refine the
    linearization using the ancestor score-based algorithm.
    dcf8462ee4
  308. [unit test] AncestorPackage 90eef8a7a3
  309. [bench] AncestorPackage and linearization using fees 20a120bac2
  310. [fuzz] AncestorPackage topological sorting and skipping 7a51a99eb2
  311. [refactor] AcceptPackage loop using AncestorPackage and switch statement
    Adds usage of AncestorPackage to linearize transactions and skip ones
    that are submitted or already in mempool. This linearization is just
    topological for now, but a subsequent commit will add logic to gather
    fee information and use that to refine the linearization. Even though
    packages are currently required to be sorted, the order may be changed,
    since AncestorPackage can produce a different but valid topological
    sort.
    
    Use a switch statement to ensure we are handling all possible
    TxValidationResults. Also simplifies the diff of later commits.
    b103803eb2
  312. [validation] linearize using fees + submit ancestor subpackages
    General algorithm:
    1. Basic sanitization to ensure package is well formed and not too big
       to work on.
    2. Linearize (based on topological sort).
    3. PreChecks loop: call PreChecks on all transactions to get fees and
       vsizes, and to find obvious errors for which we should skip parts of
       the package or abort validation altogether.
    4. Linearize (based on ancestor set score).
    5. For each transaction in the new linearized order, submit with its
       subpackage (or skip if subpackage/individual feerate is too low).
    6. Limit mempool size and backfill map of MempoolAcceptResults.
    
    Q: What's so great about doing a fee-based linearization?
    Linearization helps us understand which transactions need to be grouped
    together as CPFPs. This helps us skip the low feerate parent and wait
    until we see it in the high feerate child's ancestor package; another
    approach would be to retry submission with another grouping, but we want
    to avoid repeated validation. Linearization also helps us try to accept
    the highest feerate subset of the package when we don't don't have room
    for all of it. For example, if each of the transactions in the package
    share an ancestor whose descendant limits only permit one, we should try
    the highest feerate transaction(s) first.
    
    Q: Why use PreChecks to get fees and vsize?
    We need the fee and vsize of each transaction, which requires looking up
    inputs. While some of the work done in PreChecks might not be necessary,
    it applies fail-fast anti-DoS checks that we definitely want (e.g. a
    transaction that is too big could thus cause us to load too many UTXOs).
    In the future, we may also be interested in using information like the
    transaction's mempool conflicts and ancestor set (calculated in
    PreChecks) in the linearization or quit early logic. So PreChecks is
    best for this purpose.
    
    Q: We don't know if txns have valid signatures yet, so if something is
    invalid, we might have a suboptimal linearization/grouping. Should we
    regroup or retry in that case?
    No. If the peer maliciously constructs a package this way, there isn't
    much we can do to find the "optimal subset" without doing a ton of work.
    Generally, we hope to only validate each transaction 1 time.
    Instead, we just hope we have another honest peer that will send us the
    "normal" package.
    345480799c
  313. [unit test] subpackage evaluation and package linearization
    - package_ppfp shows benefit of assessing subpackages
    - package_ppfc shows that this doesn't let us do "parent pays for
      child;" we only do this when the individual and ancestor feerates meet
    mempool minimum feerate
    - package_needs_reorder shows necessity of linearizing using ancestor
      set scoring. If we used the original order, we would reject
    everything, even if we submit subpackages.
    - package_desc_limits shows that linearization can help us pick the
      most incentive-compatible tx to accept when transactions affect each
    other's eligibility (descendant package limits)
    - package_confirmed_parent shows benefit of treating
      txns-already-known as "in mempool" tx
    - package_with_rbf tests existing behavior that shouldn't change. Even
      though package RBF is not enabled, it's important that submitting a
    transaction as part of a package does not block it from doing a normal
    replacement. Otherwise we may blind ourselves to a replacement simply
    because it has a child and we happened to download it as a package.
    
    Co-authored-by: Andrew Chow <achow101@gmail.com>
    2e2c81e0fc
  314. glozow force-pushed on Nov 9, 2023
  315. glozow closed this on Nov 10, 2023


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: 2024-07-01 10:13 UTC

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