RPC: Improve performance of `getblockstats` & `getblock`
Co-Authored-By: Axel Gembe derago@gmail.com Co-Authored-By: MarcoFalke falke.marco@gmail.com
Summary
It was observed during the recent round of ScaleNet tests by @ago that
the getblockstats
call is very slow. This call is used by block
explorers and as such the extant block explorers were timing out when
issuing getblockstats
on large ScaleNet blocks.
getblockstats
has a bunch of problems:
- It keeps
cs_main
held the entire time, which is not necessary. - This is particularly problematic because it must iterate over all the inputs in a block, reading their ancestor tx's from disk. This operation can take tens of seconds to minutes on very large 300k input ScaleNet blocks.
- During this time
cs_main
is held and the entire node freezes. - What's more, it would potentially read the same tx multiple times from disk as it iterated over the inputs.
- It also had a bunch of un-optimized UniValue API usage (this is minor).
What's changed:
- Release
cs_main
as soon as we don't need it anymore. It's only needed to look-up the block. Once we have theCBlockIndex
, we don't need it anymore. All of the rest of the code was reviewed carefully and is lock-free. - Don't call
GetTransaction()
because it grabscs_main
and also is very slow and redundant.It is not needed since if you examineWe instead backported part of Core#14802, and we use the undo (.rev) file to get previous inputs in order to calculate fees.GetTransaction()
with the args we were calling it with, it ends up just usingg_txindex
always. So we use the lock-freeg_txindex
ourselves directly to find tx's. - Optimized the UniValue usage to our new (faster, safer) API.
- Leverage NRVO/RVO in C++ by returning only 1 thing in 1 branch (this is a minor optimizaton compared to all the others).
In addition, we modified getblock
to release the lock earlier (was MR !850 (closed)):
- We refactored
GetBlockChecked
(called in only 2 places,getblock
andgetblockstats
) out into 2 functions:ThrowIfPrunedBlock
(requirescs_main
) and the slowerReadBlockChecked
(does not require locks). We also added the newReadUndoChecked
for reading the inputs from the undo file.
Sorry for having 2 commits in 1 MR but the refactor was necessary, since to do it right we
needed to touch getblock
.
Test Plan
- Basic:
ninja check check-functional
- Advanced: We want to illustrate the speedup here, so you will need
ScaleNet for best results.
- Synch up a ScaleNet node, be sure to enable RPC on it in the conf
file or via the right CLI arg magic. Also enable
txindex=1
(you will need this only for testing the version of the client without this MR) - Create a shell script similar to the below, call it
cmd.sh
:
- Synch up a ScaleNet node, be sure to enable RPC on it in the conf
file or via the right CLI arg magic. Also enable
#!/bin/bash
blocks="16029 16026 16037 16130 16179 15813 16124"
for ht in $blocks; do
src/bitcoin-cli -conf=$HOME/bitcoin/bchn/scalenet.conf getblockstats $ht &
done
wait
-
Note: Be sure to replace the
-conf
arg in the shell script above with the conf file you created for your scalenet node. - Next, build
bitcoind
with this commit and start the node, wait for it to synch fully and settle.Note that the txindex takes maybe a minute to catch up if this was a full IBD you just did. - Execute the following:
time ./cmd.sh
(if you get an error about txindex, this is normal and unrelated to this commit. Wait a bit for it to catch up and try again). - You should see it spit out a bunch of JSON results and a time.
- Do 5-6 above again but this time with a
bitcoind
built against master (without this commit). Note how the time is multiple times longer, or more, depending on your machine.
This commit does not change behavior does in fact enhance the node to
no longer need txindex
for getblockstats
, otherwise correctness of the
getblockstats
call is identical, and now more efficient.
Special thanks to Axel Gembe <derago@gmail.com>
for his hard work in
troubleshooting and investigating this. This code is largely inspired
by his test code here: https://gitlab.com/ago/bitcoin-cash-node/-/tree/optimize_getblockstats
Also special thanks to Mark Lundeberg <5505128-markblundeberg@users.noreply.gitlab.com>
for his tip on using the latest core changes that use the undo file rather
than the (slower) txindex lookups per input.
Merge instructions: Preferably, do not squash if at all possible, maybe use this merge request message in the merge-commit message.