diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a02a0ee5df42..9f1bdd4f0cb56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,7 @@ option(BUILD_UTIL "Build bitcoin-util executable." ${BUILD_TESTS}) option(BUILD_UTIL_CHAINSTATE "Build experimental bitcoin-chainstate executable." OFF) option(BUILD_KERNEL_LIB "Build experimental bitcoinkernel library." ${BUILD_UTIL_CHAINSTATE}) +option(BUILD_KERNEL_TEST "Build tests for the experimental bitcoinkernel library." ${BUILD_KERNEL_LIB}) option(ENABLE_WALLET "Enable wallet." ON) option(WITH_SQLITE "Enable SQLite wallet support." ${ENABLE_WALLET}) @@ -216,6 +217,7 @@ if(BUILD_FOR_FUZZING) set(BUILD_UTIL OFF) set(BUILD_UTIL_CHAINSTATE OFF) set(BUILD_KERNEL_LIB OFF) + set(BUILD_KERNEL_TEST OFF) set(BUILD_WALLET_TOOL OFF) set(BUILD_GUI OFF) set(ENABLE_EXTERNAL_SIGNER OFF) @@ -598,6 +600,7 @@ message(" bitcoin-util ........................ ${BUILD_UTIL}") message(" bitcoin-wallet ...................... ${BUILD_WALLET_TOOL}") message(" bitcoin-chainstate (experimental) ... ${BUILD_UTIL_CHAINSTATE}") message(" libbitcoinkernel (experimental) ..... ${BUILD_KERNEL_LIB}") +message(" kernel-test (experimental) .......... ${BUILD_KERNEL_TEST}") message("Optional features:") message(" wallet support ...................... ${ENABLE_WALLET}") if(ENABLE_WALLET) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22c850e775595..544c8fec146e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -443,6 +443,9 @@ if(BUILD_FUZZ_BINARY) add_subdirectory(test/fuzz) endif() +if (BUILD_KERNEL_TEST) + add_subdirectory(test/kernel) +endif() install(TARGETS ${installable_targets} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/src/kernel/CMakeLists.txt b/src/kernel/CMakeLists.txt index 2e07ba042a40a..4ec107a51e873 100644 --- a/src/kernel/CMakeLists.txt +++ b/src/kernel/CMakeLists.txt @@ -142,3 +142,19 @@ install(TARGETS bitcoinkernel DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Kernel ) + +install(FILES bitcoinkernel.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT Kernel) + +add_executable(kernel-bitcoin-chainstate + bitcoin-chainstate.cpp +) + +target_link_libraries(kernel-bitcoin-chainstate + PRIVATE + core_interface + bitcoinkernel +) + +set_target_properties(kernel-bitcoin-chainstate PROPERTIES + SKIP_BUILD_RPATH OFF +) diff --git a/src/kernel/bitcoin-chainstate.cpp b/src/kernel/bitcoin-chainstate.cpp new file mode 100644 index 0000000000000..70aa84a2ecd7e --- /dev/null +++ b/src/kernel/bitcoin-chainstate.cpp @@ -0,0 +1,197 @@ +#include + +#include +#include +#include +#include +#include + +std::vector hex_string_to_char_vec(const std::string& hex) +{ + std::vector bytes; + + for (size_t i{0}; i < hex.length(); i += 2) { + std::string byteString{hex.substr(i, 2)}; + unsigned char byte = (char)std::strtol(byteString.c_str(), nullptr, 16); + bytes.push_back(byte); + } + + return bytes; +} + +class KernelLog +{ +public: + void LogMessage(const char* message) + { + std::cout << "kernel: " << message; + } +}; + +class TestValidationInterface : public ValidationInterface +{ +public: + TestValidationInterface() : ValidationInterface() {} + + std::optional m_expected_valid_block = std::nullopt; + + void BlockChecked(const UnownedBlock block, const BlockValidationState state) override + { + auto mode{state.ValidationMode()}; + switch (mode) { + case kernel_ValidationMode::kernel_VALIDATION_STATE_VALID: { + std::cout << "Valid block" << std::endl; + return; + } + case kernel_ValidationMode::kernel_VALIDATION_STATE_INVALID: { + std::cout << "Invalid block: "; + auto result{state.BlockValidationResult()}; + switch (result) { + case kernel_BlockValidationResult::kernel_BLOCK_RESULT_UNSET: + std::cout << "initial value. Block has not yet been rejected" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_HEADER_LOW_WORK: + std::cout << "the block header may be on a too-little-work chain" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_CONSENSUS: + std::cout << "invalid by consensus rules (excluding any below reasons)" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_CACHED_INVALID: + std::cout << "this block was cached as being invalid and we didn't store the reason why" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_INVALID_HEADER: + std::cout << "invalid proof of work or time too old" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_MUTATED: + std::cout << "the block's data didn't match the data committed to by the PoW" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_MISSING_PREV: + std::cout << "We don't have the previous block the checked one is built on" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_INVALID_PREV: + std::cout << "A block this one builds on is invalid" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_TIME_FUTURE: + std::cout << "block timestamp was > 2 hours in the future (or our clock is bad)" << std::endl; + break; + case kernel_BlockValidationResult::kernel_BLOCK_CHECKPOINT: + std::cout << "the block failed to meet one of our checkpoints" << std::endl; + break; + } + return; + } + case kernel_ValidationMode::kernel_VALIDATION_STATE_ERROR: { + std::cout << "Internal error" << std::endl; + return; + } + } + } +}; + +class TestKernelNotifications : public KernelNotifications +{ +public: + void BlockTipHandler(kernel_SynchronizationState state, kernel_BlockIndex* index) override + { + std::cout << "Block tip changed" << std::endl; + } + + void ProgressHandler(const char* title, int progress_percent, bool resume_possible) override + { + std::cout << "Made progress: " << title << " " << progress_percent << "%" << std::endl; + } + + void WarningSetHandler(kernel_Warning warning, const char* message) override + { + std::cout << message << std::endl; + } + + void WarningUnsetHandler(kernel_Warning warning) override + { + std::cout << "Warning unset: " << warning << std::endl; + } + + void FlushErrorHandler(const char* error) override + { + std::cout << error << std::endl; + } + + void FatalErrorHandler(const char* error) override + { + std::cout << error << std::endl; + } +}; + +int main(int argc, char* argv[]) +{ + // SETUP: Argument parsing and handling + if (argc != 2) { + std::cerr + << "Usage: " << argv[0] << " DATADIR" << std::endl + << "Display DATADIR information, and process hex-encoded blocks on standard input." << std::endl + << std::endl + << "IMPORTANT: THIS EXECUTABLE IS EXPERIMENTAL, FOR TESTING ONLY, AND EXPECTED TO" << std::endl + << " BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl; + return 1; + } + std::filesystem::path abs_datadir{std::filesystem::absolute(argv[1])}; + std::filesystem::create_directories(abs_datadir); + + kernel_LoggingOptions logging_options = { + .log_timestamps = true, + .log_time_micros = false, + .log_threadnames = false, + .log_sourcelocations = false, + .always_print_category_levels = true, + }; + + Logger logger{std::make_unique(KernelLog{}), logging_options}; + + ContextOptions options{}; + ChainParams params{kernel_ChainType::kernel_CHAIN_TYPE_REGTEST}; + options.SetChainParams(params); + + TestKernelNotifications notifications{}; + options.SetNotifications(notifications); + + Context context{options}; + assert(context); + + ChainstateManagerOptions chainman_opts{context, abs_datadir}; + assert(chainman_opts); + BlockManagerOptions blockman_opts{context, abs_datadir / "blocks"}; + assert(blockman_opts); + + auto chainman{std::make_unique(context, chainman_opts, blockman_opts)}; + assert(chainman); + + ChainstateLoadOptions chainstate_load_opts{}; + assert(chainman->LoadChainstate(chainstate_load_opts)); + + std::cout << "Enter the block you want to validate on the next line:" << std::endl; + + for (std::string line; std::getline(std::cin, line);) { + if (line.empty()) { + std::cerr << "Empty line found, try again:" << std::endl; + continue; + } + + auto raw_block{hex_string_to_char_vec(line)}; + auto block = Block{raw_block}; + if (!block) { + std::cout << "Failed to parse entered block, try again:" << std::endl; + continue; + } + + bool new_block = false; + bool accepted = chainman->ProcessBlock(block, &new_block); + if (accepted) { + std::cout << "Validated block successfully." << std::endl; + } else { + std::cout << "Block was not accepted" << std::endl; + } + if (!new_block) { + std::cout << "Block is a duplicate" << std::endl; + } + } +} diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index bb101ba186a22..ed201061b7b8e 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -1,10 +1,1476 @@ -// Copyright (c) 2022 The Bitcoin Core developers +// Copyright (c) 2022-present The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include