mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-13 11:35:20 +01:00
Merge 2951395589
into a50af6e4c4
This commit is contained in:
commit
df3c60f999
17 changed files with 4197 additions and 247 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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=""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
1193
src/kernel/bitcoinkernel.h
Normal file
File diff suppressed because it is too large
Load diff
639
src/kernel/bitcoinkernel_wrapper.h
Normal file
639
src/kernel/bitcoinkernel_wrapper.h
Normal 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
|
|
@ -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{
|
||||
|
|
|
@ -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 */
|
||||
|
|
15
src/test/kernel/CMakeLists.txt
Normal file
15
src/test/kernel/CMakeLists.txt
Normal 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)
|
418
src/test/kernel/block_data.h
Normal file
418
src/test/kernel/block_data.h
Normal file
File diff suppressed because one or more lines are too long
612
src/test/kernel/test_kernel.cpp
Normal file
612
src/test/kernel/test_kernel.cpp
Normal 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;
|
||||
}
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Reference in a new issue