Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpc: getblockfrompeer #20295

Merged
merged 2 commits into from
Dec 8, 2021
Merged

Conversation

Sjors
Copy link
Member

@Sjors Sjors commented Nov 3, 2020

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:

bitcoin-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

@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 3, 2020

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.

@Fi3
Copy link

Fi3 commented Nov 6, 2020

@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.

@Sjors
Copy link
Member Author

Sjors commented Nov 11, 2020

@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.

@Sjors Sjors force-pushed the 2020/11/getblockfrompeer branch from 1a192c4 to f74c19e Compare November 11, 2020 13:39
@Sjors Sjors marked this pull request as ready for review November 11, 2020 13:53
@jonathanbier
Copy link

Concept ACK

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

@benthecarman
Copy link
Contributor

Concept ACK

This would be very useful for testing

@Fi3
Copy link

Fi3 commented Nov 13, 2020

@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.

@luke-jr
Copy link
Member

luke-jr commented Nov 13, 2020

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...?

@Sjors
Copy link
Member Author

Sjors commented Nov 14, 2020

@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.

@Sjors
Copy link
Member Author

Sjors commented Dec 9, 2020

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

Copy link
Contributor

@fjahr fjahr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested ACK 9181e2e

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.

return RPCHelpMan{"getblockfrompeer",
"\nAttempt to fetch block from a given peer.\n"
"\nWe must have the header for this block, e.g. using submitheader.\n"
"\nReturns {} if a block-request was successfully scheduled\n",
Copy link
Contributor

@fjahr fjahr Nov 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Sjors Sjors force-pushed the 2020/11/getblockfrompeer branch 2 times, most recently from 434dbbc to 4edaf67 Compare December 2, 2021 06:05
Sjors and others added 2 commits December 2, 2021 13:15
Co-authored-by: John Newbery <john@johnnewbery.com>
@jnewbery
Copy link
Contributor

jnewbery commented Dec 7, 2021

ACK dce8c4c

@fjahr
Copy link
Contributor

fjahr commented Dec 7, 2021

re-ACK dce8c4c

@maflcko maflcko merged commit f601326 into bitcoin:master Dec 8, 2021
@Sjors Sjors deleted the 2020/11/getblockfrompeer branch December 8, 2021 09:45
@@ -1427,6 +1428,41 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
(GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
}

bool PeerManagerImpl::FetchBlock(NodeId id, const uint256& hash, const CBlockIndex& index)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

The caller could then

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

This will make users and tests more happy

if (!state->fHaveWitness) return false;

// Mark block as in-flight unless it already is
if (!BlockRequested(id, index)) return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a comment that this will re-assign the block if it is in flight from another peer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

hash.ToString(), id);
} else {
RemoveBlockRequest(hash);
LogPrint(BCLog::NET, "Failed to request block %s from peer=%d\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already check that the peer exists above, so the failure would be "not fully connected"; changed...


// Check that the peer with nodeid exists
if (!connman.ForNode(nodeid, [](CNode* node) {return true;})) {
throw JSONRPCError(RPC_MISC_ERROR, strprintf("Peer nodeid %d does not exist", nodeid));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the FetchBlock call has to deal with this anyway, might be better to remove this code and let FetchBlock deal with it.

static RPCHelpMan getblockfrompeer()
{
return RPCHelpMan{"getblockfrompeer",
"\nAttempt to fetch block from a given peer.\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also mention that this will disconnect the peer if the peer pretends to not have the block

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems a bit too deep in the net_processing weeds?

@Sjors
Copy link
Member Author

Sjors commented Dec 8, 2021

@MarcoFalke thanks, working on a followup PR...

@maflcko
Copy link
Member

maflcko commented Dec 8, 2021

could make sense to base on #23702 to avoid conflicts

@Sjors
Copy link
Member Author

Sjors commented Dec 8, 2021

Followups in #23706

RandyMcMillan pushed a commit to RandyMcMillan/mempool-tab that referenced this pull request Dec 23, 2021
36b9fe1 rpc: getblockfrompeer (Sjors Provoost)
4aa9484 rpc: move Ensure* helpers to server_util.h (Sjors Provoost)

Pull request description:

  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:
  ```
  bitcoin-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

ACKs for top commit:
  jnewbery:
    ACK 36b9fe1
  fjahr:
     re-ACK 36b9fe1

Tree-SHA512: 843ba2b7a308f640770d624d0aa3265fdc5c6ea48e8db32269b96a082b7420f7953d1d8d1ef2e6529392c7172dded9d15639fbc9c24e7bfa5cfb79e13a5498c8
maflcko pushed a commit to maflcko/bitcoin-core that referenced this pull request Jan 25, 2022
923312f rpc: use peer_id, block_hash for FetchBlock (Sjors Provoost)
34d5399 rpc: more detailed errors for getblockfrompeer (Sjors Provoost)
60243ca rpc: turn already downloaded into error in getblockfrompeer (Sjors Provoost)
809d66b rpc: clarify getblockfrompeer behavior when called multiple times (Sjors Provoost)
0e3d7c5 refactor: drop redundant hash argument from FetchBlock (Sjors Provoost)
8d1a3e6 rpc: allow empty JSON object result (Sjors Provoost)
bfbf91d test: fancier Python for getblockfrompeer (Sjors Provoost)

Pull request description:

  Followups from bitcoin#20295.

ACKs for top commit:
  jonatack:
    ACK 923312f 📦
  fjahr:
    tested ACK 923312f

Tree-SHA512: da9eca76e302e249409c9d7f0d16cca668ed981e2ab6ca2d1743dad0d830b94b1bc5ffb9028a00764b863201945c273cc8f4409a4c9ca3817830007dffa2bc20
luke-jr added a commit to luke-jr/bitcoin that referenced this pull request May 21, 2022
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Nov 30, 2022
Summary:
This is a partial backport of [[bitcoin/bitcoin#20295 | core#20295]]
bitcoin/bitcoin@b884aba

Depends on D12700

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Subscribers: Fabien

Differential Revision: https://reviews.bitcoinabc.org/D12701
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Dec 3, 2022
Summary:
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`).

Limitations:

- you have to specify which peer to fetch the block from
- the node must already have the header

Co-authored-by: John Newbery <john@johnnewbery.com>

This is a backport of [[bitcoin/bitcoin#20295 | core#20295]], [[bitcoin/bitcoin#23702 | core#23702]] and [[bitcoin/bitcoin#23706 | core#23706]] (partial)
bitcoin/bitcoin@dce8c4c
bitcoin/bitcoin@aaaa34e
bitcoin/bitcoin@bfbf91d
bitcoin/bitcoin@809d66b
bitcoin/bitcoin@0e3d7c5

The main commit is the first one. The other commits are minor style & comments improvements, and one commit removing an unnecessary argument in the newly added `FetchBlock` function.

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Subscribers: Fabien

Differential Revision: https://reviews.bitcoinabc.org/D12716
@bitcoin bitcoin locked and limited conversation to collaborators Jan 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

rpc: fetch block from peer