mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-09 20:59:38 +02:00
kernel: Add pure kernel bitcoin-chainstate
This showcases a re-implementation of bitcoin-chainstate only using the kernel C++ API header.
This commit is contained in:
parent
05b7d13668
commit
5991a69ee0
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -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 -DBUILD_KERNEL_LIB=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="wallet" -DBUILD_GUI=OFF -DBUILD_FOR_FUZZING=ON -DWERROR=ON'
|
||||
|
@ -20,5 +20,5 @@ export GOAL="deploy"
|
||||
# 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 \
|
||||
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'"
|
||||
|
@ -1,53 +1,131 @@
|
||||
// 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;
|
||||
}
|
||||
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,213 +136,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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -363,6 +363,7 @@ 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…
x
Reference in New Issue
Block a user