Skip to content

Commit 5290cbd

Browse files
committed
rpc: Improve getblock / getblockstats error when only header is available.
This improves the error message of the getblock and getblockstats rpc and prevents calls to ReadRawBlockFromDisk(), which are unnecessary if we know from the header nStatus field that the block is not available.
1 parent e5b537b commit 5290cbd

File tree

6 files changed

+33
-25
lines changed

6 files changed

+33
-25
lines changed

src/rpc/blockchain.cpp

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -597,20 +597,32 @@ static RPCHelpMan getblockheader()
597597
};
598598
}
599599

600+
void CheckBlockDataAvailability(BlockManager& blockman, const CBlockIndex& blockindex, bool check_for_undo)
601+
{
602+
AssertLockHeld(cs_main);
603+
uint32_t flag = check_for_undo ? BLOCK_HAVE_UNDO : BLOCK_HAVE_DATA;
604+
if (!(blockindex.nStatus & flag)) {
605+
if (blockman.IsBlockPruned(blockindex)) {
606+
throw JSONRPCError(RPC_MISC_ERROR, strprintf("%s not available (pruned data)", check_for_undo ? "Undo data" : "Block"));
607+
}
608+
if (check_for_undo) {
609+
throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available");
610+
}
611+
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (not fully downloaded)");
612+
}
613+
}
614+
600615
static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex)
601616
{
602617
CBlock block;
603618
{
604619
LOCK(cs_main);
605-
if (blockman.IsBlockPruned(blockindex)) {
606-
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
607-
}
620+
CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false);
608621
}
609622

610623
if (!blockman.ReadBlockFromDisk(block, blockindex)) {
611-
// Block not found on disk. This could be because we have the block
612-
// header in our index but not yet have the block or did not accept the
613-
// block. Or if the block was pruned right after we released the lock above.
624+
// Block not found on disk. This shouldn't normally happen unless the block was
625+
// pruned right after we released the lock above.
614626
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
615627
}
616628

@@ -623,16 +635,13 @@ static std::vector<uint8_t> GetRawBlockChecked(BlockManager& blockman, const CBl
623635
FlatFilePos pos{};
624636
{
625637
LOCK(cs_main);
626-
if (blockman.IsBlockPruned(blockindex)) {
627-
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
628-
}
638+
CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false);
629639
pos = blockindex.GetBlockPos();
630640
}
631641

632642
if (!blockman.ReadRawBlockFromDisk(data, pos)) {
633-
// Block not found on disk. This could be because we have the block
634-
// header in our index but not yet have the block or did not accept the
635-
// block. Or if the block was pruned right after we released the lock above.
643+
// Block not found on disk. This shouldn't normally happen unless the block was
644+
// pruned right after we released the lock above.
636645
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
637646
}
638647

@@ -648,9 +657,7 @@ static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& bloc
648657

649658
{
650659
LOCK(cs_main);
651-
if (blockman.IsBlockPruned(blockindex)) {
652-
throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)");
653-
}
660+
CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/true);
654661
}
655662

