This commit is contained in:
Reproducibility Matters 2025-03-13 02:03:07 +01:00 committed by GitHub
commit df3c60f999
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 4197 additions and 247 deletions

View file

@ -72,7 +72,7 @@ jobs:
run: |
# Run tests on commits after the last merge commit and before the PR head commit
# Use clang++, because it is a bit faster and uses less memory than g++
git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_USDT=ON -DCMAKE_CXX_FLAGS='-Wno-error=unused-member-function' && cmake --build build -j $(nproc) && ctest --output-on-failure --stop-on-failure --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 )) --combinedlogslen=99999999" ${{ env.TEST_BASE }}
git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_USDT=ON -DBUILD_KERNEL_LIB=ON -DBUILD_KERNEL_TEST=ON -DCMAKE_CXX_FLAGS='-Wno-error=unused-member-function' && cmake --build build -j $(nproc) && ctest --output-on-failure --stop-on-failure --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 )) --combinedlogslen=99999999" ${{ env.TEST_BASE }}
macos-native-arm64:
name: ${{ matrix.job-name }}
@ -168,7 +168,7 @@ jobs:
job-type: [standard, fuzz]
include:
- job-type: standard
generate-options: '-DBUILD_GUI=ON -DWITH_BDB=ON -DWITH_ZMQ=ON -DBUILD_BENCH=ON -DWERROR=ON'
generate-options: '-DBUILD_GUI=ON -DWITH_BDB=ON -DWITH_ZMQ=ON -DBUILD_BENCH=ON -DWERROR=ON -DBUILD_KERNEL_LIB=ON -DBUILD_UTIL_CHAINSTATE=ON -DBUILD_KERNEL_TEST=OFF'
job-name: 'Win64 native, VS 2022'
- job-type: fuzz
generate-options: '-DVCPKG_MANIFEST_NO_DEFAULT_FEATURES=ON -DVCPKG_MANIFEST_FEATURES="sqlite" -DBUILD_GUI=OFF -DBUILD_FOR_FUZZING=ON -DWERROR=ON'

View file

@ -100,6 +100,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})
@ -222,6 +223,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)
@ -649,6 +651,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)

View file

@ -11,7 +11,7 @@ export LC_ALL=C.UTF-8
export PIP_PACKAGES="--break-system-packages zmq"
export GOAL="install"
export CMAKE_GENERATOR="Ninja"
export BITCOIN_CONFIG="-DBUILD_GUI=ON -DWITH_ZMQ=ON -DREDUCE_EXPORTS=ON"
export BITCOIN_CONFIG="-DBUILD_GUI=ON -DWITH_ZMQ=ON -DBUILD_KERNEL_LIB=ON -DBUILD_KERNEL_TEST=ON -DREDUCE_EXPORTS=ON"
export CI_OS_NAME="macos"
export NO_DEPENDS=1
export OSX_SDK=""

View file

@ -12,4 +12,4 @@ export CI_IMAGE_NAME_TAG="mirror.gcr.io/debian:bookworm"
export PACKAGES="python3-zmq clang-16 llvm-16 libc++abi-16-dev libc++-16-dev"
export DEP_OPTS="NO_WALLET=1 CC=clang-16 CXX='clang++-16 -stdlib=libc++'"
export GOAL="install"
export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_UTIL_CHAINSTATE=ON -DBUILD_KERNEL_LIB=ON -DBUILD_SHARED_LIBS=ON"
export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_UTIL_CHAINSTATE=ON -DBUILD_KERNEL_LIB=ON -DBUILD_KERNEL_TEST=ON -DBUILD_SHARED_LIBS=ON"

View file

@ -17,5 +17,8 @@ export PACKAGES="nsis g++-mingw-w64-x86-64-posix wine-binfmt wine64 wine32 file"
export RUN_UNIT_TESTS=${RUN_UNIT_TESTS:-false}
export RUN_FUNCTIONAL_TESTS=false
export GOAL="deploy"
export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_GUI_TESTS=OFF \
# Prior to 11.0.0, the mingw-w64 headers were missing noreturn attributes, causing warnings when
# cross-compiling for Windows. https://sourceforge.net/p/mingw-w64/bugs/306/
# https://github.com/mingw-w64/mingw-w64/commit/1690994f515910a31b9fb7c7bd3a52d4ba987abe
export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_GUI_TESTS=OFF -DBUILD_KERNEL_LIB=ON -DBUILD_KERNEL_TEST=ON -DBUILD_UTIL_CHAINSTATE=ON \
-DCMAKE_CXX_FLAGS='-Wno-error=maybe-uninitialized -Wno-error=array-bounds'"

View file

@ -431,6 +431,9 @@ endif()
if(BUILD_KERNEL_LIB)
add_subdirectory(kernel)
if (BUILD_KERNEL_TEST)
add_subdirectory(test/kernel)
endif()
endif()
if(BUILD_UTIL_CHAINSTATE)

View file

