refactor: TxDownloadManager + fuzzing #30110

pull glozow wants to merge 29 commits into bitcoin:master from glozow:2024-05-txdownload changing 10 files +1771 −549
  1. glozow commented at 9:49 am on May 15, 2024: member

    Part of #27463.

    This PR does 3 things:

    (1) It modularizes transaction download logic into a TxDownloadManager. Transaction download logic refers to the process of deciding what transactions to request, download, and validate.[1] There should be no behavior changes. Using --color_moved=dimmed_zebra -w may help. (2) It adds unit and fuzz (:magic_wand:) testing for transaction download. (3) It makes a few small behavioral changes:

    • Stop (debug-only) logging tx invs during IBD
    • Just like all other transactions, require orphans have RecursiveDynamicUsage < 100k before adding to vExtraTxnForCompact
    • Don’t return a 1p1c that contains a parent or child in recent rejects. Don’t process any orphan already in recent rejects. These cases should not happen in actual node operation; it’s just to allow tighter sanity checks during fuzzing.

    There are several benefits to this interface, such as:

    • Unit test coverage and fuzzing for logic that currently isn’t feasible to test as thoroughly (without lots of overhead) and/or currently only lightly tested through assert_debug_log (not good) in functional tests.
    • When we add more functionality (e.g. package relay messages, more robust orphan handling), the vast majority of it will be within TxDownloadManager instead of PeerManager, making it easier to review and test. See #28031 for what this looks like.
    • PeerManager will no longer know anything about / have access to TxOrphanage, TxRequestTracker or the rejection caches. Its primary interface with TxDownloadManager would be much simpler:
      • Passing on ValidationInterface callbacks
      • Telling txdownloadman when a peer {connects, disconnects}
      • Telling txdownloadmanwhen a {transaction, package} is {accepted, rejected} from mempool
      • Telling txdownloadman when invs, notfounds, and txs are received.
      • Getting instructions on what to download.
      • Getting instructions on what {transactions, packages, orphans} to validate.
      • Get whether a peer HaveMoreWork for the ProessMessages loop
    • (todo) Thread-safety can be handled internally.

    [1]: This module is concerned with tx download, not upload. It excludes transaction announcements/gossip which happens after we download/accept a transaction. Txreconciliation (erlay) is excluded from this module, as it only relates to deciding which invs to send or helping the other peer decide which invs to send. It is independent from this logic.

  2. glozow added the label Refactoring on May 15, 2024
  3. DrahtBot commented at 9:49 am on May 15, 2024: 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
    ACK theStack, instagibbs, naumenkogs, achow101
    Concept ACK l0rinc
    Approach ACK ismaelsadeeq

    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:

    • #30572 (Halt processing of unrequested transactions v2 by ariard)
    • #30538 (Doc: add a comment referencing past vulnerability next to where it was fixed by darosior)
    • #30116 (p2p: Fill reconciliation sets (Erlay) attempt 2 by sr-gi)
    • #29492 (refactor: Remove redundant definitions by Empact)
    • #27052 (test: rpc: add last block announcement time to getpeerinfo result by LarryRuane)

    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.

  4. glozow force-pushed on May 15, 2024
  5. DrahtBot added the label CI failed on May 15, 2024
  6. DrahtBot commented at 10:56 am on May 15, 2024: contributor

    🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the documentation.

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

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

    Debug: https://github.com/bitcoin/bitcoin/runs/24992798939

  7. glozow force-pushed on May 15, 2024
  8. DrahtBot added the label Needs rebase on May 15, 2024
  9. glozow force-pushed on May 15, 2024
  10. glozow force-pushed on May 15, 2024
  11. DrahtBot removed the label Needs rebase on May 15, 2024
  12. glozow force-pushed on May 17, 2024
  13. glozow force-pushed on May 20, 2024
  14. glozow force-pushed on May 20, 2024
  15. glozow commented at 10:55 am on May 20, 2024: member
    Rebased for #29817 and added a “ensure we can always download a tx as long as we have 1 good outbound peer” fuzz test
  16. in src/node/txdownload_impl.h:32 in b1cc0c3006 outdated
    27+static constexpr auto NONPREF_PEER_TX_DELAY{2s};
    28+/** How long to delay requesting transactions from overloaded peers (see MAX_PEER_TX_REQUEST_IN_FLIGHT). */
    29+static constexpr auto OVERLOADED_PEER_TX_DELAY{2s};
    30+/** How long to wait before downloading a transaction from an additional peer */
    31+static constexpr auto GETDATA_TX_INTERVAL{60s};
    32+struct TxDownloadOptions {
    


    dergoegge commented at 11:55 am on May 20, 2024:
    Structs that are part of the public interface should probably go into txdownloadman.h. Otherwise one needs to include the impl header, e.g. like txdownloadman.h does right now, which defeats the purpose of pimpling to some extend.

    glozow commented at 12:49 pm on May 21, 2024:

    Hm, in the previous setup, we can change any of txdownload_impl.cpp without net_processing.cpp noticing, but yeah maybe less clean to depend on txdownload_impl.h.

    I’ve updated with a new setup where I’ve added a txdownloadman.cpp depending on txdownload_impl.h, and txdownloadman.h is included by txdownload_impl.h instead of the other way around. And so net_processing.cpp no longer depends on txdownload_impl.h, which makes sense to me. Had to update lint-circular-dependencies.py but I do like that we can change txdownload_impl.h without recompiling everything. lmk if this seems better?

  17. in src/node/txdownload_impl.cpp:51 in b1cc0c3006 outdated
    50+    m_recent_confirmed_transactions.reset();
    51+}
    52+
    53+bool TxDownloadImpl::AlreadyHaveTx(const GenTxid& gtxid, bool include_reconsiderable)
    54+{
    55+    const uint256& hash = gtxid.GetHash();
    


    dergoegge commented at 12:29 pm on May 20, 2024:

    Iiuc correctly, clearing the filters here has been removed as we don’t want TxDownloadManager to depend on ChainstateManager.

    An alternative to the new synchronous validation interface callback could be to preserve the previous behavior (clearing the filters in AlreadyHaveTx on a tip change) by introducing an interface for TxDownloadMan to fetch the latest tip hash. For example, TxDownloadOptions could be extended to hold a lambda that returns the latest tip hash, which in production simply fetches the hash from the ChainstateManager (and for tests it can be mocked). This might be easier to review since it preserves the previous mechanism? Although perhaps also more difficult w.r.t to lock inversion of cs_main and m_tx_download_mutex.

    Another alternative could be to directly pass the current block hash as a param into the relevant TxDownloadManager methods.


    glozow commented at 12:43 pm on May 21, 2024:

    I don’t think we’d ever need to make TxDownloadManager depend on ChainstateManager as we could just pass in the block hash, i.e.

    Another alternative could be to directly pass the current block hash as a param into the relevant TxDownloadManager methods.

    This would avoid the addition of UpdateBlockTipSync etc, as it’s basically what we do now. I find this way of keeping synchronized with the chain tip pretty ugly - we’d need to hold cs_main and pass the blockhash to the TxDownloadManager all the time, bloating the interface. Subscribing to the validation interface and updating on every chain tip update seems much more sensible.

  18. DrahtBot removed the label CI failed on May 20, 2024
  19. glozow force-pushed on May 21, 2024
  20. DrahtBot added the label Needs rebase on Jun 3, 2024
  21. glozow force-pushed on Jun 6, 2024
  22. glozow commented at 12:36 pm on June 6, 2024: member
    rebased
  23. DrahtBot removed the label Needs rebase on Jun 6, 2024
  24. DrahtBot added the label Needs rebase on Jun 10, 2024
  25. glozow force-pushed on Jun 11, 2024
  26. DrahtBot removed the label Needs rebase on Jun 11, 2024
  27. DrahtBot commented at 11:13 am on June 11, 2024: contributor

    🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the documentation.

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

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

    Debug: https://github.com/bitcoin/bitcoin/runs/26067441690

  28. DrahtBot added the label CI failed on Jun 11, 2024
  29. glozow force-pushed on Jun 11, 2024
  30. DrahtBot removed the label CI failed on Jun 11, 2024
  31. DrahtBot added the label Needs rebase on Jun 17, 2024
  32. glozow commented at 8:53 am on June 19, 2024: member
    Planning to rebase on master along with #30111 when it’s merged
  33. glozow force-pushed on Jul 16, 2024
  34. DrahtBot added the label CI failed on Jul 16, 2024
  35. DrahtBot commented at 4:06 pm on July 16, 2024: contributor

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

    Make sure to run all tests locally, according to the documentation.

    The failure may happen due to a number of reasons, for example:

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

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

    • An intermittent issue.

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

  36. DrahtBot removed the label Needs rebase on Jul 16, 2024
  37. fanquake referenced this in commit 9607277032 on Jul 24, 2024
  38. glozow force-pushed on Jul 24, 2024
  39. glozow force-pushed on Jul 25, 2024
  40. glozow marked this as ready for review on Jul 25, 2024
  41. DrahtBot removed the label CI failed on Jul 25, 2024
  42. glozow commented at 12:14 pm on July 26, 2024: member
    Rebased, ready for review.
  43. DrahtBot added the label Needs rebase on Aug 1, 2024
  44. glozow force-pushed on Aug 1, 2024
  45. glozow commented at 1:29 pm on August 1, 2024: member
    Rebased for #30413
  46. DrahtBot removed the label Needs rebase on Aug 1, 2024
  47. glozow renamed this:
    refactor: TxDownloadManager
    refactor: TxDownloadManager + fuzzing
    on Aug 8, 2024
  48. Theschorpioen approved
  49. in src/node/txdownload_impl.cpp:16 in 41080ff0e9 outdated
    11+#include <validationinterface.h>
    12+
    13 namespace node {
    14+void TxDownloadImpl::ActiveTipChange()
    15+{
    16+    // If the chain tip has changed, previously rejected transactions might now be invalid, e.g. due
    


    theStack commented at 4:28 pm on August 13, 2024:
    0    // If the chain tip has changed, previously rejected transactions might now be valid, e.g. due
    

    also, this comment now exists both in PeerManagerImpl::ActiveTipChange and TxDownloadImpl::ActiveTipChange, probably one can be removed


    glozow commented at 9:13 am on August 14, 2024:
    Ah, that looks like a bad rebase - deleted the comment now, thanks
  50. in src/node/txdownload_impl.cpp:83 in 6c70bc621b outdated
    75@@ -75,4 +76,23 @@ bool TxDownloadImpl::AlreadyHaveTx(const GenTxid& gtxid, bool include_reconsider
    76 
    77     return RecentRejectsFilter().contains(hash) || m_opts.m_mempool.exists(gtxid);
    78 }
    79+
    80+void TxDownloadImpl::ConnectedPeer(NodeId nodeid, const TxDownloadConnectionInfo& info)
    81+{
    82+    // If already connected (shouldn't happen in practice), exit early.
    83+    if (m_peer_info.count(nodeid) > 0) return;
    


    theStack commented at 4:33 pm on August 13, 2024:

    slightly-more-modern-nit (here and in TxDownloadImpl::DisconnectedPeer below):

    0    if (m_peer_info.contains(nodeid)) return;
    

    glozow commented at 9:18 am on August 14, 2024:
    done :+1:
  51. in src/net_processing.cpp:3940 in 3f5dbd56da outdated
    4072-                if (!fAlreadyHave && !m_chainman.IsInitialBlockDownload()) {
    4073-                    AddTxAnnouncement(pfrom, gtxid, current_time);
    4074+                if (!m_chainman.IsInitialBlockDownload()) {
    4075+                    const bool fAlreadyHave{m_txdownloadman.AddTxAnnouncement(pfrom.GetId(), gtxid, current_time, /*p2p_inv=*/true)};
    4076+                    LogPrint(BCLog::NET, "got inv: %s  %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
    4077                 }
    


    theStack commented at 4:48 pm on August 13, 2024:
    nit: strictly speaking this is not a pure refactor, as there is a change in logging behaviour (INV messages received during IBD now don’t trigger the “got inv” log message any more). seems to make more sense though anyway, so feel free to ignore

    glozow commented at 9:55 am on August 14, 2024:

    Right, I don’t think it’s problematic to not log tx invs during IBD since they are ignored anyway.

    I’ve moved the logging change to its own commit, to keep the pure refactoring-ness of this commit :+1:

  52. in src/net_processing.cpp:4482 in 3f5dbd56da outdated
    4477@@ -4531,7 +4478,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
    4478                     AddKnownTx(*peer, parent_txid);
    4479                     // Exclude m_lazy_recent_rejects_reconsiderable: the missing parent may have been
    4480                     // previously rejected for being too low feerate. This orphan might CPFP it.
    4481-                    if (!m_txdownloadman.AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) AddTxAnnouncement(pfrom, gtxid, current_time);
    4482+                    if (!m_txdownloadman.AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) {
    4483+                        m_txdownloadman.AddTxAnnouncement(pfrom.GetId(), gtxid, current_time, /*p2p_inv=*/false);
    


    theStack commented at 4:55 pm on August 13, 2024:
    small note: with this approach, AlreadyHaveTx is now called twice: once in the if condition, and the second time in the AddTxAnnouncement method (not sure if that’s really a problem though)

    glozow commented at 9:55 am on August 14, 2024:
    Ah, good observation. I’ve now gated the AlreadyHaveTx check inside of AddTxAnnouncement on p2p_inv so this is no longer happening.

    instagibbs commented at 6:41 pm on August 19, 2024:

    It may have been an issue since the AddTxAnnouncement invocation of AlreadyHaveTx included reconsiderable filter.

    Since there are only two call sites of AddTxAnnouncement, and each had a filtering AlreadyHasTx call, would it make sense to just do something like this instead?

    0if (AlreadyHaveTx(gtxid, /*include_reconsiderable=*/p2p_inv)) return true;
    
  53. theStack commented at 5:43 pm on August 13, 2024: contributor

    Concept ACK

    Lightly reviewed about half of the refactoring commits, left some non-critical stuff and comments on the way below.

  54. glozow force-pushed on Aug 14, 2024
  55. glozow commented at 9:55 am on August 14, 2024: member
    Thanks @theStack!
  56. glozow commented at 10:32 am on August 14, 2024: member
    Would it help if I put the test/fuzz changes in a separate PR from the refactoring changes? It would make the first PR less exciting to review, but perhaps easier to review?
  57. maflcko commented at 10:50 am on August 14, 2024: member

    Would it help if I put the test/fuzz changes in a separate PR from the refactoring changes? It would make the first PR less exciting to review, but perhaps easier to review?

    Up to you, but there is a small benefit of going with the fuzz tests first (if possible), in that they first run on master (and pass), and then on the refactor changes (and pass as well). For unit tests this can be achieved by just putting them in an early commit and then running the tests for all commits. For fuzz tests, it is a bit more difficult, because fuzz inputs need to exist. (Also, some fuzz task sanitizers are only run in external CI, because they take a long time)

  58. dergoegge commented at 11:37 am on August 14, 2024: member

    Up to you, but there is a small benefit of going with the fuzz tests first (if possible)

    It looks like the tests here mostly operate on the split out modules after the refactoring, as opposed to a implementation agnostic test through the p2p interface (which would be a giant pita to write). So splitting the fuzz test out would be fine as they don’t directly assert that no behavior changes are made. It would of course be sad if we split them out and then only the refactoring lands (which has happened before).

  59. glozow commented at 12:12 pm on August 14, 2024: member

    to you, but there is a small benefit of going with the fuzz tests first (if possible)

    It looks like the tests here mostly operate on the split out modules after the refactoring, as opposed to a implementation agnostic test through the p2p interface (which would be a giant pita to write).

    Indeed, all of these tests require the refactors (on master we’d have to create a chain to fire validation events + construct actual transactions with each type of invalidity).

    So splitting the fuzz test out would be fine as they don’t directly assert that no behavior changes are made. It would of course be sad if we split them out and then only the refactoring lands (which has happened before).

    If people like the fuzz tests (concept acks would be nice), I’ll split them off to make this PR smaller, and keep the unit tests in here. I just don’t want to create 2 big PRs that nobody is interested in.

  60. instagibbs commented at 2:11 pm on August 14, 2024: member
    personally speaking I’m unsure what is gained by moving the fuzz tests to their own PR since they depend on the refactors. I don’t think we want to merge the other commits unless the fuzz harnesses are attached to it. I’m continuing to review either way.
  61. glozow commented at 2:45 pm on August 14, 2024: member

    I don’t think we want to merge the other commits unless the fuzz harnesses are attached to it. I’m continuing to review either way.

    Ok sounds good, I’ll leave as is. Just hoping to make review easier.

  62. hebasto added the label Needs CMake port on Aug 16, 2024
  63. in src/node/txdownload_impl.cpp:48 in 3824d1f81a outdated
    43@@ -44,4 +44,33 @@ void TxDownloadImpl::BlockDisconnected()
    44     // should be just after a new block containing it is found.
    45     RecentConfirmedTransactionsFilter().reset();
    46 }
    47+
    48+bool TxDownloadImpl::AlreadyHaveTx(const GenTxid& gtxid, bool include_reconsiderable)
    


    instagibbs commented at 4:53 pm on August 19, 2024:
    3824d1f81ad9e4ff44ec46d07f4e8f86f27ce643 decent time to rename the function since that’s been a previous discussion point?

    glozow commented at 9:56 am on August 21, 2024:
    What should the new name be?
  64. in src/net_processing.cpp:3843 in af3647050a outdated
    3839@@ -3841,6 +3840,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
    3840                        tx_relay->m_next_inv_send_time == 0s));
    3841         }
    3842 
    3843+        LOCK2(::cs_main, m_tx_download_mutex);
    


    instagibbs commented at 6:21 pm on August 19, 2024:
    af3647050a85e6f33f902b903207043f6f0c023c preference for this to be inside own scope

    glozow commented at 10:24 am on August 21, 2024:
    done
  65. in src/net_processing.cpp:3010 in 77560a690c outdated
    3006         ptx->GetWitnessHash().ToString(),
    3007         nodeid,
    3008         state.ToString());
    3009 
    3010-    if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
    3011+    // Only process a new orphan if maybe_add_extra_compact_tx, as otherwise it must be either
    


    instagibbs commented at 7:44 pm on August 19, 2024:

    maybe_add_extra_compact_tx is starting to creep in usage vs its ostensible usage

    I’m also not quire sure I parse this sentence. I presume it’s trying to say this is only used in regular situations when freshly received over p2p.


    glozow commented at 10:37 am on August 21, 2024:

    Renamed it to first_time_failure in a preceding commit.

    Yeah that’s correct. Basically, if this is the first time a tx is being rejected from mempool, we consider putting it in vExtraTxnForCompact. If it’s not the first time, we don’t need to do it again, presumably because we don’t want duplicates (it’s a ring buffer).

    Same thing for adding something as an orphan. If this bool is false, it means the tx is already an orphan or it’s the low feerate parent in a package. Either way we don’t need to bother with the unique_parents and filter checking stuff.


    instagibbs commented at 6:28 pm on August 21, 2024:
    new name is better and makes flow easier to understand,thanks
  66. in src/net_processing.cpp:3082 in 77560a690c outdated
    3078+            // parents so avoid re-requesting it from other peers.
    3079+            // Here we add both the txid and the wtxid, as we know that
    3080+            // regardless of what witness is provided, we will not accept
    3081+            // this, so we don't need to allow for redownload of this txid
    3082+            // from any of our non-wtxidrelay peers.
    3083+            RecentRejectsFilter().insert(tx.GetHash().ToUint256());
    


    instagibbs commented at 7:49 pm on August 19, 2024:
    unrelated to your move-only change, but if txid==wtxid, looks like we’re doing two insertions anyways here

    glozow commented at 10:43 am on August 21, 2024:
    agree, though leaving out of this PR to limit its scope. Could add a nonsegwit parent + segwit child test to be added to p2p_orphan_handling.py. Maybe a good first issue.
  67. in src/net_processing.cpp:607 in d3fb8ca103 outdated
    603@@ -594,8 +604,8 @@ class PeerManagerImpl final : public PeerManager
    604      *                                            Set to false if the tx has already been rejected before,
    605      *                                            e.g. is an orphan, to avoid adding duplicate entries.
    606      * Updates m_txrequest, m_lazy_recent_rejects, m_lazy_recent_rejects_reconsiderable, m_orphanage, and vExtraTxnForCompact. */
    607-    void ProcessInvalidTx(NodeId nodeid, const CTransactionRef& tx, const TxValidationState& result,
    608-                          bool maybe_add_extra_compact_tx)
    609+    std::optional<PackageToValidate> ProcessInvalidTx(NodeId nodeid, const CTransactionRef& tx, const TxValidationState& result,
    


    instagibbs commented at 7:51 pm on August 19, 2024:

    d3fb8ca103b32f6071790bf7a9471fe4f80f5479

    new ProcessInvalidTx return value needs to be documented


    glozow commented at 11:04 am on August 21, 2024:
    added
  68. in src/net_processing.cpp:3121 in d3fb8ca103 outdated
    3116@@ -3104,6 +3117,14 @@ void PeerManagerImpl::ProcessInvalidTx(NodeId nodeid, const CTransactionRef& ptx
    3117             // because we should not download or submit this transaction by itself again, but may
    3118             // submit it as part of a package later.
    3119             RecentRejectsReconsiderableFilter().insert(ptx->GetWitnessHash().ToUint256());
    3120+
    3121+            if (maybe_add_extra_compact_tx) {
    


    instagibbs commented at 7:52 pm on August 19, 2024:
    further definitional drift of maybe_add_extra_compact_tx, let’s try to find something else to call this?

    glozow commented at 11:04 am on August 21, 2024:
    done
  69. in src/net_processing.cpp:2996 in 65d773feb7 outdated
    3138-    m_txrequest.ForgetTxHash(tx->GetWitnessHash());
    3139-
    3140-    m_orphanage.AddChildrenToWorkSet(*tx);
    3141-    // If it came from the orphanage, remove it. No-op if the tx is not in txorphanage.
    3142-    m_orphanage.EraseTx(tx->GetWitnessHash());
    3143+    m_txdownloadman.MempoolAcceptedTx(tx);
    


    instagibbs commented at 1:01 pm on August 20, 2024:

    65d773feb7384a6893ee7e6a7b24dfa60f2d8d39

    commit message seems grammatically incorrect:

    “[refactor] move valid and tx processing to TxDownload”


    glozow commented at 11:19 am on August 21, 2024:
    fixed
  70. in src/net_processing.cpp:4232 in ed96f3ef3d outdated
    4231-        // already; and an adversary can already relay us old transactions
    4232-        // (older than our recency filter) if trying to DoS us, without any need
    4233-        // for witness malleation.
    4234-        if (m_txdownloadman.AlreadyHaveTx(GenTxid::Wtxid(wtxid), /*include_reconsiderable=*/true)) {
    4235+        const auto& [should_validate, package_to_validate] = m_txdownloadman.ReceivedTx(pfrom.GetId(), ptx);
    4236+        if (!should_validate) {
    


    instagibbs commented at 1:22 pm on August 20, 2024:
    should Assume that package_to_validate is std::nullopt if should_validate is true

    glozow commented at 11:20 am on August 21, 2024:
    added

    instagibbs commented at 1:17 pm on August 21, 2024:
    think you added the other way, that if should_validate is false, it must have a package to validate, which is incorrect(and caught by our 1p1c tests)

    glozow commented at 2:44 pm on August 21, 2024:
    woops, fixed now
  71. in src/node/txdownload_impl.cpp:408 in ed96f3ef3d outdated
    403+            LogPrint(BCLog::TXPACKAGES, "found tx %s (wtxid=%s) in reconsiderable rejects, looking for child in orphanage\n",
    404+                     txid.ToString(), wtxid.ToString());
    405+            return std::make_pair(false, Find1P1CPackage(ptx, nodeid));
    406+        }
    407+
    408+        // If a tx is detected by m_lazy_recent_rejects it is ignored. Because we haven't
    


    instagibbs commented at 1:29 pm on August 20, 2024:
    just noting this moved comment is extremely verbose/old comment and can probably just have the last paragraph kept?

    glozow commented at 9:55 am on August 21, 2024:
    Seems fine to do, but won’t add it to this PR because I want to limit its scope. Agree just the last paragraph is sufficient.
  72. in src/node/txdownload_impl.cpp:429 in 81f67607e8 outdated
    424@@ -425,4 +425,15 @@ std::pair<bool, std::optional<PackageToValidate>> TxDownloadImpl::ReceivedTx(Nod
    425 
    426     return std::make_pair(true, std::nullopt);
    427 }
    428+
    429+bool TxDownloadImpl::HaveMoreWork(NodeId nodeid)
    


    instagibbs commented at 1:47 pm on August 20, 2024:
    If the wrapper is named HaveMoreWork, maybe have GetTxToReconsider be called GetMoreWork? Otherwise they seem incongruous.

    glozow commented at 9:46 am on August 21, 2024:

    I called it HaveMoreWork since its purpose is to tell ThreadMessageHandler() if there is more work to do (doesn’t matter what kind of work): https://github.com/bitcoin/bitcoin/blob/3c0b66c2ece7695ab59f466d7b4d81a4c18ffd76/src/net_processing.cpp#L5083 https://github.com/bitcoin/bitcoin/blob/3c0b66c2ece7695ab59f466d7b4d81a4c18ffd76/src/net.cpp#L2902-L2903

    GetTxToReconsider’s user is peerman, in ProcessOrphanTx.

    I think scheduled reconsideration work could become more generic in the future, i.e. include packages. But I think ProcessOrphanTx is probably more readable if the function name specifies we’re getting 1 transaction to reconsider.

  73. in src/node/txdownload_impl.cpp:439 in 388c2f5d7e outdated
    435@@ -436,4 +436,13 @@ CTransactionRef TxDownloadImpl::GetTxToReconsider(NodeId nodeid)
    436     return m_orphanage.GetTxToReconsider(nodeid);
    437 }
    438 
    439+void TxDownloadImpl::CheckIsEmpty(NodeId nodeid)
    


    instagibbs commented at 1:49 pm on August 20, 2024:
    CheckRequestsEmpty?

    glozow commented at 9:39 am on August 21, 2024:

    CheckIsEmpty() seems a bit more future-proof. We’ll check

    • no requests for this peer
    • no pending orphan resolutions for this peer
    • no orphans for this peer etc.
  74. instagibbs commented at 1:56 pm on August 20, 2024: member

    reviewed all refactorings through 388c2f5d7ee5f38cbefaff0c85cf298083127299

    Pretty straight forward the whole way through.

    going to spend time reviewing/testing the fuzz portions next

  75. in src/test/txdownload_tests.cpp:70 in c1609a92ce outdated
    65+    // Ensure every transaction has a different txid by having each one spend the previous one.
    66+    static Txid prevout_hash{};
    67+
    68+    std::vector<CTransactionRef> txns;
    69+    txns.reserve(num_txns);
    70+    // Simplest spk for every tx
    


    instagibbs commented at 2:17 pm on August 20, 2024:
    simplest is a blank CScript

    glozow commented at 12:30 pm on August 21, 2024:
    done
  76. in src/test/txdownload_tests.cpp:87 in c1609a92ce outdated
    82+    return txns;
    83+}
    84+static CTransactionRef GetNewTransaction()
    85+{
    86+    // Create 2 transactions and return the second one. Transactions are created as a chain to
    87+    // ensure uniqueness. Here, we want to avoid direct parent/child relationships between transactions.
    


    instagibbs commented at 2:18 pm on August 20, 2024:

    Here, we want to avoid direct parent/child relationships between transacti

    doesn’t a chain mean direct parent/child?


    glozow commented at 10:15 am on August 21, 2024:

    So the requirements are:

    • transactions need to be unique
    • they need to appear to not be related (i.e. no direct relationships)

    One way to do this is to make unique utxos for each tx. But we also know from experience debugging fuzzers that sometimes the random outpoint’s txid matches the hash of one of the transactions, which is annoying because it creates “direct relationships”.

    Ironically, since prevouts reference hashes, the easiest way to ensure transactions aren’t directly related is to have them all be indirectly related.

    So my quick and dirty method for creating n transactions is to generate 1 utxo and make 2*n transactions chaining off of it, then use every other tx. I don’t need to know n ahead of time - I just make 2 at the bottom whenever I need one. They’re guaranteed to be unique (because sha256) and to not look like they’re spending each other (because sha256).


    instagibbs commented at 6:41 pm on August 21, 2024:
    :facepalm: I was missing the static in front of prevout_hash. This is making a lot more sense now.

    instagibbs commented at 6:41 pm on August 21, 2024:
    might be worth a comment!
  77. in src/test/txdownload_tests.cpp:101 in c1609a92ce outdated
     96+
     97+    TxValidationState state;
     98+
     99+    for (const auto& [result, expected_behavior] : expected_behaviors) {
    100+        node::TxDownloadImpl txdownload_impl{node::TxDownloadOptions{pool, det_rand, DEFAULT_MAX_ORPHAN_TRANSACTIONS}};
    101+        const auto ptx = GetNewTransaction();
    


    instagibbs commented at 2:24 pm on August 20, 2024:

    ptx/txid/wtxid can just be made once outside the loop since it’s the same each time

    as well as nodeid, now


    glozow commented at 12:31 pm on August 21, 2024:
    done
  78. in src/test/txdownload_tests.cpp:86 in c1609a92ce outdated
    81+    }
    82+    return txns;
    83+}
    84+static CTransactionRef GetNewTransaction()
    85+{
    86+    // Create 2 transactions and return the second one. Transactions are created as a chain to
    


    instagibbs commented at 2:36 pm on August 20, 2024:

    what is this chaining doing? I only see the ultimate tx being used anyways.

    It might be useful to have the parent tx rejected optionally, to check that TX_MISSING_INPUTS, fills out txid and wtxid reject filters properly.


    glozow commented at 4:19 pm on August 21, 2024:
    added tests for orphans with rejected parents
  79. in src/test/fuzz/txdownloadman.cpp:178 in 2d6a41102e outdated
    149+    CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
    150+    const auto max_orphan_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 300);
    151+    FastRandomContext det_rand{true};
    152+    node::TxDownloadManager txdownloadman{node::TxDownloadOptions{pool, det_rand, max_orphan_count}};
    153+
    154+    std::chrono::microseconds time{244466666};
    


    instagibbs commented at 2:49 pm on August 20, 2024:
    1 2, 3 4s, 5 6s womp womp

    glozow commented at 2:45 pm on August 21, 2024:

    womp womp

    Can you be a bit more specific :joy:


    instagibbs commented at 3:02 pm on August 22, 2024:
    No. I just had to google this magic number and felt slightly rickrolled

    glozow commented at 3:04 pm on August 22, 2024:
    ohhh i get it now. i thought you found a crash and were giving a cryptic description of the input
  80. in src/test/fuzz/txdownloadman.cpp:187 in 2d6a41102e outdated
    158+        NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS);
    159+
    160+        // Transaction can be one of the premade ones or a randomly generated one
    161+        auto rand_tx = fuzzed_data_provider.ConsumeBool() ?
    162+            MakeTransactionSpending({fuzzed_data_provider.PickValueInArray(COINS)},
    163+                                    /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500),
    


    instagibbs commented at 2:54 pm on August 20, 2024:
    500 outputs is probably too much unless you think it’s gaining some meaningful coverage?

    glozow commented at 4:06 pm on August 21, 2024:
    It makes them potentially larger transactions?
  81. in src/test/fuzz/txdownloadman.cpp:215 in 2d6a41102e outdated
    210+                txdownloadman.ReceivedTx(rand_peer, rand_tx);
    211+                const auto& [should_validate, maybe_package] = txdownloadman.ReceivedTx(rand_peer, rand_tx);
    212+                Assert(should_validate || !maybe_package.has_value());
    213+                if (maybe_package.has_value()) {
    214+                    Assert(maybe_package->m_senders.size() == 2);
    215+                    Assert(maybe_package->m_senders.front() == rand_peer);
    


    instagibbs commented at 2:58 pm on August 20, 2024:
    m_senders.back() should be <= NUM_PEERS

    glozow commented at 4:07 pm on August 21, 2024:
    done
  82. in src/test/fuzz/txdownloadman.cpp:433 in 2d6a41102e outdated
    387+                    txdownload_impl.MempoolRejectedTx(ptx, state_missing_inputs, rand_peer, fuzzed_data_provider.ConsumeBool());
    388+                }
    389+            }
    390+        );
    391+
    392+        // Jump ahead in time
    


    instagibbs commented at 2:59 pm on August 20, 2024:
    not just ahead due to negative skips

    glozow commented at 4:08 pm on August 21, 2024:
    I thought they were all positive?

    instagibbs commented at 11:30 am on August 22, 2024:
    hmm, must have been referencing something else, nevermind
  83. in src/test/fuzz/txdownloadman.cpp:336 in 2d6a41102e outdated
    300+                txdownload_impl.DisconnectedPeer(rand_peer);
    301+                txdownload_impl.CheckIsEmpty(rand_peer);
    302+            },
    303+            [&] {
    304+                txdownload_impl.ActiveTipChange();
    305+                // After a block update, nothing should be in the rejection caches
    


    instagibbs commented at 3:04 pm on August 20, 2024:
    iterate over all TRANSACTIONS and assert this?

    glozow commented at 4:08 pm on August 21, 2024:
    done
  84. in src/test/fuzz/txdownloadman.cpp:292 in 2d6a41102e outdated
    257+    // We should never have more than the maximum in-flight requests out for a peer.
    258+    for (NodeId peer = 0; peer < NUM_PEERS; ++peer) {
    259+        if (!HasRelayPermissions(peer)) {
    260+            Assert(txdownload_impl.m_txrequest.CountInFlight(peer) <= node::MAX_PEER_TX_REQUEST_IN_FLIGHT);
    261+        }
    262+    }
    


    instagibbs commented at 3:11 pm on August 20, 2024:
    0    }
    1    txdownload_impl.m_txrequest.SanityCheck();
    

    glozow commented at 4:09 pm on August 21, 2024:
    added
  85. in src/test/fuzz/txdownloadman.cpp:361 in 2d6a41102e outdated
    323+            [&] {
    324+                txdownload_impl.MempoolAcceptedTx(rand_tx);
    325+            },
    326+            [&] {
    327+                TxValidationState state;
    328+                state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), "");
    


    instagibbs commented at 3:13 pm on August 20, 2024:
    MempoolRejectedTx should probably Assume that state is invalid at the top of its definition

    glozow commented at 4:18 pm on August 21, 2024:
    should it? seems more complete if it handles all types, and we test them

    instagibbs commented at 11:29 am on August 22, 2024:
    The fuzz target is always setting state.Invalid, so I don’t see where this coverage would come from?

    glozow commented at 10:18 pm on October 1, 2024:
    Added the Assume. I agree it makes more sense.
  86. in src/test/fuzz/txdownloadman.cpp:399 in 2d6a41102e outdated
    353+                    Assert(maybe_package->m_senders.size() == 2);
    354+                    Assert(maybe_package->m_senders.front() == rand_peer);
    355+
    356+                    const auto& package = maybe_package->m_txns;
    357+                    Assert(package.size() == 2);
    358+                    // Parent is in m_lazy_recent_rejects_reconsiderable and child is in m_orphanage
    


    instagibbs commented at 3:23 pm on August 20, 2024:

    Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(maybe_package->m_txns.back()->GetWitnessHash().ToUint256()));

    I considered this assertion, is it valid? Can we have something in our orphanage and reconsiderable filter?


    glozow commented at 4:11 pm on August 21, 2024:
    I think something would be wrong then. It wouldn’t be missing inputs if we knew what the fee was.
  87. in src/test/fuzz/txdownloadman.cpp:415 in 2d6a41102e outdated
    369+                txdownload_impl.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()});
    370+            },
    371+            [&] {
    372+                const bool expect_work{txdownload_impl.HaveMoreWork(rand_peer)};
    373+                const auto ptx = txdownload_impl.GetTxToReconsider(rand_peer);
    374+                // expect_work=true doesn't necessarily mean the next item from the workset isn't a
    


    instagibbs commented at 3:28 pm on August 20, 2024:
    Can you remind me of a case where this happens? Running with an erroneous assertion added and it’s not seeming to hit.

    glozow commented at 4:13 pm on August 21, 2024:
    1. tx is added to workset
    2. tx is removed from orphanage
    3. peer workset is nonempty, but its wtxids correspond to stuff that isn’t there anymore
  88. in src/test/fuzz/tx_download.cpp:170 in 04856ed32c outdated
    165+    TestPeer peer;
    166+    peer.m_nodeid = nodeid;
    167+    peer.m_good = force_good || fuzzed_data_provider.ConsumeBool();
    168+    peer.m_connection_preferred = preferred;
    169+    peer.m_relay_permissions = false;
    170+    peer.m_wtxid_relay = force_good || fuzzed_data_provider.ConsumeBool();
    


    instagibbs commented at 3:37 pm on August 20, 2024:
    maybe leave a comment why good peers do wtxid relay

    glozow commented at 4:18 pm on August 21, 2024:
    added
  89. in src/test/fuzz/tx_download.cpp:114 in 04856ed32c outdated
    109+    bool m_good;
    110+
    111+    // for TxDownloadConnectionInfo
    112+    // Note that this is used to indicate when somebody is an outbound peer.
    113+    bool m_connection_preferred{false};
    114+    bool m_relay_permissions{false};
    


    instagibbs commented at 4:23 pm on August 20, 2024:
    seems to always be false?

    glozow commented at 4:18 pm on August 21, 2024:
    changed to come from fuzzer
  90. instagibbs commented at 8:48 pm on August 20, 2024: member

    ok I reviewed the unit and fuzz tests.

    For the tx_download fuzz target there’s a couple issues:

    1. the time could adversarially be moved forward only 1ms at a time, 100k iterations results in 100 seconds. I hacked the target to only allow the forced good peer to respond to the getdata. I got a case where the while loop exits when only 100 seconds have passed in simulated time. Stalling peer A refuses to send TX, then after 60 seconds, at the same time, an honest peer and stalling peer connect and offer the same INV. First INV times out, then second stalling peer gets chosen as next CANIDATE_BEST. Runs out of iterations 40 seconds later.

    2. More annoyingly, the case I found had instability. I think it’s because TxRequests’ PriorityComputer is not being set to deterministic random even when TxDownloadMan is. So sometimes the case was passing due to the ordering of the candidates based on this randomness.

    In general I think this last fuzz target should try to target more on the end of “only one good peer”, at least switch to that with a bool, since I think when allowing all peers to perhaps be somewhat good, it hides potential issues.

  91. glozow force-pushed on Aug 21, 2024
  92. glozow commented at 11:55 am on August 21, 2024: member
    Pushed to address the first batch of comments, going to work on test parts next
  93. dergoegge commented at 12:53 pm on August 21, 2024: member

    The naming of the files and tests is a bit inconsistent, I would suggest the following:

    • txdownloadman.h, txdownloadman.cpp, txdownloadman_impl.h and txdownloadman_impl.cpp with the classes: TxDownloadManager and TxDownloadManagerImpl
    • fuzz/txdownloadman.cpp: txdownloadman + txdownloadman_impl
    • fuzz/txdownloadman_one_honest_peer.cpp: txdownloadman_one_honest_peer

    I don’t care about the specific names just the consistency, e.g. don’t mix tx_download and txdownload or txdownloadman and txdownload. The test names should roughly convey what is being tested as well.

    I’m fuzzing all the harnesses atm, should have coverage reports up by tomorrow.

  94. DrahtBot added the label CI failed on Aug 21, 2024
  95. glozow force-pushed on Aug 21, 2024
  96. glozow force-pushed on Aug 21, 2024
  97. glozow force-pushed on Aug 21, 2024
  98. DrahtBot removed the label CI failed on Aug 21, 2024
  99. in src/test/txdownload_tests.cpp:238 in 8f5163e8a1 outdated
    227+
    228+    // Reconsiderable parent of a tx already in orphanage: the only time PackageToValidate is returned.
    229+    {
    230+        node::TxDownloadImpl txdownload_impl{node::TxDownloadOptions{pool, det_rand, DEFAULT_MAX_ORPHAN_TRANSACTIONS}};
    231+
    232+        // Child missing inputs, should be added to orphanage.
    


    instagibbs commented at 7:00 pm on August 21, 2024:
    nit: just assert it’s in orphanage instead

    glozow commented at 9:33 am on August 23, 2024:
    added
  100. instagibbs commented at 7:06 pm on August 21, 2024: member

    reviewed through bbec8e261467637ce26ecf551b528d9061cf4ffe

    via git range-diff master 388c2f5d7ee5f38cbefaff0c85cf298083127299 bbec8e261467637ce26ecf551b528d9061cf4ffe

    LGTM aside from waiting on fuzzer coverage results + fixes

  101. instagibbs commented at 1:53 pm on August 22, 2024: member

    Doesn’t look like ReceivedTx ever returns a package to validate in any of the fuzz tests (500 CPU hours each):

    At a minimum TESTED_TX_RESULTS is missing TxValidationResult::TX_RECONSIDERABLE, so nothing is ever failing for the right reason to try a package.

    I restricted the set of possible transactions down to just a parent/child combo in TRANSACTIONS and was able to trigger it quickly, so it’s likely reachable with that one change.

  102. in src/test/fuzz/txdownloadman.cpp:212 in 0de01c034e outdated
    207+                txdownloadman.GetRequestsToSend(rand_peer, time);
    208+            },
    209+            [&] {
    210+                txdownloadman.ReceivedTx(rand_peer, rand_tx);
    211+                const auto& [should_validate, maybe_package] = txdownloadman.ReceivedTx(rand_peer, rand_tx);
    212+                Assert(should_validate || !maybe_package.has_value());
    


    instagibbs commented at 3:02 pm on August 22, 2024:
    this fails if we have a package to validate…

    glozow commented at 9:32 am on August 23, 2024:
    I wrote the assertion incorrectly. The only combination that’s not ok is both.
  103. instagibbs commented at 3:06 pm on August 22, 2024: member

    3yTMzMzMzMzMzMzMzMw2lcV+LVVYVVX//y1maZY/xcWVxX5VWFVVVVUlugr///8tZmlVCg== for txdownloadman target

    if you add TX_RECONSIDERABLE to the end of TESTED_TX_RESULTS, this should crash because it found a package to try

  104. glozow force-pushed on Aug 23, 2024
  105. glozow commented at 9:32 am on August 23, 2024: member

    TESTED_TX_RESULTS is missing TxValidationResult::TX_RECONSIDERABLE

    That’s an oversight, I’ve added that and UNKNOWN now which should mean we see packages.

  106. in src/node/txdownloadman.h:101 in 6a84ab77f3 outdated
     96+    std::optional<PackageToValidate> m_package_to_validate;
     97+};
     98+
     99+
    100+class TxDownloadManager {
    101+    const std::unique_ptr<TxDownloadImpl> m_impl;
    


    dergoegge commented at 12:05 pm on August 23, 2024:
    0    const std::unique_ptr<TxDownloadManagerImpl> m_impl;
    

    👉👈


    glozow commented at 11:45 am on August 27, 2024:
    done

    ismaelsadeeq commented at 12:12 pm on August 28, 2024:
    The update was effected in the second commit

    glozow commented at 1:33 pm on August 28, 2024:
    Woops, ran the script from HEAD~24 instead of HEAD~25. Fixed now.
  107. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:226 in 6a84ab77f3 outdated
    221+// malicious, we'll always be able to download a transaction.
    222+// Connect to 8 outbounds and 0-120 inbound peers. 1 outbound is guaranteed to be good (see m_good
    223+// above for definition), while the rest may or may not be.
    224+// A non-good node has a superset of the actions available to a good node: it may stall a getdata,
    225+// send a malleated or witness stripped tx/inv, send a notfound, etc.
    226+FUZZ_TARGET(tx_download_one_honest_peer, .init = initialize)
    


    dergoegge commented at 12:05 pm on August 23, 2024:
    0FUZZ_TARGET(txdownloadman_one_honest_peer, .init = initialize)
    

    👉👈


    glozow commented at 3:09 pm on August 23, 2024:
    done
  108. in src/test/fuzz/txdownloadman.cpp:280 in 6a84ab77f3 outdated
    275+        }
    276+    }
    277+    txdownload_impl.m_txrequest.SanityCheck();
    278+}
    279+
    280+FUZZ_TARGET(txdownload_impl, .init = initialize)
    


    dergoegge commented at 12:06 pm on August 23, 2024:
    0FUZZ_TARGET(txdownloadman_impl, .init = initialize)
    

    (if you take the other naming suggestion)

    👉👈


    glozow commented at 3:10 pm on August 23, 2024:
    done
  109. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:284 in 6a84ab77f3 outdated
    279+    // within the first round and will never be disconnected.
    280+    int counter = 0;
    281+
    282+    LIMITED_WHILE(!got_tx, 100000)
    283+    {
    284+        std::shuffle(indexes.begin(), indexes.end(), det_rand);
    


    dergoegge commented at 12:20 pm on August 23, 2024:

    Any reason not to let the fuzzer decide the order?

    If the order is interesting (i.e. a specific order might trigger a bug) then I think it’d make more sense to let the fuzzer choose the order.


    glozow commented at 3:10 pm on August 23, 2024:
    done
  110. in src/node/txdownloadman.cpp:15 in 6a84ab77f3 outdated
    10+TxDownloadManager::TxDownloadManager(const TxDownloadOptions& options) :
    11+    m_impl{std::make_unique<TxDownloadImpl>(options)}
    12+{}
    13+TxDownloadManager::~TxDownloadManager() = default;
    14+
    15+void TxDownloadManager::ActiveTipChange()
    


    dergoegge commented at 12:25 pm on August 23, 2024:
    To avoid the circular dependency, you could get rid of this file and move its contents to txdownloadman_impl.cpp.

    glozow commented at 11:45 am on August 27, 2024:
    done
  111. dergoegge commented at 1:17 pm on August 23, 2024: member
    0$ FUZZ=txdownload_impl src/test/fuzz/fuzz solution-60b4aafd2c355198b269114d31545a12b9059fd6.txt
    1test/fuzz/txdownloadman.cpp:384 operator(): Assertion `!txdownload_impl.RecentRejectsFilter().contains(package.back()->GetWitnessHash().ToUint256())' failed.
    

    solution-60b4aafd2c355198b269114d31545a12b9059fd6.txt

  112. instagibbs commented at 1:42 pm on August 23, 2024: member

    solution-60b4aafd2c355198b269114d31545a12b9059fd6.txt

    Find1P1CPackage doesn’t actually check the normal reject filter at all with respect to the child, only reconsiderable, so that assertion gets hit. I suspect that’s something we don’t want it to do, even if it’s not possible to hit inside net_processing.

    LGTM modulo existing concerns about the tx_download_one_honest_peer harness:

    1. no det rand for tx_request module in downloadman, leading to instability
    2. timesteps can be so small that the tx is never downloaded in 100 seconds
    3. scenario should flip a bool to only allow the good peer to hand over real tx

    reviewed via git range-diff master bbec8e261467637ce26ecf551b528d9061cf4ffe 6a84ab77f36445fd37d46a08c71bd4373201d300

  113. glozow force-pushed on Aug 23, 2024
  114. glozow commented at 3:09 pm on August 23, 2024: member

    Find1P1CPackage doesn’t actually check the normal reject filter at all with respect to the child, only reconsiderable, so that assertion gets hit.

    I think it might be more that MempoolRejectedTx doesn’t remember if a tx failed for a different reason before considering an orphan?

  115. in src/net_processing.cpp:3083 in 1468112dda outdated
    3082             }
    3083 
    3084-            if (m_orphanage.AddTx(ptx, nodeid)) {
    3085-                AddToCompactExtraTransactions(ptx);
    3086-            }
    3087+            add_extra_compact_tx &= m_orphanage.AddTx(ptx, nodeid);
    


    theStack commented at 4:04 pm on August 23, 2024:
    just to be sure, was it intentional to use the bit-wise AND operator here, rather than the logical one (&&=), to avoid potential short-circuiting (see e.g. https://stackoverflow.com/questions/23107162/do-the-and-operators-for-bool-short-circuit)? IIUC, add_extra_compact_tx is always true at this point anyway (otherwise the if condition at the very top couldn’t be true), so could also just do a regular assignment, I guess. But not sure what’s better, one could argue that the current way is more future-proof.

    glozow commented at 11:46 am on August 27, 2024:
    Yes, we don’t want a short circuit, though in practice add_extra_compact_tx should always be true at this point in the code. I’ve added a comment describing what the expected changes can be.
  116. theStack commented at 4:24 pm on August 23, 2024: contributor

    Reviewed up to e164614e32010bc8061cc99ed486188235efa780 (i.e. everything modulo fuzz and unit tests in the last 3 commits), lgtm so far.

    Slightly off-topic, but: on some commits like 4e58d84da95d97d64f563b5c3e116769cd9ff89b --color-moved=dimmed-zebra didn’t detect any lines as move-only for me (with 2-3 exceptions that seem completely arbitrary), any suggestions for advanced git options and/or recommendations for other fancy diff tools that can cope with that? (Or well, maybe I should just try with a newer git version.)

  117. glozow force-pushed on Aug 27, 2024
  118. glozow commented at 11:49 am on August 27, 2024: member

    Thanks @dergoegge @theStack @instagibbs! Addressed all comments.

    Slightly off-topic, but: on some commits like https://github.com/bitcoin/bitcoin/commit/4e58d84da95d97d64f563b5c3e116769cd9ff89b –color-moved=dimmed-zebra didn’t detect any lines as move-only for me (with 2-3 exceptions that seem completely arbitrary), any suggestions for advanced git options and/or recommendations for other fancy diff tools that can cope with that?

    I see that happen too :( very annoying. Sometimes if I see there are indentation changes, adding a -w or manually (un)indenting helps the diff display better. Unfortunately I don’t have any other tips.

  119. instagibbs commented at 1:42 pm on August 27, 2024: member

    ACK 6f6fc1464ad2f2981e7c3ff5d6492f60f89a6504

    Attends to my concerns with the fuzz tests. Now in txdownloadman_one_honest_peer no “non-good” peer will ever deliver TX_REAL, ensuring that the test is actually testing what we want. Determinstic randomness was also given to the member txrequest modules.

    Lastly, minimum time skips were increased to 100ms, meaning 100ms*100000=~166 minutes of simulated time. Since there are up to 8+120=128 peers, this means each peer can stall out the node for a full minute and we still have 40 minutes left for the honest peer to succeed in giving the tx.

    If you touch this again, I might suggest adding some static asserts to the above effect in case people want to modify these values in the future.

  120. DrahtBot requested review from theStack on Aug 27, 2024
  121. instagibbs commented at 2:51 pm on August 27, 2024: member

    Just posting in case someone else learns something. Not asking for any changes:

    Lastly, minimum time skips were increased to 100ms, meaning 100ms*100000=~166 minutes of simulated time. Since there are up to 8+120=128 peers, this means each peer can stall out the node for a full minute and we still have 40 minutes left for the honest peer to succeed in giving the tx.

    After doing more study, I think this simulation “only” needs a little more than 7 minutes minimum time, since the internal PriorityComputer will always select outbounds(prefered) out of the set of CANDIDATE_READYs. Incoming connections will be passed over for the good(and outgoing) peer once the good peer sends INV_REAL when choosing new CANIDATE_BEST.

  122. in src/node/txdownloadman.h:16 in 58fb38c182 outdated
    11+class TxOrphanage;
    12+class TxRequestTracker;
    13+class CRollingBloomFilter;
    14+
    15+namespace node {
    16+class TxDownloadImpl;
    


    ismaelsadeeq commented at 11:36 am on August 28, 2024:

    At first I was wondering if their is any benefit of not declaring the implementation class private to TxDownloadManager as recommended in https://en.cppreference.com/w/cpp/language/pimpl.

    But then after the first pass of this PR, I think the reason is to make TxDownloadManagerImpl visible to the entire namespace in other to allow for testing TxDownloadManagerImpl specific behaviors in fuzzing.

  123. in src/node/txdownloadman_impl.cpp:4 in 58fb38c182 outdated
    0@@ -0,0 +1,34 @@
    1+// Copyright (c) 2024
    2+// Distributed under the MIT software license, see the accompanying
    3+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
    4+
    


    ismaelsadeeq commented at 11:48 am on August 28, 2024:

    In 58fb38c182906645161af04e84becc824dee2ba5 " [refactor] add TxDownloadManager wrapping TxOrphanage, TxRequestTracker, and bloom filters"

    I think the file name should be txdownloadman.cpp ? TxDownloadManager is the public interface.


    glozow commented at 1:18 pm on August 28, 2024:
    It contains the implementation for TxDownloadManagerImpl as well.
  124. in src/net_processing.cpp:1583 in 58fb38c182 outdated
    1581@@ -1672,6 +1582,8 @@ void PeerManagerImpl::AddTxAnnouncement(const CNode& node, const GenTxid& gtxid,
    1582     AssertLockHeld(::cs_main); // for State
    1583     AssertLockHeld(m_tx_download_mutex); // For m_txrequest
    


    ismaelsadeeq commented at 12:03 pm on August 28, 2024:
    0    AssertLockHeld(m_tx_download_mutex); // For m_txdownloadman
    

    glozow commented at 1:32 pm on August 28, 2024:
    Assuming this is on one of the earlier commits - the whole line is deleted in 0e6e135e303
  125. ismaelsadeeq commented at 12:57 pm on August 28, 2024: member
    Approach ACK
  126. glozow force-pushed on Aug 28, 2024
  127. glozow force-pushed on Aug 28, 2024
  128. glozow commented at 1:53 pm on August 28, 2024: member
    Rebased for cmake
  129. in src/test/txdownload_tests.cpp:124 in caedaec23a outdated
    94+
    95+    // A new TxDownloadManagerImpl is created for each tx so we can just reuse the same one.
    96+    TxValidationState state;
    97+    NodeId nodeid{0};
    98+    std::chrono::microseconds now{GetTime()};
    99+    node::TxDownloadConnectionInfo connection_info{/*m_preferred=*/false, /*m_relay_permissions=*/false, /*m_wtxid_relay=*/true};
    


    instagibbs commented at 2:33 pm on August 28, 2024:
    IIUC this would at least give more coverage to AddTxAnnouncement getting deeper during new orphan processing

    glozow commented at 2:40 pm on August 28, 2024:
    Right. I’m also building my orphan resolution branch that effectively skips adding orphans for peers that aren’t “registered” (we need to know their connection info to decide some params). These tests started failing there, so I had to add ConnectPeer. Figured it makes sense to do this now.
  130. instagibbs approved
  131. instagibbs commented at 2:34 pm on August 28, 2024: member

    reACK 2b387a156a1021ad782dc4de2fd34d5b5b81698a

    via git range-diff master 6f6fc1464ad2f2981e7c3ff5d6492f60f89a6504 2b387a156a1021ad782dc4de2fd34d5b5b81698a

    only non-cmake changes I see were reordering the class renaming, and the added ConnectedPeer in unit tests.

  132. DrahtBot requested review from ismaelsadeeq on Aug 28, 2024
  133. jonatack commented at 6:40 pm on August 28, 2024: member
    Interesting proposal to review; note to self to look at why move peer dis(connection) logic.
  134. hebasto removed the label Needs CMake port on Aug 29, 2024
  135. glozow force-pushed on Sep 2, 2024
  136. glozow commented at 6:36 pm on September 2, 2024: member
    Rebased for autotools removal, and made the txdownloadman_one_honest_peer fuzzer more interesting. Dishonest peers now have a lot of different transactions/invs they can send that can fail for more reasons.
  137. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:44 in 1d4e33e7f8 outdated
    39+    NOTFOUND,
    40+};
    41+
    42+// What a peer may send in response to a getdata.
    43+enum class PossibleResponse {
    44+    /** Send the target transaction, in response to getdata */
    


    instagibbs commented at 8:42 pm on September 3, 2024:
    this comment is outdated, as TX_REAL now means really whatever the node getdata’d

    glozow commented at 2:25 pm on September 4, 2024:
    Yes, will fix if I retouch
  138. instagibbs approved
  139. instagibbs commented at 8:43 pm on September 3, 2024: member

    reACK 1d4e33e7f88162cf6fcd6aee0b63973015853591

    didn’t run the new fuzz test iteration though it seems reasonable to me

    via git range-diff master 2b387a156a1021ad782dc4de2fd34d5b5b81698a 1d4e33e7f88162cf6fcd6aee0b63973015853591

  140. in src/net_processing.cpp:4879 in c40cb85659 outdated
    5146+                    tx_invs.emplace_back(inv.hash);
    5147                 }
    5148             }
    5149         }
    5150+        LOCK(m_tx_download_mutex);
    5151+        m_txdownloadman.ReceivedNotFound(pfrom.GetId(), tx_invs);
    


    theStack commented at 1:52 am on September 4, 2024:
    in commit c40cb8565952f62732fa64875c99ef9082b9a6f4: now that the if-body above only reads and writes from local variables (vInv and tx_invs), acquiring the m_tx_download_mutex lock before the for-loop (line 5140 at this commit) is not needed anymore

    glozow commented at 12:48 pm on September 5, 2024:
    good point :+1:
  141. theStack approved
  142. theStack commented at 10:46 am on September 5, 2024: contributor

    Light review ACK 1d4e33e7f88162cf6fcd6aee0b63973015853591

    Reviewed the refactoring changes and the unit tests thouroughly, but didn’t ran or look in-depth at the fuzz tests.

  143. glozow commented at 12:57 pm on September 5, 2024: member

    Thanks for the reviews 🙏

    I’ve created a followups PR #30820

    After that will be the second part of #28031 (“try multiple peers for orphan resolution”). I’ll open a separate PR, since it has 140 comments. After that, I plan to open a PR that moves m_tx_download_mutex inside TxDownloadManagerImpl (i.e. make it internally thread-safe), which I mention in the PR description here. I had considered making it the next step, but I think waiting a little more is better because (1) It gives the fuzzers more time to run + test that txdownloadman can handle interleaved calls and stay internally inconsistent. (2) There is currently a function called from both peerman and within txdownloadman, but that will go away, making the lock changes cleaner.

  144. glozow requested review from dergoegge on Sep 6, 2024
  145. in src/node/txdownloadman_impl.h:118 in ecbbadf0f7 outdated
    113+    std::unique_ptr<CRollingBloomFilter> m_lazy_recent_confirmed_transactions{nullptr};
    114+
    115+    CRollingBloomFilter& RecentConfirmedTransactionsFilter()
    116+    {
    117+        if (!m_lazy_recent_confirmed_transactions) {
    118+            m_lazy_recent_confirmed_transactions = std::make_unique<CRollingBloomFilter>(120'000, 0.000'001);
    


    achow101 commented at 8:32 pm on September 16, 2024:

    In ecbbadf0f79bb32730d9e1400d84b84893e35e56 “[refactor] add TxDownloadManager wrapping TxOrphanage, TxRequestTracker, and bloom filters”

    Why have this bloom filter’s parameters changed? It was previously 48'000 and 0.000'001. If this was intentional, it should probably be a separate commit and a rationale provided.


    glozow commented at 0:32 am on September 17, 2024:
    good catch!! that was unintentional, reverted now
  146. in src/node/txdownloadman_impl.cpp:213 in c40cb85659 outdated
    206@@ -203,4 +207,13 @@ std::vector<GenTxid> TxDownloadManagerImpl::GetRequestsToSend(NodeId nodeid, std
    207     }
    208     return requests;
    209 }
    210+
    211+void TxDownloadManagerImpl::ReceivedNotFound(NodeId nodeid, const std::vector<uint256>& txhashes)
    212+{
    213+    for (const auto& txhash: txhashes) {
    


    achow101 commented at 8:59 pm on September 16, 2024:

    In c40cb8565952f62732fa64875c99ef9082b9a6f4 “[refactor] move notfound processing to txdownload”

    nit:

    0    for (const auto& txhash : txhashes) {
    

    glozow commented at 0:32 am on September 17, 2024:
    done
  147. in src/node/txdownloadman_impl.cpp:455 in f3cc632279 outdated
    450+    const uint256& txid = ptx->GetHash();
    451+    const uint256& wtxid = ptx->GetWitnessHash();
    452+
    453+    // Mark that we have received a response
    454+    m_txrequest.ReceivedResponse(nodeid, ptx->GetHash());
    455+    if (ptx->HasWitness()) m_txrequest.ReceivedResponse(nodeid, ptx->GetWitnessHash());
    


    achow101 commented at 9:19 pm on September 16, 2024:

    In f3cc6322798ca61a31f90b2ad7a0c32471db0410 “[refactor] move new tx logic to txdownload”

    nit:

    0    m_txrequest.ReceivedResponse(nodeid, txid);
    1    if (ptx->HasWitness()) m_txrequest.ReceivedResponse(nodeid, wtxid);
    

    glozow commented at 0:33 am on September 17, 2024:
    done
  148. glozow force-pushed on Sep 17, 2024
  149. glozow force-pushed on Sep 17, 2024
  150. marcofleon commented at 10:03 am on September 17, 2024: contributor

    Ran txdownloadman_impl and it crashed on this assertion:

    0operator(): Assertion `!txdownload_impl.RecentRejectsFilter().contains(package.front()->GetWitnessHash().ToUint256())' failed.
    

    Base64 of the input: 4d3Myytlcf9a3czLK2UxoFrjzMtlMQBSWt3Myyv//w==

    Input file if it’s any easier: txdownload_impl_crash.txt

  151. instagibbs commented at 1:46 pm on September 17, 2024: member

    @marcofleon Example checks out, it’s pretty simple:

    1. txA is called with txdownload_impl.MempoolRejectedTx and TX_RECONSIDERABLE
    2. txA is called with txdownload_impl.MempoolRejectedTx and TX_NOT_STANDARD or any failure that results in entry to reject filter
    3. ReceivedTx is called with txA, child is found in orphanage, package returned even though txA is in both filters.

    I think it’d make sense for ReceivedTx to abort trying to find a package if the parent is in reject filter, even if also in recent reject filter.

  152. in src/node/txdownloadman_impl.cpp:463 in 5724926a9b outdated
    447+}
    448+
    449+std::pair<bool, std::optional<PackageToValidate>> TxDownloadManagerImpl::ReceivedTx(NodeId nodeid, const CTransactionRef& ptx)
    450+{
    451+    const uint256& txid = ptx->GetHash();
    452+    const uint256& wtxid = ptx->GetWitnessHash();
    


    marcofleon commented at 5:52 pm on September 17, 2024:
    Why is it that we don’t initialize these with theTxid and Wtxid types? Have been thinking about making RelayTransaction type safe, so just wondering.

    glozow commented at 11:18 pm on September 17, 2024:
    txrequest and the bloom filters take uint256s, so this seemed easiest. I don’t feel strongly. RelayTransaction isn’t called here…?

    marcofleon commented at 9:16 am on September 18, 2024:
    Makes sense. Don’t feel strongly either, was asking for my own understanding. I meant the RelayTransaction comment as a separate topic/PR, referring to transaction types (uint256 vs Txid/Wxid) in general, not specific to this line. I guess, overall, trying to gauge how strongly we’re trying to adhere to the newer types.
  153. glozow force-pushed on Sep 17, 2024
  154. glozow commented at 11:45 pm on September 17, 2024: member

    Ran txdownloadman_impl and it crashed on this assertion:

    I think it’d make sense for ReceivedTx to abort trying to find a package if the parent is in reject filter, even if also in recent reject filter.

    Thanks @marcofleon @instagibbs! Was able to reproduce the crash.

    I hadn’t noticed that quirk about the nested if conditions before - we were kind of expecting that a tx wouldn’t have multiple things wrong with it. I agree it makes more sense to quit on anything that’s non-reconsiderable AlreadyHaveTx, added that as its own commit. The input shouldn’t crash anymore.

  155. marcofleon commented at 11:07 am on September 18, 2024: contributor
    0operator(): Assertion `!txdownload_impl.RecentRejectsFilter().contains(package.back()->GetWitnessHash().ToUint256())' failed.
    
    0ZT8AAAD/////ZaOjaf/y/////f39/f39odAq0KMAZWU+qOHh4eHdzMsrZTKgWuPMyytlcaBaZWVl
    1MaBaK8vczGUx/wwAUlrdzMsrZTGgWuPMyytlcaBa2WVl//////8xoFrczMsrZTEAUlrdzMsrZTGg
    2WuMrZTH/DABS/////w==
    

    txdownload_impl_crash.txt

    Child is still in reject filter? Still (slowly and steadily) working through the PR as a whole, so I’m not much help for now when it comes to actually addressing the bug…

  156. in src/test/fuzz/txdownloadman.cpp:402 in df0b8a055c outdated
    378+
    379+                    const auto& package = maybe_package->m_txns;
    380+                    // Parent is in m_lazy_recent_rejects_reconsiderable and child is in m_orphanage
    381+                    Assert(txdownload_impl.RecentRejectsReconsiderableFilter().contains(rand_tx->GetWitnessHash().ToUint256()));
    382+                    Assert(txdownload_impl.m_orphanage.HaveTx(maybe_package->m_txns.back()->GetWitnessHash()));
    383+                    // Package has not been rejected
    


    instagibbs commented at 1:20 pm on September 18, 2024:
    I think these checks on down can be pulled into CheckPackageToValidate?

    glozow commented at 2:51 pm on September 29, 2024:
    (Was my first instinct too). That requires we pass txdownload_impl to the function, and the fuzz target above doesn’t have access to that.
  157. instagibbs commented at 1:30 pm on September 18, 2024: member

    @glozow could we get a unit test for that specific behavior change :pray: @marcofleon I presume it’s simply things that “shouldn’t happen”: 0) Parent MempoolRejectedTx, put in reconsiderable filter

    1. ChildMempoolRejectedTx, put in orphanage
    2. Child MempoolRejectedTx for other reason, now in reject filter
    3. Parent ReceivedTx, found in reconsiderable filter, fetches package with fully rejected child

    I’d feel better if we also simply not return a package we should never consider, even if it “shouldn’t happen”

  158. glozow commented at 9:09 pm on September 18, 2024: member
    1. Child`MempoolRejectedTx`, put in orphanage
    
    2. Child `MempoolRejectedTx` for other reason, now in reject filter
    

    On first glance that sounds impossible, MempoolRejectedTx should have erased it from orphanage? Will look into the error

  159. glozow commented at 10:26 pm on September 18, 2024: member

    Ah, I think it was bugs in the if/else logic in MempoolRejectedTx. The flow should be:

     0if (missing inputs) {
     1  if (first time and not already rejected) {
     2    consider keeping orphan...
     3  }
     4  return
     5} else if (error A) {
     6  special A stuff
     7} else if (error B) {
     8  special B stuff
     9} else {
    10  cache rejections, etc
    11}
    

    But we (1) dropped the return early and (2) didn’t have nested ifs, so we started caching orphans in recent rejects. I’ll work on writing more unit tests. I also think it would maybe be clearer albeit more verbose to rewrite that function as a big switch statement…

  160. glozow force-pushed on Sep 18, 2024
  161. DrahtBot added the label CI failed on Sep 19, 2024
  162. glozow force-pushed on Sep 19, 2024
  163. DrahtBot removed the label CI failed on Sep 20, 2024
  164. glozow commented at 11:02 am on September 20, 2024: member
    Fixed ci
  165. in src/node/txdownloadman_impl.cpp:336 in 287510e9ea outdated
    331+                    if (rejected_parent_reconsiderable.has_value()) {
    332+                        fRejectedParents = true;
    333+                        break;
    334+                    }
    335+                }
    336+                rejected_parent_reconsiderable = parent_txid;
    


    instagibbs commented at 2:52 pm on September 20, 2024:
    I think this line got pulled out of the else if conditional. Won’t this set the value for all unique parents and reject any transaction with two unique prevout hashes from the orphanage?

    glozow commented at 7:54 pm on September 20, 2024:

    Ugh yes, a bad rebase sorry. I’ve been using a new setup and haven’t perfected my diff-viewing yet.

    I don’t understand how 0 tests failed, though 😬 gotta look into that


    instagibbs commented at 9:21 pm on September 22, 2024:

    I don’t understand how 0 tests failed, though 😬 gotta look into that

    yeah was a bit worrying to me too… glad we can add unit tests in this PR!


    glozow commented at 11:18 am on September 23, 2024:
    Added some unit tests yesterday btw, should definitely catch this in I think 2 cases.
  166. glozow force-pushed on Sep 20, 2024
  167. in src/net_processing.cpp:1 in f0b2b37dbb outdated


    l0rinc commented at 12:28 pm on September 22, 2024:

    Commit messages:

    add TxDownloadOptions bool to make deterministic TxRequestTracker

    nit: may be clearer as add TxDownloadOptions bool to make TxRequestTracker deterministic

    [refactor] ProcessInvalidTx logic to put peerman tasks at the end

    nit2: [refactor] put peerman tasks in ProcessInvalidTx logic at the end


    glozow commented at 2:27 pm on September 29, 2024:
    done
  168. in src/net_processing.cpp:568 in a69cdf87f1 outdated
    567@@ -583,53 +568,29 @@ class PeerManagerImpl final : public PeerManager
    568     bool MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer);
    569 
    570     /** Handle a transaction whose result was not MempoolAcceptResult::ResultType::VALID.
    571-     * @param[in]   maybe_add_extra_compact_tx    Whether this tx should be added to vExtraTxnForCompact.
    572+     * @param[in]   first_time_failure            Whether we should consider inserting into vExtraTxnForCompact, adding
    


    l0rinc commented at 12:34 pm on September 22, 2024:
    • how come we’re only documenting the first_time_failure parameter?
    • is there any particular reason for this weird formatting?

    glozow commented at 0:41 am on September 24, 2024:
    • Are the other parameters not self-explanatory?
    • What do you mean by weird?
  169. in src/net_processing.cpp:4257 in a69cdf87f1 outdated
    4281-            // regardless of false positives.
    4282             return;
    4283         }
    4284 
    4285+        // ReceivedTx should not be telling us to validate the tx and a package.
    4286+        Assume(!package_to_validate.has_value());
    


    l0rinc commented at 12:37 pm on September 22, 2024:
    0        Assume(!package_to_validate);
    
  170. in src/net_processing.cpp:4872 in a69cdf87f1 outdated
    4868@@ -5321,16 +4869,18 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
    4869     if (msg_type == NetMsgType::NOTFOUND) {
    4870         std::vector<CInv> vInv;
    4871         vRecv >> vInv;
    4872-        if (vInv.size() <= MAX_PEER_TX_ANNOUNCEMENTS + MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
    4873-            LOCK(m_tx_download_mutex);
    4874+        std::vector<uint256> tx_invs;
    4875+        if (vInv.size() <= node::MAX_PEER_TX_ANNOUNCEMENTS + MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
    4876             for (CInv &inv : vInv) {
    


    l0rinc commented at 12:45 pm on September 22, 2024:

    We could reserve the vector to the expected size to avoid resizing:

    0tx_invs.reserve(std::ranges::count_if(vInv, &CInv::IsGenTxMsg));
    1for (CInv &inv : vInv) {
    

    glozow commented at 2:30 pm on September 29, 2024:
    We don’t know the number of transaction invs ahead of time, so this could reserve a bunch of extra space that is never used. Unsure the best way to handle this - if it’s transactions, the size should generally only be a handful anyway.

    l0rinc commented at 4:41 pm on September 29, 2024:
    Isn’t that what we’d be calculating via std::ranges::count_if(vInv, &CInv::IsGenTxMsg)?

    glozow commented at 10:36 pm on October 1, 2024:
    Ah, sorry for missing that. I’m not really convinced this is more efficient overall?
  171. in src/net_processing.cpp:5894 in a69cdf87f1 outdated
    5915-                    m_txrequest.RequestedTx(pto->GetId(), gtxid.GetHash(), current_time + GETDATA_TX_INTERVAL);
    5916-                } else {
    5917-                    // We have already seen this transaction, no need to download. This is just a belt-and-suspenders, as
    5918-                    // this should already be called whenever a transaction becomes AlreadyHaveTx().
    5919-                    m_txrequest.ForgetTxHash(gtxid.GetHash());
    5920+            for (const GenTxid& gtxid : m_txdownloadman.GetRequestsToSend(pto->GetId(), current_time)) {
    


    l0rinc commented at 12:53 pm on September 22, 2024:

    same, I think we can preallocate these as well:

    0vGetData.reserve(vToDownload.size());
    1for (const CBlockIndex *pindex : vToDownload) {
    

    followed by

    0const auto& tx_requests = m_txdownloadman.GetRequestsToSend(pto->GetId(), current_time);
    1vGetData.reserve(vGetData.size() + tx_requests.size());
    2for (const GenTxid& gtxid : tx_requests) {
    
  172. in src/node/txdownloadman.h:79 in a69cdf87f1 outdated
    76+    // Move assignment
    77+    PackageToValidate& operator=(PackageToValidate&& other) {
    78+        this->m_txns = std::move(other.m_txns);
    79+        this->m_senders = std::move(other.m_senders);
    80+        return *this;
    81+    }
    


    l0rinc commented at 1:00 pm on September 22, 2024:

    We might as well simplify these:

    0    PackageToValidate(PackageToValidate&&) = default;
    1    PackageToValidate(const PackageToValidate&) = default;
    2    PackageToValidate& operator=(PackageToValidate&&) = default;
    

    glozow commented at 0:58 am on September 24, 2024:
    Considering this out of scope as it is being moved, not introduced.

    l0rinc commented at 4:39 pm on September 29, 2024:
    So when would this be in scope?
  173. in src/node/txdownloadman.h:138 in a69cdf87f1 outdated
    116+
    117+    /** Deletes all txrequest announcements and orphans for a given peer. */
    118+    void DisconnectedPeer(NodeId nodeid);
    119+
    120+    /** New inv has been received. May be added as a candidate to txrequest.
    121+     * @param[in] p2p_inv     When true, only add this announcement if we don't already have the tx.
    


    l0rinc commented at 1:01 pm on September 22, 2024:
    nit: formatting seems off

    glozow commented at 2:32 pm on September 29, 2024:
    I think you’re referring to the extra space? That’s done to leave room for other params to be added in the future, it’s more readable.
  174. in src/node/txdownloadman_impl.cpp:157 in a69cdf87f1 outdated
    148+}
    149+
    150+void TxDownloadManagerImpl::ConnectedPeer(NodeId nodeid, const TxDownloadConnectionInfo& info)
    151+{
    152+    // If already connected (shouldn't happen in practice), exit early.
    153+    if (m_peer_info.contains(nodeid)) return;
    


    l0rinc commented at 1:03 pm on September 22, 2024:
    can we log warn/error if it does? Or even better, I assume this is because of testing, can we rather move the check to the tests and assert here instead?

    glozow commented at 1:02 am on September 24, 2024:
    I prefer not to assert. My philosophy is that the interface should be simple/robust, i.e. not have a ton of assumptions that would cause the class to fall over if broken. And yes, it means we can easily fuzz that this is the case.
  175. in src/node/txdownloadman_impl.cpp:171 in a69cdf87f1 outdated
    162+    m_txrequest.DisconnectedPeer(nodeid);
    163+
    164+    if (m_peer_info.contains(nodeid)) {
    165+        if (m_peer_info.at(nodeid).m_connection_info.m_wtxid_relay) m_num_wtxid_peers -= 1;
    166+        m_peer_info.erase(nodeid);
    167+    }
    


    l0rinc commented at 1:13 pm on September 22, 2024:

    We seem to have 3 nodeid loopups here: contains(nodeid), at(nodeid), erase(nodeid). I think we can reduce it to a single iterator lookup:

    0    if (auto it = m_peer_info.find(nodeid); it != m_peer_info.end()) {
    1        if (it->second.m_connection_info.m_wtxid_relay) m_num_wtxid_peers -= 1;
    2        m_peer_info.erase(it);
    3    }
    

    glozow commented at 2:38 pm on September 29, 2024:
    Agree, changed
  176. in src/node/txdownloadman_impl.cpp:178 in a69cdf87f1 outdated
    173+    // If this is a request for the parent of an orphan, we don't drop transactions that we already have. In particular,
    174+    // we *do* want to request parents that are in m_lazy_recent_rejects_reconsiderable, since they can be CPFP'd.
    175+    if (p2p_inv && AlreadyHaveTx(gtxid, /*include_reconsiderable=*/true)) return true;
    176+
    177+    if (m_peer_info.count(peer) == 0) return false;
    178+    const auto& info = m_peer_info.at(peer).m_connection_info;
    


    l0rinc commented at 1:19 pm on September 22, 2024:

    Similarly we can avoid two lookups here:

    0    auto it = m_peer_info.find(peer);
    1    if (it == m_peer_info.end()) return false;
    2    const auto& info = it->second.m_connection_info;
    

    glozow commented at 2:40 pm on September 29, 2024:
    done
  177. in src/node/txdownloadman_impl.cpp:282 in a69cdf87f1 outdated
    270+    std::shuffle(tx_indices.begin(), tx_indices.end(), m_opts.m_rng);
    271+
    272+    for (const auto index : tx_indices) {
    273+        // If we already tried a package and failed for any reason, the combined hash was
    274+        // cached in m_lazy_recent_rejects_reconsiderable.
    275+        const auto [child_tx, child_sender] = cpfp_candidates_different_peer.at(index);
    


    l0rinc commented at 1:43 pm on September 22, 2024:

    What’s the rationale behind shuffling the indices instead of the values? That requires an O(n) copy, and the elements are accessed randomly, which could impact locality. Unless I’m missing something (likely at this stage, that’s why I’m asking), wouldn’t shuffling the values directly achieve the same outcome while avoiding the copy and potentially improving cache locality during iteration?

     0    auto cpfp_candidates_different_peer = m_orphanage.GetChildrenFromDifferentPeer(ptx, nodeid);
     1    // Find the first 1p1c that hasn't already been rejected. We randomize the order to not
     2    // create a bias that attackers can use to delay package acceptance.
     3    //
     4    std::shuffle(cpfp_candidates_different_peer.begin(), cpfp_candidates_different_peer.end(), m_opts.m_rng);
     5    for (const auto& [child_tx, child_sender] : cpfp_candidates_different_peer) {
     6        // If we already tried a package and failed for any reason, the combined hash was
     7        // cached in m_lazy_recent_rejects_reconsiderable.
     8        Package maybe_cpfp_package{ptx, child_tx};
     9        if (!RecentRejectsReconsiderableFilter().contains(GetPackageHash(maybe_cpfp_package))) {
    10            return PackageToValidate{ptx, child_tx, nodeid, child_sender};
    11        }
    12    }
    13    return std::nullopt;
    

    glozow commented at 0:57 am on September 24, 2024:

    Considering this out of scope as it is being moved, not introduced.

    Also, this is intended for deletion in #28031.

  178. in src/node/txdownloadman_impl.cpp:329 in a69cdf87f1 outdated
    316+            for (const CTxIn& txin : tx.vin) {
    317+                // We start with all parents, and then remove duplicates below.
    318+                unique_parents.push_back(txin.prevout.hash);
    319+            }
    320+            std::sort(unique_parents.begin(), unique_parents.end());
    321+            unique_parents.erase(std::unique(unique_parents.begin(), unique_parents.end()), unique_parents.end());
    


    l0rinc commented at 1:56 pm on September 22, 2024:

    sorting + erasing duplicates indicates to me that we may be using the wrong datastructure:

    0    std::set<uint256> unique_parents;
    1...
    2    // Deduplicate parent txids, so that we don't have to loop over
    3    // the same parent txid more than once down below.
    4    for (const CTxIn& txin : tx.vin) {
    5        // We start with all parents, and then remove duplicates below.
    6        unique_parents.insert(txin.prevout.hash);
    7    }
    

    (std::vector seems to have O(n) inserts + O(nlogn) sort + O(n) deduplication, std::set has simply O(nlogn) sorted inserts)


    glozow commented at 0:57 am on September 24, 2024:
    Considering this out of scope as it is being moved, not introduced.
  179. in src/test/txdownload_tests.cpp:176 in a69cdf87f1 outdated
    171+        BOOST_CHECK(!p1_package.has_value());
    172+
    173+        // Child missing inputs, should be added to orphanage.
    174+        const auto& [c_keep, c_unique_txids, c_package] = txdownload_impl.MempoolRejectedTx(ptx_child, state_orphan, nodeid, /*first_time_failure=*/true);
    175+        BOOST_CHECK(c_keep);
    176+        BOOST_CHECK(c_unique_txids == vec_parent_txids);
    


    l0rinc commented at 2:03 pm on September 22, 2024:

    While BOOST_CHECK_EQUAL_COLLECTIONS is more verbose, it produces better failure messages:

    0        BOOST_CHECK_EQUAL_COLLECTIONS(c_unique_txids.begin(), c_unique_txids.end(), vec_parent_txids.begin(), vec_parent_txids.end());
    
  180. in src/test/txdownload_tests.cpp:171 in a69cdf87f1 outdated
    166+
    167+        // Parent reconsiderable failure
    168+        const auto& [p1_keep, p1_unique_txids, p1_package] = txdownload_impl.MempoolRejectedTx(ptx_parent_1, state_reconsiderable, nodeid, /*first_time_failure=*/true);
    169+        BOOST_CHECK(p1_keep);
    170+        BOOST_CHECK(p1_unique_txids.empty());
    171+        BOOST_CHECK(!p1_package.has_value());
    


    l0rinc commented at 2:04 pm on September 22, 2024:
    0        BOOST_CHECK(!p1_package);
    
  181. in src/test/txdownload_tests.cpp:99 in a69cdf87f1 outdated
    65+    {TxValidationResult::TX_NO_MEMPOOL,              {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    66+    {TxValidationResult::TX_RECONSIDERABLE,          {/*txid_rejects*/0, /*wtxid_rejects*/0, /*txid_recon*/0, /*wtxid_recon*/1, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    67+    {TxValidationResult::TX_UNKNOWN,                 {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    68+};
    69+
    70+static std::vector<CTransactionRef> CreateTransactionChain(size_t num_txns, bool segwit)
    


    l0rinc commented at 2:06 pm on September 22, 2024:
    nit: we seem to be always calling this with num_txns == 1

    glozow commented at 9:54 pm on October 1, 2024:
    edited to no longer be for a chain
  182. in src/test/txdownload_tests.cpp:72 in a69cdf87f1 outdated
    62+    {TxValidationResult::TX_WITNESS_STRIPPED,        {/*txid_rejects*/0, /*wtxid_rejects*/0, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/0, /*txid_inv*/0, /*wtxid_inv*/0}},
    63+    {TxValidationResult::TX_CONFLICT,                {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    64+    {TxValidationResult::TX_MEMPOOL_POLICY,          {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    65+    {TxValidationResult::TX_NO_MEMPOOL,              {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    66+    {TxValidationResult::TX_RECONSIDERABLE,          {/*txid_rejects*/0, /*wtxid_rejects*/0, /*txid_recon*/0, /*wtxid_recon*/1, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    67+    {TxValidationResult::TX_UNKNOWN,                 {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    


    l0rinc commented at 2:09 pm on September 22, 2024:

    Alternatively, to avoid repetition and noise (labels AND data) we could group it slightly differently:

     0    //                                                txid_rejects, wtxid_rejects, txid_recon, wtxid_recon, keep, txid_inv, wtxid_inv
     1    {TxValidationResult::TX_CONSENSUS,               {0,            1,             0,          0,           1,    0,        1}},
     2    {TxValidationResult::TX_RECENT_CONSENSUS_CHANGE, {0,            1,             0,          0,           1,    0,        1}},
     3    {TxValidationResult::TX_INPUTS_NOT_STANDARD,     {1,            1,             0,          0,           1,    1,        1}},
     4    {TxValidationResult::TX_NOT_STANDARD,            {0,            1,             0,          0,           1,    0,        1}},
     5    {TxValidationResult::TX_MISSING_INPUTS,          {0,            0,             0,          0,           1,    0,        1}},
     6    {TxValidationResult::TX_PREMATURE_SPEND,         {0,            1,             0,          0,           1,    0,        1}},
     7    {TxValidationResult::TX_WITNESS_MUTATED,         {0,            1,             0,          0,           1,    0,        1}},
     8    {TxValidationResult::TX_WITNESS_STRIPPED,        {0,            0,             0,          0,           0,    0,        0}},
     9    {TxValidationResult::TX_CONFLICT,                {0,            1,             0,          0,           1,    0,        1}},
    10    {TxValidationResult::TX_MEMPOOL_POLICY,          {0,            1,             0,          0,           1,    0,        1}},
    11    {TxValidationResult::TX_NO_MEMPOOL,              {0,            1,             0,          0,           1,    0,        1}},
    12    {TxValidationResult::TX_RECONSIDERABLE,          {0,            0,             0,          1,           1,    0,        1}},
    13    {TxValidationResult::TX_UNKNOWN,                 {0,            1,             0,          0,           1,    0,        1}},
    

    glozow commented at 10:02 pm on October 1, 2024:
    edited somewhat. I like the annotations there but only did first row.
  183. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:512 in a69cdf87f1 outdated
    507+                }
    508+                ++counter;
    509+            }
    510+        }
    511+
    512+        // Skip time by 100-300ms
    


    l0rinc commented at 2:13 pm on September 22, 2024:
    ms or μs?

    glozow commented at 1:06 am on September 24, 2024:
    ms

    l0rinc commented at 4:28 pm on September 29, 2024:
    In that case please consider using std::chrono::milliseconds instead of multiplying micros by 1000 - making the comment redundant.

    glozow commented at 10:26 pm on October 1, 2024:
    done
  184. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:438 in a69cdf87f1 outdated
    434+                        break;
    435+                    }
    436+                }
    437+
    438+                // The peer may also send some unsolicited message. Simulate a ProcessMessage.
    439+                // Ensure the good peer sends the inv within the first 1000 rounds.
    


    l0rinc commented at 2:16 pm on September 22, 2024:
    since counter starts from 0, aren’t we sending within the first 1001 rounds?

    glozow commented at 2:58 pm on September 29, 2024:
    Right, changed
  185. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:327 in a69cdf87f1 outdated
    322+        all_peers.emplace_back(RandomPeer(fuzzed_data_provider, i, /*preferred=*/false, /*force_good=*/good));
    323+    }
    324+
    325+    // Indexes for random peer iteration
    326+    std::vector<size_t> indexes;
    327+    indexes.resize(all_peers.size());
    


    l0rinc commented at 2:21 pm on September 22, 2024:

    we can simplify this to either:

    0std::vector<size_t> indexes(all_peers.size());
    

    or shuffle all_peers directly (unless I’m missing something, as mentioned in a previous comment)


    glozow commented at 10:25 pm on October 1, 2024:
    More efficient to shuffle ints than objects. I’ve changed to use the ctor with the size.
  186. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:250 in a69cdf87f1 outdated
    244+}
    245+
    246+static CTransactionRef GetCorrectTx(const std::map<uint256, std::pair<CTransactionRef, CTransactionRef>> txmap, const GenTxid& gtxid)
    247+{
    248+    auto tx_it = txmap.find(gtxid.GetHash());
    249+    if (tx_it != txmap.end()) {
    


    l0rinc commented at 2:22 pm on September 22, 2024:

    nit: we can narrow the scope of the iterator:

    0    if (auto tx_it = txmap.find(gtxid.GetHash()); tx_it != txmap.end()) {
    

    glozow commented at 3:01 pm on September 29, 2024:
    Scope seems to be about the same? There isn’t anything happening after this section.

    l0rinc commented at 4:29 pm on September 29, 2024:
    Exactly, that’s what we’d be signalling by reducing the scope
  187. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:242 in a69cdf87f1 outdated
    237+
    238+        // The first tx in the pair needs to match by wtxid.
    239+        TRANSACTIONS.emplace(target_wtxid, std::make_pair(correct_tx, malleated_tx));
    240+        TRANSACTIONS.emplace(target_txid, std::make_pair(correct_tx, correct_tx));
    241+
    242+        TRANSACTIONS.emplace(malleated_wtxid, std::make_pair(malleated_tx, stripped_tx));
    


    l0rinc commented at 2:29 pm on September 22, 2024:

    we can modernize these slightly:

    0        TRANSACTIONS.try_emplace(target_wtxid, correct_tx, malleated_tx);
    1        TRANSACTIONS.try_emplace(target_txid, correct_tx, correct_tx);
    2
    3        TRANSACTIONS.try_emplace(malleated_wtxid, malleated_tx, stripped_tx);
    

    glozow commented at 10:23 pm on October 1, 2024:
    Thanks, done
  188. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:303 in a69cdf87f1 outdated
    298+    // Decide the target transaction. Disallow selecting a witness-stripped or non-segwit
    299+    // transaction because it can lead to false negatives. This test may mark a same-txid
    300+    // transaction as invalid, and we don't want that to cause rejection of the target tx.
    301+    const auto& target_tx = GetRandomTx(TRANSACTIONS, fuzzed_data_provider, /*correct_only=*/true);
    302+    Assert(target_tx != nullptr);
    303+    Assert(target_tx->HasWitness());
    


    l0rinc commented at 2:30 pm on September 22, 2024:
    what’s the reason for Assert in tests instead of assert (as both are used here)?

    glozow commented at 10:24 pm on October 1, 2024:
    Unsure, it’s just a habit I guess.
  189. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:274 in a69cdf87f1 outdated
    269+}
    270+static CTransactionRef GetRandomTx(const std::map<uint256, std::pair<CTransactionRef, CTransactionRef>> txmap, FuzzedDataProvider& fuzzed_data_provider, bool correct_only = false)
    271+{
    272+    auto it = txmap.begin();
    273+    // Jump forward a random number of times.
    274+    for (size_t i = 0; i <= fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txmap.size() - 1); ++i) it++;
    


    l0rinc commented at 2:31 pm on September 22, 2024:
    • aren’t we jumping one more time than needed (<=)? This way we can’t select the first value, right?
    • isn’t fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txmap.size() - 1 reevaluated on every iteration? What’s the advantage of that over generating the limit a single time, e.g.:
    0    if (txmap.empty()) return nullptr;
    1    size_t rand_index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txmap.size() - 1);
    2    auto it = std::next(txmap.begin(), rand_index);
    3    return correct_only || fuzzed_data_provider.ConsumeBool() ? it->second.first : it->second.second;
    

    glozow commented at 2:58 pm on September 29, 2024:
    Changed to something like this
  190. in src/test/fuzz/txdownloadman.cpp:85 in a69cdf87f1 outdated
    70+}
    71+void initialize()
    72+{
    73+    static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
    74+    g_setup = testing_setup.get();
    75+    for (uint32_t i = 0; i < uint32_t{NUM_COINS}; ++i) {
    


    l0rinc commented at 2:42 pm on September 22, 2024:
    Could we init it as constexpr uint32_t NUM_COINS{50}; instead?

    glozow commented at 2:48 pm on September 29, 2024:
    see L29, that’s already done?

    l0rinc commented at 4:55 pm on September 29, 2024:
    I was referring to getting rid of the cast, but not important, please resolve these.
  191. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:157 in a69cdf87f1 outdated
    152+            m_getdata_queue.push_back(request);
    153+            m_getdata_queue.pop_front();
    154+            return {PossibleResponse::NOTHING, std::nullopt};
    155+        }
    156+
    157+        m_getdata_queue.pop_front();
    


    l0rinc commented at 3:45 pm on September 22, 2024:

    seems we’re always popping, and the queue is never empty, might as well pop once after we’re getting the front value (and before we’re potentially pushing):

     0// Process up to one message from the front of m_getdata_queue. The size of m_getdata_queue may
     1// not change, as DEFER may just push it to the back again.
     2std::pair<PossibleResponse, std::optional<GenTxid>> ProcessOneMessage(FuzzedDataProvider& fuzzed_data_provider)
     3{
     4    // Nothing to do if no messages to process
     5    if (m_getdata_queue.empty()) return {PossibleResponse::NOTHING, std::nullopt};
     6
     7    auto request{m_getdata_queue.front()};
     8    m_getdata_queue.pop_front();
     9
    10    auto response{m_good ? PossibleResponse::TX_REAL : fuzzed_data_provider.PickValueInArray(NON_GOOD_RESPONSES)};
    11    if (response == PossibleResponse::DEFER) {
    12        // Move the request from the front to the back of the queue and return nothing.
    13        // This simulates responding out of order and/or stalling.
    14        m_getdata_queue.emplace_back(std::move(request));
    15        return {PossibleResponse::NOTHING, std::nullopt};
    16    } else {
    17        return {response, request};
    18    }
    19}
    

    note: changed to emplace_back and used std::move to avoid copies and merged response inits. Also note that deleting before pushing is a tiny bit better memory-wise (but I doubt it matters here)

  192. in src/node/txdownloadman_impl.h:108 in a69cdf87f1 outdated
    103+    }
    104+
    105+    /*
    106+     * Filter for transactions that have been recently confirmed.
    107+     * We use this to avoid requesting transactions that have already been
    108+     * confirnmed.
    


    l0rinc commented at 4:09 pm on September 22, 2024:
    0     * confirmed.
    
  193. in src/node/txdownloadman_impl.cpp:331 in a69cdf87f1 outdated
    318+                unique_parents.push_back(txin.prevout.hash);
    319+            }
    320+            std::sort(unique_parents.begin(), unique_parents.end());
    321+            unique_parents.erase(std::unique(unique_parents.begin(), unique_parents.end()), unique_parents.end());
    322+
    323+            // Distinguish between parents in m_lazy_recent_rejects and m_lazy_recent_rejects_reconsiderable.
    


    l0rinc commented at 4:19 pm on September 22, 2024:
    m_lazy_recent_rejects_reconsiderable is usually accessed through RecentRejectsReconsiderableFilter, might make sense to update the comments

    glozow commented at 0:58 am on September 24, 2024:
    Considering this out of scope as it is being moved, not introduced.

    l0rinc commented at 4:31 pm on September 29, 2024:
    There are many such cases, are none of them related to this change?

    glozow commented at 10:34 pm on October 1, 2024:
    This is part of a block of code that was moved from PeerManagerImpl. The members still have the same name, so I don’t think this is incorrect.
  194. in src/net_processing.cpp:873 in 85edaf2556 outdated
    903-     */
    904-    std::unique_ptr<CRollingBloomFilter> m_lazy_recent_rejects GUARDED_BY(m_tx_download_mutex){nullptr};
    905 
    906     CRollingBloomFilter& RecentRejectsFilter() EXCLUSIVE_LOCKS_REQUIRED(m_tx_download_mutex)
    907     {
    908         AssertLockHeld(m_tx_download_mutex);
    


    l0rinc commented at 4:33 pm on September 22, 2024:
    how come we’re removing all locks here?

    glozow commented at 12:48 pm on October 31, 2024:
    since txdownloadman is locked with this mutex, and it’s the only one with access to these data structures, we have the same locking happening. Since m_tx_download_mutex is external, we wouldn’t be able to have these annotations I think
  195. in src/net_processing.cpp:1600 in 85edaf2556 outdated
    1594@@ -1685,6 +1595,8 @@ void PeerManagerImpl::AddTxAnnouncement(const CNode& node, const GenTxid& gtxid,
    1595     AssertLockHeld(::cs_main); // for State
    1596     AssertLockHeld(m_tx_download_mutex); // For m_txrequest
    1597     NodeId nodeid = node.GetId();
    1598+
    1599+    auto& m_txrequest = m_txdownloadman.GetTxRequestRef();
    1600     if (!node.HasPermission(NetPermissionFlags::Relay) && m_txrequest.Count(nodeid) >= MAX_PEER_TX_ANNOUNCEMENTS) {
    


    l0rinc commented at 4:50 pm on September 22, 2024:
    what’s the reason for changing m_txrequest.Count(nodeid) to m_txdownloadman.GetTxRequestRef().Count in the init but not here (85edaf2556c9ce531573dc3dee8fff2f7b4d9abf)
  196. glozow force-pushed on Sep 22, 2024
  197. glozow force-pushed on Sep 22, 2024
  198. DrahtBot added the label CI failed on Sep 22, 2024
  199. DrahtBot commented at 10:23 pm on September 22, 2024: contributor

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

    Make sure to run all tests locally, according to the documentation.

    The failure may happen due to a number of reasons, for example:

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

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

    • An intermittent issue.

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

  200. DrahtBot removed the label CI failed on Sep 22, 2024
  201. in src/node/txdownloadman.h:89 in b543dc083d outdated
    86+                         m_txns.front()->GetHash().ToString(),
    87+                         m_txns.front()->GetWitnessHash().ToString(),
    88+                         m_senders.front(),
    89+                         m_txns.back()->GetHash().ToString(),
    90+                         m_txns.back()->GetWitnessHash().ToString(),
    91+                         m_senders.back());
    


    l0rinc commented at 9:18 am on September 23, 2024:

    nit: we could avoid the repetition via a lambda:

    0        auto format_tx{[&](const CTransactionRef& tx, NodeId sender) {
    1            return strprintf("%s (wtxid=%s, sender=%d)", tx->GetHash().ToString(), tx->GetWitnessHash().ToString(), sender);
    2        }};
    3        return strprintf("parent %s + child %s", format_tx(m_txns.front(), m_senders.front()), format_tx(m_txns.back(), m_senders.back()));
    

    glozow commented at 0:58 am on September 24, 2024:
    Considering this out of scope as it is being moved, not introduced.
  202. in src/node/txdownloadman_impl.cpp:155 in b543dc083d outdated
    150+void TxDownloadManagerImpl::ConnectedPeer(NodeId nodeid, const TxDownloadConnectionInfo& info)
    151+{
    152+    // If already connected (shouldn't happen in practice), exit early.
    153+    if (m_peer_info.contains(nodeid)) return;
    154+
    155+    m_peer_info.emplace(nodeid, PeerInfo(info));
    


    l0rinc commented at 9:22 am on September 23, 2024:

    Can be modernized to:

    0    m_peer_info.try_emplace(nodeid, info);
    

    glozow commented at 2:36 pm on September 29, 2024:
    Sure, done
  203. in src/net_processing.cpp:3939 in b543dc083d outdated
    3941-                if (!fAlreadyHave && !m_chainman.IsInitialBlockDownload()) {
    3942-                    AddTxAnnouncement(pfrom, gtxid, current_time);
    3943+
    3944+                if (!m_chainman.IsInitialBlockDownload()) {
    3945+                    const bool fAlreadyHave{m_txdownloadman.AddTxAnnouncement(pfrom.GetId(), gtxid, current_time, /*p2p_inv=*/true)};
    3946+                    LogDebug(BCLog::NET, "got inv: %s  %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
    


    l0rinc commented at 9:27 am on September 23, 2024:

    super-nit:

    0                    LogDebug(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
    

    glozow commented at 0:42 am on September 24, 2024:
    The string isn’t changed in this PR, marking as resolved. Can be done in a followup.
  204. in src/net_processing.cpp:4998 in b543dc083d outdated
    4998@@ -5449,7 +4999,7 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
    4999         //  the extra work may not be noticed, possibly resulting in an
    5000         //  unnecessary 100ms delay)
    5001         LOCK(m_tx_download_mutex);
    5002-        if (m_orphanage.HaveTxToReconsider(peer->m_id)) fMoreWork = true;
    5003+        if (m_txdownloadman.HaveMoreWork(peer->m_id)) fMoreWork = true;
    


    l0rinc commented at 9:34 am on September 23, 2024:

    do we need to call the const HaveMoreWork when fMoreWork was already true? If not, we may even avoid the the lock:

    0        if (!fMoreWork) {
    1            LOCK(m_tx_download_mutex);
    2            fMoreWork = m_txdownloadman.HaveMoreWork(peer->m_id);
    3        }
    
  205. in src/node/txdownloadman_impl.cpp:80 in b543dc083d outdated
    75+{
    76+    return m_impl->GetTxToReconsider(nodeid);
    77+}
    78+void TxDownloadManager::CheckIsEmpty() const
    79+{
    80+    return m_impl->CheckIsEmpty();
    


    l0rinc commented at 9:42 am on September 23, 2024:
    is it deliberate to return in a void method here?

    glozow commented at 2:33 pm on September 29, 2024:
    Nope - fixed, thanks
  206. in src/node/txdownloadman_impl.cpp:151 in b543dc083d outdated
    142+
    143+    if (include_reconsiderable && RecentRejectsReconsiderableFilter().contains(hash)) return true;
    144+
    145+    if (RecentConfirmedTransactionsFilter().contains(hash)) return true;
    146+
    147+    return RecentRejectsFilter().contains(hash) || m_opts.m_mempool.exists(gtxid);
    


    l0rinc commented at 9:45 am on September 23, 2024:

    Seems to me we could simplify to a single return:

    0    return (include_reconsiderable && RecentRejectsReconsiderableFilter().contains(hash)) 
    1           || RecentConfirmedTransactionsFilter().contains(hash) 
    2           || RecentRejectsFilter().contains(hash) 
    3           || m_opts.m_mempool.exists(gtxid);
    

    glozow commented at 1:00 am on September 24, 2024:
    Considering this out of scope as it is being moved, not introduced.
  207. in src/node/txdownloadman_impl.cpp:507 in b543dc083d outdated
    502+                 txid.ToString(), wtxid.ToString());
    503+        return std::make_pair(false, Find1P1CPackage(ptx, nodeid));
    504+    }
    505+
    506+
    507+    return std::make_pair(true, std::nullopt);
    


    l0rinc commented at 9:49 am on September 23, 2024:

    we should be able to modernize these:

    0        return {false, Find1P1CPackage(ptx, nodeid)};
    1    }
    2
    3
    4    return {true, std::nullopt};
    

    glozow commented at 2:45 pm on September 29, 2024:
    done
  208. in src/node/txdownloadman_impl.h:61 in b543dc083d outdated
    56+     * validation no matter the witness, we may add the txid of such
    57+     * transaction to the filter as well. This can be helpful when
    58+     * communicating with txid-relay peers or if we were to otherwise fetch a
    59+     * transaction via txid (eg in our orphan handling).
    60+     *
    61+     * Memory used: 1.3 MB
    


    l0rinc commented at 9:50 am on September 23, 2024:
    do we know if this is still the case?

    glozow commented at 2:43 pm on September 29, 2024:
    Good question. See the comment on src/common/bloom.h for the usage formula, AFAIK it has not changed.
  209. in src/test/fuzz/txdownloadman.cpp:395 in b543dc083d outdated
    390+            [&] {
    391+                txdownload_impl.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()});
    392+            },
    393+            [&] {
    394+                const bool expect_work{txdownload_impl.HaveMoreWork(rand_peer)};
    395+                const auto ptx = txdownload_impl.GetTxToReconsider(rand_peer);
    


    l0rinc commented at 9:53 am on September 23, 2024:
    nit: previous line used brace initialization, this one uses assignment

    glozow commented at 2:51 pm on September 29, 2024:
    changed
  210. in src/test/txdownload_tests.cpp:81 in b543dc083d outdated
    76+                                bool expect_orphan, bool expect_keep, unsigned int expected_parents)
    77+{
    78+    // Missing inputs can never result in a PackageToValidate.
    79+    if (ret.m_package_to_validate.has_value()) {
    80+        err_msg = strprintf("returned a PackageToValidate on missing inputs");
    81+        return false;
    


    l0rinc commented at 9:56 am on September 23, 2024:
    so we’re setting an input param and returning whether we’ve modified it, right? Could we modernize it to return an std::optional instead?

    glozow commented at 3:00 pm on September 29, 2024:
    Yeah this is so we can have BOOST_CHECK_MESSAGE easily check and print the error. IIRC I had trouble getting the std::optional way to work.

    l0rinc commented at 4:33 pm on September 29, 2024:
  211. in src/node/txdownloadman.h:25 in b543dc083d outdated
    22+namespace node {
    23+class TxDownloadManagerImpl;
    24+
    25+/** Maximum number of in-flight transaction requests from a peer. It is not a hard limit, but the threshold at which
    26+ *  point the OVERLOADED_PEER_TX_DELAY kicks in. */
    27+static constexpr int32_t MAX_PEER_TX_REQUEST_IN_FLIGHT = 100;
    


    l0rinc commented at 10:06 am on September 23, 2024:
    nit: brace inits vs assignments

    glozow commented at 0:58 am on September 24, 2024:
    Considering this out of scope as it is being moved, not introduced.
  212. in src/node/txdownloadman_impl.cpp:91 in b543dc083d outdated
    82+void TxDownloadManager::CheckIsEmpty(NodeId nodeid) const
    83+{
    84+    return m_impl->CheckIsEmpty(nodeid);
    85+}
    86+
    87+// TxDownloadManagerImpl
    


    l0rinc commented at 10:11 am on September 23, 2024:
    what’s the role of this comment, is it just a delimiter?

    glozow commented at 2:33 pm on September 29, 2024:
    Yes
  213. in src/node/txdownloadman_impl.cpp:145 in b543dc083d outdated
    136+        //
    137+        // While we won't query by txid, we can try to "guess" what the wtxid is based on the txid.
    138+        // A non-segwit transaction's txid == wtxid. Query this txid "casted" to a wtxid. This will
    139+        // help us find non-segwit transactions, saving bandwidth, and should have no false positives.
    140+        if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true;
    141+    }
    


    l0rinc commented at 10:16 am on September 23, 2024:

    I don’t understand the purpose of this, basically if orphanage has the hash transaction, we’re returning true, regardless of whether IsWtxid or not.

    0    if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true;
    

    glozow commented at 1:00 am on September 24, 2024:
    Considering this out of scope as it is being moved, not introduced.

    glozow commented at 2:34 pm on September 29, 2024:
    You may find #30000 discussion interesting
  214. l0rinc commented at 10:55 am on September 23, 2024: contributor

    Concept ACK

    I went over the code a few times and tried to document my immediate observations. I understand if you think some comments are outside the scope of this PR - I’ll provide a higher-level review later, once I understand the problem better. I still need a few iterations to be able to zoom out and comment meaningfully, for now I only left nits about implementation specifics, modernization attempts, potential off-by-one errors, readability suggestions, etc.

      0Index: src/node/txdownloadman_impl.h
      1<+>UTF-8
      2===================================================================
      3diff --git a/src/node/txdownloadman_impl.h b/src/node/txdownloadman_impl.h
      4--- a/src/node/txdownloadman_impl.h	(revision b543dc083ddbf43bb172b60ddb90609a600c86b5)
      5+++ b/src/node/txdownloadman_impl.h	(date 1727088265499)
      6@@ -10,14 +10,15 @@
      7 #include <consensus/validation.h>
      8 #include <kernel/chain.h>
      9 #include <net.h>
     10-#include <primitives/transaction.h>
     11 #include <policy/packages.h>
     12+#include <primitives/transaction.h>
     13 #include <txorphanage.h>
     14 #include <txrequest.h>
     15 
     16 class CTxMemPool;
     17 namespace node {
     18-class TxDownloadManagerImpl {
     19+class TxDownloadManagerImpl
     20+{
     21 public:
     22     TxDownloadOptions m_opts;
     23 
     24@@ -105,7 +106,7 @@
     25     /*
     26      * Filter for transactions that have been recently confirmed.
     27      * We use this to avoid requesting transactions that have already been
     28-     * confirnmed.
     29+     * confirmed.
     30      *
     31      * Blocks don't typically have more than 4000 transactions, so this should
     32      * be at least six blocks (~1 hr) worth of transactions that we can store,
     33Index: src/node/txdownloadman_impl.cpp
     34<+>UTF-8
     35===================================================================
     36diff --git a/src/node/txdownloadman_impl.cpp b/src/node/txdownloadman_impl.cpp
     37--- a/src/node/txdownloadman_impl.cpp	(revision b543dc083ddbf43bb172b60ddb90609a600c86b5)
     38+++ b/src/node/txdownloadman_impl.cpp	(date 1727088443418)
     39@@ -2,8 +2,8 @@
     40 // Distributed under the MIT software license, see the accompanying
     41 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
     42 
     43-#include <node/txdownloadman_impl.h>
     44 #include <node/txdownloadman.h>
     45+#include <node/txdownloadman_impl.h>
     46 
     47 #include <chain.h>
     48 #include <consensus/validation.h>
     49@@ -84,7 +84,8 @@
     50     return m_impl->CheckIsEmpty(nodeid);
     51 }
     52 
     53-// TxDownloadManagerImpl
     54+// ---------
     55+
     56 void TxDownloadManagerImpl::ActiveTipChange()
     57 {
     58     RecentRejectsFilter().reset();
     59@@ -121,30 +122,9 @@
     60 bool TxDownloadManagerImpl::AlreadyHaveTx(const GenTxid& gtxid, bool include_reconsiderable)
     61 {
     62     const uint256& hash = gtxid.GetHash();
     63-
     64-    if (gtxid.IsWtxid()) {
     65-        // Normal query by wtxid.
     66-        if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true;
     67-    } else {
     68-        // Never query by txid: it is possible that the transaction in the orphanage has the same
     69-        // txid but a different witness, which would give us a false positive result. If we decided
     70-        // not to request the transaction based on this result, an attacker could prevent us from
     71-        // downloading a transaction by intentionally creating a malleated version of it.  While
     72-        // only one (or none!) of these transactions can ultimately be confirmed, we have no way of
     73-        // discerning which one that is, so the orphanage can store multiple transactions with the
     74-        // same txid.
     75-        //
     76-        // While we won't query by txid, we can try to "guess" what the wtxid is based on the txid.
     77-        // A non-segwit transaction's txid == wtxid. Query this txid "casted" to a wtxid. This will
     78-        // help us find non-segwit transactions, saving bandwidth, and should have no false positives.
     79-        if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true;
     80-    }
     81-
     82-    if (include_reconsiderable && RecentRejectsReconsiderableFilter().contains(hash)) return true;
     83+    if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true;
     84 
     85-    if (RecentConfirmedTransactionsFilter().contains(hash)) return true;
     86-
     87-    return RecentRejectsFilter().contains(hash) || m_opts.m_mempool.exists(gtxid);
     88+    return (include_reconsiderable && RecentRejectsReconsiderableFilter().contains(hash)) || RecentConfirmedTransactionsFilter().contains(hash) || RecentRejectsFilter().contains(hash) || m_opts.m_mempool.exists(gtxid);
     89 }
     90 
     91 void TxDownloadManagerImpl::ConnectedPeer(NodeId nodeid, const TxDownloadConnectionInfo& info)
     92@@ -152,7 +132,7 @@
     93     // If already connected (shouldn't happen in practice), exit early.
     94     if (m_peer_info.contains(nodeid)) return;
     95 
     96-    m_peer_info.emplace(nodeid, PeerInfo(info));
     97+    m_peer_info.try_emplace(nodeid, info);
     98     if (info.m_wtxid_relay) m_num_wtxid_peers += 1;
     99 }
    100 
    101@@ -161,9 +141,9 @@
    102     m_orphanage.EraseForPeer(nodeid);
    103     m_txrequest.DisconnectedPeer(nodeid);
    104 
    105-    if (m_peer_info.contains(nodeid)) {
    106-        if (m_peer_info.at(nodeid).m_connection_info.m_wtxid_relay) m_num_wtxid_peers -= 1;
    107-        m_peer_info.erase(nodeid);
    108+    if (auto it = m_peer_info.find(nodeid); it != m_peer_info.end()) {
    109+        if (it->second.m_connection_info.m_wtxid_relay) m_num_wtxid_peers -= 1;
    110+        m_peer_info.erase(it);
    111     }
    112 }
    113 
    114@@ -171,11 +151,12 @@
    115 {
    116     // If this is an inv received from a peer and we already have it, we can drop it.
    117     // If this is a request for the parent of an orphan, we don't drop transactions that we already have. In particular,
    118-    // we *do* want to request parents that are in m_lazy_recent_rejects_reconsiderable, since they can be CPFP'd.
    119+    // we *do* want to request parents that are in RecentRejectsReconsiderableFilter, since they can be CPFP'd.
    120     if (p2p_inv && AlreadyHaveTx(gtxid, /*include_reconsiderable=*/true)) return true;
    121 
    122-    if (m_peer_info.count(peer) == 0) return false;
    123-    const auto& info = m_peer_info.at(peer).m_connection_info;
    124+    auto it = m_peer_info.find(peer);
    125+    if (it == m_peer_info.end()) return false;
    126+    const auto& info = it->second.m_connection_info;
    127     if (!info.m_relay_permissions && m_txrequest.Count(peer) >= MAX_PEER_TX_ANNOUNCEMENTS) {
    128         // Too many queued announcements for this peer
    129         return false;
    130@@ -259,20 +240,14 @@
    131     //
    132     // If we start tracking all announcers of orphans, we can restrict this logic to parent + child
    133     // pairs in which both were provided by the same peer, i.e. delete this step.
    134-    const auto cpfp_candidates_different_peer{m_orphanage.GetChildrenFromDifferentPeer(ptx, nodeid)};
    135-
    136+    auto cpfp_candidates_different_peer = m_orphanage.GetChildrenFromDifferentPeer(ptx, nodeid);
    137     // Find the first 1p1c that hasn't already been rejected. We randomize the order to not
    138     // create a bias that attackers can use to delay package acceptance.
    139     //
    140-    // Create a random permutation of the indices.
    141-    std::vector<size_t> tx_indices(cpfp_candidates_different_peer.size());
    142-    std::iota(tx_indices.begin(), tx_indices.end(), 0);
    143-    std::shuffle(tx_indices.begin(), tx_indices.end(), m_opts.m_rng);
    144-
    145-    for (const auto index : tx_indices) {
    146+    std::shuffle(cpfp_candidates_different_peer.begin(), cpfp_candidates_different_peer.end(), m_opts.m_rng);
    147+    for (const auto& [child_tx, child_sender] : cpfp_candidates_different_peer) {
    148         // If we already tried a package and failed for any reason, the combined hash was
    149-        // cached in m_lazy_recent_rejects_reconsiderable.
    150-        const auto [child_tx, child_sender] = cpfp_candidates_different_peer.at(index);
    151+        // cached in RecentRejectsReconsiderableFilter.
    152         Package maybe_cpfp_package{ptx, child_tx};
    153         if (!RecentRejectsReconsiderableFilter().contains(GetPackageHash(maybe_cpfp_package))) {
    154             return PackageToValidate{ptx, child_tx, nodeid, child_sender};
    155@@ -300,7 +275,7 @@
    156     // Whether we should call AddToCompactExtraTransactions at the end
    157     bool add_extra_compact_tx{first_time_failure};
    158     // Hashes to pass to AddKnownTx later
    159-    std::vector<uint256> unique_parents;
    160+    std::set<uint256> unique_parents;
    161     // Populated if failure is reconsiderable and eligible package is found.
    162     std::optional<node::PackageToValidate> package_to_validate;
    163 
    164@@ -312,16 +287,13 @@
    165 
    166             // Deduplicate parent txids, so that we don't have to loop over
    167             // the same parent txid more than once down below.
    168-            unique_parents.reserve(tx.vin.size());
    169             for (const CTxIn& txin : tx.vin) {
    170                 // We start with all parents, and then remove duplicates below.
    171-                unique_parents.push_back(txin.prevout.hash);
    172+                unique_parents.insert(txin.prevout.hash);
    173             }
    174-            std::sort(unique_parents.begin(), unique_parents.end());
    175-            unique_parents.erase(std::unique(unique_parents.begin(), unique_parents.end()), unique_parents.end());
    176 
    177-            // Distinguish between parents in m_lazy_recent_rejects and m_lazy_recent_rejects_reconsiderable.
    178-            // We can tolerate having up to 1 parent in m_lazy_recent_rejects_reconsiderable since we
    179+            // Distinguish between parents in m_lazy_recent_rejects and RecentRejectsReconsiderableFilter.
    180+            // We can tolerate having up to 1 parent in RecentRejectsReconsiderableFilter since we
    181             // submit 1p1c packages. However, fail immediately if any are in m_lazy_recent_rejects.
    182             std::optional<uint256> rejected_parent_reconsiderable;
    183             for (const uint256& parent_txid : unique_parents) {
    184@@ -330,7 +302,7 @@
    185                     break;
    186                 } else if (RecentRejectsReconsiderableFilter().contains(parent_txid) &&
    187                            !m_opts.m_mempool.exists(GenTxid::Txid(parent_txid))) {
    188-                    // More than 1 parent in m_lazy_recent_rejects_reconsiderable: 1p1c will not be
    189+                    // More than 1 parent in RecentRejectsReconsiderableFilter: 1p1c will not be
    190                     // sufficient to accept this package, so just give up here.
    191                     if (rejected_parent_reconsiderable.has_value()) {
    192                         fRejectedParents = true;
    193@@ -349,7 +321,7 @@
    194                     // Eventually we should replace this with an improved
    195                     // protocol for getting all unconfirmed parents.
    196                     const auto gtxid{GenTxid::Txid(parent_txid)};
    197-                    // Exclude m_lazy_recent_rejects_reconsiderable: the missing parent may have been
    198+                    // Exclude RecentRejectsReconsiderableFilter: the missing parent may have been
    199                     // previously rejected for being too low feerate. This orphan might CPFP it.
    200                     if (!AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) {
    201                         AddTxAnnouncement(nodeid, gtxid, current_time, /*p2p_inv=*/false);
    202@@ -405,7 +377,7 @@
    203         // for concerns around weakening security of unupgraded nodes
    204         // if we start doing this too early.
    205         if (state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) {
    206-            // If the result is TX_RECONSIDERABLE, add it to m_lazy_recent_rejects_reconsiderable
    207+            // If the result is TX_RECONSIDERABLE, add it to RecentRejectsReconsiderableFilter
    208             // because we should not download or submit this transaction by itself again, but may
    209             // submit it as part of a package later.
    210             RecentRejectsReconsiderableFilter().insert(ptx->GetWitnessHash().ToUint256());
    211@@ -493,18 +465,18 @@
    212         // due to node policy (vs. consensus). So we can't blanket penalize a
    213         // peer simply for relaying a tx that our m_lazy_recent_rejects has caught,
    214         // regardless of false positives.
    215-        return std::make_pair(false, std::nullopt);
    216+        return {false, std::nullopt};
    217     } else if (RecentRejectsReconsiderableFilter().contains(wtxid)) {
    218-        // When a transaction is already in m_lazy_recent_rejects_reconsiderable, we shouldn't submit
    219+        // When a transaction is already in RecentRejectsReconsiderableFilter, we shouldn't submit
    220         // it by itself again. However, look for a matching child in the orphanage, as it is
    221         // possible that they succeed as a package.
    222         LogDebug(BCLog::TXPACKAGES, "found tx %s (wtxid=%s) in reconsiderable rejects, looking for child in orphanage\n",
    223                  txid.ToString(), wtxid.ToString());
    224-        return std::make_pair(false, Find1P1CPackage(ptx, nodeid));
    225+        return {false, Find1P1CPackage(ptx, nodeid)};
    226     }
    227 
    228 
    229-    return std::make_pair(true, std::nullopt);
    230+    return {true, std::nullopt};
    231 }
    232 
    233 bool TxDownloadManagerImpl::HaveMoreWork(NodeId nodeid)
    234Index: src/node/txdownloadman.h
    235<+>UTF-8
    236===================================================================
    237diff --git a/src/node/txdownloadman.h b/src/node/txdownloadman.h
    238--- a/src/node/txdownloadman.h	(revision b543dc083ddbf43bb172b60ddb90609a600c86b5)
    239+++ b/src/node/txdownloadman.h	(date 1727088479738)
    240@@ -24,12 +24,12 @@
    241 
    242 /** Maximum number of in-flight transaction requests from a peer. It is not a hard limit, but the threshold at which
    243  *  point the OVERLOADED_PEER_TX_DELAY kicks in. */
    244-static constexpr int32_t MAX_PEER_TX_REQUEST_IN_FLIGHT = 100;
    245+static constexpr int32_t MAX_PEER_TX_REQUEST_IN_FLIGHT{100};
    246 /** Maximum number of transactions to consider for requesting, per peer. It provides a reasonable DoS limit to
    247  *  per-peer memory usage spent on announcements, while covering peers continuously sending INVs at the maximum
    248  *  rate (by our own policy, see INVENTORY_BROADCAST_PER_SECOND) for several minutes, while not receiving
    249  *  the actual transaction (from any peer) in response to requests for them. */
    250-static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 5000;
    251+static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS{5000};
    252 /** How long to delay requesting transactions via txids, if we have wtxid-relaying peers */
    253 static constexpr auto TXID_RELAY_DELAY{2s};
    254 /** How long to delay requesting transactions from non-preferred peers */
    255@@ -68,38 +68,28 @@
    256         m_senders{parent_sender, child_sender}
    257     {}
    258 
    259-    // Move ctor
    260-    PackageToValidate(PackageToValidate&& other) : m_txns{std::move(other.m_txns)}, m_senders{std::move(other.m_senders)} {}
    261-    // Copy ctor
    262-    PackageToValidate(const PackageToValidate& other) = default;
    263+    PackageToValidate(PackageToValidate&&) = default;
    264+    PackageToValidate(const PackageToValidate&) = default;
    265+    PackageToValidate& operator=(PackageToValidate&&) = default;
    266 
    267-    // Move assignment
    268-    PackageToValidate& operator=(PackageToValidate&& other) {
    269-        this->m_txns = std::move(other.m_txns);
    270-        this->m_senders = std::move(other.m_senders);
    271-        return *this;
    272-    }
    273-
    274-    std::string ToString() const {
    275+    std::string ToString() const
    276+    {
    277         Assume(m_txns.size() == 2);
    278-        return strprintf("parent %s (wtxid=%s, sender=%d) + child %s (wtxid=%s, sender=%d)",
    279-                         m_txns.front()->GetHash().ToString(),
    280-                         m_txns.front()->GetWitnessHash().ToString(),
    281-                         m_senders.front(),
    282-                         m_txns.back()->GetHash().ToString(),
    283-                         m_txns.back()->GetWitnessHash().ToString(),
    284-                         m_senders.back());
    285+        auto format_tx{[&](const CTransactionRef& tx, NodeId sender) {
    286+            return strprintf("%s (wtxid=%s, sender=%d)", tx->GetHash().ToString(), tx->GetWitnessHash().ToString(), sender);
    287+        }};
    288+        return strprintf("parent %s + child %s", format_tx(m_txns.front(), m_senders.front()), format_tx(m_txns.back(), m_senders.back()));
    289     }
    290 };
    291-struct RejectedTxTodo
    292-{
    293+struct RejectedTxTodo {
    294     bool m_should_add_extra_compact_tx;
    295-    std::vector<uint256> m_unique_parents;
    296+    std::set<uint256> m_unique_parents;
    297     std::optional<PackageToValidate> m_package_to_validate;
    298 };
    299 
    300 
    301-class TxDownloadManager {
    302+class TxDownloadManager
    303+{
    304     const std::unique_ptr<TxDownloadManagerImpl> m_impl;
    305 
    306 public:
    307@@ -118,7 +108,7 @@
    308     void DisconnectedPeer(NodeId nodeid);
    309 
    310     /** New inv has been received. May be added as a candidate to txrequest.
    311-     * [@param](/bitcoin-bitcoin/contributor/param/)[in] p2p_inv     When true, only add this announcement if we don't already have the tx.
    312+     * [@param](/bitcoin-bitcoin/contributor/param/)[in] p2p_inv When true, only add this announcement if we don't already have the tx.
    313      * Returns true if this was a dropped inv (p2p_inv=true and we already have the tx), false otherwise. */
    314     bool AddTxAnnouncement(NodeId peer, const GenTxid& gtxid, std::chrono::microseconds now, bool p2p_inv);
    315 
    316@@ -153,7 +143,6 @@
    317 
    318     /** Check that all data structures that track per-peer information have nothing for this peer. */
    319     void CheckIsEmpty(NodeId nodeid) const;
    320-
    321 };
    322 } // namespace node
    323 #endif // BITCOIN_NODE_TXDOWNLOADMAN_H
    324Index: src/test/fuzz/txdownloadman_one_honest_peer.cpp
    325<+>UTF-8
    326===================================================================
    327diff --git a/src/test/fuzz/txdownloadman_one_honest_peer.cpp b/src/test/fuzz/txdownloadman_one_honest_peer.cpp
    328--- a/src/test/fuzz/txdownloadman_one_honest_peer.cpp	(revision b543dc083ddbf43bb172b60ddb90609a600c86b5)
    329+++ b/src/test/fuzz/txdownloadman_one_honest_peer.cpp	(date 1727088723398)
    330@@ -16,9 +16,9 @@
    331 #include <test/util/script.h>
    332 #include <test/util/setup_common.h>
    333 #include <test/util/txmempool.h>
    334+#include <txmempool.h>
    335 #include <util/hasher.h>
    336 #include <util/rbf.h>
    337-#include <txmempool.h>
    338 #include <validation.h>
    339 #include <validationinterface.h>
    340 
    341@@ -81,7 +81,8 @@
    342     PossibleResponse::DEFER,
    343 };
    344 
    345-class TestPeer {
    346+class TestPeer
    347+{
    348 public:
    349     // Queue of getdatas to respond to: process from front, add to back.
    350     std::deque<GenTxid> m_getdata_queue;
    351@@ -118,7 +119,8 @@
    352     // to announce the target transaction. Updates m_sent_inv if this happens.
    353     // When force_announce=true, if this is a "good" peer and the transaction hasn't already been
    354     // announced, always return INV_REAL.
    355-    PossibleMessage SendOneMessage(FuzzedDataProvider& fuzzed_data_provider, bool force_announce = false) {
    356+    PossibleMessage SendOneMessage(FuzzedDataProvider& fuzzed_data_provider, bool force_announce = false)
    357+    {
    358         if (m_good) {
    359             // "Good" peers only send 1 unsolicited message: announcement of the tx.
    360             if (!m_sent_inv) {
    361@@ -138,24 +140,23 @@
    362 
    363     // Process up to one message from the front of m_getdata_queue. The size of m_getdata_queue may
    364     // not change, as DEFER may just push it to the back again.
    365-    std::pair<PossibleResponse, std::optional<GenTxid>> ProcessOneMessage(FuzzedDataProvider& fuzzed_data_provider) {
    366+    std::pair<PossibleResponse, std::optional<GenTxid>> ProcessOneMessage(FuzzedDataProvider& fuzzed_data_provider)
    367+    {
    368         // Nothing to do if no messages to process
    369         if (m_getdata_queue.empty()) return {PossibleResponse::NOTHING, std::nullopt};
    370 
    371-        auto request = m_getdata_queue.front();
    372-        auto response{PossibleResponse::TX_REAL};
    373-        if (!m_good) response = fuzzed_data_provider.PickValueInArray(NON_GOOD_RESPONSES);
    374-
    375-        // If DEFER, pop the request to the back of the queue and return nothing.
    376-        // This simulates responding out of order and/or stalling.
    377-        if (response == PossibleResponse::DEFER) {
    378-            m_getdata_queue.push_back(request);
    379-            m_getdata_queue.pop_front();
    380+        auto request{m_getdata_queue.front()};
    381+        m_getdata_queue.pop_front();
    382+
    383+        auto response{m_good ? PossibleResponse::TX_REAL : fuzzed_data_provider.PickValueInArray(NON_GOOD_RESPONSES)};
    384+        if (response == PossibleResponse::DEFER) {
    385+            // Move the request from the front to the back of the queue and return nothing.
    386+            // This simulates responding out of order and/or stalling.
    387+            m_getdata_queue.emplace_back(std::move(request));
    388             return {PossibleResponse::NOTHING, std::nullopt};
    389-        }
    390-
    391-        m_getdata_queue.pop_front();
    392-        return {response, request};
    393+        } else {
    394+            return {response, request};
    395+        }
    396     }
    397 };
    398 
    399@@ -236,43 +237,39 @@
    400         const auto& malleated_wtxid = malleated_tx->GetWitnessHash().ToUint256();
    401 
    402         // The first tx in the pair needs to match by wtxid.
    403-        TRANSACTIONS.emplace(target_wtxid, std::make_pair(correct_tx, malleated_tx));
    404-        TRANSACTIONS.emplace(target_txid, std::make_pair(correct_tx, correct_tx));
    405+        TRANSACTIONS.try_emplace(target_wtxid, correct_tx, malleated_tx);
    406+        TRANSACTIONS.try_emplace(target_txid, correct_tx, correct_tx);
    407 
    408-        TRANSACTIONS.emplace(malleated_wtxid, std::make_pair(malleated_tx, stripped_tx));
    409+        TRANSACTIONS.try_emplace(malleated_wtxid, malleated_tx, stripped_tx);
    410     }
    411 }
    412 
    413 static CTransactionRef GetCorrectTx(const std::map<uint256, std::pair<CTransactionRef, CTransactionRef>> txmap, const GenTxid& gtxid)
    414 {
    415-    auto tx_it = txmap.find(gtxid.GetHash());
    416-    if (tx_it != txmap.end()) {
    417+    if (auto tx_it = txmap.find(gtxid.GetHash()); tx_it != txmap.end()) {
    418         return tx_it->second.first;
    419     }
    420     return nullptr;
    421 }
    422 static CTransactionRef GetStrippedTx(const std::map<uint256, std::pair<CTransactionRef, CTransactionRef>> txmap, const GenTxid& gtxid)
    423 {
    424-    auto tx_it = txmap.find(gtxid.GetHash());
    425-    if (tx_it != txmap.end()) {
    426+    if (auto tx_it = txmap.find(gtxid.GetHash()); tx_it != txmap.end()) {
    427         return StripWitness(tx_it->second.first);
    428     }
    429     return nullptr;
    430 }
    431 static CTransactionRef GetMalleatedTx(const std::map<uint256, std::pair<CTransactionRef, CTransactionRef>> txmap, const GenTxid& gtxid)
    432 {
    433-    auto tx_it = txmap.find(gtxid.GetHash());
    434-    if (tx_it != txmap.end()) {
    435+    if (auto tx_it = txmap.find(gtxid.GetHash()); tx_it != txmap.end()) {
    436         return tx_it->second.second;
    437     }
    438     return nullptr;
    439 }
    440 static CTransactionRef GetRandomTx(const std::map<uint256, std::pair<CTransactionRef, CTransactionRef>> txmap, FuzzedDataProvider& fuzzed_data_provider, bool correct_only = false)
    441 {
    442-    auto it = txmap.begin();
    443-    // Jump forward a random number of times.
    444-    for (size_t i = 0; i <= fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txmap.size() - 1); ++i) it++;
    445-    if (it == txmap.end()) return nullptr;
    446+    if (txmap.empty()) return nullptr;
    447+    size_t rand_index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txmap.size() - 1);
    448+    auto it = std::next(txmap.begin(), rand_index);
    449     return correct_only || fuzzed_data_provider.ConsumeBool() ? it->second.first : it->second.second;
    450 }
    451 
    452@@ -299,8 +296,8 @@
    453     // transaction because it can lead to false negatives. This test may mark a same-txid
    454     // transaction as invalid, and we don't want that to cause rejection of the target tx.
    455     const auto& target_tx = GetRandomTx(TRANSACTIONS, fuzzed_data_provider, /*correct_only=*/true);
    456-    Assert(target_tx != nullptr);
    457-    Assert(target_tx->HasWitness());
    458+    assert(target_tx != nullptr);
    459+    assert(target_tx->HasWitness());
    460 
    461     const auto& target_txid = target_tx->GetHash();
    462     const auto& target_wtxid = target_tx->GetWitnessHash();
    463@@ -323,8 +320,7 @@
    464     }
    465 
    466     // Indexes for random peer iteration
    467-    std::vector<size_t> indexes;
    468-    indexes.resize(all_peers.size());
    469+    std::vector<size_t> indexes(all_peers.size());
    470     std::iota(indexes.begin(), indexes.end(), 0);
    471 
    472     // We are trying to ensure that we always download the tx as long as we have 1 good outbound
    473@@ -509,7 +505,7 @@
    474             }
    475         }
    476 
    477-        // Skip time by 100-300ms
    478+        // Skip time by 100-300μs
    479         time += std::chrono::microseconds(fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(100, 300) * 1000);
    480     }
    481     // Disconnect everybody, check that all data structures are empty.
    482Index: src/test/txdownload_tests.cpp
    483<+>UTF-8
    484===================================================================
    485diff --git a/src/test/txdownload_tests.cpp b/src/test/txdownload_tests.cpp
    486--- a/src/test/txdownload_tests.cpp	(revision b543dc083ddbf43bb172b60ddb90609a600c86b5)
    487+++ b/src/test/txdownload_tests.cpp	(date 1727088836221)
    488@@ -57,19 +57,20 @@
    489 // Map from failure reason to expected behavior for a segwit tx that fails
    490 // Txid and Wtxid are assumed to be different here. For a nonsegwit transaction, use the wtxid results.
    491 static std::map<TxValidationResult, Behaviors> expected_behaviors{
    492-    {TxValidationResult::TX_CONSENSUS,               {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    493-    {TxValidationResult::TX_RECENT_CONSENSUS_CHANGE, {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    494-    {TxValidationResult::TX_INPUTS_NOT_STANDARD,     {/*txid_rejects*/1, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/1, /*wtxid_inv*/1}},
    495-    {TxValidationResult::TX_NOT_STANDARD,            {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    496-    {TxValidationResult::TX_MISSING_INPUTS,          {/*txid_rejects*/0, /*wtxid_rejects*/0, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    497-    {TxValidationResult::TX_PREMATURE_SPEND,         {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    498-    {TxValidationResult::TX_WITNESS_MUTATED,         {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    499-    {TxValidationResult::TX_WITNESS_STRIPPED,        {/*txid_rejects*/0, /*wtxid_rejects*/0, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/0, /*txid_inv*/0, /*wtxid_inv*/0}},
    500-    {TxValidationResult::TX_CONFLICT,                {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    501-    {TxValidationResult::TX_MEMPOOL_POLICY,          {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    502-    {TxValidationResult::TX_NO_MEMPOOL,              {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    503-    {TxValidationResult::TX_RECONSIDERABLE,          {/*txid_rejects*/0, /*wtxid_rejects*/0, /*txid_recon*/0, /*wtxid_recon*/1, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    504-    {TxValidationResult::TX_UNKNOWN,                 {/*txid_rejects*/0, /*wtxid_rejects*/1, /*txid_recon*/0, /*wtxid_recon*/0, /*keep*/1, /*txid_inv*/0, /*wtxid_inv*/1}},
    505+    //                                                txid_rejects, wtxid_rejects, txid_recon, wtxid_recon, keep, txid_inv, wtxid_inv
    506+    {TxValidationResult::TX_CONSENSUS,               {0,            1,             0,          0,           1,    0,        1}},
    507+    {TxValidationResult::TX_RECENT_CONSENSUS_CHANGE, {0,            1,             0,          0,           1,    0,        1}},
    508+    {TxValidationResult::TX_INPUTS_NOT_STANDARD,     {1,            1,             0,          0,           1,    1,        1}},
    509+    {TxValidationResult::TX_NOT_STANDARD,            {0,            1,             0,          0,           1,    0,        1}},
    510+    {TxValidationResult::TX_MISSING_INPUTS,          {0,            0,             0,          0,           1,    0,        1}},
    511+    {TxValidationResult::TX_PREMATURE_SPEND,         {0,            1,             0,          0,           1,    0,        1}},
    512+    {TxValidationResult::TX_WITNESS_MUTATED,         {0,            1,             0,          0,           1,    0,        1}},
    513+    {TxValidationResult::TX_WITNESS_STRIPPED,        {0,            0,             0,          0,           0,    0,        0}},
    514+    {TxValidationResult::TX_CONFLICT,                {0,            1,             0,          0,           1,    0,        1}},
    515+    {TxValidationResult::TX_MEMPOOL_POLICY,          {0,            1,             0,          0,           1,    0,        1}},
    516+    {TxValidationResult::TX_NO_MEMPOOL,              {0,            1,             0,          0,           1,    0,        1}},
    517+    {TxValidationResult::TX_RECONSIDERABLE,          {0,            0,             0,          1,           1,    0,        1}},
    518+    {TxValidationResult::TX_UNKNOWN,                 {0,            1,             0,          0,           1,    0,        1}},
    519 };
    520 
    521 static bool CheckOrphanBehavior(node::TxDownloadManagerImpl& txdownload_impl, const CTransactionRef& tx, const node::RejectedTxTodo& ret, std::string& err_msg,
    522Index: src/test/fuzz/txdownloadman.cpp
    523<+>UTF-8
    524===================================================================
    525diff --git a/src/test/fuzz/txdownloadman.cpp b/src/test/fuzz/txdownloadman.cpp
    526--- a/src/test/fuzz/txdownloadman.cpp	(revision b543dc083ddbf43bb172b60ddb90609a600c86b5)
    527+++ b/src/test/fuzz/txdownloadman.cpp	(date 1727088582192)
    528@@ -16,9 +16,9 @@
    529 #include <test/util/script.h>
    530 #include <test/util/setup_common.h>
    531 #include <test/util/txmempool.h>
    532+#include <txmempool.h>
    533 #include <util/hasher.h>
    534 #include <util/rbf.h>
    535-#include <txmempool.h>
    536 #include <validation.h>
    537 #include <validationinterface.h>
    538 
    539@@ -26,7 +26,7 @@
    540 
    541 const TestingSetup* g_setup;
    542 
    543-constexpr size_t NUM_COINS{50};
    544+constexpr uint32_t NUM_COINS{50};
    545 COutPoint COINS[NUM_COINS];
    546 
    547 static TxValidationResult TESTED_TX_RESULTS[] = {
    548@@ -72,7 +72,7 @@
    549 {
    550     static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
    551     g_setup = testing_setup.get();
    552-    for (uint32_t i = 0; i < uint32_t{NUM_COINS}; ++i) {
    553+    for (uint32_t i = 0; i < NUM_COINS; ++i) {
    554         COINS[i] = COutPoint{Txid::FromUint256((HashWriter() << i).GetHash()), i};
    555     }
    556     size_t outpoints_index = 0;
    557@@ -228,8 +228,8 @@
    558                 // - Don't validate the tx, package.
    559                 // - Validate the tx, no package.
    560                 // The only combination that doesn't make sense is validate both tx and package.
    561-                Assert(!(should_validate && maybe_package.has_value()));
    562-                if (maybe_package.has_value()) CheckPackageToValidate(*maybe_package, rand_peer);
    563+                Assert(!(should_validate && maybe_package));
    564+                if (maybe_package) CheckPackageToValidate(*maybe_package, rand_peer);
    565             },
    566             [&] {
    567                 txdownloadman.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()});
    568@@ -357,7 +357,7 @@
    569             [&] {
    570                 const auto getdata_requests = txdownload_impl.GetRequestsToSend(rand_peer, time);
    571                 // TxDownloadManager should not be telling us to request things we already have.
    572-                // Exclude m_lazy_recent_rejects_reconsiderable because it may request low-feerate parent of orphan.
    573+                // Exclude RecentRejectsReconsiderableFilter because it may request low-feerate parent of orphan.
    574                 for (const auto& gtxid : getdata_requests) {
    575                     Assert(!txdownload_impl.AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false));
    576                 }
    577@@ -369,15 +369,15 @@
    578                 // - Don't validate the tx, package.
    579                 // - Validate the tx, no package.
    580                 // The only combination that doesn't make sense is validate both tx and package.
    581-                Assert(!(should_validate && maybe_package.has_value()));
    582+                Assert(!(should_validate && maybe_package));
    583                 if (should_validate) {
    584                     Assert(!txdownload_impl.AlreadyHaveTx(GenTxid::Wtxid(rand_tx->GetWitnessHash()), /*include_reconsiderable=*/true));
    585                 }
    586-                if (maybe_package.has_value()) {
    587+                if (maybe_package) {
    588                     CheckPackageToValidate(*maybe_package, rand_peer);
    589 
    590                     const auto& package = maybe_package->m_txns;
    591-                    // Parent is in m_lazy_recent_rejects_reconsiderable and child is in m_orphanage
    592+                    // Parent is in RecentRejectsReconsiderableFilter and child is in m_orphanage
    593                     Assert(txdownload_impl.RecentRejectsReconsiderableFilter().contains(rand_tx->GetWitnessHash().ToUint256()));
    594                     Assert(txdownload_impl.m_orphanage.HaveTx(maybe_package->m_txns.back()->GetWitnessHash()));
    595                     // Package has not been rejected
    596Index: src/test/fuzz/mini_miner.cpp
    597<+>UTF-8
    598===================================================================
    599diff --git a/src/test/fuzz/mini_miner.cpp b/src/test/fuzz/mini_miner.cpp
    600--- a/src/test/fuzz/mini_miner.cpp	(revision b543dc083ddbf43bb172b60ddb90609a600c86b5)
    601+++ b/src/test/fuzz/mini_miner.cpp	(date 1727088265502)
    602@@ -26,7 +26,7 @@
    603 {
    604     static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
    605     g_setup = testing_setup.get();
    606-    for (uint32_t i = 0; i < uint32_t{100}; ++i) {
    607+    for (uint32_t i = 0; i < 100; ++i) {
    608         g_available_coins.emplace_back(Txid::FromUint256(uint256::ZERO), i);
    609     }
    610 }
    611Index: src/net_processing.cpp
    612<+>UTF-8
    613===================================================================
    614diff --git a/src/net_processing.cpp b/src/net_processing.cpp
    615--- a/src/net_processing.cpp	(revision b543dc083ddbf43bb172b60ddb90609a600c86b5)
    616+++ b/src/net_processing.cpp	(date 1727088265570)
    617@@ -568,12 +568,12 @@
    618     bool MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer);
    619 
    620     /** Handle a transaction whose result was not MempoolAcceptResult::ResultType::VALID.
    621-     * [@param](/bitcoin-bitcoin/contributor/param/)[in]   first_time_failure            Whether we should consider inserting into vExtraTxnForCompact, adding
    622-     *                                            a new orphan to resolve, or looking for a package to submit.
    623-     *                                            Set to true for transactions just received over p2p.
    624-     *                                            Set to false if the tx has already been rejected before,
    625-     *                                            e.g. is already in the orphanage, to avoid adding duplicate entries.
    626-     * Updates m_txrequest, m_lazy_recent_rejects, m_lazy_recent_rejects_reconsiderable, m_orphanage, and vExtraTxnForCompact.
    627+     * [@param](/bitcoin-bitcoin/contributor/param/)[in] first_time_failure Whether we should consider inserting into vExtraTxnForCompact, adding
    628+     *                               a new orphan to resolve, or looking for a package to submit.
    629+     *                               Set to true for transactions just received over p2p.
    630+     *                               Set to false if the tx has already been rejected before,
    631+     *                               e.g. is already in the orphanage, to avoid adding duplicate entries.
    632+     * Updates m_txrequest, m_lazy_recent_rejects, RecentRejectsReconsiderableFilter, m_orphanage, and vExtraTxnForCompact.
    633      *
    634      * [@returns](/bitcoin-bitcoin/contributor/returns/) a PackageToValidate if this transaction has a reconsiderable failure and an eligible package was found,
    635      * or std::nullopt otherwise.
    636@@ -734,8 +734,8 @@
    637      * Lock invariants:
    638      * - A txhash (txid or wtxid) in m_txrequest is not also in m_orphanage.
    639      * - A txhash (txid or wtxid) in m_txrequest is not also in m_lazy_recent_rejects.
    640-     * - A txhash (txid or wtxid) in m_txrequest is not also in m_lazy_recent_rejects_reconsiderable.
    641-     * - A txhash (txid or wtxid) in m_txrequest is not also in m_lazy_recent_confirmed_transactions.
    642+     * - A txhash (txid or wtxid) in m_txrequest is not also in RecentRejectsReconsiderableFilter.
    643+     * - A txhash (txid or wtxid) in m_txrequest is not also in RecentConfirmedTransactionsFilter.
    644      * - Each data structure's limits hold (m_orphanage max size, m_txrequest per-peer limits, etc).
    645      */
    646     Mutex m_tx_download_mutex ACQUIRED_BEFORE(m_mempool.cs);
    647@@ -3915,7 +3915,7 @@
    648 
    649             if (inv.IsMsgBlk()) {
    650                 const bool fAlreadyHave = AlreadyHaveBlock(inv.hash);
    651-                LogDebug(BCLog::NET, "got inv: %s  %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
    652+                LogDebug(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
    653 
    654                 UpdateBlockAvailability(pfrom.GetId(), inv.hash);
    655                 if (!fAlreadyHave && !m_chainman.m_blockman.LoadingBlocks() && !IsBlockRequested(inv.hash)) {
    656@@ -3938,7 +3938,7 @@
    657 
    658                 if (!m_chainman.IsInitialBlockDownload()) {
    659                     const bool fAlreadyHave{m_txdownloadman.AddTxAnnouncement(pfrom.GetId(), gtxid, current_time, /*p2p_inv=*/true)};
    660-                    LogDebug(BCLog::NET, "got inv: %s  %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
    661+                    LogDebug(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
    662                 }
    663             } else {
    664                 LogDebug(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
    665@@ -4256,7 +4256,7 @@
    666         }
    667 
    668         // ReceivedTx should not be telling us to validate the tx and a package.
    669-        Assume(!package_to_validate.has_value());
    670+        Assume(!package_to_validate);
    671 
    672         const MempoolAcceptResult result = m_chainman.ProcessTransaction(ptx);
    673         const TxValidationState& state = result.m_state;
    674@@ -4871,6 +4871,7 @@
    675         vRecv >> vInv;
    676         std::vector<uint256> tx_invs;
    677         if (vInv.size() <= node::MAX_PEER_TX_ANNOUNCEMENTS + MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
    678+            tx_invs.reserve(std::ranges::count_if(vInv, &CInv::IsGenTxMsg));
    679             for (CInv &inv : vInv) {
    680                 if (inv.IsGenTxMsg()) {
    681                     // If we receive a NOTFOUND message for a tx we requested, mark the announcement for it as
    682@@ -4998,8 +4999,10 @@
    683         //  by another peer that was already processed; in that case,
    684         //  the extra work may not be noticed, possibly resulting in an
    685         //  unnecessary 100ms delay)
    686-        LOCK(m_tx_download_mutex);
    687-        if (m_txdownloadman.HaveMoreWork(peer->m_id)) fMoreWork = true;
    688+        if (!fMoreWork) {
    689+            LOCK(m_tx_download_mutex);
    690+            fMoreWork = m_txdownloadman.HaveMoreWork(peer->m_id);
    691+        }
    692     } catch (const std::exception& e) {
    693         LogDebug(BCLog::NET, "%s(%s, %u bytes): Exception '%s' (%s) caught\n", __func__, SanitizeString(msg.m_type), msg.m_message_size, e.what(), typeid(e).name());
    694     } catch (...) {
    695@@ -5875,6 +5878,7 @@
    696                     vToDownload, from_tip,
    697                     Assert(m_chainman.GetSnapshotBaseBlock()));
    698             }
    699+            vGetData.reserve(vToDownload.size());
    700             for (const CBlockIndex *pindex : vToDownload) {
    701                 uint32_t nFetchFlags = GetFetchFlags(*peer);
    702                 vGetData.emplace_back(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash());
    703@@ -5895,7 +5899,9 @@
    704         //
    705         {
    706             LOCK(m_tx_download_mutex);
    707-            for (const GenTxid& gtxid : m_txdownloadman.GetRequestsToSend(pto->GetId(), current_time)) {
    708+            const auto& tx_requests = m_txdownloadman.GetRequestsToSend(pto->GetId(), current_time);
    709+            vGetData.reserve(vGetData.size() + tx_requests.size());
    710+            for (const GenTxid& gtxid : tx_requests) {
    711                 vGetData.emplace_back(gtxid.IsWtxid() ? MSG_WTX : (MSG_TX | GetFetchFlags(*peer)), gtxid.GetHash());
    712                 if (vGetData.size() >= MAX_GETDATA_SZ) {
    713                     MakeAndPushMessage(*pto, NetMsgType::GETDATA, vGetData);
    
  215. glozow force-pushed on Sep 29, 2024
  216. glozow commented at 3:07 pm on September 29, 2024: member

    Thanks for the review @l0rinc. I’ve gone through them and addressed some of your comments, and marked some as out of scope. As you can see this PR is fairly large, so it is my preference not to add changes to the code that is being moved. Perhaps we can take some into a followup.

    I’ll provide a higher-level review later, once I understand the problem better. I still need a few iterations to be able to zoom out and comment meaningfully, for now I only left nits about implementation specifics, modernization attempts, potential off-by-one errors, readability suggestions, etc.

    As written in the contributing guidelines, it is best to do conceptual review before looking at syntax modernization and style. Hopefully it is obvious why this is done on a PR level. But also for individual reviewers, for a few reasons:

    (1) It will help you understand what to pay attention to and look out for. For example, you’ve left 40+ review comments, and about 10 of them are suggestions for code that isn’t actually introduced in this PR. That’s probably not a great use of your time, as it’s outside the scope of the PR.

    (2) It’s less productive to request minor changes to something if you haven’t decided whether it looks good on a macro level. For instance, if you ask for 40 minor changes now, and later realize you think the general approach is unsafe (e.g. actually parts of the interface should look entirely different, or the PR is simply a bad idea), it would have been unproductive for both of us to be using the 500grit sandpaper on a chunk of wood that was going to be discarded anyway.

    I hope this explanation makes sense. I felt it productive to write this up publicly as it’s not uncommon to leave surface-level feedback, which is of course better than no review. However, our goal should be to make progress on PRs (towards yes or no). So when you decide to review a PR, I request that you start with conceptual review.

    I’ll provide a higher-level review later, once I understand the problem better.

    Please do! Let me know if I can answer any questions and thank you very much for your review.

  217. l0rinc commented at 4:51 pm on September 29, 2024: contributor

    It’s less productive to request minor changes to something if you haven’t decided whether it looks good on a macro level.

    I had to reduce the scope of the reviews since I’m not yet familiar with this part of the code. But I understand it’s not what’s needed here, so I will let others (having deeper, more holistic experience) do concept reviews.

  218. in src/test/txdownload_tests.cpp:227 in 9d6c88fa11 outdated
    227+        if (parent_recent_rej_recon) txdownload_impl.RecentRejectsReconsiderableFilter().insert(single_parent->GetHash().ToUint256());
    228+        if (parent_recent_conf) txdownload_impl.RecentConfirmedTransactionsFilter().insert(single_parent->GetHash().ToUint256());
    229+        if (parent_in_mempool) {
    230+            const auto mempool_result = WITH_LOCK(::cs_main, return m_node.chainman->ProcessTransaction(single_parent));
    231+            BOOST_CHECK(mempool_result.m_result_type == MempoolAcceptResult::ResultType::VALID);
    232+            coinbase_idx += 1;
    


    instagibbs commented at 2:46 pm on September 30, 2024:
    could assert that new idx doesn’t overflow the m_coinbase_txns vector size

    glozow commented at 9:49 pm on October 1, 2024:
    added
  219. in src/test/txdownload_tests.cpp:237 in 9d6c88fa11 outdated
    232+            coinbase_idx += 1;
    233+        }
    234+
    235+        // Whether or not the transaction is added as an orphan depends solely on whether or not
    236+        // it's in RecentRejectsFilter. Specifically, the parent is allowed to be in
    237+        // RecentRejectsReconsiderableFilter, but it cannot be in both rejection filters.
    


    instagibbs commented at 2:51 pm on September 30, 2024:
    Do you mean it cannot be in both, and result in a stored orphan?

    glozow commented at 9:50 pm on October 1, 2024:
    changed to “cannot be inRecentRejectsFilter”
  220. in src/test/txdownload_tests.cpp:259 in 9d6c88fa11 outdated
    254+            auto ptx_parent = MakeTransactionRef(mtx_parent);
    255+            parents.emplace_back(ptx_parent);
    256+            outpoints.emplace_back(ptx_parent->GetHash(), 0);
    257+        }
    258+
    259+        // Send all coins to 1 address.
    


    instagibbs commented at 2:52 pm on September 30, 2024:
    0        // Send all coins to 1 output.
    

    glozow commented at 9:50 pm on October 1, 2024:
    done
  221. instagibbs approved
  222. instagibbs commented at 3:18 pm on September 30, 2024: member

    reACK ce205abe10ebccfdbea60adf4c568a8ba61390c3

    via git range-diff master 1d4e33e7f88162cf6fcd6aee0b63973015853591 ce205abe10ebccfdbea60adf4c568a8ba61390c3

    new unit test coverage looks superior, and catches the move error previously encountered.

  223. DrahtBot requested review from l0rinc on Sep 30, 2024
  224. DrahtBot requested review from theStack on Sep 30, 2024
  225. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:194 in ce205abe10 outdated
    189+std::map<uint256, std::pair<CTransactionRef, CTransactionRef>> TRANSACTIONS;
    190+
    191+static CTransactionRef MakeTransactionSpending(const std::vector<COutPoint>& outpoints, size_t num_outputs, bool add_witness)
    192+{
    193+    CMutableTransaction tx;
    194+    // If no outpoints are given, create a random one.
    


    instagibbs commented at 3:29 pm on September 30, 2024:
    If no outpoints are given, it’s no inputs at all? I think every call is using non-empty vectors, and if so should be asserted.

    glozow commented at 10:22 pm on October 1, 2024:
    A very incorrect comment! Deleted it now.
  226. in src/test/fuzz/txdownloadman.cpp:302 in 3c1c9e766a outdated
    297+    {
    298+        NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS);
    299+
    300+        // Transaction can be one of the premade ones or a randomly generated one
    301+        auto rand_tx = fuzzed_data_provider.ConsumeBool() ?
    302+            MakeTransactionSpending({fuzzed_data_provider.PickValueInArray(COINS)},
    


    instagibbs commented at 3:42 pm on September 30, 2024:
    Ideally this would select N inputs rather than a static one, otherwise this target really only covers multi-input case with the // 2 parents 1 child case, and doesn’t even cover duplicate prevout hashes.

    glozow commented at 10:17 pm on October 1, 2024:
    Good point. Added a PickCoins to select more.
  227. in src/test/fuzz/txdownloadman.cpp:212 in 3c1c9e766a outdated
    207+                txdownloadman.MempoolAcceptedTx(rand_tx);
    208+            },
    209+            [&] {
    210+                TxValidationState state;
    211+                state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), "");
    212+                txdownloadman.MempoolRejectedTx(rand_tx, state, rand_peer, fuzzed_data_provider.ConsumeBool());
    


    instagibbs commented at 3:58 pm on September 30, 2024:

    A few more assertions here and other spot for MempoolRejectedTx?

    0                bool first_time_failure{fuzzed_data_provider.ConsumeBool()};
    1                bool reject_contains_wtxid{txdownload_impl.RecentRejectsFilter().contains(rand_tx->GetWitnessHash().ToUint256())};
    2                node::RejectedTxTodo todo = txdownload_impl.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure);
    3                Assert(first_time_failure || !todo.m_should_add_extra_compact_tx);
    4                if (!reject_contains_wtxid) Assert(todo.m_unique_parents.size() <= rand_tx->vin.size());
    

    glozow commented at 10:14 pm on October 1, 2024:
    Added. Note we can’t see the filter from txdownloadman.
  228. in src/test/fuzz/txdownloadman.cpp:58 in 3c1c9e766a outdated
    53+constexpr int NUM_PEERS = 16;
    54+
    55+// Precomputed random durations (positive and negative, each ~exponentially distributed).
    56+std::chrono::microseconds TIME_SKIPS[128];
    57+
    58+static CTransactionRef MakeTransactionSpending(const std::vector<COutPoint>& outpoints, size_t num_outputs, bool add_witness)
    


    theStack commented at 9:05 pm on September 30, 2024:
    in commit 3c1c9e766ad4fc6defdc9b4814c1e184f6603003: the num_outputs parameter is currently unused, so all txs created with this helper only have one output

    glozow commented at 10:08 pm on October 1, 2024:
    woops! changed
  229. in src/test/fuzz/txdownloadman.cpp:172 in 3c1c9e766a outdated
    167+
    168+    std::chrono::microseconds time{244466666};
    169+
    170+    LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
    171+    {
    172+        NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS);
    


    theStack commented at 9:18 pm on September 30, 2024:

    was this meant to be

    0        NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1);
    

    instead, considering that the specified range end is inclusive? the peer loops and asserts have also currently different conditions (< NUM_PEERS in CheckInvariants vs. <= NUM_PEERS in the disconnect loops), so at least one of those needs to be adapted.


    glozow commented at 10:10 pm on October 1, 2024:
    you’re right, taken
  230. DrahtBot requested review from theStack on Sep 30, 2024
  231. in src/test/fuzz/txdownloadman.cpp:406 in 3c1c9e766a outdated
    382+                    Assert(txdownload_impl.m_orphanage.HaveTx(maybe_package->m_txns.back()->GetWitnessHash()));
    383+                    // Package has not been rejected
    384+                    Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(GetPackageHash(package)));
    385+                    // Neither is in m_lazy_recent_rejects
    386+                    Assert(!txdownload_impl.RecentRejectsFilter().contains(package.front()->GetWitnessHash().ToUint256()));
    387+                    Assert(!txdownload_impl.RecentRejectsFilter().contains(package.back()->GetWitnessHash().ToUint256()));
    


    theStack commented at 5:01 pm on October 1, 2024:
    considering that (rolling) bloom filters always have a certain false-positive rate, I’m wondering if these assertions could unintentionally fail on long fuzzing runs where the filters aren’t reset for many iterations? (didn’t look at any concrete math yet tho, maybe the set of of possible packages we create/pick from the generated data is so small that the likelihood of this happening is negligible, and everything is fine)

    instagibbs commented at 5:55 pm on October 1, 2024:

    I believe it’s very rare based on our filter parameters. Each generation has 60k entries expected, and according to my debug-fu has 20 hash functions, and 21,56,646 filter bits. Since the harness does up to 10k iterations(let’s assume each iteration makes a unique tx), that is 10k transactions, this leads to:

    0P_fp = (1 - e^-(20*10000/2156646))^20
    1        ~= 8.8183022e-22
    

    This assumes independence of probabilities of each bit being set. Once we get up to 60k iterations, assuming they are all added to the filter, then FP rate will hit ~1/1M. If we set total iterations to 1.5k, then we drop below cryptographic safety margins of 2^-128.


    glozow commented at 10:19 pm on October 1, 2024:
    I hadn’t really considered this, thanks @theStack and @instagibbs. It does seem safe to assume we won’t have anything roll out within our (limited) fuzz iterations.
  232. DrahtBot requested review from theStack on Oct 1, 2024
  233. glozow force-pushed on Oct 2, 2024
  234. in src/test/fuzz/txdownloadman.cpp:68 in 6ae4acc537 outdated
    63+        tx.vin.emplace_back(outpoint);
    64+    }
    65+    if (add_witness) {
    66+        tx.vin[0].scriptWitness.stack.push_back({1});
    67+    }
    68+    for (size_t o = 0; o <= num_outputs; ++o) tx.vout.emplace_back(CENT, P2WSH_OP_TRUE);
    


    theStack commented at 11:33 am on October 2, 2024:

    for this fuzz test it probably doesn’t matter if one more output than specified is added, but…

    0    for (size_t o = 0; o < num_outputs; ++o) tx.vout.emplace_back(CENT, P2WSH_OP_TRUE);
    

    glozow commented at 6:13 pm on October 5, 2024:
    fixed
  235. instagibbs commented at 1:36 pm on October 2, 2024: member

    reACK ce205abe10ebccfdbea60adf4c568a8ba61390c3

    thanks for picking up on some of those old nits

  236. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:282 in 114a5405c4 outdated
    277+
    278+// This test asserts that, as long as we have 1 good outbound peer, even if all other peers are
    279+// malicious, we'll always be able to download a transaction.
    280+// Connect to 8 outbounds and 0-120 inbound peers. 1 outbound is guaranteed to be good (see m_good
    281+// above for definition), while the rest may or may not be.
    282+// A non-good node has a superset of the actions available to a good node: it may stall a getdata,
    


    instagibbs commented at 1:56 pm on October 2, 2024:
    not strictly a superset, right? Uses NON_GOOD_RESPONSES if not good which excludes TX_REAL

    glozow commented at 1:59 am on October 3, 2024:
    edited
  237. in src/test/fuzz/txdownloadman.cpp:176 in 6ae4acc537 outdated
    171+    {
    172+        NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1);
    173+
    174+        // Transaction can be one of the premade ones or a randomly generated one
    175+        auto rand_tx = fuzzed_data_provider.ConsumeBool() ?
    176+            MakeTransactionSpending({fuzzed_data_provider.PickValueInArray(COINS)},
    


    instagibbs commented at 1:58 pm on October 2, 2024:
    Could use PickCoins here as well

    glozow commented at 2:05 am on October 3, 2024:
    added
  238. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:520 in 114a5405c4 outdated
    509+
    510+        time += std::chrono::milliseconds(fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(100, 300));
    511+    }
    512+    // Disconnect everybody, check that all data structures are empty.
    513+    for (const auto& peer : all_peers) {
    514+        txdownloadman.DisconnectedPeer(peer.m_nodeid);
    


    instagibbs commented at 2:47 pm on October 2, 2024:

    This should catch issues marking things as received properly, at least for wtxid version since we’re forcing wtxid relay for good peer.

    0        // Honest peer only tells us about INV_REAL in this scenario, should be empty since REAL_TX received
    1        if (peer.m_nodeid == honestid) {
    2            txdownloadman.CheckIsEmpty(honestid);
    3        }
    4        txdownloadman.DisconnectedPeer(peer.m_nodeid);
    

    glozow commented at 2:04 am on October 3, 2024:
    added
  239. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:237 in 114a5405c4 outdated
    232+
    233+        const auto& target_txid = correct_tx->GetHash().ToUint256();
    234+        const auto& target_wtxid = correct_tx->GetWitnessHash().ToUint256();
    235+        const auto& malleated_wtxid = malleated_tx->GetWitnessHash().ToUint256();
    236+
    237+        // The first tx in the pair needs to match by wtxid.
    


    instagibbs commented at 4:42 pm on October 2, 2024:
    I’m not sure what this comment means. I know the honestid peer needs to be wtxid relaying to ensure that it can properly serve malleated_tx when necessary, otherwise GetCorrectTx will fetch the wrong response by giving back correct_tx.
  240. instagibbs commented at 4:43 pm on October 2, 2024: member
    some last nits/suggestions
  241. in src/test/fuzz/txdownloadman.cpp:438 in 6ae4acc537 outdated
    433+        // Jump ahead in time
    434+        time += fuzzed_data_provider.PickValueInArray(TIME_SKIPS);
    435+        CheckInvariants(txdownload_impl, max_orphan_count);
    436+    }
    437+    // Disconnect everybody, check that all data structures are empty.
    438+    for (NodeId nodeid = 0; nodeid <= NUM_PEERS; ++nodeid) {
    


    theStack commented at 5:13 pm on October 2, 2024:
    0    for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) {
    

    glozow commented at 6:13 pm on October 5, 2024:
    fixed, thanks
  242. in src/test/fuzz/txdownloadman.cpp:159 in 6ae4acc537 outdated
    144+
    145+void CheckPackageToValidate(const node::PackageToValidate& package_to_validate, NodeId peer)
    146+{
    147+    Assert(package_to_validate.m_senders.size() == 2);
    148+    Assert(package_to_validate.m_senders.front() == peer);
    149+    Assert(package_to_validate.m_senders.back() <= NUM_PEERS);
    


    theStack commented at 5:14 pm on October 2, 2024:
    0    Assert(package_to_validate.m_senders.back() < NUM_PEERS);
    

    glozow commented at 6:13 pm on October 5, 2024:
    fixed
  243. in src/test/fuzz/txdownloadman.cpp:269 in 6ae4acc537 outdated
    254+        auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS);
    255+        if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1;
    256+        time += time_skip;
    257+    }
    258+    // Disconnect everybody, check that all data structures are empty.
    259+    for (NodeId nodeid = 0; nodeid <= NUM_PEERS; ++nodeid) {
    


    theStack commented at 5:15 pm on October 2, 2024:
    0    for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) {
    

    glozow commented at 6:14 pm on October 5, 2024:
    fixed
  244. marcofleon commented at 3:53 pm on October 3, 2024: contributor

    So, txdownloadman_impl still crashes on

    0Assert(!txdownload_impl.RecentRejectsFilter().contains(package.back()->GetWitnessHash().ToUint256()));
    

    Here’s the input: FVVaWm9vHQpOVVr/dQA9GnItbAAAPv///////3UA83ByLGwAAD7//////xZvb28=

    Did some investigating.

    1. Non-segwit child gets added to the orphanage.
    2. Same child but segwit gets rejected (TX_INPUTS_NOT_STANDARD).
    3. Parent gets added to rejects reconsiderable. Forms a package with the first child.
    4. Txid of that child is found in recent rejects.

    I don’t think this particular path is possible in practice because the second version of the child doesn’t have missing inputs, which means the first one got removed from the orphanage at some point (succinct explanation by Niklas). But is it possible to have this same thing happen if a child gets rejected because its parent is rejected? In that case, both children would enter the TX_MISSING_INPUTS branch. The parent would have to be in both recent rejects and rejects reconsiderable… which doesn’t seem to make sense.

    Either way, the fuzz harness is just picking randomly from the possible invalid states, so at some point a child ends up in both a package and recent rejects. It feels to me that the fix would be in the harness, rather than the implementation. But I’m not sure. Maybe more checks could be added in Find1P1CPackage before returning a package to validate?

  245. marcofleon commented at 4:39 pm on October 3, 2024: contributor

    From #30110 (comment))

    Find1P1CPackage doesn’t actually check the normal reject filter at all with respect to the child, only reconsiderable, so that assertion gets hit. I suspect that’s something we don’t want it to do, even if it’s not possible to hit inside net_processing.

    Yes, this was my initial thought too.

    I think it might be more that MempoolRejectedTx doesn’t remember if a tx failed for a different reason before considering an orphan?

    Maybe a check if the child is already in the orphanage before adding it to recent rejects?

    edit: I guess my actual question is, is there ever a reason to have the same child in both the orphanage and the recent rejects filter?

  246. instagibbs commented at 6:09 pm on October 3, 2024: member

    @marcofleon ah, this makes sense. We “know” that when TX_INPUTS_NOT_STANDARD is hit, the witness doesn’t matter, so the txid of the witness-having child transaction is added to the reject filter. The orphanage will only delete the witness-having version as well, so you now have a witness-free orphan which is also in the reject filter.

    This could also happen in the harness I think if the error is TX_MISSING_INPUTS and has fRejectedParents is true while the witness-free version of child is already in orphanage.

    AFAIK these “shouldn’t happen” for real.

    Some choices we could take:

    1. don’t return package if child is in rejects filter(seems most robust?)
    2. only assert that failed invariant on child when child has witness data (brittle?)
    3. Call TxOrphanage::EraseTx using both txid and wtxid in this case (witness-free tx can never be valid)
  247. in src/node/txdownloadman.h:16 in 4410fa0f6b outdated
     7@@ -8,10 +8,12 @@
     8 #include <cstdint>
     9 #include <memory>
    10 
    11+class CBlock;
    12+class CBlockIndex;
    


    theStack commented at 10:08 pm on October 3, 2024:

    never-used-nit


    glozow commented at 6:14 pm on October 5, 2024:
    done
  248. in src/node/txdownloadman.h:21 in 4410fa0f6b outdated
    13+class CRollingBloomFilter;
    14 class TxOrphanage;
    15 class TxRequestTracker;
    16-class CRollingBloomFilter;
    17-
    18+enum class ChainstateRole;
    


    theStack commented at 10:08 pm on October 3, 2024:

    never-used-nit


    glozow commented at 6:14 pm on October 5, 2024:
    done
  249. in src/node/txdownloadman.h:51 in 0ef6cdc5c4 outdated
    47+    /** Check whether we already have this gtxid in:
    48+     *  - mempool
    49+     *  - orphanage
    50+     *  - m_recent_rejects
    51+     *  - m_recent_rejects_reconsiderable (if include_reconsiderable = true)
    52+     *  - m_recent_confirmed_transactions
    


    theStack commented at 10:39 pm on October 3, 2024:

    nit (these would be moved again in the later commit 4b8cb00fcb429630f717954b5f1c397ce54a8475 ["[refactor] make AlreadyHaveTx and Find1P1CPackage private to TxDownloadImpl"], so might be easier to just fix in a follow-up)

    0     *  - m_lazy_recent_rejects
    1     *  - m_lazy_recent_rejects_reconsiderable (if include_reconsiderable = true)
    2     *  - m_lazy_recent_confirmed_transactions
    

    glozow commented at 1:48 am on October 7, 2024:
    (yeah, marking pretty much all the moved stuff resolved)
  250. in src/node/txdownloadman_impl.cpp:235 in 75a96decec outdated
    208@@ -205,4 +209,13 @@ std::vector<GenTxid> TxDownloadManagerImpl::GetRequestsToSend(NodeId nodeid, std
    209     }
    210     return requests;
    211 }
    212+
    213+void TxDownloadManagerImpl::ReceivedNotFound(NodeId nodeid, const std::vector<uint256>& txhashes)
    214+{
    215+    for (const auto& txhash : txhashes) {
    216+        // If we receive a NOTFOUND message for a tx we requested, mark the announcement for it as
    217+        // completed in TxRequestTracker.
    


    theStack commented at 11:16 pm on October 3, 2024:
    nit: that comment is duplicated on the call-site

    glozow commented at 6:14 pm on October 5, 2024:
    deduplicated, thanks
  251. in src/net_processing.cpp:2980 in b93d678d60 outdated
    3173-            AddToCompactExtraTransactions(ptx);
    3174-        }
    3175+    }
    3176+    if (add_extra_compact_tx && RecursiveDynamicUsage(*ptx) < 100000) {
    3177+        AddToCompactExtraTransactions(ptx);
    3178+    }
    


    theStack commented at 1:38 am on October 4, 2024:
    Missed this on previous review rounds, but this commit seems to change behaviour, as the <100k memusage condition for txs to be added to the compact-block-extra-txn cache (originally introduced in #9499, commits https://github.com/bitcoin/bitcoin/pull/9499/commits/7f8c8cab1e537809848088d3bfaa13ecca7ac8cc / https://github.com/bitcoin/bitcoin/pull/9499/commits/863edb45b9841c3058e883015c7576e6f69e3cc6) is now also applied to orphan transactions.

    instagibbs commented at 2:06 pm on October 4, 2024:
    just noting that previously the MAX_STANDARD_TX_WEIGHT was enforced via TxOrphanage::AddTx, just not the computed memory usage. Honestly seems a bit accidentally asymmetric to begin with.

    glozow commented at 5:48 pm on October 15, 2024:
    Thanks for noticing, very subtle! Added a commit to make this change explicit, though it is overwritten immediately afterwards.
  252. in src/net_processing.cpp:3184 in b93d678d60 outdated
    3183     MaybePunishNodeForTx(nodeid, state);
    3184 
    3185     // If the tx failed in ProcessOrphanTx, it should be removed from the orphanage unless the
    3186     // tx was still missing inputs. If the tx was not in the orphanage, EraseTx does nothing and returns 0.
    3187-    if (Assume(state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) && m_orphanage.EraseTx(ptx->GetWitnessHash()) > 0) {
    3188+    if (state.GetResult() != TxValidationResult::TX_MISSING_INPUTS && m_orphanage.EraseTx(ptx->GetWitnessHash()) > 0) {
    


    theStack commented at 2:04 am on October 4, 2024:
    nit: any reason why this Assume is removed? If the validation result state is TX_MISSING_INPUTS, the function still returns early.

    glozow commented at 6:14 pm on October 5, 2024:
    Accidental, I think. changed back

    naumenkogs commented at 11:52 am on October 28, 2024:

    nit

    f497414ce76a4cf44fa669e3665746cc17710fc6 still has Assume removed


    instagibbs commented at 6:18 pm on October 28, 2024:
    pretty sure f497414ce76a4cf44fa669e3665746cc17710fc6 means you can’t Assume anymore?

    glozow commented at 7:28 pm on October 28, 2024:
    Yeah, we don’t quit early, so we can’t assume that it isn’t missing inputs here anymore.
  253. theStack commented at 2:22 am on October 4, 2024: contributor
    Left some more review comments below; these are mostly pedantic nits that can be done in a follow-up, but also one (minor?) behavior change.
  254. marcofleon commented at 10:45 am on October 4, 2024: contributor

    @instagibbs thanks, that makes sense. I did see the fRejectedParents is true case with an input that crashed on 3c1c9e766ad4fc6defdc9b4814c1e184f6603003 (before the last push).

    Option 1 in your list seems most robust to me as well.

  255. glozow force-pushed on Oct 5, 2024
  256. glozow commented at 3:25 pm on October 5, 2024: member
    I just pushed from 114a5405c41 to 693a1d079ea for this review #30110#pullrequestreview-2343039535 I’ll push again to address the silent merge conflict, and also address #30110#pullrequestreview-2342669240
  257. glozow force-pushed on Oct 5, 2024
  258. glozow force-pushed on Oct 5, 2024
  259. in src/test/fuzz/txdownloadman_one_honest_peer.cpp:238 in ff1f2dc055 outdated
    233+        const auto& target_txid = correct_tx->GetHash().ToUint256();
    234+        const auto& target_wtxid = correct_tx->GetWitnessHash().ToUint256();
    235+        const auto& malleated_wtxid = malleated_tx->GetWitnessHash().ToUint256();
    236+
    237+        // Provide a way to query each transaction by wtxid or by txid. Each pair of transactions
    238+        // have identical txids. Additionally, the correct_tx only ever appears as the first
    


    instagibbs commented at 3:26 pm on October 7, 2024:
    correct_tx appears in both for the txid-indexed version

    glozow commented at 9:50 pm on October 7, 2024:
    Yeah, need to rework this, it’s confusing
  260. instagibbs commented at 3:38 pm on October 7, 2024: member

    Remaining (non nitty) things I think to be addressed:

    1. #30110 (comment)
    2. #30110 (review)

    git range-diff master ce205abe10ebccfdbea60adf4c568a8ba61390c3 ff1f2dc055efe92b3202387c66a12948134eced2

  261. glozow commented at 9:50 pm on October 7, 2024: member

    Remaining (non nitty) things I think to be addressed:

    1. [refactor: TxDownloadManager + fuzzing [#30110](/bitcoin-bitcoin/30110/) (comment)](/bitcoin-bitcoin/30110/#issuecomment-2391768053)
    
    2. [refactor: TxDownloadManager + fuzzing [#30110](/bitcoin-bitcoin/30110/) (comment)](https://github.com/bitcoin/bitcoin/pull/30110#discussion_r1787010637)
    

    Yes 👍 and thanks for looking through what happened with the nonstandard input txns @marcofleon @instagibbs! Sorry for the delay, I still need to sit down and finely comb.

  262. in src/node/txdownloadman_impl.h:142 in 6fbb246384 outdated
    135+        PeerInfo(const TxDownloadConnectionInfo& info) : m_connection_info{info} {}
    136+    };
    137+
    138+    /** Information for all of the peers we may download transactions from. This is not necessarily
    139+     * all peers we are connected to (no block-relay-only and temporary connections). */
    140+    std::map<NodeId, PeerInfo> m_peer_info;
    


    naumenkogs commented at 2:53 pm on October 8, 2024:

    6fbb2463840f47f9f0632431e98a68f6747805ea

    Since this is a new variable… why not using a name more aligned with the description above? Otherwise it’s hard too tell what this is about.


    glozow commented at 8:34 am on October 15, 2024:
    Can you suggest another name? It’s a map to get peer info, hence m_peer_info.

    naumenkogs commented at 11:08 am on October 28, 2024:
    now that i realize it’s a very internal thing to the download manager, i understand why this name was chosen. resolve pls.
  263. in src/node/txdownloadman_impl.h:145 in 6fbb246384 outdated
    138+    /** Information for all of the peers we may download transactions from. This is not necessarily
    139+     * all peers we are connected to (no block-relay-only and temporary connections). */
    140+    std::map<NodeId, PeerInfo> m_peer_info;
    141+
    142+    /** Number of wtxid relay peers we have. */
    143+    uint32_t m_num_wtxid_peers{0};
    


    naumenkogs commented at 3:03 pm on October 8, 2024:

    6fbb2463840f47f9f0632431e98a68f6747805ea

    This variable is kinda synchronous with m_peer_info. Would be unfortunate to decouple it accidentally in future code. Perhaps comment on it?


    glozow commented at 5:49 pm on October 15, 2024:

    Added a comment that this specifically refers to peers in the map.

    I also added a doxygen comment to the TxDownloadManager definition saying that this class isn’t thread-safe and needs to be externally synced.

  264. in src/node/txdownloadman_impl.cpp:180 in 1dd47af96c outdated
    150@@ -142,4 +151,58 @@ void TxDownloadManagerImpl::DisconnectedPeer(NodeId nodeid)
    151     }
    152 
    153 }
    154+
    155+bool TxDownloadManagerImpl::AddTxAnnouncement(NodeId peer, const GenTxid& gtxid, std::chrono::microseconds now, bool p2p_inv)
    156+{
    157+    // If this is an inv received from a peer and we already have it, we can drop it.
    158+    if (p2p_inv && AlreadyHaveTx(gtxid, /*include_reconsiderable=*/true)) return true;
    


    naumenkogs commented at 3:20 pm on October 8, 2024:
    1dd47af96cdc37706cd6d3f47892822e785b6fbe What does the following mean: p2p_inv=false but AlreadyHaveTx is true?

    glozow commented at 8:38 am on October 15, 2024:
    Grep will show AddTxAnnouncement called with p2p_inv=false in the new orphan handling code. We don’t exit because we might already have a low feerate parent in reconsiderable rejects. You can also comment this out to see what fails.

    naumenkogs commented at 11:26 am on October 28, 2024:

    Okay, so

    /** New inv has been received. May be added as a candidate to txrequest. */ is inaccurate, right? There is no new INV received in p2p_inv=false case, but rather it’s called from orphan consideration after processing a TX?


    glozow commented at 12:47 pm on October 31, 2024:
    Agree the comment isn’t 100% accurate, edited in #31190
  265. naumenkogs commented at 3:39 pm on October 8, 2024: member

    Reviewed up to 3b78d8d5dc325bb32ea9ff167617e4ef80110301

    Still learning all the stuff i missed with 1p1c, so my eyes were mostly focused on the refactoring correctness.

  266. in src/net_processing.cpp:3122 in 673d6ee17d outdated
    3117@@ -3115,9 +3118,11 @@ std::optional<PeerManagerImpl::PackageToValidate> PeerManagerImpl::ProcessInvali
    3118                 m_txrequest.ForgetTxHash(tx.GetWitnessHash());
    3119             }
    3120         }
    3121-        // If it's not a first time failure, do nothing.
    3122+        // Whether or not we added an orphan, quit here.
    3123         return std::nullopt;
    


    naumenkogs commented at 11:06 am on October 9, 2024:

    673d6ee17dd48281fa42983f8b95553d5494e914

    In this particular commit the updated value of add_extra_compact_tx is dropped on the floor. I think this is fixed in the final version on this PR :) (and AddToCompactExtraTransactions is not called anymore inside if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { )

    I’m not much worried about the quality of intermediate commits, but it makes following the changes a bit difficult.


    naumenkogs commented at 11:11 am on October 9, 2024:
    Similarly, the AddKnownTx won’t be ever called upon this return.

    naumenkogs commented at 11:34 am on October 9, 2024:
    Not sure why it says the file is outdated. Probably because i commented on the un-touched line?

    glozow commented at 5:14 pm on October 15, 2024:
    Ooh good catch.
  267. in src/node/txdownloadman_impl.cpp:304 in 2658d2b2df outdated
    300@@ -301,6 +301,10 @@ void TxDownloadManagerImpl::MempoolAcceptedTx(const CTransactionRef& tx)
    301 
    302 node::RejectedTxTodo TxDownloadManagerImpl::MempoolRejectedTx(const CTransactionRef& ptx, const TxValidationState& state, NodeId nodeid, bool first_time_failure)
    303 {
    304+    if (!Assume(state.IsInvalid())) {
    


    naumenkogs commented at 4:12 pm on October 9, 2024:

    2658d2b2dfaacbfb2ddf2f1305ed8c3c9e134b71

    won’t this fail in DIFFERENT_WITNESS case?


    instagibbs commented at 2:26 pm on October 10, 2024:

    this is definitely problematic, but I can’t seem to figure out a way to hit it. In prod this would “just” cause it to not call m_txrequest.ForgetTxHash(ptx->GetWitnessHash()); for it, but would have to be in a 1p1c package setting, and we fetch the parent via txid.

    To retain current behavior, I think it can just be expanded to

    0    // DIFFERENT_WITNESS has "valid" status, but we still want to stop requesting it
    1    if (!Assume(state.IsInvalid() ||
    2        tx_result.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS)) {
    

    glozow commented at 5:14 pm on October 15, 2024:
    Good point - I feel like we actually shouldn’t be adding the Assume(IsInvalid()) here then. We don’t have the MempoolAcceptResult here (and we shouldn’t do that imo as txdownloadman.h would have a dependency on validation.h)
  268. glozow force-pushed on Oct 15, 2024
  269. glozow commented at 5:55 pm on October 15, 2024: member

    Resolved #30110 (comment) by adding this:

    don’t return package if child is in rejects filter(seems most robust?)

    I like this suggestion as well:

    Call TxOrphanage::EraseTx using both txid and wtxid in this case (witness-free tx can never be valid)

    I’ve dropped the one_honest_peer fuzzer and plan to open it as a separate PR, because it’s quite a large portion of the new code and imo underripe compared to the rest of the changes. I’m a bit unhappy with the way I manage the transactions in that test, so I’ll iterate some more before opening. I think the txdownloadman and txdownloadman_impl fuzzers added here are a good start and much less cumbersome to review.

  270. in src/node/txdownloadman.h:20 in 11dc07f423 outdated
    15+namespace node {
    16+class TxDownloadManagerImpl;
    17+
    18+/**
    19+ * Class responsible for deciding what transactions to request and, once
    20+ * downloaded, whether and how to valiate them. It is also responsible for
    


    theStack commented at 8:36 pm on October 23, 2024:
    0 * downloaded, whether and how to validate them. It is also responsible for
    

    glozow commented at 1:24 am on October 25, 2024:
    Fixed, thanks :)
  271. theStack commented at 8:59 pm on October 23, 2024: contributor
    Re-reviewed up to 190d87f912737c46668072ea9a460c520f778c92, lgtm. The introduced minor behaviour changes to address #30110 (comment) and #30110 (review) seem reasonable and harmless. Planning to finish reviewing unit test and fuzzer tomorrow (not opposed to defer the one_honest_peer fuzz test for a future PR at all :)).
  272. [refactor] add TxDownloadManager wrapping TxOrphanage, TxRequestTracker, and bloom filters
    This module is going to be responsible for managing everything related
    to transaction download, including txrequest, orphan transactions and
    package relay. It will be responsible for managing usage of the
    TxOrphanage and instructing PeerManager:
    - what tx or package-related messages to send to which peer
    - whether a tx or package-related message is allowed or useful
    - what transactions are available to try accepting to mempool
    
    Future commits will consolidate the interface and re-delegate
    interactions from PeerManager to TxDownloadManager.
    5f9004e155
  273. [doc] fix typo in m_lazy_recent_confirmed_transactions doc f6c860efb1
  274. [refactor] move ValidationInterface functions to TxDownloadManager
    This is move-only.
    af918349de
  275. [txdownload] add read-only reference to mempool
    This will become necessary in later commits that query mempool. We also
    introduce the TxDownloadOptions in this commit to make the later diff
    easier to review.
    84e4ef843d
  276. [refactor] move AlreadyHaveTx to TxDownload
    This is move-only.
    Also delete external RecentConfirmedTransactionsFilter() access since it
    is no longer necessary.
    f61d9e4b4b
  277. [refactor] move peer (dis)connection logic to TxDownload
    The information stored in TxDownloadConnectionInfo isn't used until the
    next commit.
    f48d36cd97
  278. [refactor] rename maybe_add_extra_compact_tx to first_time_failure
    The usage of this bool will increase in scope in the next commit.
    For this commit, the value of this bool is accurate at each
    ProcessInvalidTx callsite:
    - ProcessOrphanTx -> this tx is an orphan i.e. has been rejected before
    - ProcessPackageResult -> 1p1c only, each transaction is either an
      orphan or in m_lazy_recent_rejects_reconsiderable
    - ProcessMessage -> tx was received over p2p and validated for the first
      time
    288865338f
  279. [p2p] don't log tx invs when in IBD
    These invs are ignored anyway, and this allows us to more easily move
    the inv handling to TxDownloadManager in the next commit.
    58e09f244b
  280. [refactor] move tx inv/getdata handling to txdownload 042a97ce7f
  281. [refactor] move notfound processing to txdownload 3a41926d1b
  282. [refactor] move ProcessInvalidTx and ProcessValidTx definitions down
    ProcessInvalidTx will return a PackageToValidate, so it needs to be
    defined afterward.
    c8e67b9169
  283. [refactor] move new orphan handling to ProcessInvalidTx 416fbc952b
  284. [refactor] move Find1P1CPackage into ProcessInvalidTx 798cc8f5aa
  285. [p2p] restrict RecursiveDynamicUsage of orphans added to vExtraTxnForCompact
    There does not appear to be any reason why orphan transactions should be
    given special treatment.
    6797bc42a7
  286. [refactor] put peerman tasks at the end of ProcessInvalidTx f497414ce7
  287. [refactor] move Find1P1CPackage to txdownload
    Move-only.
    a8cf3b6e84
  288. [refactor] move valid tx processing to TxDownload c6b21749ca
  289. [refactor] move invalid tx processing to TxDownload
    Move-only. Also delete external RecentRejectsFilter() access since it is
    no longer necessary.
    c4ce0c1218
  290. [refactor] move invalid package processing to TxDownload 257568eab5
  291. [refactor] move new tx logic to txdownload
    Also delete external RecentRejectsReconsiderableFilter() access since it
    is no longer necessary.
    1e08195135
  292. [refactor] make AlreadyHaveTx and Find1P1CPackage private to TxDownloadImpl f150fb94e7
  293. [refactor] wrap {Have,Get}TxToReconsider in txdownload 969b07237b
  294. [refactor] add CheckIsEmpty and GetOrphanTransactions, remove access to TxDownloadMan internals fa7027d0fc
  295. [p2p] don't find 1p1cs for reconsiderable txns that are AlreadyHaveTx
    This is a slight behavior change: if a transaction is in both
    reconsiderable rejects and AlreadyHaveTx in another way, we don't try to
    return a 1p1c package. This is the correct thing to do, as we don't want
    to reconsider transactions that have multiple things wrong with them.
    For example, if a transaction is low feerate, and then later found to
    have a bad signature, we shouldn't try it again in a package.
    2266eba43a
  296. [p2p] don't process orphan if in recent rejects
    This should never happen normally, but just in case.
    5269d57e6d
  297. [p2p] filter 1p1c for child txid in recent rejects
    Avoid the fuzzer situation where:
    1. Orphanage has 2 transactions with the same txid, one with witness,
       one without witness.
    2. The transaction with witness is found to have
       `TX_INPUTS_NOT_STANDARD` error. The txid is added to recent rejects
    filter, and the tx with witness is deleted from orphanage.
    3. A low feerate parent is found. Find1P1CPackage finds the transaction
       with no witness in orphanage, and returns the package.
    4. net_processing has just been handed a package in which the child is
       already in recent rejects.
    f803c8ce8d
  298. [p2p] add TxDownloadOptions bool to make TxRequestTracker deterministic
    Forward this bool to the TxRequestTracker ctor. This is needed for
    stablity in TxDownloadManager fuzzers
    fa584cbe72
  299. [unit test] MempoolRejectedTx 699643f23a
  300. [fuzz] txdownloadman and txdownload_impl
    The txdownload_impl is similar but allows us to check specific
    invariants within its implementation. It will also change a lot more
    than the external interface (txdownloadman) will, so we will add more to
    this target later.
    0f4bc63585
  301. glozow force-pushed on Oct 25, 2024
  302. in src/test/txdownload_tests.cpp:30 in 699643f23a outdated
    25+    bool m_wtxid_in_rejects_recon;
    26+    bool m_keep_for_compact;
    27+    bool m_ignore_inv_txid;
    28+    bool m_ignore_inv_wtxid;
    29+
    30+    // Constructor. We are passing and casting ints because they are more readable in a table (see all_expected_results).
    


    theStack commented at 12:53 pm on October 25, 2024:
    0    // Constructor. We are passing and casting ints because they are more readable in a table (see expected_behaviors).
    

    glozow commented at 7:28 pm on October 28, 2024:
    Will fix if I retouch / in the next PR

    glozow commented at 12:46 pm on October 31, 2024:
    picked up in #31190
  303. in src/test/fuzz/txdownloadman.cpp:435 in 0f4bc63585
    430+            }
    431+        );
    432+
    433+        // Jump ahead in time
    434+        time += fuzzed_data_provider.PickValueInArray(TIME_SKIPS);
    435+        CheckInvariants(txdownload_impl, max_orphan_count);
    


    theStack commented at 1:20 pm on October 25, 2024:
    is it intentional to do no backward jumps in time here? (in contrast to the txdownloadman fuzz test above)

    glozow commented at 7:26 pm on October 28, 2024:
    Not intentional! I haven’t tried negative yet.

    glozow commented at 12:46 pm on October 31, 2024:
    added in #31190
  304. theStack approved
  305. theStack commented at 1:24 pm on October 25, 2024: contributor
    ACK 0f4bc635854597e15ea6968767fc4e5cf5bdd790
  306. DrahtBot requested review from instagibbs on Oct 25, 2024
  307. instagibbs approved
  308. instagibbs commented at 4:38 pm on October 25, 2024: member

    reACK 0f4bc635854597e15ea6968767fc4e5cf5bdd790

    could use some fuzzing c @marcofleon

  309. in src/node/txdownloadman_impl.cpp:199 in 042a97ce7f outdated
    194+    for (const GenTxid& gtxid : requestable) {
    195+        if (!AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) {
    196+            LogDebug(BCLog::NET, "Requesting %s %s peer=%d\n", gtxid.IsWtxid() ? "wtx" : "tx",
    197+                gtxid.GetHash().ToString(), nodeid);
    198+            requests.emplace_back(gtxid);
    199+            m_txrequest.RequestedTx(nodeid, gtxid.GetHash(), current_time + GETDATA_TX_INTERVAL);
    


    naumenkogs commented at 11:18 am on October 28, 2024:

    042a97ce7fc672021cdb1dee62a550ef19c208fb

    code-wise i think this better belongs around MakeAndPushMessage (what if the caller decides to postpone the actual sending), but no big deal.


    glozow commented at 7:27 pm on October 28, 2024:
    That would require peerman having access to the txrequesttracker directly, so not really a fan 🤔

    naumenkogs commented at 11:20 am on October 29, 2024:
    Yeah i agree, was confused initially. Please resolve.
  310. naumenkogs commented at 12:47 pm on October 28, 2024: member
    ACK 0f4bc635854597e15ea6968767fc4e5cf5bdd790
  311. marcofleon commented at 12:11 pm on October 29, 2024: contributor

    Fuzzed the two targets for about 250 cpu hours each. Coverage looks good. txdownloadman txdownloadman_impl

    Finally, the children hanging out in the recent reject filter are quiet 😌

    I’ll look to run the one honest peer test as well once it’s ready.

  312. achow101 commented at 6:11 pm on October 29, 2024: member
    light ACK 0f4bc635854597e15ea6968767fc4e5cf5bdd790
  313. achow101 merged this on Oct 29, 2024
  314. achow101 closed this on Oct 29, 2024

  315. glozow deleted the branch on Oct 30, 2024
  316. glozow commented at 8:03 pm on October 30, 2024: member
    Thanks for the reviews. Working on the followups + one_honest_peer fuzzer PR now

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-11-21 12:12 UTC

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