





























The Bitcoin Core is a truly unique and crucial software project - it makes the Bitcoin Network possible, supporting a decentralized, neutral, censorship-resistant and permissionless monetary system with a limited supply, independent of any central authority.
As we have recently analyzed how the Bitcoin P2P Network works, let's delve into arguably its most important software component - the Bitcoin Core, the reference client and the reference implementation of all aspects of the Bitcoin system. With the main goal of having a few questions answered:
Let's take a look and find out!
As we can see on GitHub, code is mostly C/C++ with some Python (functional/integration tests) and scripts:
By cloning it, we might get more detailed stats using the Count Lines of Code (cloc) tool:
cd <your-projects-dir>
git clone https://github.com/bitcoin/bitcoin
cd bitcoin
docker run --rm -v "$PWD:/tmp" aldanial/cloc .
github.com/AlDanial/cloc v 1.98 T=2.37 s (1099.8 files/s, 386167.1 lines/s)
-----------------------------------------------------------------------------------
Language files blank comment code
-----------------------------------------------------------------------------------
Qt Linguist 131 408 0 337393
C++ 810 29050 28433 185234
C/C++ Header 626 14178 27909 63224
Python 350 13932 14041 58115
Markdown 242 8237 38 31678
C 24 1314 1429 28586
JSON 96 340 0 26853
Qt 19 2 0 8226
CMake 80 800 843 5749
XML 2 5 0 5608
Bourne Shell 49 474 952 5478
Text 6 5 0 2588
make 46 374 189 2117
YAML 21 249 175 1852
...
-----------------------------------------------------------------------------------
SUM: 2609 70544 75239 770307
-----------------------------------------------------------------------------------
Ignoring Qt (UI) and other minor files, it mostly is C/C++ and Python:
----------------------------------------------------
Language files code
----------------------------------------------------
C++ 810 185234
C/C++ Header 626 63224
Python 350 58115
C 24 28586
----------------------------------------------------
SUM: 1810 335159
----------------------------------------------------
Omitting comments and blank lines, we have a solid few hundred files and a few hundred thousand lines of code to work with. It is not small but not enormous either; there are open-source codebases that have lines of code counted in millions - Postgres, Java Virtual Machine (OpenJDK), Google Chromium, Mozilla Firefox or Linux Kernel for example.
Looking at the current repo structure, there mainly are these directories:
C++ code lives under the src dir. All the most important logic and functionalities of the bitcoin node and wallet are implemented here (some mining as well). Things like:
validation.cpp; some interesting functions: AcceptToMemoryPool(...), ProcessNewBlock(...) or ConnectBlock(...)validation.cpp and txmempool.cppnet_processing.cpp mainly crypto/, pubkey.cpp or secp256k1/script/validation.cpp, consensus/, script/ and pow.cpppolicy/, but also some of validation.cpp and txmempool.cpprpc/ and rpc/server.cpp especially - StartRPC(), StopRPC() and execute(...) functions (in this file) being particularly interestinginit.cpp; in particular functions called from AppInit(...) of the bitcoind.cpp file
What is worth noting is that at this point the Bitcoin Core codebase is quite old - it has been there for at least 16 years! The network was created on 3 January 2009, when Satoshi Nakamoto mined the genesis block; it is known that he has worked on the implementation for two years before the release, so the codebase originated somewhere in 2007. Because of its age, naturally, it has its own quirks and inconsistencies.
For example, there are a few styles and naming conventions employed. In the beginning, Satoshi used something resembling the Hungarian notation which is rather outdated; new code does not use it but there still is lots of old code that does. At the beginning, the code was also not modular at all and there was a rather heavy usage of global state and objects - it has undergone significant improvements since then, but there still are some related issues. For example, the consensus logic is spread over a few places; there are some God objects and functions which are way too large and take too many responsibilities; global cs_main lock is still being used in several spots. That said, the codebase is constantly getting better and is quite modular right now; there also are ongoing efforts in place to make it even more modular, thus easier to grasp and work on various parts. We can find internal libraries with described dependencies and scope; there also is a growing number of internal interfaces, increasingly serving as an explicit contract of communication between codebase modules. Even the Bitcoin Kernel Library Project has been started, aiming to extract Bitcoin Core's consensus logic/engine into a separate library, which might then be used by other software.
The Bitcoin Core has its issues, as all mature and complex codebases do; there are areas that do not adhere to Single Responsibility Principle or do not respect Loose Coupling, High Cohesion rule and are more tangled and messy. Aside from a few coding styles and conventions, there is a heavy use of macros in some places (such as the serialization framework - serialize.h) and some parts of the code are using older C++ constructs, but it is being refactored out and modernized. As said, the codebase was born in 2007, when C++03 was the newest C++ version; it then moved to C++11, C++17 and is now at C++20, using many modern C++ features like: smart pointers, range-based loops, auto type deduction, constant expressions with constexpr, lambda expressions or standard <thread> library. Having looked at the code and spending a considerable time reading and playing with it, I can genuinely say that it is of high quality already, and is only getting better and better with time.
Interestingly, there are a lot of cryptographic primitives and algorithms implemented from scratch:
crypto/sha256.cppcrypto/hmac_sha256.cppcrypto/chacha20.cppcrypto/ripemd160.cppcrypto/ctaes/secp256k1/It might seem odd to implement such concepts from scratch, where there exist libraries that provide ready-to-use implementations, but there are solid reasons to have it this way. First of all, their proper and exactly the same byte-for-byte implementation is crucial to reach consensus by the nodes - it is too risky to bet on even the smallest possibility of a difference or bug in the external library here; one might even argue that the exact cryptography implementation is a part of the Bitcoin protocol. Secondly, for this unique kind of project and its scope of responsibility, Bitcoin Core tries to limit external dependencies as much as possible and use them only if it really is necessary and for less important functionality, reducing all possible attack vectors. Additionally, it is also easier to audit and verify consensus code, having embedded implementations of the most important cryptographic operations, primitives and algorithms in the codebase.
Speaking of limiting all possible attack vectors - the codebase had a few serious bugs, an exhaustive list of which can be found here, here and here. Most notably:
184,467,440,737 bitcoins for three different addresses, out of thin air. Satoshi released a patch within a few hours that rejected output value overflow transactions or any transaction that paid more than 21 million bitcoins in any output. The blockchain was effectively forked by everyone agreeing to discard the block containing an overflow causing transactionLooking at the dates, we might note that all these critical bugs and vulnerabilities are quite old at this point at - last one was in 2018, over 7 years ago as of now. It does not mean that certainly there will be no bugs in the future (this possibility will always exist), but as the codebase continues to mature and improve, having better internal structure (lots of refactorings) and increasingly better test coverage the number of bugs and vulnerabilities go down and those that are discovered are of a milder kind. But, this desirable state of affairs should never be taken for granted; it does require constant vigilance and employing the best engineering practices and discipline. At least one known bug, that must be addressed at some point, is the future timestamp overflow problem. Block timestamps are stored as unsigned 32-bit integers, representing seconds since the Unix epoch; maximum value of 2^32 -1 corresponds to February 7, 2106. Sadly, timestamp encoding format is a part of consensus-critical block header and will require a coordinated hard fork to pull it off - this is why fixing it is postponed for now but nobody will object to this change, since without it the network will cease to function properly.
Let's conclude this section by saying that as Bitcoin's domain is quite unique, so are the solutions implemented in its code. Thus, something that might seem odd at the first glance is often a natural consequence of how the Bitcoin system works. The codebase had its fair share of bugs and was not structured the best, but ongoing efforts of maintainers have significantly improved the system's quality - it already is at an excellent level - and their continuous work raises this bar still further.
In the src/test dir there are lots of unit tests, written with the help of Boost.Test library. Assuming that we have built various bitcoin binaries, in the standard build dir there is a unit tests binary:
./build/bin/test_bitcoin --log_level=test_suite
Running 611 test cases...
Entering test module "Bitcoin Core Test Suite"
./test/addrman_tests.cpp(63): Entering test suite "addrman_tests"
./test/addrman_tests.cpp(65): Entering test case "addrman_simple"
./test/addrman_tests.cpp(65): Leaving test case "addrman_simple"; testing time: 2371us
./test/addrman_tests.cpp(109): Entering test case "addrman_ports"
./test/addrman_tests.cpp(109): Leaving test case "addrman_ports"; testing time: 1035us
./test/addrman_tests.cpp(137): Entering test case "addrman_select"
...
./test/versionbits_tests.cpp(185): Entering test suite "versionbits_tests"
./test/versionbits_tests.cpp(187): Entering test case "versionbits_test"
./test/versionbits_tests.cpp(187): Leaving test case "versionbits_test"; testing time: 346485us
./test/versionbits_tests.cpp(440): Entering test case "versionbits_computeblockversion"
./test/versionbits_tests.cpp(440): Leaving test case "versionbits_computeblockversion"; testing time: 112303us
./test/versionbits_tests.cpp(185): Leaving test suite "versionbits_tests"; testing time: 458849us
Leaving test module "Bitcoin Core Test Suite"; testing time: 35502915us
*** No errors detected
611 test cases have run in 35s(35502915us) on my Ubuntu 24.04.3 LTS, AMD Ryzen™ 7 PRO 7840U w/ Radeon™ 780M Graphics × 16 machine (compiled for coverage). These tests test individual functions and classes as separate units and are written in C++ as the whole code being tested is.
There also are functional/integration tests written in Python; they work quite differently. Using previously compiled Bitcoin Core binaries, these tests start and stop node processes. They interact with the nodes through Bitcoin's Remote Procedure Call (RPC) API and P2P network messages. In the spirit of limiting external dependencies, all that is required to write and run them is a Python interpreter - there is a custom test framework implemented in the codebase. We can then simply run them all (it takes a while):
build/test/functional/test_runner.py
Temporary test directory at /tmp/test_runner_₿_🏃_20250813_195204
1/271 - feature_taproot.py skipped (wallet has not been compiled.)
2/271 - wallet_conflicts.py skipped (wallet has not been compiled.)
3/271 - feature_fee_estimation.py passed, Duration: 42 s
4/271 - p2p_opportunistic_1p1c.py passed, Duration: 46 s
5/271 - feature_block.py passed, Duration: 49 s
6/271 - p2p_node_network_limited.py --v1transport passed, Duration: 12 s
7/271 - mempool_ephemeral_dust.py passed, Duration: 60 s
8/271 - p2p_node_network_limited.py --v2transport passed, Duration: 13 s
9/271 - feature_assumeutxo.py passed, Duration: 23 s
...
264/271 - p2p_permissions.py passed, Duration: 12 s
265/271 - p2p_handshake.py passed, Duration: 4 s
266/271 - p2p_handshake.py --v2transport passed, Duration: 5 s
267/271 - wallet_migration.py skipped (wallet has not been compiled.)
Remaining jobs: [feature_framework_startup_failures.py, feature_shutdown.py, p2p_ibd_txrelay.py, p2p_seednode.py]
268/271 - feature_shutdown.py passed, Duration: 2 s
Remaining jobs: [feature_framework_startup_failures.py, p2p_ibd_txrelay.py, p2p_seednode.py]
269/271 - p2p_ibd_txrelay.py passed, Duration: 1 s
Remaining jobs: [feature_framework_startup_failures.py, p2p_seednode.py]
270/271 - feature_framework_startup_failures.py passed, Duration: 4 s
Remaining jobs: [p2p_seednode.py]
271/271 - p2p_seednode.py passed, Duration: 3 s
...
ALL | ✓ Passed | 1379 s (accumulated)
Runtime: 365 s
Functional tests verify that the whole features and functionalities work as expected.
Since we know how to build the Bitcoin Core, we might follow the instructions to build it specifically to measure test coverage. I use GCC as a compiler and lcov for reports, so building for coverage with my toolchain looks like this (I skip GUI):
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=depends/x86_64-pc-linux-gnu/toolchain.cmake \
-DENABLE_WALLET=OFF \
-DCMAKE_BUILD_TYPE=Coverage
# adjust j value based on desirable parallelism
cmake --build build -j8
And then, the coverage report could be generated. It takes a while, as it runs all unit and functional tests:
# additionally generate branch coverage and ignore some errors
cmake \
-DLCOV_OPTS="--ignore-errors mismatch,negative --rc branch_coverage=1" \
-DJOBS=8 \
-P build/Coverage.cmake
Test (code) coverage report:
Text version:
Coverage Total Hit
-----------------------------------
Lines 87.4% 79514 69511
Functions 87.3% 10791 9416
Branches 51.7% 224711 116177
More detailed view:
This is pretty solid coverage - close to 90% for line and function coverage. What is a little surprising though, is significantly lower branch coverage - around 50%. That could be for a variety of reasons; some if/else branches or switch cases might be very hard to trigger; some might simply not be worth the effort, if the code executed there is not that important. But even if that is the case, branch coverage should still be higher - closer to 70% or even 80%; there definitely is room for improvement here.
As a side note, Bitcoin Core also employs Fuzzing - testing strategy where you input a rather large volume of random or semi-random and unexpected/malformed data into your software; it is done in order to find nonobvious vulnerabilities and bugs.
Let's now analyze the build and distribution processes of the Bitcoin Core:
To build and compile the code we must have a few things. First, a C++ compiler - either Clang or GCC are supported. Needless to say, we also must have an operating system on which either of these compilers works; OS is a dependency as well. Fortunately, they support pretty much all modern operating systems and architectures. The Bitcoin Core officially supports these operating systems (29.0 release):
Compatibility
Bitcoin Core is supported and tested on operating systems using the Linux Kernel 3.17+, macOS 13+, and Windows 10+. Bitcoin Core should also work on most other Unix-like systems but is not as frequently tested on them. It is not recommended to use Bitcoin Core on unsupported systems.
For these OSes, there are formally distributed binaries - but nothing stops from compiling them to work on other platforms; it just isn't officially advised nor supported.
Then, there are only a few required and external dependencies: Boost library, CMake build tool and the libevent library. Also, for most platforms CMake uses Make underneath the hood, but it can also adapt Ninja or Microsoft Build Engine (MSBuild) with Microsoft Visual Studio. If we want to build GUI and wallet (optional), there are a few more dependencies, but these are not crucial. In general, there are two options to get those dependencies:
Additionally, some of the Bitcoin Core dependencies are directly embedded as git subtrees; 1:1 copies of the code developed in other repositories. Examples include LevelDB, CRC32C, libsecp256k1, ctaes, UniValue or Minisketch. While the exact rules as to why some dependencies are explicitly specified versus being embedded directly as copies of other repositories (this is what the git subtrees are) are not specified, there seems to be a few rules of thumb. Usually when the dependency is small, self-contained and security/consensus-critical it is a subtree; also when it is tailored to the Bitcoin Core and in practice is maintained by its developers. Other than that, it is not a subtree but explicitly listed, external dependency that must be downloaded or built from the source.
For building external dependencies directly from sources, Bitcoin Core has its own, custom build system - depends. It was mainly created so that:
We will go to reproducible builds in a bit. Firstly, let's go over how the depends system works; on a high level:
{platform}/toolchain.cmake file so that these locally built dependencies can be picked up by CMake when compiling Bitcoin CoreOn my platform - Ubuntu 24.04.3 LTS, AMD Ryzen™ 7 PRO 7840U w/ Radeon™ 780M Graphics × 16 - I made sure that I have a GCC compiler and depends prerequisites:
apt install cmake curl make patch
Having that, we go to the depends dir:
# skip GUI and run it on 8 cores
export NO_QT=1
make -j8
...
[ 6%] Building CXX object CMakeFiles/objects.dir/src/curve_mechanism_base.cpp.o
[ 6%] Building CXX object CMakeFiles/objects.dir/src/precompiled.cpp.o
[ 6%] Building CXX object CMakeFiles/objects.dir/src/curve_client.cpp.o
[ 6%] Building CXX object CMakeFiles/objects.dir/src/ctx.cpp.o
[ 8%] Building CXX object CMakeFiles/objects.dir/src/channel.cpp.o
[ 8%] Building CXX object CMakeFiles/objects.dir/src/client.cpp.o
[ 8%] Building CXX object CMakeFiles/objects.dir/src/address.cpp.o
[ 8%] Building CXX object CMakeFiles/objects.dir/src/clock.cpp.o
...
[ 92%] Building CXX object CMakeFiles/objects.dir/src/zap_client.cpp.o
[ 93%] Building CXX object CMakeFiles/objects.dir/src/zmtp_engine.cpp.o
[ 94%] Building CXX object CMakeFiles/objects.dir/src/stream_connecter_base.cpp.o
[ 95%] Building CXX object CMakeFiles/objects.dir/src/stream_listener_base.cpp.o
[ 96%] Building CXX object CMakeFiles/objects.dir/src/tipc_address.cpp.o
[ 97%] Building CXX object CMakeFiles/objects.dir/src/tipc_connecter.cpp.o
[ 98%] Building CXX object CMakeFiles/objects.dir/src/tipc_listener.cpp.o
...
copying packages: boost libevent sqlite systemtap zeromq
to: /home/igor/ws/code/CProjects/bitcoin/depends/x86_64-pc-linux-gnu
To build Bitcoin Core with these packages, pass '--toolchain /home/igor/ws/code/CProjects/bitcoin/depends/x86_64-pc-linux-gnu/toolchain.cmake' to the first CMake invocation
We right now have locally produced binaries of the Bitcoin Core dependencies. Let's then use them to build the codebase; from the root dir, run:
# no wallet and no GUI; change toolchain file to your specific path
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=depends/x86_64-pc-linux-gnu/toolchain.cmake \
-DENABLE_WALLET=OFF
-- The CXX compiler identification is GNU 13.3.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /bin/g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Setting build type to "RelWithDebInfo" as none was specified
-- Performing Test CXX_SUPPORTS__WERROR
-- Performing Test CXX_SUPPORTS__WERROR - Success
-- Performing Test CXX_SUPPORTS__G3
-- Performing Test CXX_SUPPORTS__G3 - Success
-- Performing Test LINKER_SUPPORTS__G3
-- Performing Test LINKER_SUPPORTS__G3 - Success
-- Performing Test CXX_SUPPORTS__FTRAPV
-- Performing Test CXX_SUPPORTS__FTRAPV - Success
-- Performing Test LINKER_SUPPORTS__FTRAPV
-- Performing Test LINKER_SUPPORTS__FTRAPV - Success
...
NOTE: The summary above may not exactly match the final applied build flags
if any additional CMAKE_* or environment variables have been modified.
To see the exact flags applied, build with the --verbose option.
Treat compiler warnings as errors ..... OFF
Use ccache for compiling .............. OFF
-- Configuring done (12.5s)
-- Generating done (0.1s)
-- Build files have been written to: /home/igor/ws/code/CProjects/bitcoin/build
And then:
# change based on the desired level of parallelism
cmake --build build -j8
[ 0%] Generating bitcoin-build-info.h
[ 0%] Building CXX object src/CMakeFiles/minisketch.dir/minisketch/src/minisketch.cpp.o
[ 0%] Building CXX object src/CMakeFiles/crc32c.dir/crc32c/src/crc32c.cc.o
[ 0%] Building CXX object src/univalue/CMakeFiles/univalue.dir/lib/univalue.cpp.o
[ 0%] Building CXX object src/CMakeFiles/bitcoin_consensus.dir/arith_uint256.cpp.o
[ 0%] Building CXX object src/CMakeFiles/leveldb.dir/leveldb/db/builder.cc.o
[ 1%] Building CXX object src/crypto/CMakeFiles/bitcoin_crypto.dir/aes.cpp.o
[ 1%] Building CXX object src/zmq/CMakeFiles/bitcoin_zmq.dir/zmqabstractnotifier.cpp.o
[ 1%] Building CXX object src/crypto/CMakeFiles/bitcoin_crypto.dir/chacha20.cpp.o
[ 1%] Building CXX object src/CMakeFiles/crc32c.dir/crc32c/src/crc32c_portable.cc.o
[ 1%] Building CXX object src/CMakeFiles/crc32c.dir/crc32c/src/crc32c_sse42.cc.o
[ 1%] Built target generate_build_info
...
[ 98%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/txvalidationcache_tests.cpp.o
[ 98%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/uint256_tests.cpp.o
[ 98%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/util_string_tests.cpp.o
[ 98%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/util_tests.cpp.o
[ 98%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/util_threadnames_tests.cpp.o
[ 99%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/util_trace_tests.cpp.o
[ 99%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/validation_block_tests.cpp.o
[ 99%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/validation_chainstate_tests.cpp.o
[ 99%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/validation_chainstatemanager_tests.cpp.o
[ 99%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/validation_flush_tests.cpp.o
[100%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/validation_tests.cpp.o
[100%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/validationinterface_tests.cpp.o
[100%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/versionbits_tests.cpp.o
[100%] Linking CXX executable ../../bin/test_bitcoin
[100%] Built target test_bitcoin
That is it; we have ready to be used Bitcoin binaries in the build/bin directory. All compiled from scratch, from the pure source code - dependencies included.
The depends build system also allows supporting Reproducible Builds - compiling software deterministically, so that multiple people can do it and verify that they get identical binaries. Conducting this verification is a little bit more complicated than just using the depends build system - it requires setting up the Guix package manager and a few other things. In the end, we are able to:
How dependent the Bitcoin Core is on its dependencies? As we saw, it intentionally tries to reduce their number to the absolutely lowest necessary level. Additionally, ones that are critical are directly embedded as git subtrees and maintained, or at least audited, by the Bitcoin Core developers. Multiple operating systems and CPU architectures are supported, so there is no dependency on any particular ones, it is quite diversified. We must have a C++ compiler, but there are at least two solid open-source ones - GCC and Clang - and so many important open-source projects are written in C++ that we can safely assume - there will always be a reliable C++ compiler. Custom build system - depends - relies on Make software but it could be substituted by a bunch of bash scripts, if the need arises. Same goes for the CMake build tool that glues it all together - dependencies of this kind are not a problem at all. Therefore, it is fair to say that the Bitcoin Core is as little dependent on its dependencies as it is pragmatically possible.
Knowing how it all works - let's change something!
We will register a new Remote Procedure Call (RPC) called getmagicnumber that returns a magic number. How can we do this?
Let's take a look at the src/rpc dir. There are lots of files related to various parts of the RPC functionality. There is a file where all the RPCs are registered - src/rpc/register.h:
/** These are in one header file to avoid creating tons of single-function
* headers for everything under src/rpc/ */
class CRPCTable;
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
void RegisterFeeRPCCommands(CRPCTable&);
void RegisterMempoolRPCCommands(CRPCTable&);
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
void RegisterNodeRPCCommands(CRPCTable&);
void RegisterNetRPCCommands(CRPCTable&);
void RegisterOutputScriptRPCCommands(CRPCTable&);
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
void RegisterSignMessageRPCCommands(CRPCTable&);
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
void RegisterTxoutProofRPCCommands(CRPCTable&);
...
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);
RegisterFeeRPCCommands(t);
RegisterMempoolRPCCommands(t);
...
}
Here, we should register our new commands. Let's call the registration function RegisterMagicNumberRPCCommands and modify the above src/rpc/register.h file:
...
void RegisterMagicNumberRPCCommands(CRPCTable&);
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
...
RegisterMagicNumberRPCCommands(t);
#ifdef ENABLE_EXTERNAL_SIGNER
RegisterSignerRPCCommands(t);
#endif // ENABLE_EXTERNAL_SIGNER
RegisterTxoutProofRPCCommands(t);
}
#endif // BITCOIN_RPC_REGISTER_H
That is the header (declaration) part, now we should implement it. Create the following src/rpc/magicnumber.cpp file:
#include <rpc/util.h>
#include <univalue.h>
#include <rpc/server.h>
static RPCHelpMan getmagicnumber()
{
return RPCHelpMan{
"getmagicnumber",
"Returns a magic number. Remember that magic can only be positive",
{
{"multiplier", RPCArg::Type::NUM, RPCArg::Default{1}, "Makes it even more magical. Must be > 0"}
},
RPCResult{
RPCResult::Type::NUM, "", "A magic number"
},
RPCExamples{
HelpExampleCli("getmagicnumber", "")
+ HelpExampleCli("getmagicnumber", "5")
+ HelpExampleRpc("getmagicnumber", "")
+ HelpExampleRpc("getmagicnumber", "5")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const int multiplier{request.params[0].isNull() ? 1 : request.params[0].getInt<int>()};
if (multiplier < 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Multiplier must be greater than zero; magic is positive only");
}
return UniValue(multiplier * 437);
}
};
}
void RegisterMagicNumberRPCCommands(CRPCTable &t)
{
static const CRPCCommand commands[] {
{"util", &getmagicnumber},
};
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
}
What we are doing here:
src/rpc/util.h filegetmagicnumber RPC command in a function and registering it in the CRPCTablemultiplier with the default value of 1 in our casehelp command through the bitcoin-cli interface - we are about to see it in action
There are two more things we must add before compiling and testing our changes. First, go the src/rpc/client.cpp file and modify CRPCConvertParam vRPCConvertParams[]:
/**
* Specify a (method, idx, name) here if the argument is a non-string RPC
* argument and needs to be converted from JSON.
*
* @note Parameter indexes start from 0.
*/
static const CRPCConvertParam vRPCConvertParams[] =
{
{ "setmocktime", 0, "timestamp" },
{ "mockscheduler", 0, "delta_time" },
{ "utxoupdatepsbt", 1, "descriptors" },
{ "generatetoaddress", 0, "nblocks" },
{ "generatetoaddress", 2, "maxtries" },
...
{ "getmagicnumber", 0, "multiplier" },
};
It is needed so that the multiplier argument is parsed correctly as an integer, not a raw string.
Then, head to the src/CMakeLists.txt file and modify the bitcoin_node target so that the new RPC command is linked properly:
# P2P and RPC server functionality used by `bitcoind` and `bitcoin-qt` executables.
add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
addrdb.cpp
addrman.cpp
banman.cpp
bip324.cpp
...
rest.cpp
rpc/blockchain.cpp
rpc/external_signer.cpp
rpc/fees.cpp
rpc/magicnumber.cpp
...
)
Finally, we might successfully compile our changes:
# as previously, adjust the toolchain and desired parallelism
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=depends/x86_64-pc-linux-gnu/toolchain.cmake \
-DENABLE_WALLET=OFF
cmake --build build -j8
We right now have bitcoin binaries with our brand new RPC command available! To test it, let's run a bitcoin node in the regtest (local, mocked) network. From the build/bin dir, run:
./bitcoind -regtest
Then, we are able to issue RPC commands against this node. Let's see a help message of our new command:
./bitcoin-cli -regtest help getmagicnumber
getmagicnumber ( multiplier )
Returns a magic number. Remember that magic can only be positive
Arguments:
1. multiplier (numeric, optional, default=1) Makes it even more magical. Must be > 0
Result:
n (numeric) A magic number
Examples:
> bitcoin-cli getmagicnumber
> bitcoin-cli getmagicnumber 5
> curl --user myusername --data-binary '{"jsonrpc": "2.0", "id": "curltest", "method": "getmagicnumber", "params": []}' -H 'content-type: application/json' http://127.0.0.1:8332/
> curl --user myusername --data-binary '{"jsonrpc": "2.0", "id": "curltest", "method": "getmagicnumber", "params": [5]}' -H 'content-type: application/json' http://127.0.0.1:8332/
This is exactly how we have defined it. We can call it with and without an argument:
./bitcoin-cli -regtest getmagicnumber
437
./bitcoin-cli -regtest getmagicnumber 2
874
./bitcoin-cli -regtest getmagicnumber 5
2185
So, we now possess the ability to modify Bitcoin Core code! Let's also add a functional test so that we can prove to others that our change works in an automated and future-proof way. All we have to do is to create the following rpc_getmagicnumber.py file in the test/functional dir:
#!/usr/bin/env python3
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal, assert_raises_rpc_error
)
class GetMagicNumberRPCTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def run_test(self):
node = self.nodes[0]
# Default is 437 * 1 (multiplier)
magic = node.getmagicnumber()
assert_equal(437, magic)
# Respects multiplier arg
multiplier = 5
magic_multiplied = node.getmagicnumber(multiplier)
assert_equal(multiplier * 437, magic_multiplied)
# Does not allow 0 multiplier
assert_raises_rpc_error(-8, "Multiplier must be greater than zero; magic is positive only", node.getmagicnumber, 0)
# Does not allow negative multiplier
assert_raises_rpc_error(-8, "Multiplier must be greater than zero; magic is positive only", node.getmagicnumber, -11)
if __name__ == '__main__':
GetMagicNumberRPCTest(__file__).main()
What is going on here:
test_framework; it mostly comes down to inheriting from the BitcoinTestFramework class, overriding a few methods and setting up some paramsrun_test method we call our new RPC command by using the dynamically generated node.getmagicnumber() functionRunning it:
# make it executable
chmod +x test/functional/rpc_getmagicnumber.py
# trigger test files with config generation - needs to be done only once per newly created test file;
# as previously, adjust the toolchain and desired parallelism
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=depends/x86_64-pc-linux-gnu/toolchain.cmake \
-DENABLE_WALLET=OFF
# run version with the linked config
build/test/functional/rpc_getmagicnumber.py
2025-08-17T11:44:11.266897Z TestFramework (INFO): PRNG seed is: 3185391037459194884
2025-08-17T11:44:11.268210Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test_w41spy_7
2025-08-17T11:44:11.583631Z TestFramework (INFO): Stopping nodes
2025-08-17T11:44:11.686584Z TestFramework (INFO): Cleaning up /tmp/bitcoin_func_test_w41spy_7 on exit
2025-08-17T11:44:11.686734Z TestFramework (INFO): Tests successful
We should also add it to the functional test suite so that it runs together with all other tests; in the test/functional/test_runner.py:
BASE_SCRIPTS = [
# Scripts that are run by default.
# Longest test should go first, to favor running tests in parallel
# vv Tests less than 5m vv
'feature_fee_estimation.py',
'feature_taproot.py',
'feature_block.py',
'mempool_ephemeral_dust.py',
'wallet_conflicts.py',
'p2p_opportunistic_1p1c.py',
...
'feature_presegwit_node_upgrade.py',
'feature_settings.py',
'rpc_getdescriptorinfo.py',
'rpc_getmagicnumber.py',
...
'p2p_seednode.py',
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
]
We can then run all functional tests and verify that our test is executed as well:
build/test/functional/test_runner.py
...
259/272 - rpc_getmagicnumber.py passed, Duration: 1 s
...
We now possess the ability not only to modify the Bitcoin Core codebase, but also to prove that our change works as intended - now, as well as in the future, thanks to automated tests. There still is knowledge to be gained about development conventions and contributing workflow, but by and large, if we understand the above change example, we are pretty much ready to start contributing to the Bitcoin Core repo!
We went through many complex topics and have learned a ton about the Bitcoin Core code, the reference client and the reference implementation of all aspects of the Bitcoin system:
Naturally, there is much more to learn about the Bitcoin Core code and the various parts of the Bitcoin system, but this definitely is a solid start. It surely helped me understand various pieces of the code, assess its quality and build confidence in the development and distribution processes. Ultimately, everything can be verified and decisions are publicly debated, explained and justified. Now, nothing stops us from contributing - maybe see you then in the next Pull Request!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。