656663
if (!blockman.UndoReadFromDisk(blockUndo, blockindex)) {

src/rpc/blockchain.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,6 @@ UniValue CreateUTXOSnapshot(
6060

6161
//! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
6262
std::optional<int> GetPruneHeight(const node::BlockManager& blockman, const CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
63+
void CheckBlockDataAvailability(node::BlockManager& blockman, const CBlockIndex& blockindex, bool check_for_undo) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
6364

6465
#endif // BITCOIN_RPC_BLOCKCHAIN_H

test/functional/feature_assumeutxo.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ def test_sync_from_assumeutxo_node(self, snapshot):
313313
self.connect_nodes(snapshot_node.index, miner.index)
314314
self.sync_blocks(nodes=(miner, snapshot_node))
315315
# Check the base snapshot block was stored and ensure node signals full-node service support
316-
self.wait_until(lambda: not try_rpc(-1, "Block not found", snapshot_node.getblock, snapshot_block_hash))
316+
self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", snapshot_node.getblock, snapshot_block_hash))
317317
self.wait_until(lambda: 'NETWORK' in snapshot_node.getnetworkinfo()['localservicesnames'])
318318

319319
# Now that the snapshot_node is synced, verify the ibd_node can sync from it
@@ -485,7 +485,7 @@ def check_dump_output(output):
485485
# find coinbase output at snapshot height on node0 and scan for it on node1,
486486
# where the block is not available, but the snapshot was loaded successfully
487487
coinbase_tx = n0.getblock(snapshot_hash, verbosity=2)['tx'][0]
488-
assert_raises_rpc_error(-1, "Block not found on disk", n1.getblock, snapshot_hash)
488+
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, snapshot_hash)
489489
coinbase_output_descriptor = coinbase_tx['vout'][0]['scriptPubKey']['desc']
490490
scan_result = n1.scantxoutset('start', [coinbase_output_descriptor])
491491
assert_equal(scan_result['success'], True)
@@ -557,7 +557,7 @@ def check_tx_counts(final: bool) -> None:
557557
self.log.info("Submit a spending transaction for a snapshot chainstate coin to the mempool")
558558
# spend the coinbase output of the first block that is not available on node1
559559
spend_coin_blockhash = n1.getblockhash(START_HEIGHT + 1)
560-
assert_raises_rpc_error(-1, "Block not found on disk", n1.getblock, spend_coin_blockhash)
560+
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, spend_coin_blockhash)
561561
prev_tx = n0.getblock(spend_coin_blockhash, 3)['tx'][0]
562562
prevout = {"txid": prev_tx['txid'], "vout": 0, "scriptPubKey": prev_tx['vout'][0]['scriptPubKey']['hex']}
563563
privkey = n0.get_deterministic_priv_key().key

test/functional/p2p_node_network_limited.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ def test_avoid_requesting_historical_blocks(self):
102102
tip_height = pruned_node.getblockcount()
103103
limit_buffer = 2
104104
# Prevent races by waiting for the tip to arrive first
105-
self.wait_until(lambda: not try_rpc(-1, "Block not found", full_node.getblock, pruned_node.getbestblockhash()))
105+
self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", full_node.getblock, pruned_node.getbestblockhash()))
106106
for height in range(start_height_full_node + 1, tip_height + 1):
107107
if height <= tip_height - (NODE_NETWORK_LIMITED_MIN_BLOCKS - limit_buffer):
108-
assert_raises_rpc_error(-1, "Block not found on disk", full_node.getblock, pruned_node.getblockhash(height))
108+
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", full_node.getblock, pruned_node.getblockhash(height))
109109
else:
110110
full_node.getblock(pruned_node.getblockhash(height)) # just assert it does not throw an exception
111111

test/functional/p2p_unrequested_blocks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def run_test(self):
119119
assert_equal(x['status'], "headers-only")
120120
tip_entry_found = True
121121
assert tip_entry_found
122-
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_h1f.hash)
122+
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_h1f.hash)
123123

124124
# 4. Send another two block that build on the fork.
125125
block_h2f = create_block(block_h1f.sha256, create_coinbase(2), block_time)
@@ -191,7 +191,7 @@ def run_test(self):
191191
# Blocks 1-287 should be accepted, block 288 should be ignored because it's too far ahead
192192
for x in all_blocks[:-1]:
193193
self.nodes[0].getblock(x.hash)
194-
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[-1].hash)
194+
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[-1].hash)
195195

196196
# 5. Test handling of unrequested block on the node that didn't process
197197
# Should still not be processed (even though it has a child that has more
@@ -230,7 +230,7 @@ def run_test(self):
230230
assert_equal(self.nodes[0].getblockcount(), 290)
231231
self.nodes[0].getblock(all_blocks[286].hash)
232232
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash)
233-
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[287].hash)
233+
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[287].hash)
234234
self.log.info("Successfully reorged to longer chain")
235235

236236
# 8. Create a chain which is invalid at a height longer than the
@@ -260,7 +260,7 @@ def run_test(self):
260260
assert_equal(x['status'], "headers-only")
261261
tip_entry_found = True
262262
assert tip_entry_found
263-
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_292.hash)
263+
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_292.hash)
264264

265265
test_node.send_message(msg_block(block_289f))
266266
test_node.send_and_ping(msg_block(block_290f))

test/functional/rpc_getblockfrompeer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def run_test(self):
5858
self.log.info("Node 0 should only have the header for node 1's block 3")
5959
x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips()))
6060
assert_equal(x['status'], "headers-only")
61-
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, short_tip)
61+
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, short_tip)
6262

6363
self.log.info("Fetch block from node 1")
6464
peers = self.nodes[0].getpeerinfo()

0 commit comments

Comments
 (0)