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 6b1ee82417
commit 42c106df05
No known key found for this signature in database
GPG Key ID: 9B79B45691DB4173
5 changed files with 698 additions and 2 deletions

View File

@ -6,6 +6,7 @@
#include <kernel/bitcoinkernel.h>
#include <chain.h>
#include <consensus/amount.h>
#include <consensus/validation.h>
#include <kernel/caches.h>
@ -18,6 +19,7 @@
#include <logging.h>
#include <node/blockstorage.h>
#include <node/chainstate.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <script/interpreter.h>
#include <script/script.h>
@ -25,6 +27,7 @@
#include <streams.h>
#include <sync.h>
#include <tinyformat.h>
#include <uint256.h>
#include <util/fs.h>
#include <util/result.h>
#include <util/signalinterrupt.h>
@ -44,8 +47,6 @@
#include <utility>
#include <vector>
class CBlockIndex;
// Define G_TRANSLATION_FUN symbol in libbitcoinkernel library so users of the
// library aren't required to export this symbol
extern const std::function<std::string(const char*)> G_TRANSLATION_FUN{nullptr};
@ -133,6 +134,7 @@ struct Handle {
} // namespace
struct btck_BlockTreeEntry: Handle<btck_BlockTreeEntry, CBlockIndex> {};
struct btck_Block : Handle<btck_Block, std::shared_ptr<const CBlock>> {};
namespace {
@ -757,3 +759,53 @@ void btck_chainstate_manager_destroy(btck_ChainstateManager* chainman)
delete chainman;
}
btck_Block* btck_block_create(const void* raw_block, size_t raw_block_length)
{
auto block{std::make_shared<CBlock>()};
DataStream stream{std::span{reinterpret_cast<const std::byte*>(raw_block), raw_block_length}};
try {
stream >> TX_WITH_WITNESS(*block);
} catch (...) {
LogDebug(BCLog::KERNEL, "Block decode failed.");
return nullptr;
}
return btck_Block::create(block);
}
btck_Block* btck_block_copy(const btck_Block* block)
{
return btck_Block::copy(block);
}
size_t btck_block_count_transactions(const btck_Block* block)
{
return btck_Block::get(block)->vtx.size();
}
const btck_Transaction* btck_block_get_transaction_at(const btck_Block* block, size_t index)
{
assert(index < btck_Block::get(block)->vtx.size());
return btck_Transaction::ref(&btck_Block::get(block)->vtx[index]);
}
void btck_block_destroy(btck_Block* block)
{
delete block;
}
int btck_chainstate_manager_process_block(
btck_ChainstateManager* chainman,
const btck_Block* block,
int* _new_block)
{
bool new_block;
auto result = btck_ChainstateManager::get(chainman).m_chainman->ProcessNewBlock(btck_Block::get(block), /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
if (_new_block) {
*_new_block = new_block ? 1 : 0;
}
return result ? 0 : -1;
}

View File

@ -185,6 +185,11 @@ typedef struct btck_ChainstateManagerOptions btck_ChainstateManagerOptions;
*/
typedef struct btck_ChainstateManager btck_ChainstateManager;
/**
* Opaque data structure for holding a block.
*/
typedef struct btck_Block btck_Block;
/** Current sync state passed to tip changed callbacks. */
typedef uint8_t btck_SynchronizationState;
#define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0))
@ -749,6 +754,23 @@ BITCOINKERNEL_API void btck_chainstate_manager_options_destroy(btck_ChainstateMa
BITCOINKERNEL_API btck_ChainstateManager* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_create(
const btck_ChainstateManagerOptions* chainstate_manager_options) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Process and validate the passed in block with the chainstate
* manager. Processing first does checks on the block, and if these passed,
* saves it to disk. It then validates the block against the utxo set. If it is
* valid, the chain is extended with it. The return value is not indicative of
* the block's validity.
*
* @param[in] chainstate_manager Non-null.
* @param[in] block Non-null, block to be validated.
* @param[out] new_block Nullable, will be set to 1 if this block was not processed before.
* @return 0 if processing the block was successful. Will also return 0 for valid, but duplicate blocks.
*/
BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_process_block(
btck_ChainstateManager* chainstate_manager,
const btck_Block* block,
int* new_block) BITCOINKERNEL_ARG_NONNULL(1, 2, 3);
/**
* Destroy the chainstate manager.
*/
@ -756,6 +778,58 @@ BITCOINKERNEL_API void btck_chainstate_manager_destroy(btck_ChainstateManager* c
///@}
/** @name Block
* Functions for working with blocks.
*/
///@{
/**
* @brief Parse a serialized raw block into a new block object.
*
* @param[in] raw_block Non-null, serialized block.
* @param[in] raw_block_len Length of the serialized block.
* @return The allocated block, or null on error.
*/
BITCOINKERNEL_API btck_Block* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_create(
const void* raw_block, size_t raw_block_len) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Copy a block. Blocks are reference counted, so this just increments
* the reference count.
*
* @param[in] block Non-null.
* @return The copied block.
*/
BITCOINKERNEL_API btck_Block* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_copy(
const btck_Block* block) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Count the number of transactions contained in a block.
*
* @param[in] block Non-null.
* @return The number of transactions in the block.
*/
BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_count_transactions(
const btck_Block* block) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the transaction at the provided index. The returned transaction
* is not owned and depends on the lifetime of the block.
*
* @param[in] block Non-null.
* @param[in] transaction_index The index of the transaction to be retrieved.
* @return The transaction.
*/
BITCOINKERNEL_API const btck_Transaction* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_get_transaction_at(
const btck_Block* block, size_t transaction_index) BITCOINKERNEL_ARG_NONNULL(1);
/**
* Destroy the block.
*/
BITCOINKERNEL_API void btck_block_destroy(btck_Block* block);
///@}
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus

View File

@ -463,11 +463,20 @@ public:
}
};
class TransactionView : public View<btck_Transaction>, public TransactionApi<TransactionView>
{
public:
explicit TransactionView(const btck_Transaction* ptr) : View{ptr} {}
};
class Transaction : public Handle<btck_Transaction, btck_transaction_copy, btck_transaction_destroy>, public TransactionApi<Transaction>
{
public:
explicit Transaction(std::span<const std::byte> raw_transaction)
: Handle{btck_transaction_create(raw_transaction.data(), raw_transaction.size())} {}
Transaction(const TransactionView& view)
: Handle{view} {}
};
template <typename Derived>
@ -499,6 +508,34 @@ bool ScriptPubkeyApi<Derived>::Verify(int64_t amount,
return result == 1;
}
class Block : public Handle<btck_Block, btck_block_copy, btck_block_destroy>
{
public:
Block(const std::span<const std::byte> raw_block)
: Handle{btck_block_create(raw_block.data(), raw_block.size())}
{
}
Block(btck_Block* block) : Handle{block} {}
size_t CountTransactions() const
{
return btck_block_count_transactions(get());
}
TransactionView GetTransaction(size_t index) const
{
return TransactionView{btck_block_get_transaction_at(get(), index)};
}
auto Transactions() const
{
return Range<Block, &Block::CountTransactions, &Block::GetTransaction>{*this};
}
friend class ChainMan;
};
void logging_disable()
{
btck_logging_disable();
@ -646,6 +683,14 @@ public:
: UniqueHandle{btck_chainstate_manager_create(chainman_opts.get())}
{
}
bool ProcessBlock(const Block& block, bool* new_block)
{
int _new_block;
int res = btck_chainstate_manager_process_block(get(), block.get(), &_new_block);
if (new_block) *new_block = _new_block == 1;
return res == 0;
}
};
} // namespace btck

