mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-08 13:49:35 +02:00
Merge bitcoin/bitcoin#30410: rpc, rest: Improve block rpc error handling, check header before attempting to read block data.
6a1aa510e3rpc: check block index before reading block / undo data (Martin Zumsande)6cbf2e5f81rpc: Improve gettxoutproof error when only header is available. (Martin Zumsande)69fc867ea1test: add coverage to getblock and getblockstats (Martin Zumsande)5290cbd585rpc: Improve getblock / getblockstats error when only header is available. (Martin Zumsande)e5b537bbdfrest: improve error when only header of a block is available. (Martin Zumsande) Pull request description: Fixes #20978 If a block was pruned, `getblock` already returns a specific error: "Block not available (pruned data)". But if we haven't received the full block yet (e.g. in a race with block downloading after a new block was received headers-first, or during IBD) we just return an unspecific "Block not found on disk" error and log `ERROR: ReadBlockFromDisk: OpenBlockFile failed for FlatFilePos(nFile=-1, nPos=0) ` which suggest something went wrong even though this is a completely normal and expected situation. This PR improves the error message and stops calling `ReadRawBlockFromDisk()`, when we already know from the header that the block is not available on disk. Similarly, it prevents all other rpcs from calling blockstorage read functions unless we expect the data to be there, so that `LogError()` will only be thrown when there is an actual file system problem. I'm not completely sure if the cause is important enough to change the wording of the rpc error, that some scripts may rely on. If reviewers prefer it, an alternative solution would be to keep returning the current "Block not found on disk" error, but return it immediately instead of calling `ReadRawBlockFromDisk`, which would at least prevent the log error and also be an improvement in my opinion. ACKs for top commit: fjahr: re-ACK6a1aa510e3achow101: ACK6a1aa510e3andrewtoth: re-ACK6a1aa510e3Tree-SHA512: 491aef880e8298a05841c4bf8eb913ef84820d1ad5415fd17d9b441bff181959ebfdd432b5eb8347dc9c568433f9a2384ca9d84cd72c79d8a58323ca117538fe
This commit is contained in:
@@ -201,8 +201,10 @@ UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIn
|
||||
case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
|
||||
CBlockUndo blockUndo;
|
||||
const bool is_not_pruned{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))};
|
||||
const bool have_undo{is_not_pruned && blockman.UndoReadFromDisk(blockUndo, blockindex)};
|
||||
|
||||
bool have_undo{is_not_pruned && WITH_LOCK(::cs_main, return blockindex.nStatus & BLOCK_HAVE_UNDO)};
|
||||
if (have_undo && !blockman.UndoReadFromDisk(blockUndo, blockindex)) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.");
|
||||
}
|
||||
for (size_t i = 0; i < block.vtx.size(); ++i) {
|
||||
const CTransactionRef& tx = block.vtx.at(i);
|
||||
// coinbase transaction (i.e. i == 0) doesn't have undo data
|
||||
@@ -597,20 +599,32 @@ static RPCHelpMan getblockheader()
|
||||
};
|
||||
}
|
||||
|
||||
void CheckBlockDataAvailability(BlockManager& blockman, const CBlockIndex& blockindex, bool check_for_undo)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
uint32_t flag = check_for_undo ? BLOCK_HAVE_UNDO : BLOCK_HAVE_DATA;
|
||||
if (!(blockindex.nStatus & flag)) {
|
||||
if (blockman.IsBlockPruned(blockindex)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, strprintf("%s not available (pruned data)", check_for_undo ? "Undo data" : "Block"));
|
||||
}
|
||||
if (check_for_undo) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available");
|
||||
}
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (not fully downloaded)");
|
||||
}
|
||||
}
|
||||
|
||||
static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex)
|
||||
{
|
||||
CBlock block;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
if (blockman.IsBlockPruned(blockindex)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
|
||||
}
|
||||
CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false);
|
||||
}
|
||||
|
||||
if (!blockman.ReadBlockFromDisk(block, blockindex)) {
|
||||
// Block not found on disk. This could be because we have the block
|
||||
// header in our index but not yet have the block or did not accept the
|
||||
// block. Or if the block was pruned right after we released the lock above.
|
||||
// Block not found on disk. This shouldn't normally happen unless the block was
|
||||
// pruned right after we released the lock above.
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
|
||||
}
|
||||
|
||||
@@ -623,16 +637,13 @@ static std::vector<uint8_t> GetRawBlockChecked(BlockManager& blockman, const CBl
|
||||
FlatFilePos pos{};
|
||||
{
|
||||
LOCK(cs_main);
|
||||
if (blockman.IsBlockPruned(blockindex)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
|
||||
}
|
||||
CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false);
|
||||
pos = blockindex.GetBlockPos();
|
||||
}
|
||||
|
||||
if (!blockman.ReadRawBlockFromDisk(data, pos)) {
|
||||
// Block not found on disk. This could be because we have the block
|
||||
// header in our index but not yet have the block or did not accept the
|
||||
// block. Or if the block was pruned right after we released the lock above.
|
||||
// Block not found on disk. This shouldn't normally happen unless the block was
|
||||
// pruned right after we released the lock above.
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
|
||||
}
|
||||
|
||||
@@ -648,9 +659,7 @@ static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& bloc
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
if (blockman.IsBlockPruned(blockindex)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)");
|
||||
}
|
||||
CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/true);
|
||||
}
|
||||
|
||||
if (!blockman.UndoReadFromDisk(blockUndo, blockindex)) {
|
||||
|
||||
@@ -60,5 +60,6 @@ UniValue CreateUTXOSnapshot(
|
||||
|
||||
//! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
|
||||
std::optional<int> GetPruneHeight(const node::BlockManager& blockman, const CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
void CheckBlockDataAvailability(node::BlockManager& blockman, const CBlockIndex& blockindex, bool check_for_undo) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
|
||||
#endif // BITCOIN_RPC_BLOCKCHAIN_H
|
||||
|
||||
@@ -405,11 +405,16 @@ static RPCHelpMan getrawtransaction()
|
||||
CBlockUndo blockUndo;
|
||||
CBlock block;
|
||||
|
||||
if (tx->IsCoinBase() || !blockindex || WITH_LOCK(::cs_main, return chainman.m_blockman.IsBlockPruned(*blockindex)) ||
|
||||
!(chainman.m_blockman.UndoReadFromDisk(blockUndo, *blockindex) && chainman.m_blockman.ReadBlockFromDisk(block, *blockindex))) {
|
||||
if (tx->IsCoinBase() || !blockindex || WITH_LOCK(::cs_main, return !(blockindex->nStatus & BLOCK_HAVE_MASK))) {
|
||||
TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
|
||||
return result;
|
||||
}
|
||||
if (!chainman.m_blockman.UndoReadFromDisk(blockUndo, *blockindex)) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.");
|
||||
}
|
||||
if (!chainman.m_blockman.ReadBlockFromDisk(block, *blockindex)) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.");
|
||||
}
|
||||
|
||||
CTxUndo* undoTX {nullptr};
|
||||
auto it = std::find_if(block.vtx.begin(), block.vtx.end(), [tx](CTransactionRef t){ return *t == *tx; });
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <merkleblock.h>
|
||||
#include <node/blockstorage.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <rpc/blockchain.h>
|
||||
#include <rpc/server.h>
|
||||
#include <rpc/server_util.h>
|
||||
#include <rpc/util.h>
|
||||
@@ -96,6 +97,10 @@ static RPCHelpMan gettxoutproof()
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CheckBlockDataAvailability(chainman.m_blockman, *pblockindex, /*check_for_undo=*/false);
|
||||
}
|
||||
CBlock block;
|
||||
if (!chainman.m_blockman.ReadBlockFromDisk(block, *pblockindex)) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
|
||||
|
||||
Reference in New Issue
Block a user