From e573f2420244c583e218f51cd0d3a3cac6731003 Mon Sep 17 00:00:00 2001
From: Matthew Zipkin <pinheadmz@gmail.com>
Date: Tue, 5 Sep 2023 09:07:02 -0400
Subject: unit test: add coverage for BlockManager

---
 src/test/blockmanager_tests.cpp | 71 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

(limited to 'src/test')

diff --git a/src/test/blockmanager_tests.cpp b/src/test/blockmanager_tests.cpp
index 553bb31ba1..d9bd71f868 100644
--- a/src/test/blockmanager_tests.cpp
+++ b/src/test/blockmanager_tests.cpp
@@ -7,10 +7,12 @@
 #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/logging.h>
 #include <test/util/setup_common.h>
 
 using node::BLOCK_SERIALIZATION_HEADER_SIZE;
@@ -129,4 +131,73 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
     BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *last_pruned_block));
 }
 
+BOOST_AUTO_TEST_CASE(blockmanager_flush_block_file)
+{
+    KernelNotifications notifications{m_node.exit_status};
+    node::BlockManager::Options blockman_opts{
+        .chainparams = Params(),
+        .blocks_dir = m_args.GetBlocksDirPath(),
+        .notifications = notifications,
+    };
+    BlockManager blockman{m_node.kernel->interrupt, blockman_opts};
+
+    // Test blocks with no transactions, not even a coinbase
+    CBlock block1;
+    block1.nVersion = 1;
+    CBlock block2;
+    block2.nVersion = 2;
+    CBlock block3;
+    block3.nVersion = 3;
+
+    // They are 80 bytes header + 1 byte 0x00 for vtx length
+    constexpr int TEST_BLOCK_SIZE{81};
+
+    // Blockstore is empty
+    BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), 0);
+
+    // Write the first block; dbp=nullptr means this block doesn't already have a disk
+    // location, so allocate a free location and write it there.
+    FlatFilePos pos1{blockman.SaveBlockToDisk(block1, /*nHeight=*/1, /*dbp=*/nullptr)};
+
+    // Write second block
+    FlatFilePos pos2{blockman.SaveBlockToDisk(block2, /*nHeight=*/2, /*dbp=*/nullptr)};
+
+    // Two blocks in the file
+    BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), (TEST_BLOCK_SIZE + BLOCK_SERIALIZATION_HEADER_SIZE) * 2);
+
+    // First two blocks are written as expected
+    // Errors are expected because block data is junk, thrown AFTER successful read
+    CBlock read_block;
+    BOOST_CHECK_EQUAL(read_block.nVersion, 0);
+    {
+        ASSERT_DEBUG_LOG("ReadBlockFromDisk: Errors in block header");
+        BOOST_CHECK(!blockman.ReadBlockFromDisk(read_block, pos1));
+        BOOST_CHECK_EQUAL(read_block.nVersion, 1);
+    }
+    {
+        ASSERT_DEBUG_LOG("ReadBlockFromDisk: Errors in block header");
+        BOOST_CHECK(!blockman.ReadBlockFromDisk(read_block, pos2));
+        BOOST_CHECK_EQUAL(read_block.nVersion, 2);
+    }
+
+    // When FlatFilePos* dbp is given, SaveBlockToDisk() will not write or
+    // overwrite anything to the flat file block storage. It will, however,
+    // update the blockfile metadata. This is to facilitate reindexing
+    // when the user has the blocks on disk but the metadata is being rebuilt.
+    // Verify this behavior by attempting (and failing) to write block 3 data
+    // to block 2 location.
+    CBlockFileInfo* block_data = blockman.GetBlockFileInfo(0);
+    BOOST_CHECK_EQUAL(block_data->nBlocks, 2);
+    BOOST_CHECK(blockman.SaveBlockToDisk(block3, /*nHeight=*/3, /*dbp=*/&pos2) == pos2);
+    // Metadata is updated...
+    BOOST_CHECK_EQUAL(block_data->nBlocks, 3);
+    // ...but there are still only two blocks in the file
+    BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), (TEST_BLOCK_SIZE + BLOCK_SERIALIZATION_HEADER_SIZE) * 2);
+
+    // Block 2 was not overwritten:
+    //   SaveBlockToDisk() did not call WriteBlockToDisk() because `FlatFilePos* dbp` was non-null
+    blockman.ReadBlockFromDisk(read_block, pos2);
+    BOOST_CHECK_EQUAL(read_block.nVersion, 2);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
-- 
cgit v1.2.3