File diff suppressed because one or more lines are too long

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>
@ -63,6 +65,11 @@ constexpr auto VERIFY_ALL_PRE_SEGWIT{ScriptVerificationFlags::P2SH | ScriptVerif
ScriptVerificationFlags::CHECKSEQUENCEVERIFY};
constexpr auto VERIFY_ALL_PRE_TAPROOT{VERIFY_ALL_PRE_SEGWIT | ScriptVerificationFlags::WITNESS};
std::span<const std::byte> as_bytes(std::vector<unsigned char> data)
{
return std::span{reinterpret_cast<const std::byte*>(data.data()), data.size()};
}
void check_equal(std::span<const std::byte> _actual, std::span<const std::byte> _expected, bool equal = true)
{
std::span<const uint8_t> actual{reinterpret_cast<const unsigned char*>(_actual.data()), _actual.size()};
@ -473,6 +480,15 @@ BOOST_AUTO_TEST_CASE(btck_context_tests)
}
}
BOOST_AUTO_TEST_CASE(btck_block)
{
Block block{as_bytes(REGTEST_BLOCK_DATA[0])};
Block block_1{as_bytes(REGTEST_BLOCK_DATA[1])};
CheckHandle(block, block_1);
Block block_tx{as_bytes(REGTEST_BLOCK_DATA[205])};
CheckRange(block_tx.Transactions(), block_tx.CountTransactions());
}
Context create_context(std::shared_ptr<TestKernelNotifications> notifications, ChainType chain_type)
{
ContextOptions options{};
@ -515,3 +531,94 @@ 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)
{
btck_LoggingOptions logging_options = {
.log_timestamps = true,
.log_time_micros = true,
.log_threadnames = false,
.log_sourcelocations = false,
.always_print_category_levels = true,
};
Logger logger{std::make_unique<TestLog>(TestLog{}), logging_options};
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{as_bytes(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{as_bytes(REGTEST_BLOCK_DATA[i])};
bool new_block{false};
BOOST_CHECK(chainman->ProcessBlock(block, &new_block));
BOOST_CHECK(new_block);
}
}