kernel: Add pure kernel bitcoin-chainstate

Re-write the bitcoin-chainstate utility by only using the kernel C++ API
header instead of internal Bitcoin Core code.
This commit is contained in:
TheCharlatan 2024-06-14 18:33:08 +02:00
parent 7918a4d17d
commit 79d3f30ebe
No known key found for this signature in database
GPG Key ID: 9B79B45691DB4173
2 changed files with 171 additions and 236 deletions

View File

@ -1,53 +1,135 @@
// Copyright (c) 2022 The Bitcoin Core developers #include <kernel/bitcoinkernel_wrapper.h>
// 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 <cassert> #include <cassert>
#include <cstdint> #include <charconv>
#include <functional> #include <filesystem>
#include <iosfwd> #include <iostream>
#include <memory> #include <optional>
#include <string> #include <string>
#include <string_view>
#include <vector>
using namespace btck;
std::vector<std::byte> hex_string_to_byte_vec(std::string_view hex)
{
std::vector<std::byte> bytes;
bytes.reserve(hex.length() / 2);
for (size_t i{0}; i < hex.length(); i += 2) {
uint8_t byte_value;
auto [ptr, ec] = std::from_chars(hex.data() + i, hex.data() + i + 2, byte_value, 16);
if (ec != std::errc{} || ptr != hex.data() + i + 2) {
throw std::invalid_argument("Invalid hex character");
}
bytes.push_back(static_cast<std::byte>(byte_value));
}
return bytes;
}
class KernelLog
{
public:
void LogMessage(std::string_view message)
{
std::cout << "kernel: " << message;
}
};
class TestValidationInterface : public ValidationInterface<TestValidationInterface>
{
public:
TestValidationInterface() = default;
std::optional<std::string> m_expected_valid_block = std::nullopt;
void BlockChecked(const Block block, const BlockValidationState state) override
{
auto mode{state.GetValidationMode()};
switch (mode) {
case ValidationMode::VALID: {
std::cout << "Valid block" << std::endl;
return;
}
case ValidationMode::INVALID: {
std::cout << "Invalid block: ";
auto result{state.GetBlockValidationResult()};
switch (result) {
case BlockValidationResult::UNSET:
std::cout << "initial value. Block has not yet been rejected" << std::endl;
break;
case BlockValidationResult::HEADER_LOW_WORK:
std::cout << "the block header may be on a too-little-work chain" << std::endl;
break;
case BlockValidationResult::CONSENSUS:
std::cout << "invalid by consensus rules (excluding any below reasons)" << std::endl;
break;
case BlockValidationResult::CACHED_INVALID:
std::cout << "this block was cached as being invalid and we didn't store the reason why" << std::endl;
break;
case BlockValidationResult::INVALID_HEADER:
std::cout << "invalid proof of work or time too old" << std::endl;
break;
case BlockValidationResult::MUTATED:
std::cout << "the block's data didn't match the data committed to by the PoW" << std::endl;
break;
case BlockValidationResult::MISSING_PREV:
std::cout << "We don't have the previous block the checked one is built on" << std::endl;
break;
case BlockValidationResult::INVALID_PREV:
std::cout << "A block this one builds on is invalid" << std::endl;
break;
case BlockValidationResult::TIME_FUTURE:
std::cout << "block timestamp was > 2 hours in the future (or our clock is bad)" << std::endl;
break;
}
return;
}
case ValidationMode::INTERNAL_ERROR: {
std::cout << "Internal error" << std::endl;
return;
}
}
}
};
class TestKernelNotifications : public KernelNotifications<TestKernelNotifications>
{
public:
void BlockTipHandler(SynchronizationState, const BlockTreeEntry, double) 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(Warning warning, std::string_view message) override
{
std::cout << message << std::endl;
}
void WarningUnsetHandler(Warning warning) override
{
std::cout << "Warning unset: " << static_cast<std::underlying_type_t<Warning>>(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[]) 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 // SETUP: Argument parsing and handling
if (argc != 2) { if (argc != 2) {
std::cerr std::cerr
@ -58,213 +140,65 @@ int main(int argc, char* argv[])
<< " BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl; << " BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl;
return 1; return 1;
} }
fs::path abs_datadir{fs::absolute(argv[1])}; std::filesystem::path abs_datadir{std::filesystem::absolute(argv[1])};
fs::create_directories(abs_datadir); std::filesystem::create_directories(abs_datadir);
btck_LoggingOptions logging_options = {
// SETUP: Context .log_timestamps = true,
kernel::Context kernel_context{}; .log_time_micros = false,
// We can't use a goto here, but we can use an assert since none of the .log_threadnames = false,
// things instantiated so far requires running the epilogue to be torn down .log_sourcelocations = false,
// properly .always_print_category_levels = true,
assert(kernel::SanityChecks(kernel_context));
ValidationSignals validation_signals{std::make_unique<util::ImmediateTaskRunner>()};
class KernelNotifications : public kernel::Notifications
{
public:
kernel::InterruptResult blockTip(SynchronizationState, const CBlockIndex&, double) 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;
}
}; };
auto notifications = std::make_unique<KernelNotifications>();
kernel::CacheSizes cache_sizes{DEFAULT_KERNEL_CACHE}; Logger logger{std::make_unique<KernelLog>(KernelLog{}), logging_options};
// SETUP: Chainstate ContextOptions options{};
auto chainparams = CChainParams::Main(); ChainParams params{ChainType::MAINNET};
const ChainstateManager::Options chainman_opts{ options.SetChainParams(params);
.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};
node::ChainstateLoadOptions options; options.SetNotifications(std::make_shared<TestKernelNotifications>());
auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options); options.SetValidationInterface(std::make_shared<TestValidationInterface>());
if (status != node::ChainstateLoadStatus::SUCCESS) {
std::cerr << "Failed to load Chain state from your datadir." << std::endl; Context context{options};
goto epilogue;
} else { ChainstateManagerOptions chainman_opts{context, abs_datadir.string(), (abs_datadir / "blocks").string()};
std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options); chainman_opts.SetWorkerThreads(4);
if (status != node::ChainstateLoadStatus::SUCCESS) {
std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl; std::unique_ptr<ChainMan> chainman;
goto epilogue; try {
} chainman = std::make_unique<ChainMan>(context, chainman_opts);
} catch (std::exception&) {
std::cerr << "Failed to instantiate ChainMan, exiting" << std::endl;
return 1;
} }
for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { std::cout << "Enter the block you want to validate on the next line:" << std::endl;
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;
}
}
for (std::string line; std::getline(std::cin, line);) { for (std::string line; std::getline(std::cin, line);) {
if (line.empty()) { if (line.empty()) {
std::cerr << "Empty line found" << std::endl; std::cerr << "Empty line found, try again:" << std::endl;
break; continue;
} }
std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>(); auto raw_block{hex_string_to_byte_vec(line)};
CBlock& block = *blockptr; std::unique_ptr<Block> block;
try {
if (!DecodeHexBlk(block, line)) { block = std::make_unique<Block>(raw_block);
std::cerr << "Block decode failed" << std::endl; } catch (std::exception&) {
break; std::cerr << "Block decode failed, try again:" << std::endl;
continue;
} }
{ bool new_block = false;
LOCK(cs_main); bool accepted = chainman->ProcessBlock(*block, &new_block);
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock); if (accepted) {
if (pindex) { std::cerr << "Block has not yet been rejected" << std::endl;
chainman.UpdateUncommittedBlockStructures(block, pindex); } else {
} std::cerr << "Block was not accepted" << std::endl;
} }
if (!new_block) {
// Adapted from rpc/mining.cpp std::cerr << "Block is a duplicate" << std::endl;
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 std::shared_ptr<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();
}
} }
} }
} }

View File

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