This is a first attempt at introducing a C header for the libbitcoinkernel library that may be used by external applications for interfacing with Bitcoin Core’s validation logic. It currently is limited to operations on blocks. This is a conscious choice, since it already offers a lot of powerful functionality, but sits just on the cusp of still being reviewable scope-wise while giving some pointers on how the rest of the API could look like.
The current design was informed by the development of some tools using the C header:
- A re-implementation (part of this pull request) of bitcoin-chainstate.
- A re-implementation of the python block linearize scripts: https://github.com/TheCharlatan/bitcoin/tree/kernelLinearize
- A silent payment scanner: https://github.com/josibake/silent-payments-scanner
- An electrs index builder: https://github.com/josibake/electrs/commits/electrs-kernel-integration
- A rust bitcoin node: https://github.com/TheCharlatan/kernel-node
- A reindexer: https://github.com/TheCharlatan/bitcoin/tree/kernelApi_Reindexer
Next to the C++ header also made available in this pull request, rust bindings are available here: https://github.com/TheCharlatan/rust-bitcoinkernel, and python bindings here: https://github.com/stickies-v/py-bitcoinkernel/tree/main. The rust bindings include unit and fuzz tests for the API.
The header currently exposes logic for enabling the following functionality:
- Feature-parity with the now deprecated libbitcoin-consensus
- Optimized sha256 implementations that were not available to previous users of libbitcoin-consensus thanks to a static kernel context
- Full support for logging as well as control over categories and severity
- Feature parity with the existing experimental bitcoin-chainstate
- Traversing the block index as well and using block index entries for reading block and undo data.
- Running the chainstate in memory
- Reindexing (both full and chainstate-only)
- Interrupting long-running functions
The pull request introduces a new kernel-only test binary that purely relies on the kernel C header and the C++ standard library. This is intentionally done to show its capabilities without relying on other code inside the project. This may be relaxed to include some of the existing utilities, or even be merged into the existing test suite.
The complete docs for the API as well as some usage examples are hosted on thecharlatan.ch/kernel-docs. The docs are generated from the following repository (which also holds the examples): github.com/TheCharlatan/kernel-docs.
How can I review this PR?
Scrutinize the commit messages, run the tests, write your own little applications using the library, let your favorite code sanitizer loose on it, hook it up to your fuzzing infrastructure, profile the difference between the existing bitcoin-chainstate and the bitcoin-chainstate introduced here, be nitty on the documentation, police the C interface, opine on your own API design philosophy.
To get a feeling for the API, read through the tests, or one of the examples.
Please try to avoid nits for the tests, these can wait for later and easily be improved over time. Docs exhaustively explaining all the intricacies of the internal goings-on of the library can also be added later.
To configure this PR for making the shared library and the bitcoin-chainstate and test_kernel utilities available:
0cmake -B build -DBUILD_KERNEL_LIB=ON -DBUILD_UTIL_CHAINSTATE=ON
Once compiled the library is part of the build artifacts that can be installed with:
0cmake --install build
Python headers might also be useful for testing. ctypeslib2’s clang2py can be used to auto-generate bindings:
0clang2py src/kernel/bitcoinkernel.h -l /path/to/bitcoin/src/.libs/libbitcoinkernel.so > test_wrapper.py
Or alternatively on macOS (after cmake --install build
):
0clang2py /usr/local/include/bitcoinkernel.h -l /usr/local/lib/libbitcoinkernel.dylib --nm $(PWD)/nm_patch.py > test_wrapper.py
Why a C header (and not a C++ header)
- Shipping a shared library with a C++ header is hard, because of name mangling.
- Mature and well-supported tooling for integrating C exists for nearly every popular language.
- C offers a reasonably stable ABI
Also see #30595 (comment).
What about versioning?
The header and library are still experimental and I would expect this to remain so for some time, so best not to worry about versioning yet.
Potential future additions
In future, the C header could be expanded to support (some of these have been roughly implemented):
- Handling transactions, block headers, coins cache, utxo set, meta data, and the mempool
- Adapters for an abstract coins store
- Adapters for an abstract block store
- Allocators and buffers for more efficient memory usage
- An “io-less” interface
Current drawbacks
- For external applications to read the block index of an existing Bitcoin Core node, Bitcoin Core needs to shut down first, since leveldb does not support reading across multiple processes. Other than migrating away from leveldb, there does not seem to be a solution for this problem.
- The fatal error handling through the notifications is awkward.
- Handling shared pointers in the interfaces is unfortunate. They make ownership and freeing of the resources fuzzy and poison the interfaces with additional types and complexity. However, they seem to be an artifact of the current code that interfaces with the validation engine. The validation engine itself does not seem to make extensive use of these shared pointers.
- If multiple instances of the same type of objects are used, there is no mechanism for distinguishing the log messages produced by each of them.
- The background leveldb compaction thread may not finish in time leading to a non-clean exit. There seems to be nothing we can do about this, outside of patching leveldb.