@ -1,53 +1,134 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
// The bitcoin-chainstate executable serves to surface the dependencies required
// by a program wishing to use Bitcoin Core's consensus engine as it is right
// now.
//
// DEVELOPER NOTE: Since this is a "demo-only", experimental, etc. executable,
// it may diverge from Bitcoin Core's coding style.
//
// It is part of the libbitcoinkernel project.
#include <kernel/chainparams.h>
#include <kernel/chainstatemanager_opts.h>
#include <kernel/checks.h>
#include <kernel/context.h>
#include <kernel/warning.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <kernel/caches.h>
#include <logging.h>
#include <node/blockstorage.h>
#include <node/chainstate.h>
#include <random.h>
#include <script/sigcache.h>
#include <util/chaintype.h>
#include <util/fs.h>
#include <util/signalinterrupt.h>
#include <util/task_runner.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <kernel/bitcoinkernel_wrapper.h>
#include <cassert>
#include <cstdint>
#include <functional>
#include <iosfwd>
#include <memory>
#include <charconv>
#include <filesystem>
#include <iostream>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
std::vector<unsigned char> hex_string_to_char_vec(std::string_view hex)
{
std::vector<unsigned char> bytes;
bytes.reserve(hex.length() / 2);
for (size_t i{0}; i < hex.length(); i += 2) {
unsigned char byte;
auto [ptr, ec] = std::from_chars(hex.data() + i, hex.data() + i + 2, byte, 16);
if (ec == std::errc{} && ptr == hex.data() + i + 2) {
bytes.push_back(byte);
}
}
return bytes;
}
class KernelLog
{
public:
void LogMessage(std::string_view message)
{
std::cout << "kernel: " << message;
}
};
class TestValidationInterface : public ValidationInterface<TestValidationInterface>
{
public:
TestValidationInterface() : ValidationInterface() {}
std::optional<std::string> 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<TestKernelNotifications>
{
public:
void BlockTipHandler(kernel_SynchronizationState state, const kernel_BlockIndex* index) override
{
std::cout << "Block tip changed" << std::endl;
}
void ProgressHandler(std::string_view title, int progress_percent, bool resume_possible) override
{
std::cout << "Made progress: " << title << " " << progress_percent << "%" << std::endl;
}
void WarningSetHandler(kernel_Warning warning, std::string_view message) override
{
std::cout << message << std::endl;
}
void WarningUnsetHandler(kernel_Warning warning) override
{
std::cout << "Warning unset: " << warning << std::endl;
}
void FlushErrorHandler(std::string_view error) override
{
std::cout << error << std::endl;
}
void FatalErrorHandler(std::string_view error) override
{
std::cout << error << std::endl;
}
};
int main(int argc, char* argv[])
{
// We do not enable logging for this app, so explicitly disable it.
// To enable logging instead, replace with:
// LogInstance().m_print_to_console = true;
// LogInstance().StartLogging();
LogInstance().DisableLogging();
// SETUP: Argument parsing and handling
if (argc != 2) {
std::cerr
@ -58,216 +139,64 @@ int main(int argc, char* argv[])
<< " BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl;
return 1;
}
fs::path abs_datadir{fs::absolute(argv[1])};
fs::create_directories(abs_datadir);
std::filesystem::path abs_datadir{std::filesystem::absolute(argv[1])};
std::filesystem::create_directories(abs_datadir);
// SETUP: Context
kernel::Context kernel_context{};
// We can't use a goto here, but we can use an assert since none of the
// things instantiated so far requires running the epilogue to be torn down
// properly
assert(kernel::SanityChecks(kernel_context));
ValidationSignals validation_signals{std::make_unique<util::ImmediateTaskRunner>()};
class KernelNotifications : public kernel::Notifications
{
public:
kernel::InterruptResult blockTip(SynchronizationState, CBlockIndex&) override
{
std::cout << "Block tip changed" << std::endl;
return {};
}
void headerTip(SynchronizationState, int64_t height, int64_t timestamp, bool presync) override
{
std::cout << "Header tip changed: " << height << ", " << timestamp << ", " << presync << std::endl;
}
void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override
{
std::cout << "Progress: " << title.original << ", " << progress_percent << ", " << resume_possible << std::endl;
}
void warningSet(kernel::Warning id, const bilingual_str& message) override
{
std::cout << "Warning " << static_cast<int>(id) << " set: " << message.original << std::endl;
}
void warningUnset(kernel::Warning id) override
{
std::cout << "Warning " << static_cast<int>(id) << " unset" << std::endl;
}
void flushError(const bilingual_str& message) override
{
std::cerr << "Error flushing block data to disk: " << message.original << std::endl;
}
void fatalError(const bilingual_str& message) override
{
std::cerr << "Error: " << message.original << std::endl;
}
kernel_LoggingOptions logging_options = {
.log_timestamps = true,
.log_time_micros = false,
.log_threadnames = false,
.log_sourcelocations = false,
.always_print_category_levels = true,
};
auto notifications = std::make_unique<KernelNotifications>();
kernel::CacheSizes cache_sizes{DEFAULT_KERNEL_CACHE};
Logger logger{std::make_unique<KernelLog>(KernelLog{}), logging_options};
// SETUP: Chainstate
auto chainparams = CChainParams::Main();
const ChainstateManager::Options chainman_opts{
.chainparams = *chainparams,
.datadir = abs_datadir,
.notifications = *notifications,
.signals = &validation_signals,
};
const node::BlockManager::Options blockman_opts{
.chainparams = chainman_opts.chainparams,
.blocks_dir = abs_datadir / "blocks",
.notifications = chainman_opts.notifications,
.block_tree_db_params = DBParams{
.path = abs_datadir / "blocks" / "index",
.cache_bytes = cache_sizes.block_tree_db,
},
};
util::SignalInterrupt interrupt;
ChainstateManager chainman{interrupt, chainman_opts, blockman_opts};
ContextOptions options{};
ChainParams params{kernel_ChainType::kernel_CHAIN_TYPE_REGTEST};
options.SetChainParams(params);
node::ChainstateLoadOptions options;
auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options);
if (status != node::ChainstateLoadStatus::SUCCESS) {
std::cerr << "Failed to load Chain state from your datadir." << std::endl;
goto epilogue;
} else {
std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options);
if (status != node::ChainstateLoadStatus::SUCCESS) {
std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl;
goto epilogue;
}
TestKernelNotifications notifications{};
options.SetNotifications(notifications);
TestValidationInterface validation_interface{};
options.SetValidationInterface(validation_interface);
Context context{options};
assert(context);
ChainstateManagerOptions chainman_opts{context, abs_datadir.string(), (abs_datadir / "blocks").string()};
assert(chainman_opts);
chainman_opts.SetWorkerThreads(4);
auto chainman{std::make_unique<ChainMan>(context, chainman_opts)};
if (!*chainman) {
return 1;
}
for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl;
goto epilogue;
}
}
// Main program logic starts here
std::cout
<< "Hello! I'm going to print out some information about your datadir." << std::endl
<< "\t"
<< "Path: " << abs_datadir << std::endl;
{
LOCK(chainman.GetMutex());
std::cout
<< "\t" << "Blockfiles Indexed: " << std::boolalpha << chainman.m_blockman.m_blockfiles_indexed.load() << std::noboolalpha << std::endl
<< "\t" << "Snapshot Active: " << std::boolalpha << chainman.IsSnapshotActive() << std::noboolalpha << std::endl
<< "\t" << "Active Height: " << chainman.ActiveHeight() << std::endl
<< "\t" << "Active IBD: " << std::boolalpha << chainman.IsInitialBlockDownload() << std::noboolalpha << std::endl;
CBlockIndex* tip = chainman.ActiveTip();
if (tip) {
std::cout << "\t" << tip->ToString() << std::endl;
}
}
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" << std::endl;
break;
std::cerr << "Empty line found, try again:" << std::endl;
continue;
}
std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>();
CBlock& block = *blockptr;
if (!DecodeHexBlk(block, line)) {
std::cerr << "Block decode failed" << std::endl;
break;
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;
}
{
LOCK(cs_main);
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock);
if (pindex) {
chainman.UpdateUncommittedBlockStructures(block, pindex);
}
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;
}
// Adapted from rpc/mining.cpp
class submitblock_StateCatcher final : public CValidationInterface
{
public:
uint256 hash;
bool found;
BlockValidationState state;
explicit submitblock_StateCatcher(const uint256& hashIn) : hash(hashIn), found(false), state() {}
protected:
void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override
{
if (block.GetHash() != hash)
return;
found = true;
state = stateIn;
}
};
bool new_block;
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
validation_signals.RegisterSharedValidationInterface(sc);
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
validation_signals.UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
std::cerr << "duplicate" << std::endl;
break;
}
if (!sc->found) {
std::cerr << "inconclusive" << std::endl;
break;
}
std::cout << sc->state.ToString() << std::endl;
switch (sc->state.GetResult()) {
case BlockValidationResult::BLOCK_RESULT_UNSET:
std::cerr << "initial value. Block has not yet been rejected" << std::endl;
break;
case BlockValidationResult::BLOCK_HEADER_LOW_WORK:
std::cerr << "the block header may be on a too-little-work chain" << std::endl;
break;
case BlockValidationResult::BLOCK_CONSENSUS:
std::cerr << "invalid by consensus rules (excluding any below reasons)" << std::endl;
break;
case BlockValidationResult::BLOCK_CACHED_INVALID:
std::cerr << "this block was cached as being invalid and we didn't store the reason why" << std::endl;
break;
case BlockValidationResult::BLOCK_INVALID_HEADER:
std::cerr << "invalid proof of work or time too old" << std::endl;
break;
case BlockValidationResult::BLOCK_MUTATED:
std::cerr << "the block's data didn't match the data committed to by the PoW" << std::endl;
break;
case BlockValidationResult::BLOCK_MISSING_PREV:
std::cerr << "We don't have the previous block the checked one is built on" << std::endl;
break;
case BlockValidationResult::BLOCK_INVALID_PREV:
std::cerr << "A block this one builds on is invalid" << std::endl;
break;
case BlockValidationResult::BLOCK_TIME_FUTURE:
std::cerr << "block timestamp was > 2 hours in the future (or our clock is bad)" << std::endl;
break;
case BlockValidationResult::BLOCK_CHECKPOINT:
std::cerr << "the block failed to meet one of our checkpoints" << std::endl;
break;
}
}
epilogue:
// Without this precise shutdown sequence, there will be a lot of nullptr
// dereferencing and UB.
validation_signals.FlushBackgroundCallbacks();
{
LOCK(cs_main);
for (Chainstate* chainstate : chainman.GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
}
if (!new_block) {
std::cout << "Block is a duplicate" << std::endl;
}
}
}

