I think this is buggy, chain tip should be considered pruned if any of the BLOCK_HAVE_MASK
flags are missing:
0 if (!((chain_tip->nStatus & BLOCK_HAVE_MASK) == BLOCK_HAVE_MASK)) return chain_tip->nHeight;
Can be verified by adding unit test to the first commit, which passes on dca1ca1f56 and fails on current HEAD for CheckGetPruneHeight(blockman, chain, 100)
:
0$ ./src/test/test_bitcoin --run_test=blockchain_tests/get_prune_height
1...
2Running 1 test case...
3Assertion failed: ((last_block->nStatus & status_mask) == status_mask), function GetFirstBlock, file blockstorage.cpp, line 602.
4unknown location:0: fatal error: in "blockchain_tests/get_prune_height": signal: SIGABRT (application abort requested)
5test/blockchain_tests.cpp:93: last checkpoint
0diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
1index 90cee9901d..b7c1bc9673 100644
2--- a/src/rpc/blockchain.cpp
3+++ b/src/rpc/blockchain.cpp
4@@ -783,7 +783,7 @@ static RPCHelpMan getblock()
5 }
6
7 //! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
8-static std::optional<int> GetPruneHeight(BlockManager& blockman, const CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
9+std::optional<int> GetPruneHeight(BlockManager& blockman, const CChain& chain) {
10 AssertLockHeld(::cs_main);
11
12 const CBlockIndex* chain_tip{chain.Tip()};
13diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
14index c2021c3608..d9bf627df5 100644
15--- a/src/rpc/blockchain.h
16+++ b/src/rpc/blockchain.h
17@@ -21,6 +21,7 @@ class CBlockIndex;
18 class Chainstate;
19 class UniValue;
20 namespace node {
21+class BlockManager;
22 struct NodeContext;
23 } // namespace node
24
25@@ -57,4 +58,7 @@ UniValue CreateUTXOSnapshot(
26 const fs::path& path,
27 const fs::path& tmppath);
28
29+//! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
30+std::optional<int> GetPruneHeight(node::BlockManager& blockman, const CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
31+
32 #endif // BITCOIN_RPC_BLOCKCHAIN_H
33diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp
34index be515a9eac..2a63d09795 100644
35--- a/src/test/blockchain_tests.cpp
36+++ b/src/test/blockchain_tests.cpp
37@@ -5,7 +5,9 @@
38 #include <boost/test/unit_test.hpp>
39
40 #include <chain.h>
41+#include <node/blockstorage.h>
42 #include <rpc/blockchain.h>
43+#include <sync.h>
44 #include <test/util/setup_common.h>
45 #include <util/string.h>
46
47@@ -74,4 +76,36 @@ BOOST_AUTO_TEST_CASE(get_difficulty_for_very_high_target)
48 TestDifficulty(0x12345678, 5913134931067755359633408.0);
49 }
50
51+//! Prune chain from height down to genesis block and check that
52+//! GetPruneHeight returns the correct value
53+static void CheckGetPruneHeight(node::BlockManager& blockman, CChain& chain, int height) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
54+{
55+ AssertLockHeld(::cs_main);
56+
57+ // Emulate pruning all blocks from `height` down to the genesis block
58+ // by unsetting the `BLOCK_HAVE_DATA` flag from `nStatus`
59+ for (CBlockIndex* it{chain[height]}; it != nullptr && it->nHeight > 0; it = it->pprev) {
60+ it->nStatus &= ~BLOCK_HAVE_DATA;
61+ }
62+
63+ const auto prune_height{GetPruneHeight(blockman, chain)};
64+ BOOST_REQUIRE(prune_height.has_value());
65+ BOOST_CHECK_EQUAL(*prune_height, height);
66+}
67+
68+BOOST_FIXTURE_TEST_CASE(get_prune_height, TestChain100Setup)
69+{
70+ LOCK(::cs_main);
71+ auto& chain = m_node.chainman->ActiveChain();
72+ auto& blockman = m_node.chainman->m_blockman;
73+
74+ // Fresh chain of 100 blocks without any pruned blocks, so std::nullopt should be returned
75+ BOOST_CHECK(!GetPruneHeight(blockman, chain).has_value());
76+
77+ // Start pruning
78+ CheckGetPruneHeight(blockman, chain, 1);
79+ CheckGetPruneHeight(blockman, chain, 99);
80+ CheckGetPruneHeight(blockman, chain, 100);
81+}
82+
83 BOOST_AUTO_TEST_SUITE_END()