rpc: getblockfrompeer #20295

pull Sjors wants to merge 2 commits into bitcoin:master from Sjors:2020/11/getblockfrompeer changing 18 files +296 −93
  1. Sjors commented at 1:39 pm on November 3, 2020: member

    This adds an RPC method to fetch a block directly from a peer. This can used to fetch stale blocks with lower proof of work that are normally ignored by the node (headers-only in getchaintips).

    Usage:

    0bitcoin-cli getblockfrompeer HASH peer_n
    

    Closes #20155

    Limitations:

    • you have to specify which peer to fetch the block from
    • the node must already have the header
  2. fanquake added the label RPC/REST/ZMQ on Nov 3, 2020
  3. fanquake added the label P2P on Nov 3, 2020
  4. DrahtBot commented at 5:47 pm on November 3, 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:

    • #23497 (Add src/node/ and src/wallet/ code to node:: and wallet:: namespaces by ryanofsky)
    • #17786 (refactor: Nuke policy/fees->mempool circular dependencies by hebasto)

    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.

  5. DONALDSULLENDER approved
  6. Fi3 commented at 6:20 pm on November 6, 2020: none

    @Sjors Please note that I’m very new to C++ and I’m here only to learn.

    Concept AKN, IMO can also be useful if the user need info about a pruned block.

    Not sure if getblockfrompeer should persist the block, maybe just print it. For consistency the interface of getblock (verbosity 0, 1, 2) could be reused. Maybe getblockfrompeer could just be a flag of getblock.

    Also, IMO GetBlockChecked should notify the user to use getblockfrompeer for block that are not on disk.

    Check if the node is connected Checking for connectivity could be done with connman->GetNodeCount as in mining.cpp.

    Mark the block as in flight Instead of mark the block as in flight and persist the block, I would add 2 fields to the public interface of CNode the first to notify ProcessMessage that a block with a certain hash is expected, the second to check (in a loop) for the requested block.

  7. Sjors commented at 11:06 am on November 11, 2020: member

    @Fi3 being able to (temporarily) retrieve pruned blocks is useful too indeed.

    It can be difficult to find nodes that still have a stale block, so I’d rather hold on to it. That also allows inspection using the regular methods, getblock, getchaintips and getrawtransaction (e.g. to learn about a specific doublespend). Pruned nodes will toss it automatically after a while (IIUC).

    I also want to be able to fully validate the block. It’s very time consuming, but it works: call invalidateblock on the main chain block for the same height, so reorgs to the block you just downloaded and verified it. Then call reconsiderblock to sync to the tip again.

  8. Sjors force-pushed on Nov 11, 2020
  9. Sjors marked this as ready for review on Nov 11, 2020
  10. jonathanbier commented at 1:59 pm on November 11, 2020: none

    Concept ACK

    This would be useful for https://forkmonitor.info/ to assess the validity of shorter chains

  11. benthecarman commented at 0:30 am on November 12, 2020: contributor

    Concept ACK

    This would be very useful for testing

  12. Fi3 commented at 12:37 pm on November 13, 2020: none

    @Sjors

    It can be difficult to find nodes that still have a stale block, so I’d rather hold on to it.

    I agree, didn’t consider it.

  13. luke-jr commented at 6:09 pm on November 13, 2020: member

    Concept -1

    I think it would be better to just do the right thing if you getblock a block you don’t have… Don’t make users figure out which peer has it…?

  14. Sjors commented at 12:31 pm on November 14, 2020: member

    @luke-jr my thinking was to expand it later, to try all peers: getblockfrompeer HASH *. Overloading getblock in a way that could make it unresponsive would seem controversial. Adding a fetch_from_peer argument to getblock could make sense as an alternative.

    In practice none of the existing peers might have it, so you’ll have to:

    • manually connect to new peers and retry; or
    • add an argument with an array of IPs to try (but not keep as a peer); or
    • go through peers.dat entries

    This could get complicated, so a fresh RPC seemed like a more future proof approach.

  15. fanquake deleted a comment on Nov 14, 2020
  16. fanquake deleted a comment on Nov 14, 2020
  17. DrahtBot added the label Needs rebase on Dec 9, 2020
  18. Sjors force-pushed on Dec 9, 2020
  19. Sjors commented at 4:15 pm on December 9, 2020: member
    I moved FetchBlock to the PeerManager while rebasing on #19910. Not sure if that’s the right home for it, cc @jnewbery.
  20. DrahtBot removed the label Needs rebase on Dec 9, 2020
  21. DrahtBot added the label Needs rebase on Jan 13, 2021
  22. Sjors force-pushed on Jan 15, 2021
  23. DrahtBot removed the label Needs rebase on Jan 15, 2021
  24. DrahtBot added the label Needs rebase on Jan 28, 2021
  25. Sjors force-pushed on Feb 3, 2021
  26. DrahtBot removed the label Needs rebase on Feb 3, 2021
  27. prusnak commented at 7:25 pm on March 26, 2021: contributor
    Does this work with the pruned node too? If yes, then this implements option 2) of #19312
  28. Sjors commented at 9:16 am on March 29, 2021: member
    It does, however I’m not sure how quickly the node re-prunes it.
  29. DrahtBot added the label Needs rebase on Apr 1, 2021
  30. Sjors commented at 9:36 am on April 1, 2021: member
    Rebased. No longer needs to haul the mempool around.
  31. Sjors force-pushed on Apr 1, 2021
  32. DrahtBot removed the label Needs rebase on Apr 1, 2021
  33. DrahtBot added the label Needs rebase on Apr 13, 2021
  34. Sjors force-pushed on Apr 14, 2021
  35. DrahtBot removed the label Needs rebase on Apr 14, 2021
  36. DrahtBot added the label Needs rebase on Apr 21, 2021
  37. Sjors commented at 7:59 am on April 21, 2021: member
    Rebased after #21719
  38. Sjors force-pushed on Apr 21, 2021
  39. Sjors commented at 8:09 am on April 21, 2021: member

    Linter:

    A new circular dependency in the form of “rpc/blockchain -> rpc/net -> rpc/blockchain” appears to have been introduced. @MarcoFalke any thoughts on how to avoid that? I could move this new RPC method to rpc/net, or maybe the EnsurePeerman and EnsureConnman helpers should be in their own file?

  40. MarcoFalke commented at 9:14 am on April 21, 2021: member
    I guess by moving the Ensure* helpers to rpc/util (or a new module)?
  41. DrahtBot removed the label Needs rebase on Apr 21, 2021
  42. in src/net_processing.cpp:249 in f8d9dbf796 outdated
    245@@ -246,6 +246,7 @@ class PeerManagerImpl final : public PeerManager
    246 
    247     /** Implement PeerManager */
    248     void CheckForStaleTipAndEvictPeers() override;
    249+    bool FetchBlock(const NodeId nodeid, const CBlockIndex* pindex) override;
    


    jnewbery commented at 10:24 am on April 21, 2021:

    No need to make the pass-by-value NodeId nodeid const.

    Style guide is to use snake_case and not hungarian notation for parameter names:

    0    bool FetchBlock(const NodeId id, const CBlockIndex* index) override;
    
  43. in src/net_processing.cpp:1256 in f8d9dbf796 outdated
    1252@@ -1252,6 +1253,33 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    1253            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    1254 }
    1255 
    1256+bool PeerManagerImpl::FetchBlock(const NodeId nodeid, const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
    


    jnewbery commented at 10:27 am on April 21, 2021:

    I don’t think this EXCLUSIVE_LOCKS_REQUIRED(cs_main) is doing anything. This function is being called without cs_main (by getblockfrompeer()).

    I think it’s better to remove this annotation and lock cs_main within this function.

  44. in src/net_processing.cpp:1260 in f8d9dbf796 outdated
    1252@@ -1252,6 +1253,33 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    1253            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    1254 }
    1255 
    1256+bool PeerManagerImpl::FetchBlock(const NodeId nodeid, const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
    1257+{
    1258+    PeerRef peer = GetPeerRef(nodeid);
    1259+    if (peer == nullptr) return false;
    1260+    uint32_t nFetchFlags = 0;
    


    jnewbery commented at 10:29 am on April 21, 2021:

    Move this down to where it’s first used, and make const:

    0    const uint32_t nFetchFlags = state->fHaveWitness ? MSG_WITNESS_FLAG : 0;
    

    Sjors commented at 2:49 pm on April 21, 2021:
    I followed your suggestion below to just ignore pre-segwit nodes, so no need to have nFetchFlags around…
  45. in src/net_processing.cpp:1273 in f8d9dbf796 outdated
    1268+    }
    1269+    if (state->fHaveWitness) {
    1270+        nFetchFlags |= MSG_WITNESS_FLAG;
    1271+    }
    1272+    std::vector<CInv> vInv(1);
    1273+    vInv[0] = CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash());
    


    jnewbery commented at 10:42 am on April 21, 2021:

    Use initializer list ctor:

    0    std::vector<CInv> vInv{CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())};
    
  46. in src/net_processing.cpp:1432 in f8d9dbf796 outdated
    1252@@ -1252,6 +1253,33 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    1253            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    1254 }
    1255 
    1256+bool PeerManagerImpl::FetchBlock(const NodeId nodeid, const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
    1257+{
    


    jnewbery commented at 10:43 am on April 21, 2021:
    Perhaps return false if we’re importing or reindexing (since the received block message will be ignored)
  47. in src/net_processing.cpp:1269 in f8d9dbf796 outdated
    1264+    }
    1265+    const int node_sync_height = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
    1266+    if (pindex->nHeight > node_sync_height) {
    1267+        return false;
    1268+    }
    1269+    if (state->fHaveWitness) {
    


    jnewbery commented at 10:45 am on April 21, 2021:
    Perhaps just return false if the peer doesn’t have MSG_WITNESS_FLAG. It’s 2021. Receiving a block without witnesses isn’t going to be useful.
  48. in src/net_processing.cpp:1279 in f8d9dbf796 outdated
    1274+    bool success = m_connman.ForNode(nodeid, [this, vInv](CNode* pnode) {
    1275+        const CNetMsgMaker msgMaker(pnode->GetCommonVersion());
    1276+        this->m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETDATA, vInv));
    1277+        return true;
    1278+    });
    1279+    MarkBlockAsInFlight(nodeid, pindex->GetBlockHash(), pindex);
    


    jnewbery commented at 10:47 am on April 21, 2021:
    Perhaps move this above the ForNode() call and don’t send the getdata if it returns false, since that indicates that the block is already in flight. We don’t want a faulty RPC client causing us to spam a peer with multiple requests for the same block.

    Sjors commented at 3:19 pm on April 21, 2021:
    Not sure if I got this right now…
  49. in src/net_processing.cpp:1274 in f8d9dbf796 outdated
    1269+    if (state->fHaveWitness) {
    1270+        nFetchFlags |= MSG_WITNESS_FLAG;
    1271+    }
    1272+    std::vector<CInv> vInv(1);
    1273+    vInv[0] = CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash());
    1274+    bool success = m_connman.ForNode(nodeid, [this, vInv](CNode* pnode) {
    


    jnewbery commented at 10:48 am on April 21, 2021:
    Maybe add a log if the message is sent.
  50. in src/net_processing.cpp:1272 in f8d9dbf796 outdated
    1267+        return false;
    1268+    }
    1269+    if (state->fHaveWitness) {
    1270+        nFetchFlags |= MSG_WITNESS_FLAG;
    1271+    }
    1272+    std::vector<CInv> vInv(1);
    


    jnewbery commented at 10:48 am on April 21, 2021:

    style guide:

    0    std::vector<CInv> invs(1);
    
  51. in src/rpc/blockchain.cpp:790 in f8d9dbf796 outdated
    785+    uint256 hash(ParseHashV(request.params[0], "hash"));
    786+
    787+    const CBlockIndex* pblockindex;
    788+    {
    789+        LOCK(cs_main);
    790+        pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
    


    jnewbery commented at 10:49 am on April 21, 2021:
    Don’t use the global chainman - use EnsureChainman()
  52. in src/rpc/blockchain.cpp:800 in f8d9dbf796 outdated
    795+    }
    796+
    797+    const UniValue &id_arg = request.params[1];
    798+    const NodeId nodeid = (NodeId) id_arg.get_int64();
    799+    const NodeContext& node = EnsureAnyNodeContext(request.context);
    800+    PeerManager& peerman = EnsurePeerman(node);
    


    jnewbery commented at 10:50 am on April 21, 2021:
    Move these to the top of the function - if these assumptions fail we should throw immediately instead of taking cs_main and looking up the block.
  53. in src/rpc/blockchain.cpp:792 in f8d9dbf796 outdated
    786+
    787+    const CBlockIndex* pblockindex;
    788+    {
    789+        LOCK(cs_main);
    790+        pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
    791+    }
    


    jnewbery commented at 10:51 am on April 21, 2021:
    0    const CBlockIndex* const pblockindex = WITH_LOCK(cs_main, return g_chainman.m_blockman.LookupBlockIndex(hash););
    
  54. in src/rpc/blockchain.cpp:762 in f8d9dbf796 outdated
    773+                "\nAttempts to fetch block from a given peer.\n",
    774+                {
    775+                    {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
    776+                    {"nodeid", RPCArg::Type::NUM, RPCArg::Optional::NO, "The node ID (see getpeerinfo for node IDs)"},
    777+                },
    778+                RPCResult{RPCResult::Type::NONE, "", ""},
    


    jnewbery commented at 10:52 am on April 21, 2021:
    Return an empty object in case we want to extend the API in the future.
  55. in src/rpc/blockchain.cpp:773 in f8d9dbf796 outdated
    766@@ -764,6 +767,46 @@ static RPCHelpMan getmempoolentry()
    767     };
    768 }
    769 
    770+static RPCHelpMan getblockfrompeer()
    771+{
    772+    return RPCHelpMan{"getblockfrompeer",
    773+                "\nAttempts to fetch block from a given peer.\n",
    


    jnewbery commented at 10:52 am on April 21, 2021:

    Imperative mood:

    0                "\nAttempt to fetch block from a given peer.\n",
    
  56. jnewbery commented at 10:54 am on April 21, 2021: member

    I moved FetchBlock to the PeerManager while rebasing on #19910. Not sure if that’s the right home for it, cc @jnewbery.

    It’s exactly the right place for it. PeerManager is the node’s interface to net_processing.

  57. Sjors force-pushed on Apr 21, 2021
  58. Sjors commented at 3:18 pm on April 21, 2021: member
    Thanks for the feedback! I moved the Ensure* helpers to util.h and addressed the above comments.
  59. Sjors force-pushed on Apr 21, 2021
  60. in src/rpc/blockchain.cpp:735 in 91abc3760e outdated
    730+                + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
    731+                },
    732+        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    733+{
    734+    const UniValue &id_arg = request.params[1];
    735+    const NodeId nodeid = (NodeId) id_arg.get_int64();
    


    jnewbery commented at 3:52 pm on April 21, 2021:

    Prefer a named cast to c-style casts (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es49-if-you-must-use-a-cast-use-a-named-cast)

    0    const NodeId nodeid = static_cast<NodeId>(id_arg.get_int64());
    

    jnewbery commented at 4:03 pm on April 21, 2021:

    Perhaps join these two lines. No point in having a id_arg var that just gets used once and thrown away:

    0    const NodeId nodeid = static_cast<NodeId>(request.params[1].get_int64());
    
  61. in src/rpc/blockchain.cpp:785 in 91abc3760e outdated
    735+    const NodeId nodeid = (NodeId) id_arg.get_int64();
    736+    const NodeContext& node = EnsureAnyNodeContext(request.context);
    737+    ChainstateManager& chainman = EnsureChainman(node);
    738+    PeerManager& peerman = EnsurePeerman(node);
    739+
    740+    uint256 hash(ParseHashV(request.params[0], "hash"));
    


    jnewbery commented at 3:54 pm on April 21, 2021:

    This ordering seems weird to me (read param 1, then state assumptions, then read param 0). I’d suggest:

    0    const NodeContext& node = EnsureAnyNodeContext(request.context);
    1    ChainstateManager& chainman = EnsureChainman(node);
    2    PeerManager& peerman = EnsurePeerman(node);
    3
    4    uint256 hash(ParseHashV(request.params[0], "hash"));
    5
    6    const UniValue &id_arg = request.params[1];
    7    const NodeId nodeid = (NodeId) id_arg.get_int64();
    
  62. jnewbery commented at 4:04 pm on April 21, 2021: member
    Looking good (apart from the linker failure :slightly_smiling_face: )
  63. Sjors force-pushed on Apr 21, 2021
  64. Sjors force-pushed on Apr 21, 2021
  65. in src/rpc/util.cpp:55 in 065540ad54 outdated
    50+ChainstateManager& EnsureChainman(const NodeContext& node)
    51+{
    52+    if (!node.chainman) {
    53+        throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found");
    54+    }
    55+    WITH_LOCK(::cs_main, CHECK_NONFATAL(std::addressof(g_chainman) == std::addressof(*node.chainman)));
    


    jnewbery commented at 7:10 pm on April 21, 2021:

    This causes a link failure for libbitcoin_common.

    Perhaps move these Ensure* functions to a new src/rpc/server_util.cpp which is part of libbitcoin_server. src/rpc/util is for utility functions that are shared between server and client.


    Sjors commented at 7:16 pm on April 21, 2021:
    I see. For some reason macOS is totally happy with it, but on Linux I can see it blow up.
  66. Sjors force-pushed on Apr 21, 2021
  67. Sjors commented at 7:38 pm on April 21, 2021: member
    I added server_util.cpp. This seems to make the linker happy.
  68. Sjors force-pushed on Apr 21, 2021
  69. in src/Makefile.am:207 in f2208cf5d4 outdated
    203   rpc/protocol.h \
    204   rpc/rawtransaction_util.h \
    205   rpc/register.h \
    206   rpc/request.h \
    207   rpc/server.h \
    208+	rpc/server_util.h \
    


    prusnak commented at 8:02 pm on April 21, 2021:
    use spaces, not tab
  70. in src/Makefile.am:348 in f2208cf5d4 outdated
    344@@ -345,6 +345,7 @@ libbitcoin_server_a_SOURCES = \
    345   rpc/net.cpp \
    346   rpc/rawtransaction.cpp \
    347   rpc/server.cpp \
    348+	rpc/server_util.cpp \
    


    prusnak commented at 8:02 pm on April 21, 2021:
    use spaces, not tab
  71. in src/Makefile.am:538 in f2208cf5d4 outdated
    534@@ -534,7 +535,7 @@ libbitcoin_common_a_SOURCES = \
    535   psbt.cpp \
    536   rpc/rawtransaction_util.cpp \
    537   rpc/external_signer.cpp \
    538-  rpc/util.cpp \
    539+	rpc/util.cpp \
    


    prusnak commented at 8:03 pm on April 21, 2021:
    use spaces, not tab
  72. prusnak changes_requested
  73. in src/rpc/server_util.h:19 in f2208cf5d4 outdated
    14+struct NodeContext;
    15+class PeerManager;
    16+
    17+/**
    18+ * Ensure* helpers
    19+ */
    


    MarcoFalke commented at 6:18 am on April 22, 2021:
    Can remove this comment? The code is already self-documenting and one can see that all helpers in this section start with Ensure.
  74. Sjors commented at 7:14 am on April 22, 2021: member
    Comments addressed.
  75. Sjors force-pushed on Apr 22, 2021
  76. jnewbery commented at 8:04 am on April 22, 2021: member

    The includes could be improved to include what’s needed and not include what’s not needed:

     0diff --git a/src/rpc/server_util.cpp b/src/rpc/server_util.cpp
     1index dfe07f5c3d..046a630b51 100644
     2--- a/src/rpc/server_util.cpp
     3+++ b/src/rpc/server_util.cpp
     4@@ -2,12 +2,18 @@
     5 // Distributed under the MIT software license, see the accompanying
     6 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
     7 
     8+#include <rpc/server_util.h>
     9+
    10 #include <net_processing.h>
    11 #include <node/context.h>
    12-#include <rpc/server_util.h>
    13+#include <policy/fees.h>
    14+#include <rpc/protocol.h>
    15+#include <rpc/request.h>
    16+#include <txmempool.h>
    17 #include <util/system.h>
    18 #include <validation.h>
    19-#include <validationinterface.h>
    20+
    21+#include <any>
    22 
    23 NodeContext& EnsureAnyNodeContext(const std::any& context)
    24 {
    25diff --git a/src/rpc/server_util.h b/src/rpc/server_util.h
    26index c330efd0e6..dd8e8a6042 100644
    27--- a/src/rpc/server_util.h
    28+++ b/src/rpc/server_util.h
    29@@ -5,11 +5,11 @@
    30 #ifndef BITCOIN_RPC_SERVER_UTIL_H
    31 #define BITCOIN_RPC_SERVER_UTIL_H
    32 
    33-#include <rpc/protocol.h>
    34-#include <rpc/request.h>
    35+#include <any>
    36 
    37 class CBlockPolicyEstimator;
    38 class CConnman;
    39+class ChainstateManager;
    40 class CTxMemPool;
    41 struct NodeContext;
    42 class PeerManager;
    
  77. Sjors force-pushed on Apr 22, 2021
  78. Sjors commented at 8:23 am on April 22, 2021: member
    Also done.
  79. in src/net_processing.cpp:1256 in 7544381c2e outdated
    1252@@ -1252,6 +1253,39 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    1253            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    1254 }
    1255 
    1256+bool PeerManagerImpl::FetchBlock(NodeId id, const CBlockIndex* index)
    


    jnewbery commented at 10:35 am on April 22, 2021:

    If you’re taking a pointer, you should check that it’s non-null before dereferencing it. Even better, pass by reference to communicate that this must be called with a valid CBlockIndex:

     0diff --git a/src/net_processing.cpp b/src/net_processing.cpp
     1index a822960dc2..f552aa36b5 100644
     2--- a/src/net_processing.cpp
     3+++ b/src/net_processing.cpp
     4@@ -246,7 +246,7 @@ public:
     5 
     6     /** Implement PeerManager */
     7     void CheckForStaleTipAndEvictPeers() override;
     8-    bool FetchBlock(NodeId id, const CBlockIndex* index) override;
     9+    bool FetchBlock(NodeId id, const CBlockIndex& index) override;
    10     bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override;
    11     bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; }
    12     void SendPings() override;
    13@@ -1253,7 +1253,7 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    14            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    15 }
    16 
    17-bool PeerManagerImpl::FetchBlock(NodeId id, const CBlockIndex* index)
    18+bool PeerManagerImpl::FetchBlock(NodeId id, const CBlockIndex& index)
    19 {
    20     PeerRef peer = GetPeerRef(id);
    21     if (peer == nullptr) return false;
    22@@ -1263,12 +1263,12 @@ bool PeerManagerImpl::FetchBlock(NodeId id, const CBlockIndex* index)
    23     if (state == nullptr) return false;
    24     if (!state->fHaveWitness) return false;
    25     const int node_sync_height = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
    26-    if (index->nHeight > node_sync_height) {
    27+    if (index.nHeight > node_sync_height) {
    28         return false;
    29     }
    30-    std::vector<CInv> invs{CInv(MSG_BLOCK | MSG_WITNESS_FLAG, index->GetBlockHash())};
    31+    std::vector<CInv> invs{CInv(MSG_BLOCK | MSG_WITNESS_FLAG, index.GetBlockHash())};
    32     // Mark block as in-flight unless it already is
    33-    if (!MarkBlockAsInFlight(id, index->GetBlockHash(), index)) return false;
    34+    if (!MarkBlockAsInFlight(id, index.GetBlockHash(), &index)) return false;
    35     bool success = m_connman.ForNode(id, [this, invs](CNode* pnode) {
    36         const CNetMsgMaker msgMaker(pnode->GetCommonVersion());
    37         this->m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETDATA, invs));
    38@@ -1276,12 +1276,12 @@ bool PeerManagerImpl::FetchBlock(NodeId id, const CBlockIndex* index)
    39     });
    40     if (success) {
    41         LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
    42-                index->GetBlockHash().ToString(), id);
    43+                index.GetBlockHash().ToString(), id);
    44     } else {
    45         // Remove block from in-flight
    46-        MarkBlockAsReceived(index->GetBlockHash());
    47+        MarkBlockAsReceived(index.GetBlockHash());
    48         LogPrint(BCLog::NET, "Failed to request block %s from peer=%d\n",
    49-                index->GetBlockHash().ToString(), id);
    50+                index.GetBlockHash().ToString(), id);
    51     }
    52     return success;
    53 }
    54diff --git a/src/net_processing.h b/src/net_processing.h
    55index 577c925562..659504397f 100644
    56--- a/src/net_processing.h
    57+++ b/src/net_processing.h
    58@@ -43,7 +43,7 @@ public:
    59     virtual ~PeerManager() { }
    60 
    61     /** Attempt to manually fetch block from a given node. */
    62-    virtual bool FetchBlock(const NodeId nodeid, const CBlockIndex* pindex) = 0;
    63+    virtual bool FetchBlock(const NodeId nodeid, const CBlockIndex& pindex) = 0;
    64 
    65     /** Get statistics from node state */
    66     virtual bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const = 0;
    67diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
    68index aa6b216ab1..cb3f2f3abc 100644
    69--- a/src/rpc/blockchain.cpp
    70+++ b/src/rpc/blockchain.cpp
    71@@ -746,7 +746,7 @@ static RPCHelpMan getblockfrompeer()
    72         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
    73     }
    74 
    75-    if (!peerman.FetchBlock(nodeid, pblockindex)) {
    76+    if (!peerman.FetchBlock(nodeid, *pblockindex)) {
    77         throw JSONRPCError(RPC_MISC_ERROR, "Failed to fetch block from peer");
    78     }
    79     return UniValue::VOBJ;
    
  80. in src/net_processing.cpp:1432 in 7544381c2e outdated
    1252@@ -1252,6 +1253,39 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    1253            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    1254 }
    1255 
    1256+bool PeerManagerImpl::FetchBlock(NodeId id, const CBlockIndex* index)
    1257+{
    


    jnewbery commented at 10:36 am on April 22, 2021:
    Consider adding some line breaks to this function to split up the units of logic. It’s a bit of a wall of text right now.

    Sjors commented at 12:42 pm on April 22, 2021:
    I added some line breaks and prose.
  81. Sjors force-pushed on Apr 22, 2021
  82. in src/net_processing.cpp:1264 in c8df3f3238 outdated
    1252@@ -1252,6 +1253,47 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    1253            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    1254 }
    1255 
    1256+bool PeerManagerImpl::FetchBlock(NodeId id, const CBlockIndex& index)
    1257+{
    1258+    PeerRef peer = GetPeerRef(id);
    1259+    if (peer == nullptr) return false;
    


    jnewbery commented at 2:17 pm on April 22, 2021:
    peer isn’t used. You can remove it.

    Sjors commented at 6:42 pm on April 22, 2021:
    This is the only place where we check that this peer actually exists (i.e. wasn’t disconnected). I’ll add a comment.

    jnewbery commented at 1:14 pm on May 13, 2021:
    Actually, you check below that a CNodeState exists, which can only happen if the peer isn’t disconnected, which makes this check redundant.

    Sjors commented at 1:52 pm on May 13, 2021:
    Ok, I’ll remove it then.
  83. in src/net_processing.cpp:1260 in c8df3f3238 outdated
    1252@@ -1252,6 +1253,47 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    1253            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    1254 }
    1255 
    1256+bool PeerManagerImpl::FetchBlock(NodeId id, const CBlockIndex& index)
    1257+{
    1258+    PeerRef peer = GetPeerRef(id);
    1259+    if (peer == nullptr) return false;
    1260+    if (fImporting && !fReindex) return false;
    


    jnewbery commented at 2:18 pm on April 22, 2021:

    Why not match the logic in ProcessMessage() when receiving a block:

    0    if (fImporting || fReindex) return false;
    

    Sjors commented at 6:37 pm on April 22, 2021:
    I think I copied this one from another method. But fewer negations make sense.
  84. in src/net_processing.h:46 in c8df3f3238 outdated
    41@@ -42,6 +42,9 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
    42                                              CTxMemPool& pool, bool ignore_incoming_txs);
    43     virtual ~PeerManager() { }
    44 
    45+    /** Attempt to manually fetch block from a given node. */
    46+    virtual bool FetchBlock(const NodeId nodeid, const CBlockIndex& pindex) = 0;
    


    jnewbery commented at 2:26 pm on April 22, 2021:

    Remove const from the pass-by-value argument:

    0    virtual bool FetchBlock(NodeId nodeid, const CBlockIndex& pindex) = 0;
    
  85. in src/net_processing.cpp:1279 in c8df3f3238 outdated
    1274+
    1275+    // Mark block as in-flight unless it already is
    1276+    if (!MarkBlockAsInFlight(id, index.GetBlockHash(), &index)) return false;
    1277+
    1278+    // Send block request message to the peer
    1279+    bool success = m_connman.ForNode(id, [this, invs](CNode* pnode) {
    


    jnewbery commented at 2:39 pm on April 22, 2021:

    Prefer to capture invs by reference than making a copy:

    0    bool success = m_connman.ForNode(id, [this, &invs](CNode* pnode) {
    

    Also maybe rename pnode to node if you’re touching this part again.

  86. in test/functional/rpc_getblockfrompeer.py:13 in c8df3f3238 outdated
     8+from test_framework.util import (
     9+    assert_equal,
    10+    assert_raises_rpc_error,
    11+)
    12+
    13+import time
    


    jnewbery commented at 2:40 pm on April 22, 2021:
    Import standard library modules first, then local modules.
  87. in test/functional/rpc_getblockfrompeer.py:25 in c8df3f3238 outdated
    20+    def setup_network(self):
    21+        self.setup_nodes()
    22+
    23+    def run_test(self):
    24+        self.log.info("Mine 4 blocks on Node 0")
    25+        self.nodes[0].generatetoaddress(4, self.nodes[0].get_deterministic_priv_key().address)
    


    jnewbery commented at 2:41 pm on April 22, 2021:
    0        self.nodes[0].generate(4)
    

    Same below.

  88. in test/functional/rpc_getblockfrompeer.py:35 in c8df3f3238 outdated
    30+        assert_equal(self.nodes[1].getblockcount(), 203)
    31+        short_tip = self.nodes[1].getbestblockhash()
    32+
    33+        self.log.info("Connect nodes to sync headers")
    34+        self.connect_nodes(0, 1)
    35+        self.sync_blocks(self.nodes[0:2])
    


    jnewbery commented at 2:44 pm on April 22, 2021:
    0        self.sync_blocks()
    
  89. in test/functional/rpc_getblockfrompeer.py:52 in c8df3f3238 outdated
    47+        peers = self.nodes[0].getpeerinfo()
    48+        assert_equal(len(peers), 1)
    49+        peer_0_peer_1_id = peers[0]["id"]
    50+        self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
    51+        time.sleep(1)
    52+        self.nodes[0].getblock(short_tip)
    


    jnewbery commented at 2:46 pm on April 22, 2021:
    Perhaps poll with wait_until rather than a sleep (sleep will take needlessly long on a fast system and may time out on a slow system).

    Sjors commented at 7:09 pm on April 22, 2021:
    Done, though it’s rather involved afaik there’s no one-liner to say “True if x doesn’t throw”

    jnewbery commented at 1:10 pm on May 13, 2021:
    Change looks good. Thanks!
  90. in src/net_processing.cpp:1287 in c8df3f3238 outdated
    1282+        return true;
    1283+    });
    1284+
    1285+    if (success) {
    1286+        LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
    1287+                index.GetBlockHash().ToString(), id);
    


    jnewbery commented at 2:47 pm on April 22, 2021:
    0                 index.GetBlockHash().ToString(), id);
    
  91. in src/net_processing.cpp:1292 in c8df3f3238 outdated
    1287+                index.GetBlockHash().ToString(), id);
    1288+    } else {
    1289+        // Remove block from in-flight
    1290+        MarkBlockAsReceived(index.GetBlockHash());
    1291+        LogPrint(BCLog::NET, "Failed to request block %s from peer=%d\n",
    1292+                index.GetBlockHash().ToString(), id);
    


    jnewbery commented at 2:47 pm on April 22, 2021:
    0                 index.GetBlockHash().ToString(), id);
    
  92. Sjors force-pushed on Apr 22, 2021
  93. Sjors force-pushed on Apr 22, 2021
  94. DrahtBot added the label Needs rebase on Apr 30, 2021
  95. Sjors force-pushed on Apr 30, 2021
  96. DrahtBot removed the label Needs rebase on Apr 30, 2021
  97. Sjors force-pushed on May 12, 2021
  98. ghost commented at 1:41 am on May 13, 2021: none

    I think it would be better to just do the right thing if you getblock a block you don’t have… Don’t make users figure out which peer has it…?

    I agree with @luke-jr

    Or this RPC can make sense if all peers would reject such request by default but if someone is okay with it, can save something like allowgetblockfrompeer=1 in bitcoin.conf

  99. Sjors commented at 11:02 am on May 13, 2021: member

    It’s not that simple. Do we fetch the block from all peers at once? That’s a huge bandwidth waste if you have 100 connection. Do we ask them one by one in sequence? What’s the timeout? Do we do in parallel? Hence my suggestion to leave that to a followup.

    allowgetblockfrompeer=1

    Peers will have no idea someone used this RPC call so that’s not a concern. Nodes request blocks from each other all the time. This PR does not introduce a new p2p message.

  100. in test/functional/rpc_getblockfrompeer.py:7 in 73c49b6d83 outdated
    0@@ -0,0 +1,62 @@
    1+#!/usr/bin/env python3
    2+# Copyright (c) 2020 The Bitcoin Core developers
    3+# Distributed under the MIT software license, see the accompanying
    4+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
    5+"""Test the getblockfrompeer RPC."""
    6+
    7+from test_framework.test_framework import BitcoinTestFramework
    8+from test_framework.authproxy import JSONRPCException
    


    jnewbery commented at 12:54 pm on May 13, 2021:
    sort
  101. in src/rpc/blockchain.cpp:755 in 73c49b6d83 outdated
    744@@ -792,6 +745,43 @@ static RPCHelpMan getmempoolentry()
    745     };
    746 }
    747 
    748+static RPCHelpMan getblockfrompeer()
    749+{
    750+    return RPCHelpMan{"getblockfrompeer",
    751+                "\nAttempt to fetch block from a given peer.\n",
    


    jnewbery commented at 1:09 pm on May 13, 2021:
    Perhaps document that this node must already have the block header in order to fetch the block (or relax that requirement?)

    Sjors commented at 1:39 pm on May 13, 2021:
    By “this node” you mean the node itself? What happens when you fetch a block that you don’t have the header for?

    Sjors commented at 1:44 pm on May 13, 2021:
    I see MarkBlockAsInFlight can handle a nullprtr index. It might indeed be simpler to drop this requirement, as well as the index.nHeight > node_sync_height check.

    jnewbery commented at 2:19 pm on May 13, 2021:
    Sorry, yes I meant “we” as “this node” (the node that you’re running this command on)
  102. Sjors force-pushed on May 13, 2021
  103. Sjors commented at 1:56 pm on May 13, 2021: member
    Rebased, dropped requirement for local node to have the header, dropped redundant GetPeerRef, dropped requirement for other node to be at the right height.
  104. Sjors force-pushed on May 13, 2021
  105. in src/net_processing.cpp:250 in 803d2f568f outdated
    246@@ -247,6 +247,7 @@ class PeerManagerImpl final : public PeerManager
    247 
    248     /** Implement PeerManager */
    249     void CheckForStaleTipAndEvictPeers() override;
    250+    bool FetchBlock(NodeId id, uint256 hash, const CBlockIndex* index) override;
    


    jnewbery commented at 2:26 pm on May 13, 2021:
    Consider passing uint256 by const reference (to avoid unnecessary copying, generally only basic types should be passed by value).

    jnewbery commented at 2:28 pm on May 13, 2021:
    Maybe document that index may be null here?

    Sjors commented at 4:52 pm on May 13, 2021:
    Added function documentation.
  106. jnewbery commented at 2:28 pm on May 13, 2021: member

    utACK 803d2f568f7f24557543f61bf9d5a73952caedb5.

    Changes look safe.

  107. Sjors force-pushed on May 13, 2021
  108. Sjors force-pushed on May 13, 2021
  109. sipa commented at 4:53 pm on May 13, 2021: member
    Is it really necessary to make this specify which peer? It doesn’t sound too hard to make the existing block fetching logic support extra requested blocks, I think.
  110. Sjors commented at 5:00 pm on May 13, 2021: member

    @sipa the current block fetching logic is buried deep inside net_processing. A MSG_BLOCK is generated when processing received headers:

    https://github.com/bitcoin/bitcoin/blob/4741aec1dd28829f45abcc529cddaa0ff04d07a0/src/net_processing.cpp#L2011-L2021

    Or when receiving a block: https://github.com/bitcoin/bitcoin/blob/4741aec1dd28829f45abcc529cddaa0ff04d07a0/src/net_processing.cpp#L1733-L1739

    There doesn’t seem to be a queue of blocks to fetch that’s distributed over peers and checked in a background process.

  111. sipa commented at 5:01 pm on May 13, 2021: member
    @Sjors Sure, but that doesn’t sound too hard to add. And doing it that way sounds a lot more useful.
  112. Sjors commented at 5:13 pm on May 13, 2021: member

    In practice I’m already able to use this PR to fetch stale blocks by simply looping over my peers and “spamming” them all. I then disconnect if the block doesn’t show up, connect to random new peers and then try again. But those are behaviors we probably shouldn’t implement here.

    So although the current incarnation is not ideal, it’s already useful. A revamp of ProcessHeadersMessage with a separate block download manager sounds doesn’t sound easy to me. It also needs different timeout rules than IBD, because most of the time a peer won’t have the block.

    The use case of re-fetching pruned blocks is more similar to IBD, since you can expect every non-pruned node with sufficiently high pindexLastCommonBlock to have it.

  113. sipa commented at 5:30 pm on May 13, 2021: member

    Yes, I imagine it’d use the “background’ fetching logic in SendMessages, not the direct fetch in response to an announcement one.

    Why would it need different timeout, or why would most blocks not have it? We know which peers are pruned.

  114. Sjors commented at 6:13 pm on May 13, 2021: member
    @sipa we don’t fetch a block if we already have (a valid) one for that height. So if you’re trying retrieve the losing block from a race, most likely your peers also saw it later and didn’t fetch it.
  115. sipa commented at 6:45 pm on May 13, 2021: member

    Oh, I see. I missed that fetching non-main-chain blocks was a goal here.

    Why would you want that?

  116. Sjors commented at 6:48 pm on May 13, 2021: member

    That’s indeed the main reason I wrote this, though I can see how it’s useful for redownloading pruned blocks too.

    I’m using this for ForkMonitor to fetch stale blocks and compare the transactions, to see if there was a double-spend: https://forkmonitor.info/stale/btc/679823 (we haven’t found a proper one in the past few months)

  117. Sjors force-pushed on May 25, 2021
  118. Sjors force-pushed on May 26, 2021
  119. MarcoFalke commented at 7:11 am on June 3, 2021: member
    Error: RPC command “getblockfrompeer” not found in RPC_COMMANDS_SAFE_FOR_FUZZING or RPC_COMMANDS_NOT_SAFE_FOR_FUZZING. Please update test/fuzz/rpc.cpp.
  120. Sjors force-pushed on Jun 3, 2021
  121. Sjors commented at 5:02 pm on June 3, 2021: member
    Rebased and marked safe for fuzzing (but I haven’t tried that).
  122. jnewbery commented at 12:35 pm on June 4, 2021: member
    ACK 1df0fd9aceaeaf8b45cb0474ce44135418c7d5b7
  123. Sjors force-pushed on Jun 10, 2021
  124. Sjors commented at 3:40 pm on June 10, 2021: member
    Rebased due to silent merge conflict with #22141, which I randomly caught while looking at IRC notifications :-) But it’s a welcome simplification.
  125. DrahtBot added the label Needs rebase on Jun 12, 2021
  126. Sjors commented at 4:32 pm on June 13, 2021: member

    Rebased after some Ensure* methods were renamed in #21391.

    In addition since #22221, BlockRequested no longer takes a pointer. Which means we can’t call it for blocks for which we don’t have a header. Paging @MarcoFalke to check if that was indeed the intention.

  127. Sjors force-pushed on Jun 13, 2021
  128. DrahtBot removed the label Needs rebase on Jun 13, 2021
  129. MarcoFalke commented at 8:04 am on July 29, 2021: member
    See also #10794
  130. Sjors commented at 3:31 pm on July 29, 2021: member
    @MarcoFalke specifically its requestblock RPC? There’s been too much refactoring to simply cherry-pick it, but it could contain inspiration for improvement.
  131. MarcoFalke commented at 4:21 pm on July 29, 2021: member
    Yes, just left the comment to remind myself that this has been worked on in the past already. No need to change anything here, but if someone wants to read some related threads, it might be helpful.
  132. DrahtBot added the label Needs rebase on Aug 4, 2021
  133. Sjors force-pushed on Aug 4, 2021
  134. DrahtBot removed the label Needs rebase on Aug 4, 2021
  135. DrahtBot added the label Needs rebase on Aug 11, 2021
  136. in src/rpc/blockchain.cpp:775 in 30e2ba6c4c outdated
    770+
    771+    uint256 hash(ParseHashV(request.params[0], "hash"));
    772+
    773+    const NodeId nodeid = static_cast<NodeId>(request.params[1].get_int64());
    774+
    775+    const CBlockIndex* const pblockindex = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash););
    


    jnewbery commented at 3:25 pm on August 11, 2021:
    0    const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash););
    

    (we no longer use hungarian notation to show what type the variable is)

  137. jnewbery commented at 3:31 pm on August 11, 2021: member

    In addition since #22221, BlockRequested no longer takes a pointer. Which means we can’t call it for blocks for which we don’t have a header.

    What do you think about restricting this RPC to where the node already has the header, by returning in getblockfrompeer() if LookupBlockIndex() fails? That’d make the functionality in net_processing more closely mirror our normal block download, where we’ll only ever request a block if we already have its header. If the node doesn’t already have the header, the user could first call submitheader with the serialized block header, followed by getblockfrompeer().

  138. in src/rpc/blockchain.cpp:786 in 30e2ba6c4c outdated
    775+    const CBlockIndex* const pblockindex = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash););
    776+
    777+    if (!peerman.FetchBlock(nodeid, hash, pblockindex)) {
    778+        throw JSONRPCError(RPC_MISC_ERROR, "Failed to fetch block from peer");
    779+    }
    780+    return UniValue::VOBJ;
    


    LarryRuane commented at 4:04 pm on August 11, 2021:
    Why do we not return NullUniValue here? Returning UniValue::VOBJ causes bitcoin-cli to print empty JSON object; returning NullUniValue would print nothing (as many other RPCs do). Is it to make this more backward-compatible if we later make it return something, like the block?

    Sjors commented at 12:55 pm on August 12, 2021:
    The first version of this PR included a success boolean, but that didn’t make sense because we don’t know if we actually get the block. So I guess I made the response an empty VOBJ. I’ll switch to Null.
  139. in test/functional/rpc_getblockfrompeer.py:48 in 30e2ba6c4c outdated
    43+        short_tip_synced = False
    44+        for x in self.nodes[0].getchaintips():
    45+            if x['hash'] == short_tip:
    46+                assert_equal(x['status'], "headers-only")
    47+                short_tip_synced = True
    48+        assert short_tip_synced
    


    LarryRuane commented at 4:30 pm on August 11, 2021:
    0        for x in self.nodes[0].getchaintips():
    1            if x['hash'] == short_tip:
    2                assert_equal(x['status'], "headers-only")
    3                break
    4        else:
    5            raise AssertionError("short tip not synced")
    

    jnewbery commented at 9:35 am on August 12, 2021:
    Much more pythonic :+1:

    Sjors commented at 2:14 pm on August 12, 2021:
    Tastes the same to me, but changed…
  140. in test/functional/rpc_getblockfrompeer.py:56 in 30e2ba6c4c outdated
    51+        self.log.info("Fetch block from node 1")
    52+        peers = self.nodes[0].getpeerinfo()
    53+        assert_equal(len(peers), 1)
    54+        peer_0_peer_1_id = peers[0]["id"]
    55+        self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
    56+        self.wait_until(lambda: self.check_for_block(short_tip), timeout=1)
    


    LarryRuane commented at 4:53 pm on August 11, 2021:

    Suggestions for additional tests:

    0        self.log.info("No error if block is not found in the index")
    1        assert_equal(self.nodes[0].getblockfrompeer("12" * 32, peer_0_peer_1_id), None)
    2        self.log.info("Non-existent peer generates error")
    3        assert_raises_rpc_error(-1, "Failed to fetch block from peer", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1)
    

    Sjors commented at 2:16 pm on August 12, 2021:
    Thanks, added the second one.
  141. LarryRuane commented at 4:56 pm on August 11, 2021: contributor
    ACK 30e2ba6c4cef9248c53542faeb8ec768079fc5fb Suggestions optional
  142. mzumsande commented at 10:27 pm on August 11, 2021: member

    What do you think about restricting this RPC to where the node already has the header, by returning in getblockfrompeer() if LookupBlockIndex() fails?

    I agree that this would make sense. My understanding is that if we’d request a block for which we don’t have the header and which has less work than our tip, then getblockfrompeer would currently signal success, we might also get the block delivered by the peer, but then AcceptBlock() would never store it because fRequested and fHasMoreOrSameWork would be false (code) But if we cannot accept the block, I don’t think it makes sense to request it in the first place (and we should instead report an RPC error).

  143. Sjors commented at 12:56 pm on August 12, 2021: member

    Yesterdays PR Review Club covered this PR, thanks everyone! https://bitcoincore.reviews/20295

    Rebased and addressed feedback. @jnewbery in the use case I have in mind the node already has the header. We could always add a getheaderfrompeer RPC method if anyone needs it, though that seems a bit tedious to use. Alternative we can later expand getblockfrompeer to be smart about fetching headers first. For now I’ll add the check you suggested so the user gets a clear error when requesting a block for which we don’t have a header.

    I’ll ponder this a bit, but for now I just added a line to the TODO warning about potentially breaking getblockfrompeer.

  144. Sjors force-pushed on Aug 12, 2021
  145. DrahtBot removed the label Needs rebase on Aug 12, 2021
  146. Sjors commented at 1:51 pm on August 16, 2021: member

    I reverted back to returning an empty object as per the developer notes “Try to make the RPC response a JSON object”.

    Also made FetchBlock take CBlockIndex as a reference rather than a pointer, since it’s now mandatory.

  147. Sjors force-pushed on Aug 16, 2021
  148. jnewbery commented at 1:55 pm on August 16, 2021: member
    reACK 29263ae14a81d4cc55de61f51db981edf54399d0
  149. laanwj commented at 2:01 pm on August 16, 2021: member
    Concept ACK
  150. in test/functional/rpc_getblockfrompeer.py:48 in 29263ae14a outdated
    43+        for x in self.nodes[0].getchaintips():
    44+            if x['hash'] == short_tip:
    45+                assert_equal(x['status'], "headers-only")
    46+                break
    47+        else:
    48+            raise AssertionError("short tip not synced")
    


    MarcoFalke commented at 7:10 pm on August 17, 2021:

    nit:

    0        x = next(x for x in self.nodes[0].getchaintips() if x['hash'] == short_tip)
    1        assert_equal(x['status'], "headers-only")
    
  151. in src/rpc/blockchain.cpp:21 in 29263ae14a outdated
    17@@ -18,6 +18,8 @@
    18 #include <hash.h>
    19 #include <index/blockfilterindex.h>
    20 #include <index/coinstatsindex.h>
    21+#include <net.h> // For NodeId
    


    0xB10C commented at 7:55 pm on September 15, 2021:
    nit: not sure if there is need for a // For NodeId comment here. I think comments like these tend to be overlooked when adding something else that needs net.h and thus out-dating the comment.

    fanquake commented at 1:30 am on September 16, 2021:
    Yea, please don’t add comments like these.
  152. in src/rpc/blockchain.cpp:763 in 29263ae14a outdated
    749@@ -748,6 +750,44 @@ static RPCHelpMan getmempoolentry()
    750     };
    751 }
    752 
    753+static RPCHelpMan getblockfrompeer()
    754+{
    755+    return RPCHelpMan{"getblockfrompeer",
    756+                "\nAttempt to fetch block from a given peer.\n"
    


    0xB10C commented at 7:59 pm on September 15, 2021:
    As a user I’d expect some documentation that the RPC call returns {} once the block-request has been successfully scheduled and that I have to manually check if my node now has the block.

    Sjors commented at 9:37 am on September 16, 2021:
    I added a sentence to the RPC doc.
  153. in src/rpc/blockchain.cpp:799 in 29263ae14a outdated
    777+    const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash););
    778+
    779+    if (!index) {
    780+        throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
    781+    }
    782+
    


    0xB10C commented at 8:03 pm on September 15, 2021:
    optional but maybe worthwile: could check if the block is already available and return true if it is. Could reduce bandwidth usage in cases where users spam/iterate their all their peers with a getblockfrompeer.

    Sjors commented at 10:02 am on September 16, 2021:
    The result now includes a warning if we already had the block. This seems better than throwing an error, since an RPC client might attempt fetching for multiple peers in parallel.
  154. 0xB10C commented at 8:22 pm on September 15, 2021: member

    I’ve manually tested getblockfrompeer against two peers on a custom signet. Both peers share the chain tip, but both know about different (now stale) branches. I purposefully black-box tested the RPC without look at the code and tests added in this PR.

    Tests:

    1. Requesting a block without having the header: returns the error message Block header missing. This behavior is documented and expected :heavy_check_mark:.
    2. Requesting a block from a peer that does not know about the block: Returns with {} and a getblock RPC call returns with Block not found on disk. This behavior is as expected as peer can’t send the block if it does not know about it :heavy_check_mark:.
    3. Requesting a block from a peer that hasn’t validated the block (getchaintips status is valid-headers which is documented as All blocks are available for this branch, but they were never fully validated): Returns with {} and a getblock RPC call returns with Block not found on disk. This behavior is fine I guess. In normal operation, we probably don’t want to serve unvalidated blocks to peers as a DoS mitigation. The RPC likely inherits this behavior :question:.
    4. Requesting a block from a peer that has only the block header (getchaintips status is ‘headers-only’): Returns with {} and a getblock RPC call returns with Block not found on disk. This behavior is as expected as peer can’t send the block if it only knows about the header :heavy_check_mark:.
    5. Requesting a block from a peer that has validated the block but it’s not in the active chain (getchaintips status is valid-fork): Returns with {} and a getblock RPC call returns the block :heavy_check_mark:.
    6. Requesting a block from a peer that has marked as ‘invalid’: Returns with {} and a getblock RPC call fails. The peer does not send the block likely to a similar reason as in test 3 :question:.

    Notes:

    Requesting an unavailable block from a peer A multiple times in a row fails with the error message Failed to fetch block from peer starting with the second request. A getdata message is only send to the peer on the first attempt. The error message is not displayed when requesting from a peer B in between the requests to peer A.

    Re-requesting already available blocks resends a getdata message and the peer responds with the block again. Maybe check if a block is available before requesting it?

    My node disconnected from a peer at some point because a getdata timed out. Timeout downloading block 00000268... from peer=0, disconnecting. This block was in a branch marked as valid-headers by peer 0. My node directly reconnected to peer 0 as peer 2 as it’s manually configured with addnode. This could be an issue when using this in the real world.

    Requesting blocks that would be an orphan to us (the previous header is unknown) is not possible as submitheader requires the previous header to be known. I’m not sure there is another way to have orphan (orphan, not stale) blocks or headers in the block tree.


    Some code review comments below too.


    RE my own comment:

    Requesting an unavailable block from a peer A multiple times in a row fails with the error message Failed to fetch block from peer starting with the second request. A getdata message is only send to the peer on the first attempt. The error message is not displayed when requesting from a peer B in between the requests to peer A.

    Looked this behavior up in the code: PeerManagerImpl::BlockRequested() returns false if the block is already requested. This causes the RPC to fail too. If the block is requested from another peer, the request is dropped.

    https://github.com/bitcoin/bitcoin/blob/29263ae14a81d4cc55de61f51db981edf54399d0/src/net_processing.cpp#L867-L868

    Doesn’t this cause us to drop a request to peer A if A is slow and we instantly request it from B too? Could it cause issues if if A then sends us the block or do we just accept it?

    Generally seems like doing getblockfrompeer for many peers without a delay between peers is not the ideal use of this RPC.

  155. Sjors commented at 10:14 am on September 16, 2021: member

    Thanks for the thorough testing @0xB10C.

    Regarding (3) and (6): if a peer doesn’t give us the block, there’s nothing we can do. Since the call is not asynchronous, we can’t give the user much useful feedback either. Changing p2p behavior for peers to give us a block before it’s evaluated, or one it considers invalid, is beyond the scope of this PR.

    Requesting an unavailable block from a peer A multiple times in a row fails with the error message Failed to fetch block from peer starting with the second request. A getdata message is only send to the peer on the first attempt.

    Interesting. I’d rather not make the RPC aware of too much internal magic of netmanager. If the user calls an RPC again after an error message, I guess they’re on their own :-)

    The error message is not displayed when requesting from a peer B in between the requests to peer A.

    The only reason I can think of for requesting a block from multiple peers, is that the user is happy as long as at least one succeeds. So this is probably not a huge deal, but still strange. I could add a recommendation to the help that it’s safer to call this method in sequence with a few second delay?

  156. Sjors force-pushed on Sep 16, 2021
  157. DrahtBot added the label Needs rebase on Sep 28, 2021
  158. Sjors force-pushed on Sep 29, 2021
  159. Sjors commented at 8:22 am on September 29, 2021: member
    Rebased after #22650 (trivial conflict with first commit).
  160. DrahtBot removed the label Needs rebase on Sep 29, 2021
  161. jnewbery commented at 8:54 am on September 30, 2021: member
    reACK 4096e6fb013aa3fbe3463d24b70310ad8517da5f
  162. DrahtBot added the label Needs rebase on Oct 5, 2021
  163. Sjors force-pushed on Oct 7, 2021
  164. Sjors commented at 8:54 am on October 7, 2021: member
    Rebased once more (dd8f7f250095e58bbf4cd4effb481b52143bd1ed touched PeerManager::make directly under the new FetchBlock, though I’m disappointed Git couldn’t figure that out).
  165. jnewbery commented at 9:34 am on October 7, 2021: member

    trivial reACK 9181e2e217

    Confirmed with git rangediff that the only conflict was for touching lines.

  166. DrahtBot removed the label Needs rebase on Oct 7, 2021
  167. luke-jr referenced this in commit 93d75b43a5 on Oct 10, 2021
  168. luke-jr referenced this in commit eab5561fba on Oct 10, 2021
  169. prusnak commented at 2:42 pm on October 19, 2021: contributor
    I’d love to see this merged in. 👍
  170. in src/net_processing.cpp:1442 in 9181e2e217 outdated
    1437+    if (state == nullptr) return false;
    1438+    // Ignore pre-segwit peers
    1439+    if (!state->fHaveWitness) return false;
    1440+
    1441+    // Construct message to request the block
    1442+    std::vector<CInv> invs{CInv(MSG_BLOCK | MSG_WITNESS_FLAG, hash)};
    


    LarryRuane commented at 4:15 am on November 3, 2021:
    nit, if you have a chance to retouch, could this be moved down to just before it’s first used by the m_connman.ForNode() call below?

    Sjors commented at 2:29 pm on December 1, 2021:
    I moved it down a bit.
  171. LarryRuane commented at 4:21 am on November 3, 2021: contributor
    ACK 9181e2e217955fff5f726b0e011afb78fb90af8f
  172. jamesob commented at 8:26 pm on November 17, 2021: member
    Concept ACK, will review in the next few days.
  173. in src/rpc/blockchain.cpp:803 in 9181e2e217 outdated
    785+    }
    786+
    787+    UniValue result = UniValue::VOBJ;
    788+
    789+    if (index->nStatus & BLOCK_HAVE_DATA) {
    790+        result.pushKV("warnings", "Block already downloaded");
    


    luke-jr commented at 2:05 am on November 22, 2021:

    Is it intentional that this succeeds even with an invalid nodeid?

    Perhaps the tests should check for the expected behaviour.


    Sjors commented at 11:57 am on November 22, 2021:
    It’s probably a good sanity check to add. I could call GetNodeStateStats on the peer as a way to find out if it’s valid, though that’s not strictly what that PeerMan function is for. @jnewbery?

    jnewbery commented at 1:49 pm on December 1, 2021:

    You could use CConnman::ForNode() to determine whether the peer with that nodeid exists. Here’s the diff (with the required changes to make the tests run on master):

     0diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
     1index 9fc586c122..fdbfe55aaf 100644
     2--- a/src/rpc/blockchain.cpp
     3+++ b/src/rpc/blockchain.cpp
     4@@ -780,11 +780,17 @@ static RPCHelpMan getblockfrompeer()
     5     const NodeContext& node = EnsureAnyNodeContext(request.context);
     6     ChainstateManager& chainman = EnsureChainman(node);
     7     PeerManager& peerman = EnsurePeerman(node);
     8+    CConnman& connman = EnsureConnman(node);
     9 
    10     uint256 hash(ParseHashV(request.params[0], "hash"));
    11 
    12     const NodeId nodeid = static_cast<NodeId>(request.params[1].get_int64());
    13 
    14+    // Check that the peer with nodeid exists
    15+    if (!connman.ForNode(nodeid, [](CNode* node) {return true;})) {
    16+        throw JSONRPCError(RPC_MISC_ERROR, strprintf("Peer nodeid %d does not exist", nodeid));
    17+    }
    18+
    19     const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash););
    20 
    21     if (!index) {
    22diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
    23index ec562c94f9..e841cba70e 100755
    24--- a/test/functional/rpc_getblockfrompeer.py
    25+++ b/test/functional/rpc_getblockfrompeer.py
    26@@ -27,11 +27,11 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
    27 
    28     def run_test(self):
    29         self.log.info("Mine 4 blocks on Node 0")
    30-        self.nodes[0].generate(4)
    31+        self.generate(self.nodes[0], 4, sync_fun=self.no_op)
    32         assert_equal(self.nodes[0].getblockcount(), 204)
    33 
    34         self.log.info("Mine competing 3 blocks on Node 1")
    35-        self.nodes[1].generate(3)
    36+        self.generate(self.nodes[1], 3, sync_fun=self.no_op)
    37         assert_equal(self.nodes[1].getblockcount(), 203)
    38         short_tip = self.nodes[1].getbestblockhash()
    39 
    40@@ -60,7 +60,7 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
    41         assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0)
    42 
    43         self.log.info("Non-existent peer generates error")
    44-        assert_raises_rpc_error(-1, "Failed to fetch block from peer", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1)
    45+        assert_raises_rpc_error(-1, f"Peer nodeid {peer_0_peer_1_id + 1} does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1)
    46 
    47         self.log.info("Successful fetch")
    48         result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
    

    Sjors commented at 2:29 pm on December 1, 2021:
    Done. I left out the sync_fun=self.no_op bit, as it results in: AttributeError: 'GetBlockFromPeerTest' object has no attribute 'no_op'

    jnewbery commented at 2:42 pm on December 1, 2021:
    This branch currently has a silent merge conflict with master. You’ll need to rebase and take the sync_fun=self.no_op bit before merge.

    Sjors commented at 6:16 am on December 2, 2021:
    Done
  174. in src/rpc/blockchain.cpp:765 in 9181e2e217 outdated
    749@@ -748,6 +750,52 @@ static RPCHelpMan getmempoolentry()
    750     };
    751 }
    752 
    753+static RPCHelpMan getblockfrompeer()
    754+{
    755+    return RPCHelpMan{"getblockfrompeer",
    756+                "\nAttempt to fetch block from a given peer.\n"
    757+                "\nWe must have the header for this block, e.g. using submitheader.\n"
    758+                "\nReturns {} if a block-request was successfully scheduled\n",
    


    fjahr commented at 8:19 pm on November 27, 2021:
    I think it would be good to add more information for pruned node users of how long the block will be around. If my understanding is correct, something like this: Note: On a pruned node this block will be pruned again when the pruneheight surpasses the blockheight at the time of fetching the block.

    luke-jr commented at 10:18 pm on November 27, 2021:

    Even that is not guaranteed at the moment (albeit very likely to hold in practice).

    Combined with #19463, the caller could set a prune lock to ensure it doesn’t get re-pruned before the caller is done with it.


    fjahr commented at 11:59 am on December 18, 2021:
    So far I wasn’t able to figure out in which cases this could not be guaranteed. I opened a follow-up PR in #23813. Please let me know if you have suggestions on improving the text there. Sure #19463 can be helpful but my aim with this for now is to just make sure that users don’t get wrong expectations, like that the block would be available forever for example.
  175. fjahr commented at 10:11 pm on November 27, 2021: member

    tested ACK 9181e2e217955fff5f726b0e011afb78fb90af8f

    I think this is ready to be merged as-is and my comments below can be addressed in a follow-up.

    The documentation should be improved on how long users can expect this block to be around on a pruned node. If someone uses this on a prune=550 node, for example, this may become confusing for certain use cases. I made a suggestion in my comment but I can also propose this in a follow-up PR since it might require further discussion.

    I also wrote an additional test for the pruned node use case here. Feel free to add it here if you have to retouch, otherwise, I will open a follow-up PR with it.

  176. Sjors force-pushed on Dec 1, 2021
  177. Sjors force-pushed on Dec 2, 2021
  178. rpc: move Ensure* helpers to server_util.h b884ababc2
  179. rpc: getblockfrompeer
    Co-authored-by: John Newbery <john@johnnewbery.com>
    dce8c4c381
  180. Sjors force-pushed on Dec 2, 2021
  181. jnewbery commented at 12:38 pm on December 7, 2021: member
    ACK dce8c4c38111556ca480aa0e63c46b71f66b508f
  182. fjahr commented at 9:05 pm on December 7, 2021: member
    re-ACK dce8c4c38111556ca480aa0e63c46b71f66b508f
  183. MarcoFalke merged this on Dec 8, 2021
  184. MarcoFalke closed this on Dec 8, 2021

  185. Sjors deleted the branch on Dec 8, 2021
  186. in src/net_processing.cpp:1431 in dce8c4c381
    1427@@ -1427,6 +1428,41 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
    1428            (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
    1429 }
    1430 
    1431+bool PeerManagerImpl::FetchBlock(NodeId id, const uint256& hash, const CBlockIndex& index)
    


    MarcoFalke commented at 9:55 am on December 8, 2021:

    What about returning the error string on failure std::optional<std::string>?

    The caller could then

    0if(const auto err{Fetch()}){
    1 throw err.value();
    2}
    

    This will make users and tests more happy

  187. in src/net_processing.cpp:1443 in dce8c4c381
    1438+    if (state == nullptr) return false;
    1439+    // Ignore pre-segwit peers
    1440+    if (!state->fHaveWitness) return false;
    1441+
    1442+    // Mark block as in-flight unless it already is
    1443+    if (!BlockRequested(id, index)) return false;
    


    MarcoFalke commented at 10:04 am on December 8, 2021:
    maybe add a comment that this will re-assign the block if it is in flight from another peer?

    Sjors commented at 12:37 pm on December 8, 2021:
    I’ll also update the RPC doc, because it looks like BLOCKTXN are ignored entirely if they’re no longer on the in flight list for a given peer.
  188. in src/net_processing.cpp:1460 in dce8c4c381
    1455+    if (success) {
    1456+        LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
    1457+                 hash.ToString(), id);
    1458+    } else {
    1459+        RemoveBlockRequest(hash);
    1460+        LogPrint(BCLog::NET, "Failed to request block %s from peer=%d\n",
    


    MarcoFalke commented at 10:07 am on December 8, 2021:
    Seems odd to log a failure to the debug log on an rpc call. I’d expect the error to go to the user instead. Also, shouldn’t this say “peer not found”?

    Sjors commented at 1:14 pm on December 8, 2021:
    We already check that the peer exists above, so the failure would be “not fully connected”; changed…
  189. in src/net_processing.h:50 in dce8c4c381
    41@@ -42,6 +42,16 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
    42                                              CTxMemPool& pool, bool ignore_incoming_txs);
    43     virtual ~PeerManager() { }
    44 
    45+    /**
    46+     * Attempt to manually fetch block from a given peer. We must already have the header.
    47+     *
    48+     * @param[in]  id       The peer id
    49+     * @param[in]  hash     The block hash
    50+     * @param[in]  pindex   The blockindex
    


    MarcoFalke commented at 10:10 am on December 8, 2021:

    why are you passing the hash twice? the blockindex already holds its own hash, no?

    Also, pindex is not a pointer, so should be called block_index.


    Sjors commented at 11:39 am on December 8, 2021:
    That may have gotten mixed up over the course of rebases and changes, I’ll take a second look.
  190. in src/rpc/blockchain.cpp:772 in dce8c4c381
    767+                    {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
    768+                    {"nodeid", RPCArg::Type::NUM, RPCArg::Optional::NO, "The node ID (see getpeerinfo for node IDs)"},
    769+                },
    770+                RPCResult{RPCResult::Type::OBJ, "", "",
    771+                {
    772+                    {RPCResult::Type::STR, "warnings", "any warnings"}
    


    MarcoFalke commented at 10:12 am on December 8, 2021:
    missing optional

    Sjors commented at 11:38 am on December 8, 2021:
    Thanks for adding in #23702
  191. in src/rpc/blockchain.cpp:785 in dce8c4c381
    780+    const NodeContext& node = EnsureAnyNodeContext(request.context);
    781+    ChainstateManager& chainman = EnsureChainman(node);
    782+    PeerManager& peerman = EnsurePeerman(node);
    783+    CConnman& connman = EnsureConnman(node);
    784+
    785+    uint256 hash(ParseHashV(request.params[0], "hash"));
    


    MarcoFalke commented at 10:13 am on December 8, 2021:
    nit: can be const
  192. in src/rpc/blockchain.cpp:787 in dce8c4c381
    782+    PeerManager& peerman = EnsurePeerman(node);
    783+    CConnman& connman = EnsureConnman(node);
    784+
    785+    uint256 hash(ParseHashV(request.params[0], "hash"));
    786+
    787+    const NodeId nodeid = static_cast<NodeId>(request.params[1].get_int64());
    


    MarcoFalke commented at 10:13 am on December 8, 2021:

    any reason to use a narrowing cast and not the safe alternative?

    0     const NodeId nodeid{request.params[1].get_int64()};
    
  193. in src/rpc/blockchain.cpp:791 in dce8c4c381
    786+
    787+    const NodeId nodeid = static_cast<NodeId>(request.params[1].get_int64());
    788+
    789+    // Check that the peer with nodeid exists
    790+    if (!connman.ForNode(nodeid, [](CNode* node) {return true;})) {
    791+        throw JSONRPCError(RPC_MISC_ERROR, strprintf("Peer nodeid %d does not exist", nodeid));
    


    MarcoFalke commented at 10:14 am on December 8, 2021:
    Since the FetchBlock call has to deal with this anyway, might be better to remove this code and let FetchBlock deal with it.
  194. in src/rpc/blockchain.cpp:763 in dce8c4c381
    756@@ -803,6 +757,58 @@ static RPCHelpMan getmempoolentry()
    757     };
    758 }
    759 
    760+static RPCHelpMan getblockfrompeer()
    761+{
    762+    return RPCHelpMan{"getblockfrompeer",
    763+                "\nAttempt to fetch block from a given peer.\n"
    


    MarcoFalke commented at 10:28 am on December 8, 2021:
    Should also mention that this will disconnect the peer if the peer pretends to not have the block

    Sjors commented at 12:21 pm on December 8, 2021:
    That seems a bit too deep in the net_processing weeds?
  195. Sjors commented at 11:29 am on December 8, 2021: member
    @MarcoFalke thanks, working on a followup PR…
  196. MarcoFalke commented at 11:34 am on December 8, 2021: member
    could make sense to base on #23702 to avoid conflicts
  197. Sjors commented at 1:28 pm on December 8, 2021: member
    Followups in #23706
  198. RandyMcMillan referenced this in commit 7dfbb0d9a2 on Dec 23, 2021
  199. MarcoFalke referenced this in commit 39d9bbe4ac on Jan 25, 2022
  200. luke-jr referenced this in commit a01f1916c2 on May 21, 2022
  201. Fabcien referenced this in commit d4cad3ef2d on Nov 30, 2022
  202. Fabcien referenced this in commit 93ae2058ec on Dec 3, 2022
  203. DrahtBot locked this on Jan 15, 2023

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-18 15:12 UTC

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