View file

@ -148,3 +148,5 @@ install(TARGETS bitcoinkernel
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT libbitcoinkernel
)
install(FILES bitcoinkernel.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT Kernel)

File diff suppressed because it is too large Load diff

1193
src/kernel/bitcoinkernel.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,639 @@
// Copyright (c) 2024-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.
#ifndef BITCOIN_KERNEL_BITCOINKERNEL_WRAPPER_H
#define BITCOIN_KERNEL_BITCOINKERNEL_WRAPPER_H
#include <kernel/bitcoinkernel.h>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
class Transaction
{
private:
struct Deleter {
void operator()(kernel_Transaction* ptr) const
{
kernel_transaction_destroy(ptr);
}
};
public:
std::unique_ptr<kernel_Transaction, Deleter> m_transaction;
Transaction(std::span<const unsigned char> raw_transaction) noexcept
: m_transaction{kernel_transaction_create(raw_transaction.data(), raw_transaction.size())}
{
}
/** Check whether this Transaction object is valid. */
explicit operator bool() const noexcept { return bool{m_transaction}; }
};
class ScriptPubkey
{
private:
struct Deleter {
void operator()(kernel_ScriptPubkey* ptr) const
{
kernel_script_pubkey_destroy(ptr);
}
};
public:
std::unique_ptr<kernel_ScriptPubkey, Deleter> m_script_pubkey;
ScriptPubkey(std::span<const unsigned char> script_pubkey) noexcept
: m_script_pubkey{kernel_script_pubkey_create(script_pubkey.data(), script_pubkey.size())}
{
}
ScriptPubkey(kernel_ScriptPubkey* script_pubkey) noexcept
: m_script_pubkey{script_pubkey}
{
}
std::vector<unsigned char> GetScriptPubkeyData() const noexcept
{
auto serialized_data{kernel_copy_script_pubkey_data(m_script_pubkey.get())};
std::vector<unsigned char> vec{serialized_data->data, serialized_data->data + serialized_data->size};
kernel_byte_array_destroy(serialized_data);
return vec;
}
/** Check whether this ScriptPubkey object is valid. */
explicit operator bool() const noexcept { return bool{m_script_pubkey}; }
};
class TransactionOutput
{
private:
struct Deleter {
void operator()(kernel_TransactionOutput* ptr) const
{
kernel_transaction_output_destroy(ptr);
}
};
public:
std::unique_ptr<kernel_TransactionOutput, Deleter> m_transaction_output;
TransactionOutput(const ScriptPubkey& script_pubkey, int64_t amount) noexcept
: m_transaction_output{kernel_transaction_output_create(script_pubkey.m_script_pubkey.get(), amount)}
{
}
TransactionOutput(kernel_TransactionOutput* output) noexcept
: m_transaction_output{output}
{
}
/** Check whether this TransactionOutput object is valid. */
explicit operator bool() const noexcept { return bool{m_transaction_output}; }
ScriptPubkey GetScriptPubkey() noexcept
{
return kernel_copy_script_pubkey_from_output(m_transaction_output.get());
}
int64_t GetOutputAmount() noexcept
{
return kernel_get_transaction_output_amount(m_transaction_output.get());
}
};
int verify_script(const ScriptPubkey& script_pubkey,
int64_t amount,
const Transaction& tx_to,
const std::span<const TransactionOutput> spent_outputs,
unsigned int input_index,
unsigned int flags,
kernel_ScriptVerifyStatus& status) noexcept
{
const kernel_TransactionOutput** spent_outputs_ptr = nullptr;
std::vector<const kernel_TransactionOutput*> raw_spent_outputs;
if (spent_outputs.size() > 0) {
raw_spent_outputs.reserve(spent_outputs.size());
for (const auto& output : spent_outputs) {
raw_spent_outputs.push_back(output.m_transaction_output.get());
}
spent_outputs_ptr = raw_spent_outputs.data();
}
return kernel_verify_script(
script_pubkey.m_script_pubkey.get(),
amount,
tx_to.m_transaction.get(),
spent_outputs_ptr, spent_outputs.size(),
input_index,
flags,
&status);
}
template <typename T>
concept Log = requires(T a, std::string_view message) {
{ a.LogMessage(message) } -> std::same_as<void>;
};
template <Log T>
class Logger
{
private:
struct Deleter {
void operator()(kernel_LoggingConnection* ptr) const
{
kernel_logging_connection_destroy(ptr);
}
};
std::unique_ptr<T> m_log;
std::unique_ptr<kernel_LoggingConnection, Deleter> m_connection;
public:
Logger(std::unique_ptr<T> log, const kernel_LoggingOptions& logging_options) noexcept
: m_log{std::move(log)},
m_connection{kernel_logging_connection_create(
[](void* user_data, const char* message, size_t message_len) { static_cast<T*>(user_data)->LogMessage({message, message_len}); },
m_log.get(),
logging_options)}
{
}
/** Check whether this Logger object is valid. */
explicit operator bool() const noexcept { return bool{m_connection}; }
};
template <typename T>
class KernelNotifications
{
private:
kernel_NotificationInterfaceCallbacks MakeCallbacks()
{
return kernel_NotificationInterfaceCallbacks{
.user_data = this,
.block_tip = [](void* user_data, kernel_SynchronizationState state, const kernel_BlockIndex* index) {
static_cast<T*>(user_data)->BlockTipHandler(state, index);
},
.header_tip = [](void* user_data, kernel_SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {
static_cast<T*>(user_data)->HeaderTipHandler(state, height, timestamp, presync);
},
.progress = [](void* user_data, const char* title, size_t title_len, int progress_percent, bool resume_possible) {
static_cast<T*>(user_data)->ProgressHandler({title, title_len}, progress_percent, resume_possible);
},
.warning_set = [](void* user_data, kernel_Warning warning, const char* message, size_t message_len) {
static_cast<T*>(user_data)->WarningSetHandler(warning, {message, message_len});
},
.warning_unset = [](void* user_data, kernel_Warning warning) { static_cast<T*>(user_data)->WarningUnsetHandler(warning); },
.flush_error = [](void* user_data, const char* error, size_t error_len) { static_cast<T*>(user_data)->FlushErrorHandler({error, error_len}); },
.fatal_error = [](void* user_data, const char* error, size_t error_len) { static_cast<T*>(user_data)->FatalErrorHandler({error, error_len}); },
};
}
const kernel_NotificationInterfaceCallbacks m_notifications;
public:
KernelNotifications() : m_notifications{MakeCallbacks()} {}
virtual ~KernelNotifications() = default;
virtual void BlockTipHandler(kernel_SynchronizationState state, const kernel_BlockIndex* index) {}
virtual void HeaderTipHandler(kernel_SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {}
virtual void ProgressHandler(std::string_view title, int progress_percent, bool resume_possible) {}
virtual void WarningSetHandler(kernel_Warning warning, std::string_view message) {}
virtual void WarningUnsetHandler(kernel_Warning warning) {}
virtual void FlushErrorHandler(std::string_view error) {}
virtual void FatalErrorHandler(std::string_view error) {}
friend class ContextOptions;
};
struct BlockHashDeleter {
void operator()(kernel_BlockHash* ptr) const
{
kernel_block_hash_destroy(ptr);
}
};
class UnownedBlock
{
private:
const kernel_BlockPointer* m_block;
public:
UnownedBlock(const kernel_BlockPointer* block) noexcept : m_block{block} {}
UnownedBlock(const UnownedBlock&) = delete;
UnownedBlock& operator=(const UnownedBlock&) = delete;
UnownedBlock(UnownedBlock&&) = delete;
UnownedBlock& operator=(UnownedBlock&&) = delete;
std::unique_ptr<kernel_BlockHash, BlockHashDeleter> GetHash() const noexcept
{
return std::unique_ptr<kernel_BlockHash, BlockHashDeleter>(kernel_block_pointer_get_hash(m_block));
}
std::vector<unsigned char> GetBlockData() const noexcept
{
auto serialized_block{kernel_copy_block_pointer_data(m_block)};
std::vector<unsigned char> vec{serialized_block->data, serialized_block->data + serialized_block->size};
kernel_byte_array_destroy(serialized_block);
return vec;
}
};
class BlockValidationState
{
private:
const kernel_BlockValidationState* m_state;
public:
BlockValidationState(const kernel_BlockValidationState* state) noexcept : m_state{state} {}
BlockValidationState(const BlockValidationState&) = delete;
BlockValidationState& operator=(const BlockValidationState&) = delete;
BlockValidationState(BlockValidationState&&) = delete;
BlockValidationState& operator=(BlockValidationState&&) = delete;
kernel_ValidationMode ValidationMode() const noexcept
{
return kernel_get_validation_mode_from_block_validation_state(m_state);
}
kernel_BlockValidationResult BlockValidationResult() const noexcept
{
return kernel_get_block_validation_result_from_block_validation_state(m_state);
}
};
template <typename T>
class ValidationInterface
{
private:
const kernel_ValidationInterfaceCallbacks m_validation_interface;
public:
ValidationInterface() noexcept : m_validation_interface{kernel_ValidationInterfaceCallbacks{
.user_data = this,
.block_checked = [](void* user_data, const kernel_BlockPointer* block, const kernel_BlockValidationState* state) {
static_cast<T*>(user_data)->BlockChecked(UnownedBlock{block}, BlockValidationState{state});
},
}}
{
}
virtual ~ValidationInterface() = default;
virtual void BlockChecked(UnownedBlock block, const BlockValidationState state) {}
friend class ContextOptions;
};
class ChainParams
{
private:
struct Deleter {
void operator()(const kernel_ChainParameters* ptr) const
{
kernel_chain_parameters_destroy(ptr);
}
};
std::unique_ptr<const kernel_ChainParameters, Deleter> m_chain_params;
public:
ChainParams(kernel_ChainType chain_type) noexcept : m_chain_params{kernel_chain_parameters_create(chain_type)} {}
friend class ContextOptions;
};
class ContextOptions
{
private:
struct Deleter {
void operator()(kernel_ContextOptions* ptr) const
{
kernel_context_options_destroy(ptr);
}
};
std::unique_ptr<kernel_ContextOptions, Deleter> m_options;
public:
ContextOptions() noexcept : m_options{kernel_context_options_create()} {}
void SetChainParams(ChainParams& chain_params) const noexcept
{
kernel_context_options_set_chainparams(m_options.get(), chain_params.m_chain_params.get());
}
template <typename T>
void SetNotifications(KernelNotifications<T>& notifications) const noexcept
{
kernel_context_options_set_notifications(m_options.get(), notifications.m_notifications);
}
template <typename T>
void SetValidationInterface(ValidationInterface<T>& validation_interface) const noexcept
{
kernel_context_options_set_validation_interface(m_options.get(), validation_interface.m_validation_interface);
}
friend class Context;
};
class Context
{
private:
struct Deleter {
void operator()(kernel_Context* ptr) const
{
kernel_context_destroy(ptr);
}
};
public:
std::unique_ptr<kernel_Context, Deleter> m_context;
Context(ContextOptions& opts) noexcept
: m_context{kernel_context_create(opts.m_options.get())}
{
}
Context() noexcept
: m_context{kernel_context_create(ContextOptions{}.m_options.get())}
{
}
/** Check whether this Context object is valid. */
explicit operator bool() const noexcept { return bool{m_context}; }
};
class ChainstateManagerOptions
{
private:
struct Deleter {
void operator()(kernel_ChainstateManagerOptions* ptr) const
{
kernel_chainstate_manager_options_destroy(ptr);
}
};
std::unique_ptr<kernel_ChainstateManagerOptions, Deleter> m_options;
public:
ChainstateManagerOptions(const Context& context, const std::string& data_dir, const std::string& blocks_dir) noexcept
: m_options{kernel_chainstate_manager_options_create(context.m_context.get(), data_dir.c_str(), data_dir.length(), blocks_dir.c_str(), blocks_dir.length())}
{
}
void SetWorkerThreads(int worker_threads) const noexcept
{
kernel_chainstate_manager_options_set_worker_threads_num(m_options.get(), worker_threads);
}
bool SetWipeDbs(bool wipe_block_tree, bool wipe_chainstate) const noexcept
{
return kernel_chainstate_manager_options_set_wipe_dbs(m_options.get(), wipe_block_tree, wipe_chainstate);
}
void SetBlockTreeDbInMemory(bool block_tree_db_in_memory) const noexcept
{
kernel_chainstate_manager_options_set_block_tree_db_in_memory(m_options.get(), block_tree_db_in_memory);
}
void SetChainstateDbInMemory(bool chainstate_db_in_memory) const noexcept
{
kernel_chainstate_manager_options_set_chainstate_db_in_memory(m_options.get(), chainstate_db_in_memory);
}
/** Check whether this ChainstateManagerOptions object is valid. */
explicit operator bool() const noexcept { return bool{m_options}; }
friend class ChainMan;
};
class Block
{
private:
struct Deleter {
void operator()(kernel_Block* ptr) const
{
kernel_block_destroy(ptr);
}
};
std::unique_ptr<kernel_Block, Deleter> m_block;
public:
Block(const std::span<const unsigned char> raw_block) noexcept
: m_block{kernel_block_create(raw_block.data(), raw_block.size())}
{
}
/** Check whether this Block object is valid. */
explicit operator bool() const noexcept { return bool{m_block}; }
Block(kernel_Block* block) noexcept : m_block{block} {}
std::unique_ptr<kernel_BlockHash, BlockHashDeleter> GetHash() const noexcept
{
return std::unique_ptr<kernel_BlockHash, BlockHashDeleter>(kernel_block_get_hash(m_block.get()));
}
std::vector<unsigned char> GetBlockData() const noexcept
{
auto serialized_block{kernel_copy_block_data(m_block.get())};
std::vector<unsigned char> vec{serialized_block->data, serialized_block->data + serialized_block->size};
kernel_byte_array_destroy(serialized_block);
return vec;
}
friend class ChainMan;
};
class BlockUndo
{
private:
struct Deleter {
void operator()(kernel_BlockUndo* ptr) const
{
kernel_block_undo_destroy(ptr);
}
};
const std::unique_ptr<kernel_BlockUndo, Deleter> m_block_undo;
public:
const uint64_t m_size;
BlockUndo(kernel_BlockUndo* block_undo) noexcept
: m_block_undo{block_undo},
m_size{kernel_block_undo_size(block_undo)}
{
}
BlockUndo(const BlockUndo&) = delete;
BlockUndo& operator=(const BlockUndo&) = delete;
uint64_t GetTxOutSize(uint64_t index) const noexcept
{
return kernel_get_transaction_undo_size(m_block_undo.get(), index);
}
TransactionOutput GetTxUndoPrevoutByIndex(
uint64_t tx_undo_index,
uint64_t tx_prevout_index) const noexcept
{
return TransactionOutput{kernel_get_undo_output_by_index(m_block_undo.get(), tx_undo_index, tx_prevout_index)};
}
};
class BlockIndex
{
private:
struct Deleter {
void operator()(kernel_BlockIndex* ptr) const
{
kernel_block_index_destroy(ptr);
}
};
std::unique_ptr<kernel_BlockIndex, Deleter> m_block_index;
public:
BlockIndex(kernel_BlockIndex* block_index) noexcept : m_block_index{block_index} {}
std::optional<BlockIndex> GetPreviousBlockIndex() const noexcept
{
if (!m_block_index) {
return std::nullopt;
}
auto index{kernel_get_previous_block_index(m_block_index.get())};
if (!index) return std::nullopt;
return index;
}
int32_t GetHeight() const noexcept
{
if (!m_block_index) {
return -1;
}
return kernel_block_index_get_height(m_block_index.get());
}
std::unique_ptr<kernel_BlockHash, BlockHashDeleter> GetHash() const noexcept
{
if (!m_block_index) {
return nullptr;
}
return std::unique_ptr<kernel_BlockHash, BlockHashDeleter>(kernel_block_index_get_block_hash(m_block_index.get()));
}
operator bool() const noexcept
{
return m_block_index && m_block_index.get();
}
friend class ChainMan;
};
class ChainMan
{
private:
kernel_ChainstateManager* m_chainman;
const Context& m_context;
public:
ChainMan(const Context& context, const ChainstateManagerOptions& chainman_opts) noexcept
: m_chainman{kernel_chainstate_manager_create(context.m_context.get(), chainman_opts.m_options.get())},
m_context{context}
{
}
/** Check whether this ChainMan object is valid. */
explicit operator bool() const noexcept { return m_chainman != nullptr; }
ChainMan(const ChainMan&) = delete;
ChainMan& operator=(const ChainMan&) = delete;
bool ImportBlocks(const std::span<const std::string> paths) const noexcept
{
std::vector<const char*> c_paths;
std::vector<size_t> c_paths_lens;
c_paths.reserve(paths.size());
c_paths_lens.reserve(paths.size());
for (const auto& path : paths) {
c_paths.push_back(path.c_str());
c_paths_lens.push_back(path.length());
}
return kernel_import_blocks(m_context.m_context.get(), m_chainman, c_paths.data(), c_paths_lens.data(), c_paths.size());
}
bool ProcessBlock(const Block& block, bool* new_block) const noexcept
{
return kernel_chainstate_manager_process_block(m_context.m_context.get(), m_chainman, block.m_block.get(), new_block);
}
BlockIndex GetBlockIndexFromTip() const noexcept
{
return kernel_get_block_index_from_tip(m_context.m_context.get(), m_chainman);
}
BlockIndex GetBlockIndexFromGenesis() const noexcept
{
return kernel_get_block_index_from_genesis(m_context.m_context.get(), m_chainman);
}
BlockIndex GetBlockIndexByHash(kernel_BlockHash* block_hash) const noexcept
{
return kernel_get_block_index_from_hash(m_context.m_context.get(), m_chainman, block_hash);
}
std::optional<BlockIndex> GetBlockIndexByHeight(int height) const noexcept
{
auto index{kernel_get_block_index_from_height(m_context.m_context.get(), m_chainman, height)};
if (!index) return std::nullopt;
return index;
}
std::optional<BlockIndex> GetNextBlockIndex(BlockIndex& block_index) const noexcept
{
auto index{kernel_get_next_block_index(m_context.m_context.get(), m_chainman, block_index.m_block_index.get())};
if (!index) return std::nullopt;
return index;
}
std::optional<Block> ReadBlock(BlockIndex& block_index) const noexcept
{
auto block{kernel_read_block_from_disk(m_context.m_context.get(), m_chainman, block_index.m_block_index.get())};
if (!block) return std::nullopt;
return block;
}
std::optional<BlockUndo> ReadBlockUndo(const BlockIndex& block_index) const noexcept
{
auto undo{kernel_read_block_undo_from_disk(m_context.m_context.get(), m_chainman, block_index.m_block_index.get())};
if (!undo) return std::nullopt;
return undo;
}
~ChainMan()
{
kernel_chainstate_manager_destroy(m_chainman, m_context.m_context.get());
}
};
#endif // BITCOIN_KERNEL_BITCOINKERNEL_WRAPPER_H

View file

@ -199,6 +199,7 @@ static const std::map<std::string, BCLog::LogFlags, std::less<>> LOG_CATEGORIES_
{"txreconciliation", BCLog::TXRECONCILIATION},
{"scan", BCLog::SCAN},
{"txpackages", BCLog::TXPACKAGES},
{"kernel", BCLog::KERNEL},
};
static const std::unordered_map<BCLog::LogFlags, std::string> LOG_CATEGORIES_BY_FLAG{

View file

@ -71,6 +71,7 @@ namespace BCLog {
TXRECONCILIATION = (CategoryMask{1} << 26),
SCAN = (CategoryMask{1} << 27),
TXPACKAGES = (CategoryMask{1} << 28),
KERNEL = (CategoryMask{1} << 29),
ALL = ~NONE,
};
enum class Level {
@ -167,6 +168,12 @@ namespace BCLog {
m_print_callbacks.erase(it);
}
size_t NumConnections()
{
StdLockGuard scoped_lock(m_cs);
return m_print_callbacks.size();
}
/** Start logging (and flush all buffered messages) */
bool StartLogging() EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
/** Only for testing */

View file

@ -0,0 +1,15 @@
add_executable(test_kernel
test_kernel.cpp
)
target_link_libraries(test_kernel
PRIVATE
core_interface
bitcoinkernel
)
set_target_properties(test_kernel PROPERTIES
SKIP_BUILD_RPATH OFF
)
add_test(NAME test_kernel COMMAND test_kernel)

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,612 @@
// Copyright (c) 2024-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 <kernel/bitcoinkernel.h>
#include <kernel/bitcoinkernel_wrapper.h>
#include <test/kernel/block_data.h>
#include <cassert>
#include <charconv>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <memory>
#include <optional>
#include <random>
#include <span>
#include <string>
#include <string_view>
#include <vector>
std::string random_string(uint32_t length)
{
const std::string chars = "0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static std::random_device rd;
static std::default_random_engine dre{rd()};
static std::uniform_int_distribution<> distribution(0, chars.size() - 1);
std::string random;
random.reserve(length);
for (uint32_t i = 0; i < length; i++) {
random += chars[distribution(dre)];
}
return random;
}
std::vector<unsigned char> hex_string_to_char_vec(std::string_view hex)
{
std::vector<unsigned char> bytes;
bytes.reserve(hex.length() / 2);
for (size_t i{0}; i < hex.length(); i += 2) {
unsigned char byte;
auto [ptr, ec] = std::from_chars(hex.data() + i, hex.data() + i + 2, byte, 16);
if (ec == std::errc{} && ptr == hex.data() + i + 2) {
bytes.push_back(byte);
}
}
return bytes;
}
class TestLog
{
public:
void LogMessage(std::string_view message)
{
std::cout << "kernel: " << message;
}
};
struct TestDirectory {
std::filesystem::path m_directory;
TestDirectory(std::string directory_name)
: m_directory{std::filesystem::temp_directory_path() / (directory_name + random_string(16))}
{
std::filesystem::create_directories(m_directory);
}
~TestDirectory()
{
std::filesystem::remove_all(m_directory);
}
};
class TestKernelNotifications : public KernelNotifications<TestKernelNotifications>
{
public:
void BlockTipHandler(kernel_SynchronizationState state, const kernel_BlockIndex* index) override
{
std::cout << "Block tip changed" << std::endl;
}
void HeaderTipHandler(kernel_SynchronizationState state, int64_t height, int64_t timestamp, bool presync) override
{
assert(timestamp > 0);
}
void ProgressHandler(std::string_view title, int progress_percent, bool resume_possible) override
{
std::cout << "Made progress: " << title << " " << progress_percent << "%" << std::endl;
}
void WarningSetHandler(kernel_Warning warning, std::string_view message) override
{
std::cout << "Kernel warning is set: " << message << std::endl;
}
void WarningUnsetHandler(kernel_Warning warning) override
{
std::cout << "Kernel warning was unset." << std::endl;
}
void FlushErrorHandler(std::string_view error) override
{
std::cout << error << std::endl;
}
void FatalErrorHandler(std::string_view error) override
{
std::cout << error << std::endl;
}
};
class TestValidationInterface : public ValidationInterface<TestValidationInterface>
{
public:
TestValidationInterface() : ValidationInterface() {}
std::optional<std::vector<unsigned char>> m_expected_valid_block = std::nullopt;
void BlockChecked(const UnownedBlock block, const BlockValidationState state) override
{
{
auto serialized_block{block.GetBlockData()};
if (m_expected_valid_block.has_value()) {
assert(m_expected_valid_block.value() == serialized_block);
}
}
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;
}
}
}
};
constexpr auto VERIFY_ALL_PRE_SEGWIT{kernel_SCRIPT_FLAGS_VERIFY_P2SH | kernel_SCRIPT_FLAGS_VERIFY_DERSIG |
kernel_SCRIPT_FLAGS_VERIFY_NULLDUMMY | kernel_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY |
kernel_SCRIPT_FLAGS_VERIFY_CHECKSEQUENCEVERIFY};
constexpr auto VERIFY_ALL_PRE_TAPROOT{VERIFY_ALL_PRE_SEGWIT | kernel_SCRIPT_FLAGS_VERIFY_WITNESS};
void run_verify_test(
const ScriptPubkey& spent_script_pubkey,
const Transaction& spending_tx,
std::span<TransactionOutput> spent_outputs,
int64_t amount,
unsigned int input_index,
bool taproot)
{
assert(spending_tx);
assert(spent_script_pubkey);
auto status = kernel_ScriptVerifyStatus::kernel_SCRIPT_VERIFY_OK;
if (taproot) {
assert(verify_script(
spent_script_pubkey,
amount,
spending_tx,
spent_outputs,
input_index,
kernel_SCRIPT_FLAGS_VERIFY_ALL,
status));
assert(status == kernel_SCRIPT_VERIFY_OK);
} else {
assert(!verify_script(
spent_script_pubkey,
amount,
spending_tx,
spent_outputs,
input_index,
kernel_SCRIPT_FLAGS_VERIFY_ALL,
status));
assert(status == kernel_SCRIPT_VERIFY_ERROR_SPENT_OUTPUTS_REQUIRED);
status = kernel_SCRIPT_VERIFY_OK;
}
assert(verify_script(
spent_script_pubkey,
amount,
spending_tx,
spent_outputs,
input_index,
VERIFY_ALL_PRE_TAPROOT,
status));
assert(status == kernel_SCRIPT_VERIFY_OK);
assert(verify_script(
spent_script_pubkey,
0,
spending_tx,
spent_outputs,
input_index,
VERIFY_ALL_PRE_SEGWIT,
status));
assert(status == kernel_SCRIPT_VERIFY_OK);
assert(!verify_script(
spent_script_pubkey,
amount,
spending_tx,
spent_outputs,
input_index,
VERIFY_ALL_PRE_TAPROOT << 2,
status));
assert(status == kernel_SCRIPT_VERIFY_ERROR_INVALID_FLAGS);
assert(!verify_script(
spent_script_pubkey,
amount,
spending_tx,
spent_outputs,
5,
VERIFY_ALL_PRE_TAPROOT,
status));
assert(status == kernel_SCRIPT_VERIFY_ERROR_TX_INPUT_INDEX);
status = kernel_SCRIPT_VERIFY_OK;
}
void transaction_test()
{
auto tx_data{hex_string_to_char_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")};
auto tx{Transaction{tx_data}};
assert(tx);
auto broken_tx_data{std::span<unsigned char>{tx_data.begin(), tx_data.begin() + 10}};
auto broken_tx{Transaction{broken_tx_data}};
assert(!broken_tx);
}
void script_verify_test()
{
// Legacy transaction aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d
run_verify_test(
/*spent_script_pubkey*/ ScriptPubkey{hex_string_to_char_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")},
/*spending_tx*/ Transaction{hex_string_to_char_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")},
/*spent_outputs*/ {},
/*amount*/ 0,
/*input_index*/ 0,
/*is_taproot*/ false);
// Segwit transaction 1a3e89644985fbbb41e0dcfe176739813542b5937003c46a07de1e3ee7a4a7f3
run_verify_test(
/*spent_script_pubkey*/ ScriptPubkey{hex_string_to_char_vec("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d")},
/*spending_tx*/ Transaction{hex_string_to_char_vec("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000")},
/*spent_outputs*/ {},
/*amount*/ 18393430,
/*input_index*/ 0,
/*is_taproot*/ false);
// Taproot transaction 33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036
auto taproot_spent_script_pubkey{ScriptPubkey{hex_string_to_char_vec("5120339ce7e165e67d93adb3fef88a6d4beed33f01fa876f05a225242b82a631abc0")}};
std::vector<TransactionOutput> spent_outputs;
spent_outputs.emplace_back(taproot_spent_script_pubkey, 88480);
run_verify_test(
/*spent_script_pubkey*/ taproot_spent_script_pubkey,
/*spending_tx*/ Transaction{hex_string_to_char_vec("01000000000101d1f1c1f8cdf6759167b90f52c9ad358a369f95284e841d7a2536cef31c0549580100000000fdffffff020000000000000000316a2f49206c696b65205363686e6f7272207369677320616e6420492063616e6e6f74206c69652e204062697462756734329e06010000000000225120a37c3903c8d0db6512e2b40b0dffa05e5a3ab73603ce8c9c4b7771e5412328f90140a60c383f71bac0ec919b1d7dbc3eb72dd56e7aa99583615564f9f99b8ae4e837b758773a5b2e4c51348854c8389f008e05029db7f464a5ff2e01d5e6e626174affd30a00")},
/*spent_outputs*/ spent_outputs,
/*amount*/ 88480,
/*input_index*/ 0,
/*is_taproot*/ true);
}
void logging_test()
{
kernel_LoggingOptions logging_options = {
.log_timestamps = true,
.log_time_micros = true,
.log_threadnames = false,
.log_sourcelocations = false,
.always_print_category_levels = true,
};
assert(kernel_add_log_level_category(kernel_LogCategory::kernel_LOG_BENCH, kernel_LogLevel::kernel_LOG_TRACE));
assert(kernel_disable_log_category(kernel_LogCategory::kernel_LOG_BENCH));
assert(kernel_enable_log_category(kernel_LogCategory::kernel_LOG_VALIDATION));
assert(kernel_disable_log_category(kernel_LogCategory::kernel_LOG_VALIDATION));
// Check that connecting, connecting another, and then disconnecting and connecting a logger again works.
{
assert(kernel_add_log_level_category(kernel_LogCategory::kernel_LOG_KERNEL, kernel_LogLevel::kernel_LOG_TRACE));
assert(kernel_enable_log_category(kernel_LogCategory::kernel_LOG_KERNEL));
Logger logger{std::make_unique<TestLog>(TestLog{}), logging_options};
assert(logger);
Logger logger_2{std::make_unique<TestLog>(TestLog{}), logging_options};
assert(logger_2);
}
Logger logger{std::make_unique<TestLog>(TestLog{}), logging_options};
assert(logger);
}
void context_test()
{
{ // test default context
Context context{};
assert(context);
}
{ // test with context options
TestKernelNotifications notifications{};
ContextOptions options{};
ChainParams params{kernel_ChainType::kernel_CHAIN_TYPE_MAINNET};
options.SetChainParams(params);
options.SetNotifications(notifications);
Context context{options};
assert(context);
}
}
Context create_context(TestKernelNotifications& notifications, kernel_ChainType chain_type, TestValidationInterface* validation_interface = nullptr)
{
ContextOptions options{};
ChainParams params{chain_type};
options.SetChainParams(params);
options.SetNotifications(notifications);
if (validation_interface) {
options.SetValidationInterface(*validation_interface);
}
return Context{options};
}
void chainman_test()
{
auto test_directory{TestDirectory{"chainman_test_bitcoin_kernel"}};
TestKernelNotifications notifications{};
auto context{create_context(notifications, kernel_ChainType::kernel_CHAIN_TYPE_MAINNET)};
ChainstateManagerOptions chainman_opts{context, test_directory.m_directory.string(), (test_directory.m_directory / "blocks").string()};
assert(chainman_opts);
chainman_opts.SetWorkerThreads(4);
assert(chainman_opts.SetWipeDbs(/*wipe_block_tree=*/false, /*wipe_chainstate=*/false));
assert(!chainman_opts.SetWipeDbs(/*wipe_block_tree=*/true, /*wipe_chainstate=*/false));
ChainMan chainman{context, chainman_opts};
assert(chainman);
}
std::unique_ptr<ChainMan> create_chainman(TestDirectory& test_directory,
bool reindex,
bool wipe_chainstate,
bool block_tree_db_in_memory,
bool chainstate_db_in_memory,
Context& context)
{
ChainstateManagerOptions chainman_opts{context, test_directory.m_directory.string(), (test_directory.m_directory / "blocks").string()};
assert(chainman_opts);
if (reindex) {
chainman_opts.SetWipeDbs(/*wipe_block_tree=*/reindex, /*wipe_chainstate=*/reindex);
}
if (wipe_chainstate) {
chainman_opts.SetWipeDbs(/*wipe_block_tree=*/false, /*wipe_chainstate=*/wipe_chainstate);
}
if (block_tree_db_in_memory) {
chainman_opts.SetBlockTreeDbInMemory(block_tree_db_in_memory);
}
if (chainstate_db_in_memory) {
chainman_opts.SetChainstateDbInMemory(chainstate_db_in_memory);
}
auto chainman{std::make_unique<ChainMan>(context, chainman_opts)};
assert(chainman);
return chainman;
}
void chainman_in_memory_test()
{
auto in_memory_test_directory{TestDirectory{"in-memory_test_bitcoin_kernel"}};
TestKernelNotifications notifications{};
auto context{create_context(notifications, kernel_ChainType::kernel_CHAIN_TYPE_REGTEST)};
auto chainman{create_chainman(in_memory_test_directory, false, false, true, true, context)};
for (auto& raw_block : REGTEST_BLOCK_DATA) {
Block block{raw_block};
assert(block);
bool new_block{false};
chainman->ProcessBlock(block, &new_block);
assert(new_block == true);
}
assert(!std::filesystem::exists(in_memory_test_directory.m_directory / "blocks" / "index"));
assert(!std::filesystem::exists(in_memory_test_directory.m_directory / "chainstate"));
}
void chainman_mainnet_validation_test(TestDirectory& test_directory)
{
TestKernelNotifications notifications{};
TestValidationInterface validation_interface{};
auto context{create_context(notifications, kernel_ChainType::kernel_CHAIN_TYPE_MAINNET, &validation_interface)};
auto chainman{create_chainman(test_directory, false, false, false, false, context)};
{
// Process an invalid block
auto raw_block = hex_string_to_char_vec("012300");
Block block{raw_block};
assert(!block);
}
{
// Process an empty block
auto raw_block = hex_string_to_char_vec("");
Block block{raw_block};
assert(!block);
}
// mainnet block 1
auto raw_block = hex_string_to_char_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000");
Block block{raw_block};
assert(block);
validation_interface.m_expected_valid_block.emplace(raw_block);
assert(block.GetBlockData() == raw_block);
bool new_block = false;
assert(chainman->ProcessBlock(block, &new_block));
assert(new_block == true);
auto tip{chainman->GetBlockIndexFromTip()};
auto read_block{chainman->ReadBlock(tip)};
assert(read_block.value().GetBlockData() == raw_block);
// Check that we can read the previous block
auto tip_2{tip.GetPreviousBlockIndex()};
auto read_block_2{chainman->ReadBlock(tip_2.value())};
// It should be an error if we go another block back, since the genesis has no ancestor
assert(!tip_2.value().GetPreviousBlockIndex().has_value());
// If we try to validate it again, it should be a duplicate
assert(chainman->ProcessBlock(block, &new_block));
assert(new_block == false);
}
void chainman_regtest_validation_test()
{
auto test_directory{TestDirectory{"regtest_test_bitcoin_kernel"}};
TestKernelNotifications notifications{};
auto context{create_context(notifications, kernel_ChainType::kernel_CHAIN_TYPE_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, false, false, false, false, context)};
for (size_t i{0}; i < mid; i++) {
Block block{REGTEST_BLOCK_DATA[i]};
assert(block);
bool new_block{false};
assert(chainman->ProcessBlock(block, &new_block));
assert(new_block == true);
}
}
auto chainman{create_chainman(test_directory, false, false, false, false, context)};
for (size_t i{mid}; i < REGTEST_BLOCK_DATA.size(); i++) {
Block block{REGTEST_BLOCK_DATA[i]};
assert(block);
bool new_block{false};
assert(chainman->ProcessBlock(block, &new_block));
assert(new_block == true);
}
auto tip = chainman->GetBlockIndexFromTip();
auto read_block = chainman->ReadBlock(tip).value();
assert(read_block.GetBlockData() == REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 1]);
auto tip_2 = tip.GetPreviousBlockIndex().value();
auto read_block_2 = chainman->ReadBlock(tip_2).value();
assert(read_block_2.GetBlockData() == REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 2]);
auto block_undo{chainman->ReadBlockUndo(tip)};
assert(block_undo);
auto tx_undo_size = block_undo->GetTxOutSize(block_undo->m_size - 1);
auto output = block_undo->GetTxUndoPrevoutByIndex(block_undo->m_size - 1, tx_undo_size - 1);
assert(output);
assert(output.GetOutputAmount() == 100000000);
auto script_pubkey = output.GetScriptPubkey();
assert(script_pubkey);
assert(script_pubkey.GetScriptPubkeyData().size() == 22);
}
void chainman_reindex_test(TestDirectory& test_directory)
{
TestKernelNotifications notifications{};
auto context{create_context(notifications, kernel_ChainType::kernel_CHAIN_TYPE_MAINNET)};
auto chainman{create_chainman(test_directory, true, false, false, false, context)};
std::vector<std::string> import_files;
assert(chainman->ImportBlocks(import_files));
// Sanity check some block retrievals
auto genesis_index{chainman->GetBlockIndexFromGenesis()};
auto genesis_block_raw{chainman->ReadBlock(genesis_index).value().GetBlockData()};
auto first_index{chainman->GetBlockIndexByHeight(0).value()};
auto first_block_raw{chainman->ReadBlock(genesis_index).value().GetBlockData()};
assert(genesis_block_raw == first_block_raw);
auto height{first_index.GetHeight()};
assert(height == 0);
auto next_index{chainman->GetNextBlockIndex(first_index).value()};
auto next_block_string{chainman->ReadBlock(next_index).value().GetBlockData()};
auto tip_index{chainman->GetBlockIndexFromTip()};
auto tip_block_string{chainman->ReadBlock(tip_index).value().GetBlockData()};
auto second_index{chainman->GetBlockIndexByHeight(1).value()};
auto second_block{chainman->ReadBlock(second_index).value()};
auto second_block_string{second_block.GetBlockData()};
auto second_height{second_index.GetHeight()};
assert(second_height == 1);
assert(next_block_string == tip_block_string);
assert(next_block_string == second_block_string);
auto hash{second_index.GetHash()};
auto another_second_index{chainman->GetBlockIndexByHash(hash.get())};
auto another_second_height{another_second_index.GetHeight()};
auto block_hash{second_block.GetHash()};
assert(std::equal(std::begin(block_hash->hash), std::end(block_hash->hash), std::begin(hash->hash)));
assert(second_height == another_second_height);
}
void chainman_reindex_chainstate_test(TestDirectory& test_directory)
{
TestKernelNotifications notifications{};
auto context{create_context(notifications, kernel_ChainType::kernel_CHAIN_TYPE_MAINNET)};
auto chainman{create_chainman(test_directory, false, true, false, false, context)};
std::vector<std::string> import_files;
import_files.push_back((test_directory.m_directory / "blocks" / "blk00000.dat").string());
chainman->ImportBlocks(import_files);
}
int main()
{
transaction_test();
script_verify_test();
logging_test();
kernel_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};
context_test();
chainman_test();
auto mainnet_test_directory{TestDirectory{"mainnet_test_bitcoin_kernel"}};
chainman_mainnet_validation_test(mainnet_test_directory);
chainman_in_memory_test();
chainman_regtest_validation_test();
chainman_reindex_test(mainnet_test_directory);
chainman_reindex_chainstate_test(mainnet_test_directory);
std::cout << "Libbitcoinkernel test completed." << std::endl;
return 0;
}

View file

@ -362,6 +362,8 @@ fn lint_std_filesystem() -> LintResult {
"--",
"./src/",
":(exclude)src/util/fs.h",
":(exclude)src/test/kernel/test_kernel.cpp",
":(exclude)src/bitcoin-chainstate.cpp",
])
.status()
.expect("command error")