Overhaul transaction request logic #19184

pull sipa wants to merge 6 commits into bitcoin:master from sipa:202004_txrequest_rand changing 12 files +1354 −438
  1. sipa commented at 2:27 am on June 6, 2020: member

    This replaces the transaction request logic with an encapsulated class that maintains all the state surrounding it. By keeping it stand alone, it can be easily tested (using included unit tests and fuzz tests).

    The new logic is close in spirit to the current one, but much easier to specify I think:

    • Always prefer outbound peers over inbound peers, as long as any are available.
    • Don’t download from inbound peers during the first 2 s after announcement, to give time for announcements from outbound peers to come in.
    • The peer who is the very first to announce a txid is preferred (compared to other inbounds or other outbounds), so that chains of dependent transactions announced by one peer will generally not be split out to other peers.
    • After the peer that announces a txid first, requests go to uniformly selected peers (outbounds first; inbounds that are past their 2 s delay afterwards), to minimize the impact an attacker can have (as attackers can easily race the network and be first).
    • A txid is never requested twice from the same peer, as long as other candidates remain (even when reannounced).
    • Transactions are requested from new candidates as soon as old requests expire, or NOTFOUND is received, or invalid-witness transactions are received (i.e. this includes the functionality added by, and replaces, #18238).

    One significant change is the removal of the in-flight limit of 100 txids. I think this limit is not exactly desirable (e.g. when a txid is only announced by one peer, we should request it, regardless of how many other txids we’re already waiting for), and with the changes to handle NOTFOUND immediately, there is less problems with high limits. This implementation’s performance is also only determined by the number of total transaction/peer pairs tracked (O(log n)), not by the number in-flight, removing another reason for having that limit.

    A few further improvements could be investigated and relatively easily added if desirable:

    • Biasing away from peers with lots of in-flight requests (but not outright limiting like now).
    • Making the 2s inbound delay Poisson-distributed.
    • Adding a wtxid flag to entries to make it compatible with #18044.
    • Adding a time limit for the time between announcement and request.

    While the implementation is non-trivial due to performance and resource usage considerations, it has a significantly simpler exact specification. This specification is documented in src/txrequest.h, and reimplemented naively in the src/test/fuzz/txrequest.cpp fuzz test. It may be a good idea to review those first to understand the desired semantics before digging into the optimized implementation.

  2. fanquake added the label P2P on Jun 6, 2020
  3. sipa force-pushed on Jun 6, 2020
  4. sipa force-pushed on Jun 6, 2020
  5. sipa force-pushed on Jun 6, 2020
  6. sipa force-pushed on Jun 6, 2020
  7. sipa force-pushed on Jun 6, 2020
  8. fanquake requested review from naumenkogs on Jun 6, 2020
  9. fanquake requested review from amitiuttarwar on Jun 6, 2020
  10. fanquake requested review from ajtowns on Jun 6, 2020
  11. DrahtBot commented at 12:51 pm on June 6, 2020: member

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

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #19306 (refactor: Replace RecursiveMutex with Mutex in CTxMemPool by hebasto)
    • #19134 (test: Replace global wait_until with BitcoinTestFramework.wait_until and mininode.wait_until by dboures)
    • #19107 (p2p: Move all header verification into the network layer, extend logging by troygiorshev)
    • #18985 (bloom: use Span instead of std::vector for insert and contains [ZAP3] by jb55)
    • #18044 (Use wtxid for transaction relay by sdaftuar)

    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.

  12. naumenkogs commented at 3:41 pm on June 9, 2020: member

    Concept ACK.

    I was reading the description and this part is a bit unclear:

    Transactions are requested from new candidates as soon as old requests expire, or NOTFOUND is received, or invalid transactions are received (i.e. this includes the functionality added by, and replaces, #18238 ).

    Why invalid transactions are re-requested? Maybe I can figure out after reading the code, but right now this is confusing.

  13. sipa commented at 4:29 pm on June 9, 2020: member

    Why invalid transactions are re-requested? Maybe I can figure out after reading the code, but right now this is confusing.

    It requires invalid witnesses. When you request a txid T from peer A, and get a response with a transaction with invalid witness, but txid T, then the announcement (T,A) gets marked as COMPLETED.

  14. amitiuttarwar commented at 5:30 pm on June 18, 2020: contributor
    Concept ACK
  15. in src/txrequest.h:36 in 0b836eafc0 outdated
    31+ */
    32+class PriorityComputer {
    33+    const uint64_t m_k0, m_k1;
    34+public:
    35+    explicit PriorityComputer(bool deterministic);
    36+    PriorityComputer(const PriorityComputer& other) = default;
    


    jnewbery commented at 9:14 pm on June 19, 2020:
    This isn’t required. We never copy construct a priority computer.

    sipa commented at 11:26 pm on June 19, 2020:
    Fixed.
  16. in src/txrequest.h:88 in 0b836eafc0 outdated
    83+ *   added to the mempool, seen in a block, or for whatever reason we no longer care about it.
    84+ *
    85+ * - ReceivedResponse(peer, txid) converts any CANDIDATE or REQUESTED entry to a COMPLETED one. It should be called
    86+ *   whenever a transaction or NOTFOUND was received from a peer. When the transaction is acceptable, AlreadyHaveTx
    87+ *   should be called instead of (or in addition to) this operation.
    88+
    


    jnewbery commented at 9:20 pm on June 19, 2020:
    missing *. I don’t know if this messes with doxygen parsing.

    sipa commented at 11:26 pm on June 19, 2020:
    Fixed.
  17. in src/txrequest.h:109 in 0b836eafc0 outdated
    106+    //! Configuration option: timeout after requesting a download.
    107+    const std::chrono::microseconds m_timeout;
    108+
    109+    //! The various states a (txid,node) pair can be in.
    110+    //! Note that CANDIDATE is split up into 3 substates (NEW, BEST, OTHER), allowing more efficient implementation.
    111+    enum class State : uint8_t {
    


    jnewbery commented at 9:30 pm on June 19, 2020:
    Is there a reason to specify the underlying type here? Is it required so that it can be packed into 3 bits in the Entry bitfield below?

    sipa commented at 11:27 pm on June 19, 2020:
    Yeah, my concern is that otherwise an unsigned type may be picked, and I don’t know how that would interact with it being used as a bitfield.

    jnewbery commented at 10:34 pm on June 23, 2020:

    I’m getting this build warning:

    0In file included from txrequest.cpp:5:0:
    1./txrequest.h:158:25: warning: TxRequestTracker::Entry::m_state is too small to hold all values of enum class TxRequestTracker::State
    2         State m_state : 3;
    3                         ^
    

    configure output:

    0  target os     = linux
    1  build os      = linux-gnu
    2
    3  CC            = /usr/bin/ccache gcc
    4  CFLAGS        = -g -O2
    5  CPPFLAGS      =   -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2  -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS
    6  CXX           = /usr/bin/ccache g++ -std=c++11
    7  CXXFLAGS      =   -fstack-reuse=none -Wstack-protector -fstack-protector-all  -Wall -Wextra -Wformat -Wvla -Wswitch -Wformat-security -Wredundant-decls -Wunused-variable -Wdate-time -Wsign-compare  -Wno-unused-parameter -Wno-implicit-fallthrough   -g -O2 -fno-extended-identifiers
    8  LDFLAGS       = -pthread  -Wl,-z,relro -Wl,-z,now -pie  
    9  ARFLAGS       = cr
    

    gcc version:

    0→ gcc --version
    1gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
    

    ajtowns commented at 0:50 am on June 24, 2020:

    Looks like that warning is kind-of a bug in gcc (you can apparently put any value from the underlying type in the enum, and it’s complaining that they won’t all fit), but it’s not fixed until gcc 8.4 or 9.3 – https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414

    EDIT: (the example given in the linked list post for putting values from the underlying type in a scoped enum without a static_cast doesn’t work for me in g++, so not really sure what’s going on there)


    sipa commented at 2:53 am on June 24, 2020:

    @jnewbery Does it help if you remove the : uint8_t?

    If this is too annoying we can just store the state as a non-enum type.


    sipa commented at 3:33 am on June 24, 2020:
    Done. Changed to store it as a uint8_t.
  18. in src/txrequest.h:119 in 0b836eafc0 outdated
    115+        //! assuming there is no REQUESTED entry already.
    116+        CANDIDATE_BEST,
    117+        //! A REQUESTED entry.
    118+        REQUESTED,
    119+        //! A CANDIDATE entry that's not CANDIDATE_NEW or CANDIDATE_BEST.
    120+        CANDIDATE_OTHER,
    


    jnewbery commented at 9:31 pm on June 19, 2020:
    nit: sort this above REQUESTED so it’s next to the other CANDIDATE_ entries.

    sipa commented at 11:29 pm on June 19, 2020:
    That doesn’t work directly, as EntryTxid’s sort order relies on the ordering of these fields. It could use its own enum with the necessary order, that ExtractTxid maps to, but that feels like overkill.

    ajtowns commented at 6:09 am on June 20, 2020:
    Maybe worth adding a comment in the enum definition that the ordering is relied upon by the code that uses the ByTxid indexes. Reordering in ExtractTxid seems like overkill to me too fwiw.

    sipa commented at 5:51 pm on June 20, 2020:
    Done.

    jnewbery commented at 10:29 pm on June 23, 2020:
    Agree that a comment is sufficient.
  19. jnewbery commented at 9:39 pm on June 19, 2020: member

    Concept ACK

    There’s a lot to review here. It’ll take a while to work through it.

    I’ve left a few small style comments from a quick read through the new header file.

  20. sipa force-pushed on Jun 19, 2020
  21. sipa force-pushed on Jun 20, 2020
  22. sipa force-pushed on Jun 20, 2020
  23. ariard commented at 9:39 am on June 22, 2020: member

    Concept ACK, I think that’s a great move to dissociate message paths by class (addr, block, transaction, filters) instead of having all of them melt down in net_processing.

    I will do an approach ACK soon, but sounds to me this add more reliance on boost ? Also as it’s a critical part of the codebase how do we build confidence of the correctness of this new transaction request logic implementation with regards to substituted old part ? (It would be great to come with a model for this as I think it would be desirable to do the same kind of changes for the messy `COMPACT_BLOCKS part of the codebase)

  24. ajtowns commented at 5:34 pm on June 22, 2020: member

    sounds to me this add more reliance on boost ?

    It uses boost multi_index, which is also needed by the mempool (which does lookups by txid and feerate eg). If we found (or created) a replacement for that there, it should work here too, so I don’t think it’s much of an increase in the dependency.

  25. sipa commented at 6:55 pm on June 22, 2020: member

    @ariard As @ajtowns said, we’re already relying on boost/multi_index for CTxMemPool. I also spent a week or two thinking about how to implement this with custom data structures, but it’s… really hard to match the same performance/memory usage characteristics without multi_index. So I think it’s just the right tool for the job, plus it’s a headers-only library (so it doesn’t introduce runtime dependencies, which is a large part of what makes boost annoying).

    As far as correctness/desirability of the behavior goes:

    • Reasoning about whether the behavior is desirable, when considered standalone, I believe is much easier than the current one. The current behavior is very much defined by interactions of randomized insertion orders in a queue, and has few guarantees about what those actually accomplish. You can look at the fuzz test, which contains a complete reimplementation of the semantics (without optimized performance).

    • At a (very) high level you can see that the existing behavior is maintained (apart from the max-in-flight rule) by looking at the changes in test/functional/p2p_tx_download.py. But at a lower level it’s again hard to say much because the existing behavior is already so hard to describe exactly.

  26. in src/txrequest.h:332 in 3d5b4f2001 outdated
    327+
    328+    //! Query configuration parameter timeout.
    329+    std::chrono::microseconds GetTimeout() const { return m_timeout; }
    330+};
    331+
    332+#endif
    


    hebasto commented at 4:12 pm on June 23, 2020:

    3d5b4f20015246b7a0dd74b2dc89f389204dd9b8

    0#endif // BITCOIN_TXREQUEST_H
    

    See: Source code organization


    sipa commented at 3:34 am on June 24, 2020:
    Done.
  27. in src/txrequest.h:72 in 3d5b4f2001 outdated
    66+ *   response for from that peer. The exptime value determines when the request times out.
    67+ *
    68+ * - COMPLETED (txid, peer) entries representing txids that have been requested from a peer, but they timed out, a
    69+ *   NOTFOUND message was received for them, or an invalid response was received. They're only kept around to prevent
    70+ *   downloading them again. If only COMPLETED entries exist for a given txid remain (so no CANDIDATE or REQUESTED
    71+ *   ones), all of them are deleted (this is an invariant, and maintained by all operations below).
    


    hebasto commented at 4:19 pm on June 23, 2020:

    3d5b4f20015246b7a0dd74b2dc89f389204dd9b8

    nanonit: the word “completed” associates with a “success” for me (but I’m not a native English speaker though). These entries are not processed successfully, i.e., a transaction has not been received. Maybe rename COMPLETED to more general PROCESSED or something similar?


    sipa commented at 2:50 am on June 24, 2020:

    I’ve struggled with a good name for this, actually, and changed it a few times. The problem is that it corresponds to a number of scenarios:

    • We’ve received the transaction (but we don’t know (yet) if it’s a valid one)
    • We’ve received the transaction with an invalid witness
    • We’ve received a NOTFOUND message for the transaction
    • The request timed out

    I don’t think PROCESSED is an improvement, but I admit COMPLETED isn’t perfect either.

  28. in src/txrequest.h:86 in 3d5b4f2001 outdated
    80+ * - AlreadyHaveTx(txid) deletes all entries for a given txid. It should be called when a transaction is successfully
    81+ *   added to the mempool, seen in a block, or for whatever reason we no longer care about it.
    82+ *
    83+ * - ReceivedResponse(peer, txid) converts any CANDIDATE or REQUESTED entry to a COMPLETED one. It should be called
    84+ *   whenever a transaction or NOTFOUND was received from a peer. When the transaction is acceptable, AlreadyHaveTx
    85+ *   should be called instead of (or in addition to) this operation.
    


    hebasto commented at 4:25 pm on June 23, 2020:

    3d5b4f20015246b7a0dd74b2dc89f389204dd9b8

    It is not clear for me why the ReceivedResponse() “should be called whenever a transaction … was received from a peer”. It is not the case for a COMPLETED entry, no?


    sipa commented at 2:51 am on June 24, 2020:
    ReceivedResponse is what marks the entry as COMPLETED.
  29. hebasto commented at 4:34 pm on June 23, 2020: member
    Concept ACK. The interface presented in the txrequest.h header is clean and simple enough.
  30. in src/txrequest.h:174 in 1edf64025e outdated
    169+
    170+        //! Whether this entry can feasibly be selected if the current IsSelected() one disappears.
    171+        bool IsSelectable() const { return m_state == State::CANDIDATE_OTHER || m_state == State::CANDIDATE_BEST; }
    172+
    173+        //! Construct a new entry from scratch
    174+        Entry(const uint256& txid, uint64_t peer, bool inbound, State state, std::chrono::microseconds time, uint64_t
    


    jnewbery commented at 9:20 pm on June 23, 2020:
    I’m trying not to leave too many nits at this stage, but what kind of maniac breaks lines between the type and parameter name?!

    sipa commented at 2:56 am on June 24, 2020:
    🤪

    sipa commented at 3:34 am on June 24, 2020:
    Fixed.
  31. in src/txrequest.h:226 in 1edf64025e outdated
    202+    private:
    203+        const PriorityComputer& m_computer;
    204+    public:
    205+        EntryTxidExtractor(const PriorityComputer& computer) : m_computer(computer) {}
    206+        using result_type = EntryTxid;
    207+        result_type operator()(const Entry& entry) const { return entry.ExtractTxid(m_computer); }
    


    jnewbery commented at 10:43 pm on June 23, 2020:
    I didn’t understand why you were defining this alias until I tried to recompile without and it barfed. Do you think a comment that this is required for the Key Extractor concept (https://www.boost.org/doc/libs/1_54_0/libs/multi_index/doc/reference/key_extraction.html#key_extractors) would help, or is this well known?

    sipa commented at 3:35 am on June 24, 2020:

    Yes sorry John, you’re obviously an idiot if you don’t know the boost reference documentation by heart ;)

    Done.

  32. in src/txrequest.h:149 in 1edf64025e outdated
    144+
    145+    //! An announcement entry.
    146+    struct Entry {
    147+        //! Txid that was announced.
    148+        const uint256 m_txid;
    149+        //! For CANDIDATE{OTHER,_DELAYED,_BEST} the reqtime; for REQUESTED the exptime
    


    jnewbery commented at 10:45 pm on June 23, 2020:
    s/CANDIDATE{OTHER,_DELAYED,_BEST}/CANDIDATE_{NEW,BEST,OTHER}/

    sipa commented at 3:35 am on June 24, 2020:
    Done.
  33. in src/net_processing.cpp:1838 in 1edf64025e outdated
    1834@@ -1951,6 +1835,7 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uin
    1835         if (setMisbehaving.count(fromPeer)) continue;
    1836         if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
    1837             LogPrint(BCLog::MEMPOOL, "   accepted orphan tx %s\n", orphanHash.ToString());
    1838+            g_txrequest.AlreadyHaveTx(orphanHash);
    


    jnewbery commented at 0:29 am on June 24, 2020:
    I don’t think this will ever do anything. By the time we process this orphan transaction, we will have already called AlreadyHaveTx() in a SendMessages() call.
  34. in src/net_processing.cpp:2738 in 1edf64025e outdated
    2711         std::list<CTransactionRef> lRemovedTxn;
    2712 
    2713         if (!AlreadyHave(inv, mempool) &&
    2714             AcceptToMemoryPool(mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
    2715             mempool.check(&::ChainstateActive().CoinsTip());
    2716+            g_txrequest.AlreadyHaveTx(inv.hash);
    


    jnewbery commented at 0:37 am on June 24, 2020:

    There’s a slight inconsistency here: if the transaction is successfully accepted to the mempool, then we call AlreadyHaveTx() immediately to remove it from g_txrequest, but if it’s an orphan, then we only call AlreadyHaveTx() the next time a peer would potentially request it in SendMessages() (and I think the AlreadyHaveTx() call in ProcessOrphanTx() is basically always a no-op).

    For consistency, perhaps we should only call AlreadyHaveTx() in SendMessages().


    ajtowns commented at 3:42 am on June 24, 2020:
    Thinking about the case where someone malleates the witness for a tx relayed by txid not wtxid; should we maybe … I don’t know, track the other peers that offered the tx as part of the orphan record, so that we can try getting the correct witness data once we have the parents and can actually see if maybe the witness data was malleated?

    jnewbery commented at 1:30 pm on June 24, 2020:

    Pieter and I were discussing just this yesterday. The behaviour you describe (not rerequesting an orphan transaction from another announcing peer if we later discover that its witness is mutated) also exists in master. As soon as we put a transaction in mapOrphanTransactions it’ll return true from AlreadyHave() and will then be removed from each peer’s TxDownloadState.

    I think it’s ok to keep this behaviour. Orphan handling is to some extent best effort, so shouldn’t be relied on for timely transaction propagation. If an adversary is able to poison a transaction in the orphan set, then we won’t rerequest it from other peers when AcceptToMemoryPool() fails, but it won’t be added to our recent rejects filter, and if just one other peer later announces it to us, we’ll attempt to fetch it again.


    sipa commented at 2:53 am on July 5, 2020:

    I prefer to keep the fast-path AlreadyHaveTx calls when transactions/blocks come in, as they may in some cases be much faster than the AlreadyHave detection after GetRequestable (in particular, when the only alternative peer(s) are timing out).

    You’re right though that the code is inconsistent: orphans in the orphan pool are considered AlreadyHave, so txids should be reported as AlreadyHaveTx as soon as they’re added to the orphan pool. I’ve adopted that approach now.

  35. sipa force-pushed on Jun 24, 2020
  36. in src/txrequest.h:41 in 272a615fad outdated
    34+    explicit PriorityComputer(bool deterministic);
    35+    uint64_t operator()(const uint256& txid, uint64_t peer, bool inbound, bool first) const
    36+    {
    37+        uint64_t low_bits = 0;
    38+        if (!first) {
    39+            low_bits = CSipHasher(m_k0, m_k1).Write(txid.begin(), txid.size()).Write(peer).Finalize() >> 1;
    


    hebasto commented at 9:03 am on June 28, 2020:

    272a615fad6f93e1b7f9b6245ce9090d07fb4a51

    There is SipHashUint256Extra(uint64_t, uint64_t, uint256&, uint32_t) in the crypto/siphash module. Is it worth to add the reusable SipHashUint256Extra64(uint64_t, uint64_t, uint256&, uint64_t) there?


    sipa commented at 9:37 pm on July 1, 2020:
    I think this may make sense as a follow-up, but probably deserves benchmarks etc.

    elichai commented at 12:17 pm on July 6, 2020:
  37. in src/txrequest.h:158 in 272a615fad outdated
    154+        const uint64_t m_sequence : 59;
    155+        //! Whether it's from an outbound peer or not.
    156+        const bool m_inbound : 1;
    157+        //! What state this announcement is in
    158+        //! This is a uint8_t instead of a State to silence a GCC warning.
    159+        uint8_t m_state : 3;
    


    hebasto commented at 9:59 am on June 28, 2020:

    272a615fad6f93e1b7f9b6245ce9090d07fb4a51

    No warnings are observed on GCC 9.3.0. What GCC version you are referring to?


    sipa commented at 9:37 pm on July 1, 2020:
  38. in src/txrequest.cpp:220 in 272a615fad outdated
    218+}
    219+
    220+std::vector<uint256> TxRequestTracker::GetRequestable(uint64_t peer, std::chrono::microseconds now)
    221+{
    222+    // Move time.
    223+    SetTimePoint(now);
    


    hebasto commented at 10:41 am on June 28, 2020:

    272a615fad6f93e1b7f9b6245ce9090d07fb4a51

    Could passing the peer to SetTimePoint() improve efficiency by not processing entries related to other peers?


    sipa commented at 9:36 pm on July 1, 2020:

    That’s a good suggestion, but I don’t think it would actually be desirable (but fortunately, it also wouldn’t gain any efficiency). An earlier design of this code worked like that, but I decided to move off that.

    Say we’re calling GetRequestable for peer A. There is a txid T that was announced by peer A and peer B. For some reason, peer B is currently the CANDIDATE_BEST peer for T, but its exptime is in the past. That means that the (T,B) pair in reality should be COMPLETED now (but the data structure hasn’t been updated to reflect that), and peer A is actually the best match. If GetRequestable for A would only invoke SetTimePoint for currently-CANDIDATE_BEST entries, T won’t be returned, at it’ll only be when B calls GetRequestable that its expiration happens. This is probably not terrible behavior, but makes it much harder to exactly specify the behavior, and perhaps it introduces a bias in the selection - it’s hard to say.

    Thankfully, it also doesn’t gain anything. Every (txid,peer) pair only ever transitions once to COMPLETED, so certainly in bulk work there cannot be any difference. Furthermore, SetTimePoint only iterates over all expired entries in its first loop, as they’re sorted by time. So no time is wasted processing unexpired entries.

    A similar reasoning applies to the second loop.

  39. in src/txrequest.h:120 in 272a615fad outdated
    116+        //! A REQUESTED entry.
    117+        REQUESTED,
    118+        //! A CANDIDATE entry that's not CANDIDATE_NEW or CANDIDATE_BEST.
    119+        CANDIDATE_OTHER,
    120+        //! A COMPLETED entry.
    121+        COMPLETED,
    


    hebasto commented at 12:48 pm on June 28, 2020:
    IIUC, TxRequestTracker::MakeCompleted() relies on the fact that COMPLETED is the last entry in the enum State. Could a relevant comment be added here to preserve any breakdown in the future?
  40. DrahtBot added the label Needs rebase on Jun 29, 2020
  41. sipa force-pushed on Jul 1, 2020
  42. sipa commented at 4:40 am on July 1, 2020: member

    @gmaxwell suggested that the “first” feature (the very first peer to announce a transaction gets priority over other outbound or other inbounds) is changed to instead apply to the first not-max-in-flight peer. The idea is that this would prevent an attacker who races announcements, but doesn’t respond, will (as soon as they reach their max-in-flight) not be able to interfere with chains of dependent transactions (which we’d like to request from the same peer).

    This seems like a good idea, and easy to implement, but it’ll require some changes to tests.

  43. DrahtBot removed the label Needs rebase on Jul 1, 2020
  44. DrahtBot added the label Needs rebase on Jul 1, 2020
  45. sipa force-pushed on Jul 1, 2020
  46. in src/txrequest.cpp:150 in c204530ae2 outdated
    147+    auto& index = m_index.get<ByPeer>();
    148+    auto it = index.lower_bound(EntryPeer{peer, false, UINT256_ZERO});
    149+    while (it != index.end() && it->m_peer == peer) {
    150+        // Check what to continue with after this iteration. Note that 'it' may change position, and std::next(it)
    151+        // may be deleted in the process, so this needs to be decided beforehand.
    152+        auto it_next = (std::next(it) == index.end() || std::next(it)->m_peer != peer) ? index.end() : std::next(it);
    


    promag commented at 9:55 pm on July 1, 2020:

    c204530ae289c263f0a9b791bbcd031a552c3253

    and std::next(it) may be deleted in the process

    And is it safe to use it if next is erased? Otherwise I was expecting lower_bound call in each iteration.


    sipa commented at 10:20 pm on July 1, 2020:
    Yes, iterator validation for multi_index entries is like std::set; erasing entries does not affect the validity of iterators to other entries.
  47. DrahtBot removed the label Needs rebase on Jul 1, 2020
  48. DrahtBot added the label Needs rebase on Jul 4, 2020
  49. sipa force-pushed on Jul 5, 2020
  50. DrahtBot removed the label Needs rebase on Jul 5, 2020
  51. practicalswift commented at 9:29 pm on July 10, 2020: contributor

    Concept ACK

    Very nice fuzzing harness!

  52. DrahtBot added the label Needs rebase on Jul 16, 2020
  53. sipa force-pushed on Jul 17, 2020
  54. sipa force-pushed on Jul 17, 2020
  55. DrahtBot removed the label Needs rebase on Jul 17, 2020
  56. sipa force-pushed on Jul 18, 2020
  57. sipa marked this as a draft on Jul 18, 2020
  58. sipa commented at 3:27 am on July 18, 2020: member

    I’ve made a few changes:

    • Instead of having an outbound/inbound flag, ReceivedInv now just takes an explicit delay parameter. Entries with delay 0 are preferred. This matches reality more closely (as the outbound property wasn’t exactly for outbound peers, but also other whitelisted ones). It is also more compatible with the approach taken in #18044 (where the delay becomes a function of both outboundness and wtxidness).
    • Renamed CANDIDATE_NEW to CANDIDATE_DELAYED
    • Renamed CANDIDATE_OTHER to CANDIDATE_READY

    As there are still other changes I want to make to the behavior, I’ve marked this as Draft for now.

  59. sipa force-pushed on Jul 19, 2020
  60. Add txrequest module 259bf92d4a
  61. Add txrequest unit tests 75fd5dcba5
  62. Switch net_processing over to txrequest-based fetching 4f4f2f2de6
  63. More accurate removal of transactions from g_txrequest 5ffa197f30
  64. Delete limitedmap as it is unused 0bcbe4ef3b
  65. Add txrequest fuzzer that compares with naive implementation 4ffeaa9987
  66. sipa force-pushed on Jul 21, 2020
  67. in src/txrequest.h:29 in 259bf92d4a outdated
    24+ * The priority is used to order candidates for a given txid; lower priority values are selected first. It has the
    25+ * following properties:
    26+ * - Entries are called delayed if they have a reqtime different from std::chrono::microseconds::min(). This is the
    27+ *   case for entries from inbound non-whitelisted nodes. The priority for delayed entries is always higher than
    28+ *   non-delayed ones, so non-delayed ones are always preferred.
    29+ * - Within the set of delayed peers and within the set of non-delayed peers:
    


    ariard commented at 11:08 pm on July 22, 2020:

    259bf92

    It wasn’t clear at first read if m_first was applying for each set or the union of them. Why not restrain m_first to non-delayed peer only to favor fastest among more trusted peers ? We always favor non-delayed ones over delayed ones, so in PromoteCandidateNew, an entry from the former is likely to be favored but we sorting them uniformly randomly obviously don’t favor the fastest one. Or do you assume we receive and fetch in same threading sequence (ThreadMessageHandler) and reqtime initialized to now ensure this ?


    sipa commented at 9:34 am on September 21, 2020:
    The “first” concept works differently now. Have a look at #19988 to see if your comment still applies.
  68. in src/txrequest.h:51 in 259bf92d4a outdated
    46+
    47+/** Data structure to keep track of, and schedule, transaction downloads from peers.
    48+ *
    49+ * === High level behavior ===
    50+ *
    51+ * Transaction downloads are attempted in random order, first choosing among non-delayed peers, then delayed
    


    ariard commented at 11:10 pm on July 22, 2020:

    259bf92

    I think it’s a bit confusing to talk about delayed/non-delayed peers. Transaction entries in function of their announcing peers types but we may process those uniformly in other context. Also random order minus bias for first announcer ?


    sipa commented at 9:36 am on September 21, 2020:
    See #19988. The “delayed”/“nondelayed” distinction doesn’t exist anymore. There is an explicit separate boolean to ReceivedInv now for preferred/nonpreferred, and a way to specify reqtime explicitly. (in particular, because txid peers get a delay, even though they’re not preferred like outbounds are - so these concepts needed to be split).
  69. in src/txrequest.h:55 in 259bf92d4a outdated
    50+ *
    51+ * Transaction downloads are attempted in random order, first choosing among non-delayed peers, then delayed
    52+ * ones whose delay has passed. When a NOTFOUND is received, an invalid transaction is received, or a download
    53+ * times out (after a configurable delay), the next candidate is immediately scheduled according to the same rules
    54+ * above. The same transaction is never re-requested from the same peer, unless the transaction was forgotten about
    55+ * in the mean time. This happens whenever no candidates remain, or when a transaction is successfully received.
    


    ariard commented at 11:12 pm on July 22, 2020:

    259bf92

    What do you mean exactly here ? If a transaction has been successfully received (AlreadyHave() == true) we won’t re-request anyway, unless g_recently_confirmed rolls over ?


    sipa commented at 9:38 am on September 21, 2020:
    It is just clarifying when a transaction is forgotten about, which can happen when either no candidates for a tx remain, or when the transaction is successfully received. Only the first of those two matters to re-requesting, but it’s still important to specify that transactions are forgotten about when they’re successfully received. See if it’s clearer now.
  70. in src/txrequest.h:57 in 259bf92d4a outdated
    52+ * ones whose delay has passed. When a NOTFOUND is received, an invalid transaction is received, or a download
    53+ * times out (after a configurable delay), the next candidate is immediately scheduled according to the same rules
    54+ * above. The same transaction is never re-requested from the same peer, unless the transaction was forgotten about
    55+ * in the mean time. This happens whenever no candidates remain, or when a transaction is successfully received.
    56+ *
    57+ *
    


    ariard commented at 11:24 pm on July 22, 2020:

    259bf92

    I think you might laid out more high-level properties of transaction request logic here, among others:

    “Transaction downloads never guarantee to succeed, namely receiving transaction if you hear about its identifier. Success should be define as a best-effort, guaranteeing we advance towards entry removal for any peer who announced a txid”

    “To optimize bandwidth-saving, we limit parallel-fetching and favor sequential fetching (there MUST be only one REQUESTED entry at anytime for any given txid)”

    “We don’t guarantee fetching again a transaction for which validity/standardness may have change between re-announcements, until next block announcement (a AlreadyHave() triggers all txid entries removal)”

    “Assuming a fast-inbound attacker-controlled peer and low outbound not-controlled ones, a transaction propagation can be delayed at most for one GETDATA_TX_INTERVAL period”


    sipa commented at 0:48 am on September 22, 2020:

    I added a lot of comments explaining the rationale and some analysis to txrequest.h in #19989

    “Transaction downloads never guarantee to succeed, namely receiving transaction if you hear about its identifier. Success should be define as a best-effort, guaranteeing we advance towards entry removal for any peer who announced a txid”

    I have difficulty parsing what you’re trying to say here, but I’ve added some related comments. Please have a look to see if it’s better addressed now.

    “To optimize bandwidth-saving, we limit parallel-fetching and favor sequential fetching (there MUST be only one REQUESTED entry at anytime for any given txid)”

    Good idea, done.

    “We don’t guarantee fetching again a transaction for which validity/standardness may have change between re-announcements, until next block announcement (a AlreadyHave() triggers all txid entries removal)”

    I don’t understand this. New block announcements don’t affect TxRequestTracker (except for transactions that are in the block, and we don’t need anymore).

    “Assuming a fast-inbound attacker-controlled peer and low outbound not-controlled ones, a transaction propagation can be delayed at most for one GETDATA_TX_INTERVAL period”

    I added a much more accurate formula.


    ariard commented at 11:12 pm on September 30, 2020:

    I have difficulty parsing what you’re trying to say here, but I’ve added some related comments. Please have a look to see if it’s better addressed now.

    IIRC I meaned the following. “Learning about a transaction identifier doesn’t guarantee the node will successfully receive the transaction, no more take special steps to ensure it as sending non-solicited GETDATAs. An entry is guarantee to never stuck, assuming the local clock is moving forward”

    The first point is a minor. The second underscores an important property of new TxRequestTracker design, its state machine always progress towards a terminal state and should never halt. Though it’s so obvious it’s worthy to comment.

    I don’t understand this. New block announcements don’t affect TxRequestTracker (except for transactions that are in the block, and we don’t need anymore).

    IIRC I meaned the following scenario:

    • a peer announces and sends Tx 1, it is rejected due to its unconfirmed parent being invalid
    • all other pending requests for Tx 1 are deleted (ForgetTxHash)
    • the unconfirmed parent is re-announced, sent and accepted under a different wtxid
    • a peer announces Tx 1, the TxRequestTracker will never learn about it due to AlreadyHave()==true
    • a block is received, the rejection filter is received
    • a peer announces Tx 1, the TxRequestTracker learns and requests it

    That said, not reevaluating validity of child transactions when the parent one has changed is already an existing issue. Maybe RequestTx comment could be updated to underscore that a received INV isn’t unconditionally process by TxRequestTracker but depends of existing transaction relay state.


    sipa commented at 11:38 pm on September 30, 2020:

    Learning about a transaction identifier doesn’t guarantee the node will successfully receive the transaction, no more take special steps to ensure it as sending non-solicited GETDATAs.

    I still don’t understand what you’re trying to say.

    The second underscores an important property of new TxRequestTracker design, its state machine always progress towards a terminal state and should never halt.

    I just today realized that there is some nuance here. As long as you have at least one non-attacker peer that announces a transaction, I believe it is probabilistically true (over time the probability of not reaching a final state, where the transaction is forgotten about, tends to 0), but not more than that. An attacker can disconnect and reconnect, and with (arbitrary) amounts of luck, an attacker can be chosen every time, making the transaction stay arbitrarily long.

    I’m not sure it’s worth going that deep in comments.

    That said, not reevaluating validity of child transactions when the parent one has changed is already an existing issue. Maybe RequestTx comment could be updated to underscore that a received INV isn’t unconditionally process by TxRequestTracker but depends of existing transaction relay state.

    I don’t think this has anything to do with TxRequestTracker - it just does as it’s told, and the current code in net_processing, will not immediately retry for entries in recentRejects. We can document this, but it’s a far more high level thing, not specifying the behavior of the TxRequestTracker. Maybe more something for the wiki?

  71. in src/txrequest.h:79 in 259bf92d4a outdated
    74+ * The following operations are supported on this data structure:
    75+ *
    76+ * - ReceivedInv(txid, peer) adds a new CANDIDATE entry, unless one already exists for that (txid, peer) combination
    77+ *   (whether it's CANDIDATE, REQUESTED, or COMPLETED).
    78+ *
    79+ * - DeletedPeer(peer) deletes all entries for a given peer. It should be called when a peer goes offline.
    


    ariard commented at 11:27 pm on July 22, 2020:

    259bf92

    You should underscore state transition to CANDIDATE_BEST for the highest-scored CANDIDATE_READY. If we reintroduce in-flight rate-limiting we should avoid that a malicious A disconnecting can slowdown fetching from B, unique peer also announcing the malevolent set of txn max-in-flight-sized.


    sipa commented at 9:41 am on September 21, 2020:

    No, the description here is about the specification, where the distinction between the various CANDIDATE classes doesn’t exist; that’s an implementation detail.

    There are many caveats to having in-flight limits… I couldn’t come up with a way to do it basically, and then realized it’s not desirable either (if a peer is overloaded, but still the only one to announce a tx, we still want to fetch it!). There are many ways by which overfull peers could be deprioritized safely though without introducing a hard cap.

  72. in src/txrequest.h:81 in 259bf92d4a outdated
    76+ * - ReceivedInv(txid, peer) adds a new CANDIDATE entry, unless one already exists for that (txid, peer) combination
    77+ *   (whether it's CANDIDATE, REQUESTED, or COMPLETED).
    78+ *
    79+ * - DeletedPeer(peer) deletes all entries for a given peer. It should be called when a peer goes offline.
    80+ *
    81+ * - AlreadyHaveTx(txid) deletes all entries for a given txid. It should be called when a transaction is successfully
    


    ariard commented at 11:28 pm on July 22, 2020:

    259bf92

    Ah naming is hard, AlreadyEvaluatedTx/AlreadyProcessedTx to avoid collision with AlreadyHave ?


    sipa commented at 0:48 am on September 22, 2020:
    Renamed to ForgetTx.
  73. in src/txrequest.h:103 in 259bf92d4a outdated
     98+ *   exptime set to (now + timeout). It can ONLY be called immediately after GetRequestable was called (for the same
     99+ *   peer), with only AlreadyHaveTx and other RequestedTx calls (both for other txids) in between. Any other
    100+ *   non-const operation removes the ability to call RequestedTx.
    101+ */
    102+class TxRequestTracker {
    103+    //! Configuration option: timeout after requesting a download.
    


    ariard commented at 11:29 pm on July 22, 2020:

    259bf92

    Why make it configurable ? A minority of nodes tuning this variable would break privacy attackers assumptions ?


    sipa commented at 9:43 am on September 21, 2020:

    It was made configurable to simplify testing.

    In the new PR the setting is gone, and RequestedTx now takes an explicit exptime parameter instead.

  74. in src/txrequest.h:114 in 259bf92d4a outdated
    109+    enum class State : uint8_t {
    110+        //! A CANDIDATE entry whose reqtime is in the future.
    111+        CANDIDATE_DELAYED,
    112+        //! The best CANDIDATE for a given txid; only if there is no REQUESTED entry already for that txid.
    113+        //! The CANDIDATE_BEST is the lowest-priority entry among all CANDIDATE_READY (and _BEST) ones for that txid.
    114+        CANDIDATE_BEST,
    


    ariard commented at 11:36 pm on July 22, 2020:

    259bf92

    Actually I think this state only exists between GetRequestable and RequestedTx. Accessing AlreadyHave inside TxRequestTracker would allow to remove it and directly transition from READY to REQUESTED (though you have to tight more PromoteCandidateNew and GetRequestable because former yells BEST among txid indice). I guess you thought about it but deferred to future work ?


    sipa commented at 9:48 am on September 21, 2020:

    It’s not that simple, unfortunately.

    GetRequestable moves time (making things best/ready if they pass reqtime, making them completed if they pass exptime) for all entries - not just those of the peer GetRequestable was called for. This is necessary, as otherwise you can’t fairly decide what to give to which peer (the peer you’re calling for may have an announcement that just became ready, but another peer may also have an announcement for the same tx with a lower priority). So it’s conceptually true what you say, but only if you think of a full cycle of GetRequestable/RequestedTx for all peers.

    I really want to avoid having TxRequestTracker depend on being able to call AlreadyHave - it would make it much less modularized and harder to test the data structure itself.

  75. in src/txrequest.h:142 in 259bf92d4a outdated
    137+    // 2 [CANDIDATE_READY,CANDIDATE_BEST], time)
    138+    using EntryTime = std::pair<int, std::chrono::microseconds>;
    139+
    140+    //! The current sequence number. Increases for every announcement. This is used to sort txids returned by
    141+    //! GetRequestable in announcement order.
    142+    uint64_t m_sequence{0};
    


    ariard commented at 11:40 pm on July 22, 2020:

    259fb92

    Can you underscore more rational of m_sequence ? As it is only effect is to implicitly ensure transaction topology-order of GETDATAs if INVs received have been so but without guaranteeing chain of transactions are all fetched from same announcing peer ?


    sipa commented at 9:48 am on September 21, 2020:
    That’s it, exactly.
  76. in src/txrequest.h:248 in 259bf92d4a outdated
    243+                boost::multi_index::const_mem_fun<Entry, EntryTime, &Entry::ExtractTime>
    244+            >
    245+        >
    246+    >;
    247+
    248+    //! This tracker's main data structure.
    


    ariard commented at 11:42 pm on July 22, 2020:

    259fb92

    Maybe annotate worst-case memory size of this data structure : MAX_PEER_ANNOUNCEMENTS * max_connections * sizeof(Entry) ?


    sipa commented at 0:48 am on September 22, 2020:
    Added a comment explaining memory usage bounds.
  77. in src/txrequest.h:291 in 259bf92d4a outdated
    286+    //! be marked CANDIDATE_BEST.
    287+    void ChangeAndReselect(typename Index::index<ByTxid>::type::iterator it, State new_state);
    288+
    289+    //! Convert a CANDIDATE_DELAYED entry into a CANDIDATE_READY. If this makes it the new best CANDIDATE_READY (and no
    290+    //! REQUESTED exists) and better than the CANDIDATE_BEST (if any), it becomes the new CANDIDATE_BEST.
    291+    void PromoteCandidateNew(typename Index::index<ByTxid>::type::iterator it);
    


    ariard commented at 11:43 pm on July 22, 2020:

    259fb92

    After last changes, PromoteCandidateReady ?


    sipa commented at 0:49 am on September 22, 2020:
    Done.
  78. in src/txrequest.h:318 in 259bf92d4a outdated
    313+    void DeletedPeer(uint64_t uint64_t);
    314+
    315+    //! For whatever reason, we no longer need this txid. Delete any data related to it.
    316+    void AlreadyHaveTx(const uint256& txid);
    317+
    318+    //! We received a new inv, enter it into the data structure.
    


    ariard commented at 0:07 am on July 23, 2020:

    259fb92

    We call ReceivedInv on the behalf of a peer sending us an orphan tx. Is following scenario plausible ?

    • Malicia sends N txn to Alice and N’ conflicting to Bob
    • Malicia sends txB child spending from all parent of N to Alice
    • Alice sends txB to Bob, at reception Bob calls RequestTx/ReceivedInv on behalf of Alice
    • Malicia repeats this trick until reaching Alice’s MAX_PEER_TX_ANNOUNCEMENTS (and also avoid hitting MAX_STANDARD_TX_WEIGHT for orphan)
    • When Bob calls GetRequestable at GETDATA sending to Alice, only MAX_GETDATA_SZ will be effectively processed, the rest obstructing Alice’s announced invs until GETDATA_TX_INTERVAL expiration
    • Alice sends transaction for the MAX_GETDATA_SZ set
    • Malicia may repeat timely repeat trick to maintain transaction propagation blocking anticipating Alice-Bob round-trips

    Attack might seem costly because you have to fulfill Alice mempool with parent transaction but making them conflicted with the rest of network mempools avoid to effectively pay their feerate.


    sipa commented at 0:12 am on September 22, 2020:
    I think you’re confused. MAX_GETDATA_SZ doesn’t stop processing; it only makes the request be split up into multiple GETDATA messages.

    ariard commented at 11:30 pm on September 30, 2020:

    You’re right on MAX_GETDATA_SZ.

    Still, do you think that a third-party can delay transaction announcement between Alice and Bob by forcing her to send him orphans until reaching MAX_PEER_TX_ANNOUNCEMENTS ?


    sipa commented at 2:21 am on October 1, 2020:
    I need to think about this more.
  79. in src/txrequest.cpp:99 in 259bf92d4a outdated
    94+            it = Erase<ByTxid>(it);
    95+        } while (it != m_index.get<ByTxid>().end() && it->m_txid == txid);
    96+        return false;
    97+    }
    98+
    99+    // Mark the entry COMPLETED, and select the best best entry if needed.
    


    ariard commented at 0:09 am on July 23, 2020:

    259fb92

    nit: The first-sorted _READY ?


    sipa commented at 0:49 am on September 22, 2020:
    Done.
  80. in src/txrequest.cpp:121 in 259bf92d4a outdated
    116+            break;
    117+        }
    118+    }
    119+
    120+    while (!m_index.empty()) {
    121+        // If time went backwards, we may need to demote CANDIDATE_BEST and CANDIDATE_READY entries back
    


    ariard commented at 0:12 am on July 23, 2020:
    Time going backward wouldn’t be a hint of some clock manipulation and thus we should interpret this as a system failure and halting ? AFAICT, GetAdjustedTime() can go backward due to relying on peers clocks but we are relying on GetTime<T> here.

    sipa commented at 9:50 am on September 21, 2020:
    Time jumping backwards can happen for completely innocuous reasons, like leap seconds. Only monotonic clocks are guaranteed not to do that.
  81. in src/txrequest.cpp:135 in 259bf92d4a outdated
    130+    }
    131+}
    132+
    133+void TxRequestTracker::AlreadyHaveTx(const uint256& txid)
    134+{
    135+    auto it = m_index.get<ByTxid>().lower_bound(EntryTxid{txid, State::CANDIDATE_DELAYED, 0});
    


    ariard commented at 0:16 am on July 23, 2020:

    259fb92

    Maybe add as a comment “If transaction has already been evaluated once for this block tip, delete unconditionally all its entries.”

    Is this a slight behavior change ? Previously we would have only delete transaction from download for this peer. If you received a block between peers processing you have flushed recentRejects and so here return a different evaluation.


    sipa commented at 9:53 am on September 21, 2020:

    Maybe add as a comment “If transaction has already been evaluated once for this block tip, delete unconditionally all its entries.”

    I don’t understand what reevaluation has to do with that. And TxRequestTracker doesn’t know or care about the concept of blocks. It’s just told that a transaction is no longer needed, so it can forget announcements about it. Why the caller decided that was the right thing to do is not its concern.

    Is this a slight behavior change ?

    The whole thing is a giant behavior change - it’s changing out the semantics for something conceptually similar, but very different in the details.


    ariard commented at 11:33 pm on September 30, 2020:

    Why the caller decided that was the right thing to do is not its concern.

    Right, I understand comments should be concerned with other transaction relay mechanisms.

  82. in src/txrequest.cpp:197 in 259bf92d4a outdated
    192+    });
    193+}
    194+
    195+void TxRequestTracker::ReceivedResponse(uint64_t peer, const uint256& txid)
    196+{
    197+    // We need to search the ByPeer index for both (peer, false, txid) and (peer, true, txid).
    


    ariard commented at 0:20 am on July 23, 2020:

    259fb92

    The ByPeer index second member as true signals a state equal to CANDIDATE_BEST but I’m not sure if you can effectively hit here. An entry state is _BEST only between GetRequestable and either RequestedTx/AlreadyHaveTx, both of them respectively transitioning to REQUESTED/entry removal.


    sipa commented at 9:54 am on September 21, 2020:
    As explained elsewhere, no, BEST exists for much longer than that, and I’m 99% sure this can be hit.
  83. in src/txrequest.cpp:87 in 259bf92d4a outdated
    82+bool TxRequestTracker::MakeCompleted(typename TxRequestTracker::Index::index<ByTxid>::type::iterator it)
    83+{
    84+    // Nothing to be done if it's already COMPLETED.
    85+    if (it->GetState() == State::COMPLETED) return true;
    86+
    87+    if ((it == m_index.get<ByTxid>().begin() || std::prev(it)->m_txid != it->m_txid) &&
    


    ariard commented at 0:23 am on July 23, 2020:

    259fb92

    nit: I think this control flow conditional could be replaced by a method IsLastCompleted like we have IsSelected/IsSelectable. Easier to reason.


    sipa commented at 0:50 am on September 22, 2020:
    Good idea, done. Added IsOnlyNonCompleted.
  84. in src/net_processing.cpp:2485 in 4f4f2f2de6 outdated
    2481@@ -2599,7 +2482,7 @@ void ProcessMessage(
    2482                     pfrom.fDisconnect = true;
    2483                     return;
    2484                 } else if (!fAlreadyHave && !chainman.ActiveChainstate().IsInitialBlockDownload()) {
    2485-                    RequestTx(State(pfrom.GetId()), inv.hash, current_time);
    2486+                    RequestTx(pfrom, inv.hash, current_time);
    


    ariard commented at 0:26 am on July 23, 2020:

    4f4f2f2

    If fAlreadyHave==true call AlreadyHaveTx to clean transaction entries early ?


    sipa commented at 0:51 am on September 22, 2020:
    I don’t think that’s necessary - we just have to call ForgetTx whenever a transaction becomes alreadyhave, and prevent adding announcements for alreadyhave ones. If that’s done correctly, an addition ForgetTx here would always be a no-op.
  85. in src/net_processing.cpp:4229 in 4f4f2f2de6 outdated
    4270-                    // requests to outbound peers).
    4271-                    const auto next_process_time = CalculateTxGetDataTime(txid, current_time, !state.fPreferredDownload);
    4272-                    tx_process_time.emplace(next_process_time, txid);
    4273+                LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId());
    4274+                vGetData.push_back(inv);
    4275+                if (vGetData.size() >= MAX_GETDATA_SZ) {
    


    ariard commented at 0:27 am on July 23, 2020:

    4f4f2f2

    Maybe you can parameterize GetRequestable with MAX_GETDATA_SZ to avoid leftover marked as REQUESTED but not effectively fetched and thus wasting a GETDATA_TX_INTERVAL for them ?


    sipa commented at 9:58 am on September 21, 2020:
    GetRequestable could be parametrized with a limit for the number of returned responses, but that would just be to avoid having it return the same thing multiple times. I don’t understand what you mean with “avoid leftover marked as REQUESTED” and “wasting a GETDATA_TX_INTERVAL.

    ariard commented at 11:34 pm on September 30, 2020:
    This comment was wrong and part of my misunderstanding you pointed above.
  86. in src/test/txrequest_tests.cpp:215 in 75fd5dcba5 outdated
    210+    CheckRequestable(tracker, peers[0], now, {txid1, txid2}); // Check that for each peer, requests go out in announcement order,
    211+    tracker.DeletedPeer(peers[0]);
    212+    CheckRequestable(tracker, peers[1], now, {txid2, txid1}); // even when the clock went backwards during announcements.
    213+}
    214+
    215+void TestAll()
    


    ariard commented at 0:30 am on July 23, 2020:

    75fd5dc

    I think AlreadyHaveTx and time-goes-backward of SetTimePoint aren’t covered? I’ll try to add them soon, also ReceivedResponse should be called unconditionally at the end of each test to verify clean state ?


    sipa commented at 9:56 am on September 21, 2020:
    The unit tests are completely rewritten. There aren’t any for the time-moving-backward though, I’ll try to add that soon.

    sipa commented at 0:26 am on September 22, 2020:
    Actually, that’s not necessary anymore. As ReceivedInv doesn’t have a “now” argument anymore, the time of announcement clearly doesn’t matter, simply by the API being agnostic of it. I’m adding a test that has a reqtime that decreases, though.
  87. ariard commented at 0:33 am on July 23, 2020: member

    Approach ACK, mostly asking for specification/behavior clarifications. I see better how boost::multi_index_container fits well for the job here, not a concern to me anymore.

    Overall, great work!

  88. sipa commented at 0:39 am on July 23, 2020: member
    @ariard Thanks for the thorough review. I’ll go over your comments soon, but know there are a few imminent changes first (namely the suggestion #19184 (comment), and adding support for wtxid relay tracking from #18044). If you want to help working on the tests, it’s probably best to wait until those are complete.
  89. jonatack commented at 5:19 am on July 30, 2020: member
    Concept ACK, on first read-through the changes look good, will review when this comes out of draft status.
  90. instagibbs commented at 7:14 pm on August 20, 2020: member
    concept ACK
  91. sipa closed this on Sep 21, 2020

  92. laanwj referenced this in commit c2c4dbaebd on Oct 14, 2020
  93. sidhujag referenced this in commit 7c94367064 on Oct 16, 2020
  94. DrahtBot locked this on Feb 15, 2022

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-17 12:12 UTC

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