#include <chain.h>
#include <chainparams.h>
#include <clientversion.h>
#include <node/blockstorage.h>
#include <node/context.h>
#include <node/kernel_notifications.h>
#include <script/solver.h>
#include <primitives/block.h>
#include <util/chaintype.h>
#include <validation.h>
#include <boost/test/unit_test.hpp>
#include <test/util/common.h>
#include <test/util/logging.h>
#include <test/util/setup_common.h>
using kernel::CBlockFileInfo;
using node::STORAGE_HEADER_BYTES;
using node::BlockManager;
using node::KernelNotifications;
using node::MAX_BLOCKFILE_SIZE;
BOOST_FIXTURE_TEST_SUITE(blockmanager_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
{
const auto params {CreateChainParams(ArgsManager{}, ChainType::MAIN)};
KernelNotifications notifications{Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings)};
const BlockManager::Options blockman_opts{
.chainparams = *params,
.blocks_dir = m_args.GetBlocksDirPath(),
.notifications = notifications,
.block_tree_db_params = DBParams{
.path = m_args.GetDataDirNet() / "blocks" / "index",
.cache_bytes = 0,
},
};
BlockManager blockman{*Assert(m_node.shutdown_signal), blockman_opts};
BOOST_CHECK_EQUAL(blockman.WriteBlock(params->GenesisBlock(), 0).nPos, STORAGE_HEADER_BYTES);
const FlatFilePos pos{0, STORAGE_HEADER_BYTES};
blockman.UpdateBlockInfo(params->GenesisBlock(), 0, pos);
FlatFilePos actual{blockman.WriteBlock(params->GenesisBlock(), 1)};
BOOST_CHECK_EQUAL(actual.nPos, STORAGE_HEADER_BYTES + ::GetSerializeSize(TX_WITH_WITNESS(params->GenesisBlock())) + STORAGE_HEADER_BYTES);
}
BOOST_FIXTURE_TEST_CASE(blockmanager_scan_unlink_already_pruned_files, TestChain100Setup)
{
auto& chainman{*Assert(m_node.chainman)};
auto& blockman{chainman.m_blockman};
const CBlockIndex* old_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
WITH_LOCK(chainman.GetMutex(), blockman.GetBlockFileInfo(old_tip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
int file_number;
{
LOCK(chainman.GetMutex());
file_number = old_tip->GetBlockPos().nFile;
blockman.PruneOneBlockFile(file_number);
}
const FlatFilePos pos(file_number, 0);
WITH_LOCK(chainman.GetMutex(), blockman.ScanAndUnlinkAlreadyPrunedFiles());
BOOST_CHECK(!blockman.OpenBlockFile(pos, true).IsNull());
blockman.m_have_pruned = true;
WITH_LOCK(chainman.GetMutex(), blockman.ScanAndUnlinkAlreadyPrunedFiles());
BOOST_CHECK(blockman.OpenBlockFile(pos, true).IsNull());
WITH_LOCK(chainman.GetMutex(), blockman.ScanAndUnlinkAlreadyPrunedFiles());
const CBlockIndex* new_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
BOOST_CHECK_NE(old_tip, new_tip);
const int new_file_number{WITH_LOCK(chainman.GetMutex(), return new_tip->GetBlockPos().nFile)};
const FlatFilePos new_pos(new_file_number, 0);
BOOST_CHECK(!blockman.OpenBlockFile(new_pos, true).IsNull());
}
BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
{
LOCK(::cs_main);
auto& chainman = m_node.chainman;
auto& blockman = chainman->m_blockman;
const CBlockIndex& tip = *chainman->ActiveTip();
const auto& func_prune_blocks = [&](CBlockIndex* last_pruned_block)
{
LOCK(::cs_main);
CBlockIndex* it = last_pruned_block;
while (it != nullptr && it->nStatus & BLOCK_HAVE_DATA) {
it->nStatus &= ~BLOCK_HAVE_DATA;
it = it->pprev;
}
};
BOOST_CHECK_EQUAL(&blockman.GetFirstBlock(tip, BLOCK_HAVE_DATA), chainman->ActiveChain()[0]);
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *chainman->ActiveChain()[0]));
CBlockIndex* lower_block = chainman->ActiveChain()[tip.nHeight / 2];
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *lower_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}));
chainman->ActiveChain()[1]->nStatus &= ~BLOCK_HAVE_UNDO;
BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *genesis, BlockStatus{BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO}));
int height_to_prune = tip.nHeight / 2;
CBlockIndex* first_available_block = chainman->ActiveChain()[height_to_prune + 1];
CBlockIndex* last_pruned_block = first_available_block->pprev;
func_prune_blocks(last_pruned_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));
first_available_block->nStatus &= ~BLOCK_HAVE_UNDO;
BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *first_available_block, BlockStatus{BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO}));
BOOST_CHECK(blockman.CheckBlockDataAvailability(tip, *first_available_block, BlockStatus{BLOCK_HAVE_DATA}));
}
BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_part, TestChain100Setup)
{
LOCK(::cs_main);
auto& chainman{m_node.chainman};
auto& blockman{chainman->m_blockman};
const CBlockIndex& tip{*chainman->ActiveTip()};
const FlatFilePos tip_block_pos{tip.GetBlockPos()};
auto block{blockman.ReadRawBlock(tip_block_pos)};
BOOST_REQUIRE(block);
BOOST_REQUIRE_GE(block->size(), 200);
const auto expect_part{[&](size_t offset, size_t size) {
auto res{blockman.ReadRawBlock(tip_block_pos, std::pair{offset, size})};
BOOST_CHECK(res);
const auto& part{res.value()};
BOOST_CHECK_EQUAL_COLLECTIONS(part.begin(), part.end(), block->begin() + offset, block->begin() + offset + size);
}};
expect_part(0, 20);
expect_part(0, block->size() - 1);
expect_part(0, block->size() - 10);
expect_part(0, block->size());
expect_part(1, block->size() - 1);
expect_part(10, 20);
expect_part(block->size() - 1, 1);
}
BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_part_error, TestChain100Setup)
{
LOCK(::cs_main);
auto& chainman{m_node.chainman};
auto& blockman{chainman->m_blockman};
const CBlockIndex& tip{*chainman->ActiveTip()};
const FlatFilePos tip_block_pos{tip.GetBlockPos()};
auto block{blockman.ReadRawBlock(tip_block_pos)};
BOOST_REQUIRE(block);
BOOST_REQUIRE_GE(block->size(), 200);
const auto expect_part_error{[&](size_t offset, size_t size) {
auto res{blockman.ReadRawBlock(tip_block_pos, std::pair{offset, size})};
BOOST_CHECK(!res);
BOOST_CHECK_EQUAL(res.error(), node::ReadRawError::BadPartRange);
}};
expect_part_error(0, 0);
expect_part_error(0, block->size() + 1);
expect_part_error(0, std::numeric_limits<size_t>::max());
expect_part_error(1, block->size());
expect_part_error(2, block->size() - 1);
expect_part_error(block->size() - 1, 2);
expect_part_error(block->size() - 2, 3);
expect_part_error(block->size() + 1, 0);
expect_part_error(block->size() + 1, 1);
expect_part_error(block->size() + 2, 2);
expect_part_error(block->size(), 0);
expect_part_error(block->size(), 1);
expect_part_error(std::numeric_limits<size_t>::max(), 1);
expect_part_error(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max());
}
BOOST_FIXTURE_TEST_CASE(blockmanager_readblock_hash_mismatch, TestingSetup)
{
CBlockIndex index;
{
LOCK(cs_main);
const auto tip{m_node.chainman->ActiveTip()};
index.nStatus = tip->nStatus;
index.nDataPos = tip->nDataPos;
index.phashBlock = &uint256::ONE; }
ASSERT_DEBUG_LOG("GetHash() doesn't match index");
CBlock block;
BOOST_CHECK(!m_node.chainman->m_blockman.ReadBlock(block, index));
}
BOOST_AUTO_TEST_CASE(blockmanager_flush_block_file)
{
KernelNotifications notifications{Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings)};
node::BlockManager::Options blockman_opts{
.chainparams = Params(),
.blocks_dir = m_args.GetBlocksDirPath(),
.notifications = notifications,
.block_tree_db_params = DBParams{
.path = m_args.GetDataDirNet() / "blocks" / "index",
.cache_bytes = 0,
},
};
BlockManager blockman{*Assert(m_node.shutdown_signal), blockman_opts};
CBlock block1;
block1.nVersion = 1;
CBlock block2;
block2.nVersion = 2;
CBlock block3;
block3.nVersion = 3;
constexpr int TEST_BLOCK_SIZE{81};
BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), 0);
FlatFilePos pos1{blockman.WriteBlock(block1, 1)};
FlatFilePos pos2{blockman.WriteBlock(block2, 2)};
BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), (TEST_BLOCK_SIZE + STORAGE_HEADER_BYTES) * 2);
CBlock read_block;
BOOST_CHECK_EQUAL(read_block.nVersion, 0);
{
ASSERT_DEBUG_LOG("Errors in block header");
BOOST_CHECK(!blockman.ReadBlock(read_block, pos1, {}));
BOOST_CHECK_EQUAL(read_block.nVersion, 1);
}
{
ASSERT_DEBUG_LOG("Errors in block header");
BOOST_CHECK(!blockman.ReadBlock(read_block, pos2, {}));
BOOST_CHECK_EQUAL(read_block.nVersion, 2);
}
CBlockFileInfo* block_data = blockman.GetBlockFileInfo(0);
BOOST_CHECK_EQUAL(block_data->nBlocks, 2);
blockman.UpdateBlockInfo(block3, 3, pos2);
BOOST_CHECK_EQUAL(block_data->nBlocks, 3);
BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), (TEST_BLOCK_SIZE + STORAGE_HEADER_BYTES) * 2);
BOOST_CHECK(!blockman.ReadBlock(read_block, pos2, {}));
BOOST_CHECK_EQUAL(read_block.nVersion, 2);
}
BOOST_FIXTURE_TEST_CASE(prune_lock_update_and_delete, TestingSetup)
{
LOCK(::cs_main);
auto& chainman{*Assert(m_node.chainman)};
auto& blockman{chainman.m_blockman};
blockman.UpdatePruneLock("test_lock", node::PruneLockInfo{.height_first = 100});
blockman.UpdatePruneLock("test_lock", node::PruneLockInfo{.height_first = 200});
BOOST_CHECK(blockman.DeletePruneLock("test_lock"));
BOOST_CHECK(!blockman.DeletePruneLock("test_lock"));
BOOST_CHECK(!blockman.DeletePruneLock("nonexistent"));
}
BOOST_AUTO_TEST_SUITE_END()