-
Notifications
You must be signed in to change notification settings - Fork 37.7k
rpc: Add getblockbyheight method #16345
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
Conversation
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); | ||
ssBlock << block; | ||
std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); | ||
return strHex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will std::string be converted to the univalue object? Do we need to cast to a JSON object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::string
is a valid UniValue
object and is used in many other RPCs.
block = GetBlockChecked(pblockindex); | ||
} | ||
|
||
if (verbosity <= 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no documentation for when verbosity is less than 0. I don't think it can ever be? Should verbosity be an unsigned int or enum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope verbosity cannot be less than zero
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove this check for less than zero and change verbosity to an unsigned short or preferably an enum of constants 0-3?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC it is undefined behavior to cast an int
to an enum
that is out of range of the enum
. Because any int
can be entered in the RPC, it could be out of range of the enum
and cause undesirable things. So I think it is actually better to just handle it as an int. Also, UniValue
does not have a get_short()
function so there would be no way to fetch a short from the request other than casting which I think is undesirable especially when it's easier to just handle it as an int.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate the response @achow101. I would then suggest we add a validate and transform for the RPC call but that is out of the scope of the PR. Validation from the client is necessary in these cases so we don't get undesirable things and it would keep the code clean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validation from the client is not possible. The client is not necessarily bitcoin-cli
. It could be any JSON-RPC client. Validation of this must happen server side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validation from the client is necessary in these cases so we don't get undesirable things and it would keep the code clean.
No.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fqlx from the server point of view, never trust the client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would test the argument for valid range and throw an rpc error if out of range. Better to accept just the valid values IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed on filtering for valid range
int verbosity = 1; | ||
if (!request.params[1].isNull()) { | ||
if(request.params[1].isNum()) | ||
verbosity = request.params[1].get_int(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't tell if this returns negative or >3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_int
can return any valid int
.
@fqlx Actually I do not think it is a good idea to move |
@fqlx I made some changes you've suggested. |
Can you add tests? |
I don't see the need for a test for such a function. |
@emilengler every change should have a test, especially a new RPC. Also in this case you should a release note too. |
The tests are good not only to increase confidence in our codebase but to also show a working example how to use it. The code will used by 1000s of people |
There should be at least a basic test that checks |
Concept ACK failing a linter:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative to overloading hash
in getblock
is to add an extra parameter to getblock
, like:
getblock hash ( height )
Then:
- if
hash
is null then use height - if both are set then they must match.
This would look good if called with named parameter getblock(height=20)
.
I can't unresolve #16345 (comment), but please address my comment there. |
@promag I pushed a new commit which fix this and unresolved the conversation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@emilengler Thanks for contributing. Before this can be merged, or reviewed, this PR needs a few changes.
The macOS build, and others, are currently failing on Travis:
rpc/blockchain.cpp:1019:19: error: calling function 'LookupBlockIndex' requires holding mutex 'cs_main' exclusively [-Werror,-Wthread-safety-analysis]
pblockindex = LookupBlockIndex(hash);
^
1 error generated.
Once you've fixed any problems, and you believe your code is ready for review, can you please squash your commits. You could either squash everything into a single commit, or two commits. One that adds the new RPC
command and a second commit that adds the test. When you do so, make sure you use clear and meaningful commit messages. The general convention is to prefix with the part of the code you are modifying.
I've also updated the PR title and body. We don't use @ mentions in commits or PR text, because they just lead to spam when downstream projects pull our changes.
src/rpc/blockchain.cpp
Outdated
} | ||
|
||
// Check if block exists or is negative | ||
if(::ChainActive().Height() < request.params[0].get_int() || request.params[0].get_int() <= -1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: having a blockheight
local will make this more legible imo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is only used 2 times, I think it is not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4 times (see below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, add {}
@fanquake Thank you for writing such an informative comment! |
I've also updated the commit messages with the rpc prefix |
Is how we get RPC bugs into releases :D concept ACK |
src/rpc/blockchain.cpp
Outdated
|
||
// Check if block exists or is negative | ||
if(::ChainActive().Height() < request.params[0].get_int() || request.params[0].get_int() <= -1) | ||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block " + std::to_string(request.params[0].get_int()) + " not found"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feel-free-to-ignore-nit: error could be RPC_INVALID_PARAMETER
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
node = self.nodes[0] | ||
node.add_p2p_connection(P2PInterface()) | ||
assert_equal(node.getblock(node.getblockhash(20)), node.getblockbyheight(20)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good follow-up testing would be to make sure -1
, 0
, getblockcount()
, and getblockcount()+1
args respond properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, exercising the 2nd arg once in the test would be 👍
block = GetBlockChecked(pblockindex); | ||
} | ||
|
||
if (verbosity <= 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed on filtering for valid range
src/rpc/blockchain.cpp
Outdated
} | ||
|
||
// Check if block exists or is negative | ||
if(::ChainActive().Height() < request.params[0].get_int() || request.params[0].get_int() <= -1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, add {}
@promag First there was a type sorry To the second point: I don't have numbers to prove it but I'm very sure that 2 separate calls are slower. |
I know it's slower, but how much? How much this saves? |
I don't think speed is a good rationale for this PR. If speed was really a concern, you wouldn't be using Bitcoin Core RPCs in the first place. The only real reason to want this is convenience. I'm sure that's the real reason this is often requested, and I'm mildly in favor of it because of that. |
See also " GUI: Add generate method #16000 ". Basically it would be nice to have a way to set aliases like |
How about just treating a blockhash
+inline uint256 GetBlockHashFromParam(const UniValue& param, const std::string& name) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+{
+ const std::string& v = param.get_str();
+ if (v.size() > 1 && v[0] == '@') {
+ // treat as height, and lookup
+ int32_t height;
+ if (!ParseInt32(v.substr(1), &height)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %s must be @ followed by a number", v));
+ }
+ if (height < 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
+ }
+ const int current_tip = ::ChainActive().Height();
+ if (height > current_tip) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip));
+ }
+ return *(::ChainActive()[height]->phashBlock);
+ } else {
+ return ParseHashV(param, name);
+ }
+}
+
static UniValue getblock(const JSONRPCRequest& request)
{
const RPCHelpMan help{"getblock",
@@ -904,8 +926,6 @@ static UniValue getblock(const JSONRPCRequest& request)
throw std::runtime_error(help.ToString());
}
- uint256 hash(ParseHashV(request.params[0], "blockhash"));
-
int verbosity = 1;
if (!request.params[1].isNull()) {
if(request.params[1].isNum())
@@ -919,6 +939,7 @@ static UniValue getblock(const JSONRPCRequest& request)
const CBlockIndex* tip;
{
LOCK(cs_main);
+ uint256 hash(GetBlockHashFromParam(request.params[0], "blockhash"));
pblockindex = LookupBlockIndex(hash);
tip = ::ChainActive().Tip(); |
@ajtowns I think it is better to have a seperate command for this. This could cause confusion |
Concept NAK from me sorry, please provide benchmarks to support the new RPC - which IMO is the only valid reason to add redundancy to the RPC interface. |
This would make it even faster to get a block just by its height. Currently you need to execute two separate commands which is slower and more complicated in two ways: For the machine and for the user and such a feature get requested a lot |
I went ahead and made a quick profile: count = 5000
node.generate(count)
for i in range(count):
node.getblock(self.nodes[0].getblockhash(i))
# takes ~ 3742ms
for i in range(count):
node.getblockbyheight(i)
# takes ~ 2193ms Note that it fetches 5000 blocks but these are really small blocks and you should try with bigger blocks - I think it will make a difference in the comparison - I mean |
@promag Thanks for doing the test but it is even faster for the user to type the command |
Currently there are: |
rpc: Fix whitespace in rpc/blockchain.cpp rpc: getblockbyheight: Remove hash
It looks like even with bigger blocks avoiding the two RPCs is a fair bit faster:
|
Strictly speaking, "getblock(hash)" can't be cached either, because it includes a "confirmations" key which changes every time a new block gets mined |
I'm a mild concept NACK and an approach NACK on this.
Overall, I prefer #16439. I'm not a fan of magic incantations like |
@emilengler re: your question in IRC
It's just a property of the clear button bitcoin/src/qt/forms/debugwindow.ui Lines 564 to 566 in 3f288a1
|
@ryanofsky Thank you, already got this :) |
Concept NACK. getblock(getblockhash(1)) is simple enough. |
@luke-jr It probably is but in my opinion it isn't really shown that using RPC commands as parameters is possible (at least I didn't saw something like this) |
Needs rebase |
Closed because of lack of interest and merging conflicts |
As such a feature get requested a lot (#16317, #14858 and #8457), I've implemented it into a new RPC call called
getblockbyheight
as it was discussed in #16317In the past this feature got requested for the
getblock
command but it isn't a good idea to interpret a string as a number because it breaks API consistency.The function is basically just a duplicate of
getblock
with the exception that it gets the hash by the height (first parameter) and not by the hash as a parameter.It also checks if the block exists by comparing the height with the total height or if the block is negative.
EDIT: I improved the code with the help of fqlx so it no longer contains some of the getblock legacy.