kernel: Add block validation to C header

The added function allows the user process and validate a given block
with the chainstate manager. The *_process_block(...) function does some
preliminary checks on the block before passing it to
`ProcessNewBlock(...)`. These are similar to the checks in the
`submitblock()` rpc.

Richer processing of the block validation result will be made available
in the following commits through the validation interface.

The commits also adds a utility for deserializing a `CBlock`
(`kernel_block_create()`) that may then be passed to the library for
processing.

The tests exercise the function for both mainnet and regtest. The
commit also adds the data of 206 regtest blocks (some blocks also
contain transactions).
This commit is contained in:
TheCharlatan
2024-06-17 22:50:23 +02:00
parent cb1590b05e
commit ad80abc73d
5 changed files with 477 additions and 2 deletions

View File

@@ -8,6 +8,8 @@
#define BOOST_TEST_MODULE Bitcoin Kernel Test Suite
#include <boost/test/included/unit_test.hpp>
#include <test/kernel/block_data.h>
#include <charconv>
#include <cstdint>
#include <cstdlib>
@@ -470,6 +472,15 @@ BOOST_AUTO_TEST_CASE(btck_context_tests)
}
}
BOOST_AUTO_TEST_CASE(btck_block)
{
Block block{hex_string_to_byte_vec(REGTEST_BLOCK_DATA[0])};
Block block_1{hex_string_to_byte_vec(REGTEST_BLOCK_DATA[1])};
CheckHandle(block, block_1);
Block block_tx{hex_string_to_byte_vec(REGTEST_BLOCK_DATA[205])};
CheckRange(block_tx.Transactions(), block_tx.CountTransactions());
}
Context create_context(std::shared_ptr<TestKernelNotifications> notifications, ChainType chain_type)
{
ContextOptions options{};
@@ -505,3 +516,85 @@ BOOST_AUTO_TEST_CASE(btck_chainman_tests)
chainman_opts.SetWorkerThreads(4);
ChainMan chainman{context, chainman_opts};
}
std::unique_ptr<ChainMan> create_chainman(TestDirectory& test_directory,
Context& context)
{
ChainstateManagerOptions chainman_opts{context, test_directory.m_directory.string(), (test_directory.m_directory / "blocks").string()};
auto chainman{std::make_unique<ChainMan>(context, chainman_opts)};
return chainman;
}
BOOST_AUTO_TEST_CASE(btck_chainman_mainnet_tests)
{
auto mainnet_test_directory{TestDirectory{"mainnet_test_bitcoin_kernel"}};
auto notifications{std::make_shared<TestKernelNotifications>()};
auto context{create_context(notifications, ChainType::MAINNET)};
auto chainman{create_chainman(mainnet_test_directory, context)};
{
// Process an invalid block
auto raw_block = hex_string_to_byte_vec("012300");
BOOST_CHECK_THROW(Block{raw_block}, std::runtime_error);
}
{
// Process an empty block
auto raw_block = hex_string_to_byte_vec("");
BOOST_CHECK_THROW(Block{raw_block}, std::runtime_error);
}
// mainnet block 1
auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000");
Block block{raw_block};
TransactionView tx{block.GetTransaction(block.CountTransactions() - 1)};
BOOST_CHECK_EQUAL(tx.CountInputs(), 1);
Transaction tx2 = tx;
BOOST_CHECK_EQUAL(tx2.CountInputs(), 1);
for (auto transaction : block.Transactions()) {
BOOST_CHECK_EQUAL(transaction.CountInputs(), 1);
}
auto output_counts = *(block.Transactions() | std::views::transform([](const auto& tx) {
return tx.CountOutputs();
})).begin();
BOOST_CHECK_EQUAL(output_counts, 1);
bool new_block = false;
BOOST_CHECK(chainman->ProcessBlock(block, &new_block));
BOOST_CHECK(new_block);
// If we try to validate it again, it should be a duplicate
BOOST_CHECK(chainman->ProcessBlock(block, &new_block));
BOOST_CHECK(!new_block);
}
BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
{
auto test_directory{TestDirectory{"regtest_test_bitcoin_kernel"}};
auto notifications{std::make_shared<TestKernelNotifications>()};
auto context{create_context(notifications, ChainType::REGTEST)};
// Validate 206 regtest blocks in total.
// Stop halfway to check that it is possible to continue validating starting
// from prior state.
const size_t mid{REGTEST_BLOCK_DATA.size() / 2};
{
auto chainman{create_chainman(test_directory, context)};
for (size_t i{0}; i < mid; i++) {
Block block{hex_string_to_byte_vec(REGTEST_BLOCK_DATA[i])};
bool new_block{false};
BOOST_CHECK(chainman->ProcessBlock(block, &new_block));
BOOST_CHECK(new_block);
}
}
auto chainman{create_chainman(test_directory, context)};
for (size_t i{mid}; i < REGTEST_BLOCK_DATA.size(); i++) {
Block block{hex_string_to_byte_vec(REGTEST_BLOCK_DATA[i])};
bool new_block{false};
BOOST_CHECK(chainman->ProcessBlock(block, &new_block));
BOOST_CHECK(new_block);
}
}