support multiple block status checks in CheckBlockDataAvailability
What changed, and why it matters
This commit expands an internal Bitcoin Core function so it can check for multiple types of stored block data, not just raw block files. It also adds a special case so the check does not incorrectly fail because the genesis block has no undo data. The change is primarily a code-quality and correctness improvement; there is no direct evidence it fixes an active security vulnerability.
No immediate action required. Treat as a normal code-quality/refactoring change. Review any future callers that pass BLOCK_HAVE_UNDO to ensure they handle the genesis-block edge case correctly.
Security signals we found
Correctness fix for data-availability check
New parameter allows callers to require undo data as well as block data
Genesis-block edge case handled explicitly
Unit tests added for missing-undo detection
Evidence from the diff
CheckBlockDataAvailability previously hard-coded a check for BLOCK_HAVE_DATA. The patch adds a BlockStatus parameter (defaulting to BLOCK_HAVE_DATA) and handles the case where the caller asks for BLOCK_HAVE_UNDO and the range reaches the genesis block. Because the genesis block has no undo data, the function now checks whether all other requested flags are present up to the genesis block rather than returning false. Unit tests cover the new behavior, including detecting missing undo data in non-genesis blocks.
Changed components
src/node/blockstorage.cppsrc/node/blockstorage.hsrc/test/blockmanager_tests.cppInspect captured patch +25 / −4
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index 37bf1799..a8ff110c 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -623,10 +623,18 @@ const CBlockIndex& BlockManager::GetFirstBlock(const CBlockIndex& upper_block, u
return *last_block;
}
-bool BlockManager::CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block)
+bool BlockManager::CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block, BlockStatus block_status)
{
- if (!(upper_block.nStatus & BLOCK_HAVE_DATA)) return false;
- return &GetFirstBlock(upper_block, BLOCK_HAVE_DATA, &lower_block) == &lower_block;
+ if (!(upper_block.nStatus & block_status)) return false;
+ const auto& first_block = GetFirstBlock(upper_block, block_status, &lower_block);
+ // Special case: the genesis block has no undo data
+ if (block_status & BLOCK_HAVE_UNDO && lower_block.nHeight == 0 && first_block.nHeight == 1) {
+ // This might indicate missing data, or it could simply reflect the expected absence of undo data for the genesis block.
+ // To distinguish between the two, check if all required block data *except* undo is available up to the genesis block.
+ BlockStatus flags{block_status & ~BLOCK_HAVE_UNDO};
+ return first_block.pprev && first_block.pprev->nStatus & flags;
+ }
+ return &first_block == &lower_block;
}
// If we're using -prune with -reindex, then delete block files that will be ignored by the
diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
index d8b716e9..8cb398fe 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -414,7 +414,7 @@ public:
//! Check if all blocks in the [upper_block, lower_block] range have data available.
//! The caller is responsible for ensuring that lower_block is an ancestor of upper_block
//! (part of the same chain).
- bool CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ bool CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block, BlockStatus block_status = BLOCK_HAVE_DATA) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* @brief Returns the earliest block with specified `status_mask` flags set after
diff --git a/src/test/blockmanager_tests.cpp b/src/test/blockmanager_tests.cpp
index db59931b..ff99a1ee 100644
--- a/src/test/blockmanager_tests.cpp
+++ b/src/test/blockmanager_tests.cpp
@@ -126,6 +126,14 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
CBlockIndex* lower_block = chainman->ActiveChain()[tip.nHeight / 2];
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *lower_block));
+ // Ensure we don't fail due to the expected absence of undo data in the genesis block
+ CBlockIndex* upper_block = chainman->ActiveChain()[2];
+ CBlockIndex* genesis = chainman->ActiveChain()[0];
+ BOOST_CHECK(blockman.CheckBlockDataAvailability(*upper_block, *genesis, BlockStatus{BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO}));
+ // Ensure we detect absence of undo data in the first block
+ chainman->ActiveChain()[1]->nStatus &= ~BLOCK_HAVE_UNDO;
+ BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *genesis, BlockStatus{BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO}));
+
// Prune half of the blocks
int height_to_prune = tip.nHeight / 2;
CBlockIndex* first_available_block = chainman->ActiveChain()[height_to_prune + 1];
@@ -136,6 +144,11 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
BOOST_CHECK_EQUAL(&blockman.GetFirstBlock(tip, BLOCK_HAVE_DATA), first_available_block);
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *first_available_block));
BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *last_pruned_block));
+
+ // Simulate that the first available block is missing undo data and
+ // detect this by using a status mask.
+ first_available_block->nStatus &= ~BLOCK_HAVE_UNDO;
+ BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *first_available_block, BlockStatus{BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO}));
}
BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_part, TestChain100Setup)
Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.