aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/node/blockstorage.cpp8
-rw-r--r--src/node/blockstorage.h31
-rw-r--r--src/rpc/blockchain.cpp25
-rw-r--r--src/rpc/blockchain.h4
-rw-r--r--src/test/blockchain_tests.cpp34
-rw-r--r--src/test/blockmanager_tests.cpp5
6 files changed, 93 insertions, 14 deletions
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index 576c07a833..6dbc111eb6 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -595,12 +595,12 @@ bool BlockManager::IsBlockPruned(const CBlockIndex& block)
return m_have_pruned && !(block.nStatus & BLOCK_HAVE_DATA) && (block.nTx > 0);
}
-const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& upper_block, const CBlockIndex* lower_block)
+const CBlockIndex* BlockManager::GetFirstBlock(const CBlockIndex& upper_block, uint32_t status_mask, const CBlockIndex* lower_block) const
{
AssertLockHeld(::cs_main);
const CBlockIndex* last_block = &upper_block;
- assert(last_block->nStatus & BLOCK_HAVE_DATA); // 'upper_block' must have data
- while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) {
+ assert((last_block->nStatus & status_mask) == status_mask); // 'upper_block' must satisfy the status mask
+ while (last_block->pprev && ((last_block->pprev->nStatus & status_mask) == status_mask)) {
if (lower_block) {
// Return if we reached the lower_block
if (last_block == lower_block) return lower_block;
@@ -617,7 +617,7 @@ const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& upper_bl
bool BlockManager::CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block)
{
if (!(upper_block.nStatus & BLOCK_HAVE_DATA)) return false;
- return GetFirstStoredBlock(upper_block, &lower_block) == &lower_block;
+ return GetFirstBlock(upper_block, BLOCK_HAVE_DATA, &lower_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 ce514cc645..d1e46fde2b 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -335,10 +335,33 @@ public:
//! (part of the same chain).
bool CheckBlockDataAvailability(const CBlockIndex& upper_block LIFETIMEBOUND, const CBlockIndex& lower_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
- //! Find the first stored ancestor of start_block immediately after the last
- //! pruned ancestor. Return value will never be null. Caller is responsible
- //! for ensuring that start_block has data is not pruned.
- const CBlockIndex* GetFirstStoredBlock(const CBlockIndex& start_block LIFETIMEBOUND, const CBlockIndex* lower_block=nullptr) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ /**
+ * @brief Returns the earliest block with specified `status_mask` flags set after
+ * the latest block _not_ having those flags.
+ *
+ * This function starts from `upper_block`, which must have all
+ * `status_mask` flags set, and iterates backwards through its ancestors. It
+ * continues as long as each block has all `status_mask` flags set, until
+ * reaching the oldest ancestor or `lower_block`.
+ *
+ * @pre `upper_block` must have all `status_mask` flags set.
+ * @pre `lower_block` must be null or an ancestor of `upper_block`
+ *
+ * @param upper_block The starting block for the search, which must have all
+ * `status_mask` flags set.
+ * @param status_mask Bitmask specifying required status flags.
+ * @param lower_block The earliest possible block to return. If null, the
+ * search can extend to the genesis block.
+ *
+ * @return A non-null pointer to the earliest block between `upper_block`
+ * and `lower_block`, inclusive, such that every block between the
+ * returned block and `upper_block` has `status_mask` flags set.
+ */
+ const CBlockIndex* GetFirstBlock(
+ const CBlockIndex& upper_block LIFETIMEBOUND,
+ uint32_t status_mask,
+ const CBlockIndex* lower_block = nullptr
+ ) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** True if any block files have ever been pruned. */
bool m_have_pruned = false;
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index a1135c27d4..822498c6d0 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -782,6 +782,24 @@ static RPCHelpMan getblock()
};
}
+//! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
+std::optional<int> GetPruneHeight(const BlockManager& blockman, const CChain& chain) {
+ AssertLockHeld(::cs_main);
+
+ const CBlockIndex* chain_tip{chain.Tip()};
+ if (!(chain_tip->nStatus & BLOCK_HAVE_DATA)) return chain_tip->nHeight;
+
+ // Get first block with data, after the last block without data.
+ // This is the start of the unpruned range of blocks.
+ const auto& first_unpruned{*Assert(blockman.GetFirstBlock(*chain_tip, /*status_mask=*/BLOCK_HAVE_DATA))};
+ if (!first_unpruned.pprev) {
+ // No block before the first unpruned block means nothing is pruned.
+ return std::nullopt;
+ }
+ // Block before the first unpruned block is the last pruned block.
+ return Assert(first_unpruned.pprev)->nHeight;
+}
+
static RPCHelpMan pruneblockchain()
{
return RPCHelpMan{"pruneblockchain", "",
@@ -834,8 +852,7 @@ static RPCHelpMan pruneblockchain()
}
PruneBlockFilesManual(active_chainstate, height);
- const CBlockIndex& block{*CHECK_NONFATAL(active_chain.Tip())};
- return block.nStatus & BLOCK_HAVE_DATA ? active_chainstate.m_blockman.GetFirstStoredBlock(block)->nHeight - 1 : block.nHeight;
+ return GetPruneHeight(chainman.m_blockman, active_chain).value_or(-1);
},
};
}
@@ -1288,8 +1305,8 @@ RPCHelpMan getblockchaininfo()
obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
if (chainman.m_blockman.IsPruneMode()) {
- bool has_tip_data = tip.nStatus & BLOCK_HAVE_DATA;
- obj.pushKV("pruneheight", has_tip_data ? chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight : tip.nHeight + 1);
+ const auto prune_height{GetPruneHeight(chainman.m_blockman, active_chainstate.m_chain)};
+ obj.pushKV("pruneheight", prune_height ? prune_height.value() + 1 : 0);
const bool automatic_pruning{chainman.m_blockman.GetPruneTarget() != BlockManager::PRUNE_TARGET_MANUAL};
obj.pushKV("automatic_pruning", automatic_pruning);
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index c2021c3608..f6a7fe236c 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -21,6 +21,7 @@ class CBlockIndex;
class Chainstate;
class UniValue;
namespace node {
+class BlockManager;
struct NodeContext;
} // namespace node
@@ -57,4 +58,7 @@ UniValue CreateUTXOSnapshot(
const fs::path& path,
const fs::path& tmppath);
+//! 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);
+
#endif // BITCOIN_RPC_BLOCKCHAIN_H
diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp
index be515a9eac..2a63d09795 100644
--- a/src/test/blockchain_tests.cpp
+++ b/src/test/blockchain_tests.cpp
@@ -5,7 +5,9 @@
#include <boost/test/unit_test.hpp>
#include <chain.h>
+#include <node/blockstorage.h>
#include <rpc/blockchain.h>
+#include <sync.h>
#include <test/util/setup_common.h>
#include <util/string.h>
@@ -74,4 +76,36 @@ BOOST_AUTO_TEST_CASE(get_difficulty_for_very_high_target)
TestDifficulty(0x12345678, 5913134931067755359633408.0);
}
+//! Prune chain from height down to genesis block and check that
+//! GetPruneHeight returns the correct value
+static void CheckGetPruneHeight(node::BlockManager& blockman, CChain& chain, int height) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
+{
+ AssertLockHeld(::cs_main);
+
+ // Emulate pruning all blocks from `height` down to the genesis block
+ // by unsetting the `BLOCK_HAVE_DATA` flag from `nStatus`
+ for (CBlockIndex* it{chain[height]}; it != nullptr && it->nHeight > 0; it = it->pprev) {
+ it->nStatus &= ~BLOCK_HAVE_DATA;
+ }
+
+ const auto prune_height{GetPruneHeight(blockman, chain)};
+ BOOST_REQUIRE(prune_height.has_value());
+ BOOST_CHECK_EQUAL(*prune_height, height);
+}
+
+BOOST_FIXTURE_TEST_CASE(get_prune_height, TestChain100Setup)
+{
+ LOCK(::cs_main);
+ auto& chain = m_node.chainman->ActiveChain();
+ auto& blockman = m_node.chainman->m_blockman;
+
+ // Fresh chain of 100 blocks without any pruned blocks, so std::nullopt should be returned
+ BOOST_CHECK(!GetPruneHeight(blockman, chain).has_value());
+
+ // Start pruning
+ CheckGetPruneHeight(blockman, chain, 1);
+ CheckGetPruneHeight(blockman, chain, 99);
+ CheckGetPruneHeight(blockman, chain, 100);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/blockmanager_tests.cpp b/src/test/blockmanager_tests.cpp
index d7ac0bf823..12f3257700 100644
--- a/src/test/blockmanager_tests.cpp
+++ b/src/test/blockmanager_tests.cpp
@@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <chain.h>
#include <chainparams.h>
#include <clientversion.h>
#include <node/blockstorage.h>
@@ -113,7 +114,7 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
};
// 1) Return genesis block when all blocks are available
- BOOST_CHECK_EQUAL(blockman.GetFirstStoredBlock(tip), chainman->ActiveChain()[0]);
+ BOOST_CHECK_EQUAL(blockman.GetFirstBlock(tip, BLOCK_HAVE_DATA), chainman->ActiveChain()[0]);
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *chainman->ActiveChain()[0]));
// 2) Check lower_block when all blocks are available
@@ -127,7 +128,7 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
func_prune_blocks(last_pruned_block);
// 3) The last block not pruned is in-between upper-block and the genesis block
- BOOST_CHECK_EQUAL(blockman.GetFirstStoredBlock(tip), first_available_block);
+ 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));
}