kernel: Add chainstate manager object to C header
What changed, and why it matters
This commit adds a new 'chainstate manager' object to Bitcoin Core's public C API. It is a routine feature addition that exposes existing internal validation machinery through a stable library interface. There is no direct evidence in the commit that it fixes a security bug or introduces a vulnerability; it mainly expands what external programs can do with the Bitcoin kernel library.
Treat as a normal API-expansion commit. Reviewers may want to confirm that directory creation errors are handled safely and that the new destroy ordering cannot deadlock or leave partially flushed state, but no immediate security action is indicated by the supplied materials.
Security signals we found
New C API surface for chainstate/validation objects
Directory creation performed inside option constructor based on caller-supplied paths
Destroy routine acquires ChainstateManager mutex and forces state flush before teardown
No explicit security claim, CVE reference, or bug-fix language in commit message
Evidence from the diff
The patch extends src/kernel/bitcoinkernel.h/cpp with opaque C handles and lifecycle functions for ChainstateManagerOptions and ChainstateManager, plus a C++ wrapper and tests. It creates configured data directories during option construction and associates the manager with a kernel context for its lifetime. The destroy path flushes chainstates to disk and resets coins views before deletion. A lint exclusion is added for std::filesystem use in the new test file.
Changed components
src/kernel/bitcoinkernel.hsrc/kernel/bitcoinkernel.cppsrc/kernel/bitcoinkernel_wrapper.hsrc/test/kernel/test_kernel.cpptest/lint/test_runner/src/main.rsInspect captured patch +249 / −0
diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
index 2670287a..445ccca9 100644
--- a/src/kernel/bitcoinkernel.cpp
+++ b/src/kernel/bitcoinkernel.cpp
@@ -7,6 +7,7 @@
#include <kernel/bitcoinkernel.h>
#include <consensus/amount.h>
+#include <kernel/caches.h>
#include <kernel/chainparams.h>
#include <kernel/checks.h>
#include <kernel/context.h>
@@ -14,6 +15,7 @@
#include <kernel/notifications_interface.h>
#include <kernel/warning.h>
#include <logging.h>
+#include <node/blockstorage.h>
#include <primitives/transaction.h>
#include <script/interpreter.h>
#include <script/script.h>
@@ -21,6 +23,7 @@
#include <streams.h>
#include <sync.h>
#include <tinyformat.h>
+#include <util/fs.h>
#include <util/result.h>
#include <util/signalinterrupt.h>
#include <util/translation.h>
@@ -354,6 +357,39 @@ public:
}
};
+//! Helper struct to wrap the ChainstateManager-related Options
+struct ChainstateManagerOptions {
+ mutable Mutex m_mutex;
+ ChainstateManager::Options m_chainman_options GUARDED_BY(m_mutex);
+ node::BlockManager::Options m_blockman_options GUARDED_BY(m_mutex);
+ std::shared_ptr<const Context> m_context;
+
+ ChainstateManagerOptions(const std::shared_ptr<const Context>& context, const fs::path& data_dir, const fs::path& blocks_dir)
+ : m_chainman_options{ChainstateManager::Options{
+ .chainparams = *context->m_chainparams,
+ .datadir = data_dir,
+ .notifications = *context->m_notifications}},
+ m_blockman_options{node::BlockManager::Options{
+ .chainparams = *context->m_chainparams,
+ .blocks_dir = blocks_dir,
+ .notifications = *context->m_notifications,
+ .block_tree_db_params = DBParams{
+ .path = data_dir / "blocks" / "index",
+ .cache_bytes = kernel::CacheSizes{DEFAULT_KERNEL_CACHE}.block_tree_db,
+ }}},
+ m_context{context}
+ {
+ }
+};
+
+struct ChainMan {
+ std::unique_ptr<ChainstateManager> m_chainman;
+ std::shared_ptr<const Context> m_context;
+
+ ChainMan(std::unique_ptr<ChainstateManager> chainman, std::shared_ptr<const Context> context)
+ : m_chainman(std::move(chainman)), m_context(std::move(context)) {}
+};
+
} // namespace
struct btck_Transaction : Handle<btck_Transaction, std::shared_ptr<const CTransaction>> {};
@@ -363,6 +399,8 @@ struct btck_LoggingConnection : Handle<btck_LoggingConnection, LoggingConnection
struct btck_ContextOptions : Handle<btck_ContextOptions, ContextOptions> {};
struct btck_Context : Handle<btck_Context, std::shared_ptr<const Context>> {};
struct btck_ChainParameters : Handle<btck_ChainParameters, CChainParams> {};
+struct btck_ChainstateManagerOptions : Handle<btck_ChainstateManagerOptions, ChainstateManagerOptions> {};
+struct btck_ChainstateManager : Handle<btck_ChainstateManager, ChainMan> {};
btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len)
{
@@ -636,3 +674,52 @@ void btck_context_destroy(btck_Context* context)
{
delete context;
}
+
+btck_ChainstateManagerOptions* btck_chainstate_manager_options_create(const btck_Context* context, const char* data_dir, size_t data_dir_len, const char* blocks_dir, size_t blocks_dir_len)
+{
+ try {
+ fs::path abs_data_dir{fs::absolute(fs::PathFromString({data_dir, data_dir_len}))};
+ fs::create_directories(abs_data_dir);
+ fs::path abs_blocks_dir{fs::absolute(fs::PathFromString({blocks_dir, blocks_dir_len}))};
+ fs::create_directories(abs_blocks_dir);
+ return btck_ChainstateManagerOptions::create(btck_Context::get(context), abs_data_dir, abs_blocks_dir);
+ } catch (const std::exception& e) {
+ LogError("Failed to create chainstate manager options: %s", e.what());
+ return nullptr;
+ }
+}
+
+void btck_chainstate_manager_options_destroy(btck_ChainstateManagerOptions* options)
+{
+ delete options;
+}
+
+btck_ChainstateManager* btck_chainstate_manager_create(
+ const btck_ChainstateManagerOptions* chainman_opts)
+{
+ try {
+ auto& opts{btck_ChainstateManagerOptions::get(chainman_opts)};
+ LOCK(opts.m_mutex);
+ auto& context{opts.m_context};
+ auto chainman{std::make_unique<ChainstateManager>(*context->m_interrupt, opts.m_chainman_options, opts.m_blockman_options)};
+ return btck_ChainstateManager::create(std::move(chainman), context);
+ } catch (const std::exception& e) {
+ LogError("Failed to create chainstate manager: %s", e.what());
+ return nullptr;
+ }
+}
+
+void btck_chainstate_manager_destroy(btck_ChainstateManager* chainman)
+{
+ {
+ LOCK(btck_ChainstateManager::get(chainman).m_chainman->GetMutex());
+ for (Chainstate* chainstate : btck_ChainstateManager::get(chainman).m_chainman->GetAll()) {
+ if (chainstate->CanFlushToDisk()) {
+ chainstate->ForceFlushStateToDisk();
+ chainstate->ResetCoinsViews();
+ }
+ }
+ }
+
+ delete chainman;
+}
diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h
index 25f39a11..59094aef 100644
--- a/src/kernel/bitcoinkernel.h
+++ b/src/kernel/bitcoinkernel.h
@@ -166,6 +166,26 @@ typedef struct btck_Context btck_Context;
*/
typedef struct btck_BlockTreeEntry btck_BlockTreeEntry;
+/**
+ * Opaque data structure for holding options for creating a new chainstate
+ * manager.
+ *
+ * The chainstate manager options are used to set some parameters for the
+ * chainstate manager.
+ */
+typedef struct btck_ChainstateManagerOptions btck_ChainstateManagerOptions;
+
+/**
+ * Opaque data structure for holding a chainstate manager.
+ *
+ * The chainstate manager is the central object for doing validation tasks as
+ * well as retrieving data from the chain. Internally it is a complex data
+ * structure with diverse functionality.
+ *
+ * Its functionality will be more and more exposed in the future.
+ */
+typedef struct btck_ChainstateManager btck_ChainstateManager;
+
/** Current sync state passed to tip changed callbacks. */
typedef uint8_t btck_SynchronizationState;
#define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0))
@@ -686,6 +706,58 @@ BITCOINKERNEL_API void btck_context_destroy(btck_Context* context);
///@}
+/** @name ChainstateManagerOptions
+ * Functions for working with chainstate manager options.
+ */
+///@{
+
+/**
+ * @brief Create options for the chainstate manager.
+ *
+ * @param[in] context Non-null, the created options and through it the chainstate manager will
+ associate with this kernel context for the duration of their lifetimes.
+ * @param[in] data_directory Non-null, path string of the directory containing the chainstate data.
+ * If the directory does not exist yet, it will be created.
+ * @param[in] blocks_directory Non-null, path string of the directory containing the block data. If
+ * the directory does not exist yet, it will be created.
+ * @return The allocated chainstate manager options, or null on error.
+ */
+BITCOINKERNEL_API btck_ChainstateManagerOptions* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_options_create(
+ const btck_Context* context,
+ const char* data_directory,
+ size_t data_directory_len,
+ const char* blocks_directory,
+ size_t blocks_directory_len) BITCOINKERNEL_ARG_NONNULL(1, 2);
+
+/**
+ * Destroy the chainstate manager options.
+ */
+BITCOINKERNEL_API void btck_chainstate_manager_options_destroy(btck_ChainstateManagerOptions* chainstate_manager_options);
+
+///@}
+
+/** @name ChainstateManager
+ * Functions for chainstate management.
+ */
+///@{
+
+/**
+ * @brief Create a chainstate manager. This is the main object for many
+ * validation tasks as well as for retrieving data from the chain. *
+ *
+ * @param[in] chainstate_manager_options Non-null, created by @ref btck_chainstate_manager_options_create.
+ * @return The allocated chainstate manager, or null on error.
+ */
+BITCOINKERNEL_API btck_ChainstateManager* BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_create(
+ const btck_ChainstateManagerOptions* chainstate_manager_options) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * Destroy the chainstate manager.
+ */
+BITCOINKERNEL_API void btck_chainstate_manager_destroy(btck_ChainstateManager* chainstate_manager);
+
+///@}
+
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h
index 8e30b6ac..7d5c3063 100644
--- a/src/kernel/bitcoinkernel_wrapper.h
+++ b/src/kernel/bitcoinkernel_wrapper.h
@@ -11,6 +11,7 @@
#include <memory>
#include <span>
#include <stdexcept>
+#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
@@ -620,6 +621,24 @@ public:
: Handle{btck_context_create(ContextOptions{}.get())} {}
};
+class ChainstateManagerOptions : public UniqueHandle<btck_ChainstateManagerOptions, btck_chainstate_manager_options_destroy>
+{
+public:
+ ChainstateManagerOptions(const Context& context, const std::string& data_dir, const std::string& blocks_dir)
+ : UniqueHandle{btck_chainstate_manager_options_create(context.get(), data_dir.c_str(), data_dir.length(), blocks_dir.c_str(), blocks_dir.length())}
+ {
+ }
+};
+
+class ChainMan : UniqueHandle<btck_ChainstateManager, btck_chainstate_manager_destroy>
+{
+public:
+ ChainMan(const Context& context, const ChainstateManagerOptions& chainman_opts)
+ : UniqueHandle{btck_chainstate_manager_create(chainman_opts.get())}
+ {
+ }
+};
+
} // namespace btck
#endif // BITCOIN_KERNEL_BITCOINKERNEL_WRAPPER_H
diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
index ef669cb6..bc00e85c 100644
--- a/src/test/kernel/test_kernel.cpp
+++ b/src/test/kernel/test_kernel.cpp
@@ -11,15 +11,36 @@
#include <charconv>
#include <cstdint>
#include <cstdlib>
+#include <filesystem>
#include <iostream>
#include <memory>
+#include <random>
#include <ranges>
#include <span>
+#include <string>
#include <string_view>
#include <vector>
using namespace btck;
+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<std::byte> hex_string_to_byte_vec(std::string_view hex)
{
std::vector<std::byte> bytes;
@@ -60,6 +81,20 @@ public:
}
};
+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
{
public:
@@ -434,3 +469,38 @@ BOOST_AUTO_TEST_CASE(btck_context_tests)
Context context{options};
}
}
+
+Context create_context(std::shared_ptr<TestKernelNotifications> notifications, ChainType chain_type)
+{
+ ContextOptions options{};
+ ChainParams params{chain_type};
+ options.SetChainParams(params);
+ options.SetNotifications(notifications);
+ auto context{Context{options}};
+ return context;
+}
+
+BOOST_AUTO_TEST_CASE(btck_chainman_tests)
+{
+ Logger logger{std::make_unique<TestLog>()};
+ auto test_directory{TestDirectory{"chainman_test_bitcoin_kernel"}};
+
+ { // test with default context
+ Context context{};
+ ChainstateManagerOptions chainman_opts{context, test_directory.m_directory.string(), (test_directory.m_directory / "blocks").string()};
+ ChainMan chainman{context, chainman_opts};
+ }
+
+ { // test with default context options
+ ContextOptions options{};
+ Context context{options};
+ ChainstateManagerOptions chainman_opts{context, test_directory.m_directory.string(), (test_directory.m_directory / "blocks").string()};
+ ChainMan chainman{context, chainman_opts};
+ }
+
+ auto notifications{std::make_shared<TestKernelNotifications>()};
+ auto context{create_context(notifications, ChainType::MAINNET)};
+
+ ChainstateManagerOptions chainman_opts{context, test_directory.m_directory.string(), (test_directory.m_directory / "blocks").string()};
+ ChainMan chainman{context, chainman_opts};
+}
diff --git a/test/lint/test_runner/src/main.rs b/test/lint/test_runner/src/main.rs
index ba5aaee7..53d24ca4 100644
--- a/test/lint/test_runner/src/main.rs
+++ b/test/lint/test_runner/src/main.rs
@@ -373,6 +373,7 @@ fn lint_std_filesystem() -> LintResult {
"./src/",
":(exclude)src/ipc/libmultiprocess/",
":(exclude)src/util/fs.h",
+ ":(exclude)src/test/kernel/test_kernel.cpp",
])
.status()
.expect("command error")
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.