diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 9702c1c51..536596177 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -1,1246 +1,1259 @@ Developer Notes =============== **Table of Contents** - [Developer Notes](#developer-notes) - [Coding Style (General)](#coding-style-general) - [Coding Style (C++)](#coding-style-c) - [Doxygen comments](#doxygen-comments) - [Coding Style (Python)](#coding-style-python) - [Development tips and tricks](#development-tips-and-tricks) - [Compiling for debugging](#compiling-for-debugging) - [Compiling for gprof profiling](#compiling-for-gprof-profiling) - [debug.log](#debuglog) - [Writing tests](#writing-tests) - [Writing script integration tests](#writing-script-integration-tests) - [Testnet and Regtest modes](#testnet-and-regtest-modes) - [DEBUG_LOCKORDER](#debug_lockorder) + - [DEBUG_LOCKCONTENTION](#debug_lockcontention) - [Valgrind suppressions file](#valgrind-suppressions-file) - [Compiling for test coverage](#compiling-for-test-coverage) - [Performance profiling with perf](#performance-profiling-with-perf) - [Sanitizers](#sanitizers) - [Locking/mutex usage notes](#lockingmutex-usage-notes) - [Threads](#threads) - [Ignoring IDE/editor files](#ignoring-ideeditor-files) - [Development guidelines](#development-guidelines) - [Wallet](#wallet) - [General C++](#general-c) - [C++ data structures](#c-data-structures) - [Strings and formatting](#strings-and-formatting) - [Variable names](#variable-names) - [Threads and synchronization](#threads-and-synchronization) - [Scripts](#scripts) - [Shebang](#shebang) - [Source code organization](#source-code-organization) - [GUI](#gui) - [Unit tests](#unit-tests) - [Third party libraries](#third-party-libraries) - [Git and GitHub tips](#git-and-github-tips) - [Release notes](#release-notes) - [RPC interface guidelines](#rpc-interface-guidelines) - [Internal interface guidelines](#internal-interface-guidelines) Coding Style (General) ---------------------- Various coding styles have been used during the history of the codebase, and the result is not very consistent. However, we're now trying to converge to a single style, so please use it in new code. Old code will be converted gradually and a handful of linters will help you to clean up your patches before submitting them for review. These linters are run automatically when using `arc diff` but can also be explicitly called with `arc lint`. Coding Style (C++) ------------------ - Basic rules specified in [.clang-format](/.clang-format). - Braces on new lines for namespaces, classes, functions, methods. - Braces on the same line for everything else. - 4 space indentation (no tabs) for every block except namespaces. - No indentation for `public`/`protected`/`private` or for `namespace`. - No extra spaces inside parenthesis; don't do ( this ) - No space after function names; one space after `if`, `for` and `while`. - Always add braces for block statements (e.g. `if`, `for`, `while`). - `++i` is preferred over `i++`. - `static_assert` is preferred over `assert` where possible. Generally; compile-time checking is preferred over run-time checking. - Use CamelCase for functions/methods, and lowerCamelCase for variables. - GLOBAL_CONSTANTS should use UPPER_SNAKE_CASE. - namespaces should use lower_snake_case. - Function names should generally start with an English command-form verb (e.g. `ValidateTransaction`, `AddTransactionToMempool`, `ConnectBlock`) - Variable names should generally be nouns or past/future tense verbs. (e.g. `canDoThing`, `signatureOperations`, `didThing`) - Avoid using globals, remove existing globals whenever possible. - Class member variable names should be prepended with `m_` - DO choose easily readable identifier names. - DO favor readability over brevity. - DO NOT use Hungarian notation. - DO NOT use abbreviations or contractions within identifiers. - WRONG: mempool - RIGHT: MemoryPool - WRONG: ChangeDir - RIGHT: ChangeDirectory - DO NOT use obscure acronyms, DO uppercase any acronyms. - FINALLY, do not migrate existing code unless refactoring. It makes forwarding-porting from Bitcoin Core more difficult. The naming convention roughly mirrors [Microsoft Naming Conventions](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/general-naming-conventions) C++ Coding Standards should strive to follow the [LLVM Coding Standards](https://llvm.org/docs/CodingStandards.html) Code style example: ```c++ // namespaces should be lower_snake_case namespace foo_bar_bob { /** * Class is used for doing classy things. All classes should * have a doxygen comment describing their PURPOSE. That is to say, * why they exist. Functional details can be determined from the code. * @see PerformTask() */ class Class { private: //! memberVariable's name should be lowerCamelCase, and be a noun. int m_memberVariable; public: /** * The documentation before a function or class method should follow Doxygen * spec. The name of the function should start with an english verb which * indicates the intended purpose of this code. * * The function name should be should be CamelCase. * * @param[in] s A description * @param[in] n Another argument description * @pre Precondition for function... */ bool PerformTask(const std::string& s, int n) { // Use lowerChamelCase for local variables. bool didMore = false; // Comment summarizing the intended purpose of this section of code for (int i = 0; i < n; ++i) { if (!DidSomethingFail()) { return false; } ... if (IsSomethingElse()) { DoMore(); didMore = true; } else { DoLess(); } } return didMore; } } } // namespace foo ``` Doxygen comments ----------------- To facilitate the generation of documentation, use doxygen-compatible comment blocks for functions, methods and fields. For example, to describe a function use: ```c++ /** * ... text ... * @param[in] arg1 A description * @param[in] arg2 Another argument description * @pre Precondition for function... */ bool function(int arg1, const char *arg2) ``` A complete list of `@xxx` commands can be found at http://www.doxygen.nl/manual/commands.html. As Doxygen recognizes the comments by the delimiters (`/**` and `*/` in this case), you don't *need* to provide any commands for a comment to be valid; just a description text is fine. To describe a class use the same construct above the class definition: ```c++ /** * Alerts are for notifying old versions if they become too obsolete and * need to upgrade. The message is displayed in the status bar. * @see GetWarnings() */ class CAlert { ``` To describe a member or variable use: ```c++ int var; //!< Detailed description after the member ``` or ```cpp //! Description before the member int var; ``` Also OK: ```c++ /// /// ... text ... /// bool function2(int arg1, const char *arg2) ``` Not OK (used plenty in the current source, but not picked up): ```c++ // // ... text ... // ``` A full list of comment syntaxes picked up by doxygen can be found at http://www.doxygen.nl/manual/docblocks.html, but if possible use one of the above styles. To build doxygen locally to test changes to the Doxyfile or visualize your comments before landing changes: ``` ninja doc-doxygen # output goes to doc/doxygen/html/ ``` Coding Style (Python) --------------------- Refer to [functional-tests.md#style-guidelines](functional-tests.md#style-guidelines). Development tips and tricks --------------------------- ### Compiling for debugging Run `cmake` with `-DCMAKE_BUILD_TYPE=Debug` to add additional compiler flags that produce better debugging builds. ### Compiling for gprof profiling ``` cmake -GNinja .. -DENABLE_HARDENING=OFF -DENABLE_PROFIILING=gprof ``` ### debug.log If the code is behaving strangely, take a look in the debug.log file in the data directory; error and debugging messages are written there. The `-debug=...` command-line option controls debugging; running with just `-debug` or `-debug=1` will turn on all categories (and give you a very large debug.log file). The Qt code routes `qDebug()` output to debug.log under category "qt": run with `-debug=qt` to see it. ### Writing tests For details on unit tests, see [Compiling/running unit tests](unit-tests.md). For details on functional tests, see [Functional tests](functional-tests.md). ### Writing script integration tests Script integration tests are built using `src/test/script_tests.cpp`: 1. Uncomment the line with `#define UPDATE_JSON_TESTS` 2. Add a new TestBuilder to the `script_build` test to cover your test case. 3. `ninja check-bitcoin-script_tests` 4. Copy your newly generated test JSON from `/src/script_tests.json.gen` to `src/test/data/script_tests.json`. Please commit your TestBuilder along with your generated test JSON and cleanup the uncommented #define before code review. ### Testnet and Regtest modes Run with the `-testnet` option to run with "play bitcoins" on the test network, if you are testing multi-machine code that needs to operate across the internet. If you are testing something that can run on one machine, run with the `-regtest` option. In regression test mode, blocks can be created on-demand; see [test/functional/](/test/functional) for tests that run in `-regtest` mode. ### DEBUG_LOCKORDER Bitcoin ABC is a multi-threaded application, and deadlocks or other multi-threading bugs can be very difficult to track down. The `-DCMAKE_BUILD_TYPE=Debug` cmake option adds `-DDEBUG_LOCKORDER` to the compiler flags. This inserts run-time checks to keep track of which locks are held, and adds warnings to the debug.log file if inconsistencies are detected. +### DEBUG_LOCKCONTENTION + +Defining `DEBUG_LOCKCONTENTION` adds a "lock" logging category that, when enabled, +logs the location and duration of each lock contention to the `debug.log` file. + +To enable it, run cmake with `-DDEBUG_LOCKCONTENTION` added to your CPPFLAGS, +e.g. `-DCMAKE_CXX_FLAGS="-DDEBUG_LOCKCONTENTION"`, then build and run bitcoind. + +You can then use the `-debug=lock` configuration option at bitcoind startup or +`bitcoin-cli logging '["lock"]'` at runtime to turn on lock contention logging. +It can be toggled off again with `bitcoin-cli logging [] '["lock"]'`. + ### Assertions and Checks The util file `src/util/check.h` offers helpers to protect against coding and internal logic bugs. They must never be used to validate user, network or any other input. * `assert` or `Assert` should be used to document assumptions when any violation would mean that it is not safe to continue program execution. The code is always compiled with assertions enabled. - For example, a nullptr dereference or any other logic bug in validation code means the program code is faulty and must terminate immediately. * `CHECK_NONFATAL` should be used for recoverable internal logic bugs. On failure, it will throw an exception, which can be caught to recover from the error. - For example, a nullptr dereference or any other logic bug in RPC code means that the RPC code is faulty and can not be executed. However, the logic bug can be shown to the user and the program can continue to run. * `Assume` should be used to document assumptions when program execution can safely continue even if the assumption is violated. In debug builds it behaves like `Assert`/`assert` to notify developers and testers about nonfatal errors. In production it doesn't warn or log anything, though the expression is always evaluated. - For example it can be assumed that a variable is only initialized once, but a failed assumption does not result in a fatal bug. A failed assumption may or may not result in a slightly degraded user experience, but it is safe to continue program execution. ### Valgrind suppressions file Valgrind is a programming tool for memory debugging, memory leak detection, and profiling. The repo contains a Valgrind suppressions file ([`valgrind.supp`](/contrib/valgrind.supp)) which includes known Valgrind warnings in our dependencies that cannot be fixed in-tree. Example use: ```shell $ valgrind --suppressions=contrib/valgrind.supp src/test/test_bitcoin $ valgrind --suppressions=contrib/valgrind.supp --leak-check=full \ --show-leak-kinds=all src/test/test_bitcoin --log_level=test_suite $ valgrind -v --leak-check=full src/bitcoind -printtoconsole ``` ### Compiling for test coverage LCOV can be used to generate a test coverage report based upon some test targets execution. Some packages are required to generate the coverage report: `c++filt`, `gcov`, `genhtml`, `lcov` and `python3`. To install these dependencies on Debian 10: ```shell sudo apt install binutils-common g++ lcov python3 ``` To enable LCOV report generation during test runs: ```shell cmake -GNinja .. -DENABLE_COVERAGE=ON ninja coverage-check-all ``` A coverage report will now be accessible at `./check-all.coverage/index.html`. To include branch coverage, you can add the `-DENABLE_BRANCH_COVERAGE=ON` option to the `cmake` command line. ### Performance profiling with perf Profiling is a good way to get a precise idea of where time is being spent in code. One tool for doing profiling on Linux platforms is called [`perf`](http://www.brendangregg.com/perf.html), and has been integrated into the functional test framework. Perf can observe a running process and sample (at some frequency) where its execution is. Perf installation is contingent on which kernel version you're running; see [this StackExchange thread](https://askubuntu.com/questions/50145/how-to-install-perf-monitoring-tool) for specific instructions. Certain kernel parameters may need to be set for perf to be able to inspect the running process' stack. ```sh $ sudo sysctl -w kernel.perf_event_paranoid=-1 $ sudo sysctl -w kernel.kptr_restrict=0 ``` Make sure you [understand the security trade-offs](https://lwn.net/Articles/420403/) of setting these kernel parameters. To profile a running bitcoind process for 60 seconds, you could use an invocation of `perf record` like this: ```sh $ perf record \ -g --call-graph dwarf --per-thread -F 140 \ -p `pgrep bitcoind` -- sleep 60 ``` You could then analyze the results by running ```sh perf report --stdio | c++filt | less ``` or using a graphical tool like [Hotspot](https://github.com/KDAB/hotspot). See the functional test documentation for how to invoke perf within tests. ### Sanitizers Bitcoin ABC can be compiled with various "sanitizers" enabled, which add instrumentation for issues regarding things like memory safety, thread race conditions, or undefined behavior. This is controlled with the `-DENABLE_SANITIZERS` cmake flag, which should be a semicolon separated list of sanitizers to enable. The sanitizer list should correspond to supported `-fsanitize=` options in your compiler. These sanitizers have runtime overhead, so they are most useful when testing changes or producing debugging builds. Some examples: ```bash # Enable both the address sanitizer and the undefined behavior sanitizer cmake -GNinja .. -DENABLE_SANITIZERS="address;undefined" # Enable the thread sanitizer cmake -GNinja .. -DENABLE_SANITIZERS=thread ``` If you are compiling with GCC you will typically need to install corresponding "san" libraries to actually compile with these flags, e.g. libasan for the address sanitizer, libtsan for the thread sanitizer, and libubsan for the undefined sanitizer. If you are missing required libraries, the cmake script will fail with an error when testing the sanitizer flags. Note that the sanitizers will give a better output if they are run with a Debug build configuration. There are a number of known problems for which suppressions files are provided under `test/sanitizer_suppressions`. These files are intended to be used with the `suppressions` option from the sanitizers. If you are using the `check-*` targets to run the tests, the suppression options are automatically set. Otherwise they need to be set manually using environment variables; refer to your compiler manual for the correct syntax. The address sanitizer is known to fail in [sha256_sse4::Transform](/src/crypto/sha256_sse4.cpp) which makes it unusable unless you also use `-DCRYPTO_USE_ASM=OFF` when running cmake. We would like to fix sanitizer issues, so please send pull requests if you can fix any errors found by the address sanitizer (or any other sanitizer). Not all sanitizer options can be enabled at the same time, e.g. trying to build with `-DENABLE_SANITIZERS=="address;thread" will fail in the cmake script as these sanitizers are mutually incompatible. Refer to your compiler manual to learn more about these options and which sanitizers are supported by your compiler. Examples: Build and run the test suite with the address sanitizer enabled: ```bash mkdir build_asan cd build_asan cmake -GNinja .. \ -DCMAKE_BUILD_TYPE=Debug \ -DENABLE_SANITIZERS=address \ -DCRYPTO_USE_ASM=OFF ninja check check-functional ``` Build and run the test suite with the thread sanitizer enabled (it can take a very long time to complete): ```bash mkdir build_tsan cd build_tsan cmake -GNinja .. \ -DCMAKE_BUILD_TYPE=Debug \ -DENABLE_SANITIZERS=thread ninja check check-functional ``` Build and run the test suite with the undefined sanitizer enabled: ```bash mkdir build_ubsan cd build_ubsan cmake -GNinja .. \ -DCMAKE_BUILD_TYPE=Debug \ -DENABLE_SANITIZERS=undefined ninja check check-functional ``` Additional resources: * [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) * [LeakSanitizer](https://clang.llvm.org/docs/LeakSanitizer.html) * [MemorySanitizer](https://clang.llvm.org/docs/MemorySanitizer.html) * [ThreadSanitizer](https://clang.llvm.org/docs/ThreadSanitizer.html) * [UndefinedBehaviorSanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) * [GCC Instrumentation Options](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html) * [Google Sanitizers Wiki](https://github.com/google/sanitizers/wiki) * [Issue #12691: Enable -fsanitize flags in Travis](https://github.com/bitcoin/bitcoin/issues/12691) Locking/mutex usage notes ------------------------- The code is multi-threaded, and uses mutexes and the `LOCK` and `TRY_LOCK` macros to protect data structures. Deadlocks due to inconsistent lock ordering (thread 1 locks `cs_main` and then `cs_wallet`, while thread 2 locks them in the opposite order: result, deadlock as each waits for the other to release its lock) are a problem. Compile with `-DDEBUG_LOCKORDER` (or use `-DCMAKE_BUILD_TYPE=Debug`) to get lock order inconsistencies reported in the debug.log file. Re-architecting the core code so there are better-defined interfaces between the various components is a goal, with any necessary locking done by the components (e.g. see the self-contained `FillableSigningProvider` class and its `cs_KeyStore` lock for example). Threads ------- - [Main thread (`bitcoind`)](https://www.bitcoinabc.org/doc/dev/bitcoind_8cpp.html#a0ddf1224851353fc92bfbff6f499fa97) : Started from `main()` in `bitcoind.cpp`. Responsible for starting up and shutting down the application. - [ThreadImport (`b-loadblk`)](https://www.bitcoinabc.org/doc/dev/init_8cpp.html#ae9e290a0e829ec0198518de2eda579d1) : Loads blocks from `blk*.dat` files or `-loadblock=` on startup. - [ThreadScriptCheck (`b-scriptch.x`)](https://www.bitcoinabc.org/doc/dev/validation_8cpp.html#a925a33e7952a157922b0bbb8dab29a20) : Parallel script validation threads for transactions in blocks. - [ThreadHTTP (`b-http`)](https://www.bitcoinabc.org/doc/dev/httpserver_8cpp.html#abb9f6ea8819672bd9a62d3695070709c) : Libevent thread to listen for RPC and REST connections. - [HTTP worker threads(`b-httpworker.x`)](https://www.bitcoinabc.org/doc/dev/httpserver_8cpp.html#aa6a7bc27265043bc0193220c5ae3a55f) : Threads to service RPC and REST requests. - [Indexer threads (`b-txindex`, etc)](https://www.bitcoinabc.org/doc/dev/class_base_index.html#a96a7407421fbf877509248bbe64f8d87) : One thread per indexer. - [SchedulerThread (`b-scheduler`)](https://www.bitcoinabc.org/doc/dev/class_c_scheduler.html#a14d2800815da93577858ea078aed1fba) : Does asynchronous background tasks like dumping wallet contents, dumping addrman and running asynchronous validationinterface callbacks. - [TorControlThread (`b-torcontrol`)](https://www.bitcoinabc.org/doc/dev/torcontrol_8cpp.html#a4faed3692d57a0d7bdbecf3b37f72de0) : Libevent thread for tor connections. - Net threads: - [ThreadMessageHandler (`b-msghand`)](https://www.bitcoinabc.org/doc/dev/class_c_connman.html#aacdbb7148575a31bb33bc345e2bf22a9) : Application level message handling (sending and receiving). Almost all net_processing and validation logic runs on this thread. - [ThreadDNSAddressSeed (`b-dnsseed`)](https://www.bitcoinabc.org/doc/dev/class_c_connman.html#aa7c6970ed98a4a7bafbc071d24897d13) : Loads addresses of peers from the DNS. - [ThreadMapPort (`b-upnp`)](https://www.bitcoinabc.org/doc/dev/net_8cpp.html#a63f82a71c4169290c2db1651a9bbe249) : Universal plug-and-play startup/shutdown. - [ThreadSocketHandler (`b-net`)](https://www.bitcoinabc.org/doc/dev/class_c_connman.html#a765597cbfe99c083d8fa3d61bb464e34) : Sends/Receives data from peers on port 8333. - [ThreadOpenAddedConnections (`b-addcon`)](https://www.bitcoinabc.org/doc/dev/class_c_connman.html#a0b787caf95e52a346a2b31a580d60a62) : Opens network connections to added nodes. - [ThreadOpenConnections (`b-opencon`)](https://www.bitcoinabc.org/doc/dev/class_c_connman.html#a55e9feafc3bab78e5c9d408c207faa45) : Initiates new connections to peers. Ignoring IDE/editor files -------------------------- In closed-source environments in which everyone uses the same IDE it is common to add temporary files it produces to the project-wide `.gitignore` file. However, in open source software such as Bitcoin ABC, where everyone uses their own editors/IDE/tools, it is less common. Only you know what files your editor produces and this may change from version to version. The canonical way to do this is thus to create your local gitignore. Add this to `~/.gitconfig`: ``` [core] excludesfile = /home/.../.gitignore_global ``` (alternatively, type the command `git config --global core.excludesfile ~/.gitignore_global` on a terminal) Then put your favorite tool's temporary filenames in that file, e.g. ``` # NetBeans nbproject/ ``` Another option is to create a per-repository excludes file `.git/info/exclude`. These are not committed but apply only to one repository. If a set of tools is used by the build system or scripts the repository (for example, lcov) it is perfectly acceptable to add its files to `.gitignore` and commit them. Development guidelines ============================ A few non-style-related recommendations for developers, as well as points to pay attention to for reviewers of Bitcoin ABC code. Wallet ------- - Make sure that no crashes happen with run-time option `-disablewallet`. - *Rationale*: In RPC code that conditionally uses the wallet (such as `validateaddress`) it is easy to forget that global pointer `pwalletMain` can be NULL. See `test/functional/disablewallet.py` for functional tests exercising the API with `-disablewallet` - Include `db_cxx.h` (BerkeleyDB header) only when `ENABLE_WALLET` is set - *Rationale*: Otherwise compilation of the disable-wallet build will fail in environments without BerkeleyDB General C++ ------------- - Assertions should not have side-effects - *Rationale*: Even though the source code is set to refuse to compile with assertions disabled, having side-effects in assertions is unexpected and makes the code harder to understand - If you use the `.h`, you must link the `.cpp` - *Rationale*: Include files define the interface for the code in implementation files. Including one but not linking the other is confusing. Please avoid that. Moving functions from the `.h` to the `.cpp` should not result in build errors - Use the RAII (Resource Acquisition Is Initialization) paradigm where possible. For example by using `unique_ptr` for allocations in a function. - *Rationale*: This avoids memory and resource leaks, and ensures exception safety - Use `std::make_unique()` to construct objects owned by `unique_ptr`s - *Rationale*: `std::make_unique` is concise and ensures exception safety in complex expressions. C++ data structures -------------------- - Never use the `std::map []` syntax when reading from a map, but instead use `.find()` - *Rationale*: `[]` does an insert (of the default element) if the item doesn't exist in the map yet. This has resulted in memory leaks in the past, as well as race conditions (expecting read-read behavior). Using `[]` is fine for *writing* to a map - Do not compare an iterator from one data structure with an iterator of another data structure (even if of the same type) - *Rationale*: Behavior is undefined. In C++ parlor this means "may reformat the universe", in practice this has resulted in at least one hard-to-debug crash bug - Watch out for out-of-bounds vector access. `&vch[vch.size()]` is illegal, including `&vch[0]` for an empty vector. Use `vch.data()` and `vch.data() + vch.size()` instead. - Vector bounds checking is only enabled in debug mode. Do not rely on it - Initialize all non-static class members where they are defined. If this is skipped for a good reason (i.e., optimization on the critical path), add an explicit comment about this - *Rationale*: Ensure determinism by avoiding accidental use of uninitialized values. Also, static analyzers balk about this. Initializing the members in the declaration makes it easy to spot uninitialized ones. ```cpp class A { uint32_t m_count{0}; } ``` - By default, declare single-argument constructors `explicit`. - *Rationale*: This is a precaution to avoid unintended conversions that might arise when single-argument constructors are used as implicit conversion functions. - Use explicitly signed or unsigned `char`s, or even better `uint8_t` and `int8_t`. Do not use bare `char` unless it is to pass to a third-party API. This type can be signed or unsigned depending on the architecture, which can lead to interoperability problems or dangerous conditions such as out-of-bounds array accesses - Prefer explicit constructions over implicit ones that rely on 'magical' C++ behavior - *Rationale*: Easier to understand what is happening, thus easier to spot mistakes, even for those that are not language lawyers - Use `Span` as function argument when it can operate on any range-like container. - *Rationale*: Compared to `Foo(const vector&)` this avoids the need for a (potentially expensive) conversion to vector if the caller happens to have the input stored in another type of container. However, be aware of the pitfalls documented in [span.h](../src/span.h). ```cpp void Foo(Span data); std::vector vec{1,2,3}; Foo(vec); ``` Strings and formatting ------------------------ - Use `std::string`, avoid C string manipulation functions - *Rationale*: C++ string handling is marginally safer, less scope for buffer overflows and surprises with `\0` characters. Also some C string manipulations tend to act differently depending on platform, or even the user locale - Use `ParseInt32`, `ParseInt64`, `ParseUInt32`, `ParseUInt64`, `ParseDouble` from `utilstrencodings.h` for number parsing - *Rationale*: These functions do overflow checking, and avoid pesky locale issues Variable names -------------- The shadowing warning (`-Wshadow`) is enabled by default. It prevents issues rising from using a different variable with the same name. E.g. in member initializers, prepend `_` to the argument name shadowing the member name: ```c++ class AddressBookPage { Mode m_mode; } AddressBookPage::AddressBookPage(Mode _mode) : m_mode(_mode) ... ``` When using nested cycles, do not name the inner cycle variable the same as in upper cycle etc. Please name variables so that their names do not shadow variables defined in the source code. Threads and synchronization ---------------------------- - Prefer `Mutex` type to `RecursiveMutex` one - Consistently use [Clang Thread Safety Analysis](https://clang.llvm.org/docs/ThreadSafetyAnalysis.html) annotations to get compile-time warnings about potential race conditions in code. Combine annotations in function declarations with run-time asserts in function definitions: ```C++ // txmempool.h class CTxMemPool { public: ... mutable RecursiveMutex cs; ... void UpdateTransactionsFromBlock(...) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, cs); ... } // txmempool.cpp void CTxMemPool::UpdateTransactionsFromBlock(...) { AssertLockHeld(::cs_main); AssertLockHeld(cs); ... } ``` ```C++ // validation.h class ChainstateManager { public: ... bool ProcessNewBlock(...) LOCKS_EXCLUDED(::cs_main); ... } // validation.cpp bool ChainstateManager::ProcessNewBlock(...) { AssertLockNotHeld(::cs_main); ... LOCK(::cs_main); ... } ``` - Build and run tests with `-DDEBUG_LOCKORDER` to verify that no potential deadlocks are introduced. As of 0.12, this is defined by default when configuring with `-DCMAKE_BUILD_TYPE=Debug` - When using `LOCK`/`TRY_LOCK` be aware that the lock exists in the context of the current scope, so surround the statement and the code that needs the lock with braces OK: ```c++ { TRY_LOCK(cs_vNodes, lockNodes); ... } ``` Wrong: ```c++ TRY_LOCK(cs_vNodes, lockNodes); { ... } ``` Scripts -------------------------- ### Shebang - Use `#!/usr/bin/env bash` instead of obsolete `#!/bin/bash`. - [*Rationale*](https://github.com/dylanaraps/pure-bash-bible#shebang): `#!/bin/bash` assumes it is always installed to /bin/ which can cause issues; `#!/usr/bin/env bash` searches the user's PATH to find the bash binary. OK: ```bash #!/usr/bin/env bash ``` Wrong: ```bash #!/bin/bash ``` Source code organization -------------------------- - Implementation code should go into the `.cpp` file and not the `.h`, unless necessary due to template usage or when performance due to inlining is critical - *Rationale*: Shorter and simpler header files are easier to read, and reduce compile time - Use only the lowercase alphanumerics (`a-z0-9`), underscore (`_`) and hyphen (`-`) in source code filenames. - *Rationale*: `grep`:ing and auto-completing filenames is easier when using a consistent naming pattern. Potential problems when building on case-insensitive filesystems are avoided when using only lowercase characters in source code filenames. - Don't import anything into the global namespace (`using namespace ...`). Use fully specified types such as `std::string`. - *Rationale*: Avoids symbol conflicts - Terminate namespaces with a comment (`// namespace mynamespace`). The comment should be placed on the same line as the brace closing the namespace, e.g. ```c++ namespace mynamespace { ... } // namespace mynamespace namespace { ... } // namespace ``` - *Rationale*: Avoids confusion about the namespace context Header Inclusions ----------------- - Header inclusions should use angle brackets (`#include <>`). The include path should be relative to the `src` folder. e.g.: `#include ` - Native C++ headers should be preferred over C compatibility headers. e.g.: use `` instead of `` - In order to make the code consistent, header files should be included in the following order, with each section separated by a newline: 1. In a .cpp file, the associated .h is in first position. In a test source, this is the header file under test. 2. The project headers. 3. The test headers. 4. The 3rd party libraries headers. Different libraries should be in different sections. 5. The system libraries. All headers should be lexically ordered inside their block. - Use include guards to avoid the problem of double inclusion. The header file `foo/bar.h` should use the include guard identifier `BITCOIN_FOO_BAR_H`, e.g. ```c++ #ifndef BITCOIN_FOO_BAR_H #define BITCOIN_FOO_BAR_H ... #endif // BITCOIN_FOO_BAR_H ``` GUI ----- - Do not display or manipulate dialogs in model code (classes `*Model`) - *Rationale*: Model classes pass through events and data from the core, they should not interact with the user. That's where View classes come in. The converse also holds: try to not directly access core data structures from Views. - Avoid adding slow or blocking code in the GUI thread. In particular do not add new `interface::Node` and `interface::Wallet` method calls, even if they may be fast now, in case they are changed to lock or communicate across processes in the future. Prefer to offload work from the GUI thread to worker threads (see `RPCExecutor` in console code as an example) or take other steps (see ) to keep the GUI responsive. - *Rationale*: Blocking the GUI thread can increase latency, and lead to hangs and deadlocks. Unit Tests ----------- - Test suite naming convention: The Boost test suite in file `src/test/foo_tests.cpp` should be named `foo_tests`. Test suite names must be unique. Third party libraries --------------------- Several parts of the repository are software maintained elsewhere. Changes to these should preferably be sent upstream but bugfixes may also be submitted to Bitcoin ABC so that they can be integrated quickly. Cosmetic changes should be purely taken upstream. Current third party libraries include: - src/leveldb - Upstream at ; Maintained by Google. - **Note**: Follow the instructions in [Upgrading LevelDB](#upgrading-leveldb) when merging upstream changes to Bitcoin ABC. - src/secp256k1 - Upstream at ; actively maintained by Bitcoin Core contributors. Bitcoin ABC is using a modified version of libsecp256k1, some changes might be directly submitted to Bitcoin ABC. See the [secp256k1 README](../src/secp256k1/README.md) for details. - src/crypto/ctaes - Upstream at https://github.com/bitcoin-core/ctaes ; maintained by Bitcoin Core contributors. - src/univalue - Upstream at https://github.com/bitcoin-core/univalue ; actively maintained by Bitcoin Core contributors, deviates from upstream https://github.com/jgarzik/univalue Upgrading LevelDB --------------------- Extra care must be taken when upgrading LevelDB. This section explains issues you must be aware of. ### File Descriptor Counts In most configurations we use the default LevelDB value for `max_open_files`, which is 1000 at the time of this writing. If LevelDB actually uses this many file descriptors it will cause problems with Bitcoin's `select()` loop, because it may cause new sockets to be created where the fd value is >= 1024. For this reason, on 64-bit Unix systems we rely on an internal LevelDB optimization that uses `mmap()` + `close()` to open table files without actually retaining references to the table file descriptors. If you are upgrading LevelDB, you must sanity check the changes to make sure that this assumption remains valid. In addition to reviewing the upstream changes in `env_posix.cc`, you can use `lsof` to check this. For example, on Linux this command will show open `.ldb` file counts: ```bash $ lsof -p $(pidof bitcoind) |\ awk 'BEGIN { fd=0; mem=0; } /ldb$/ { if ($4 == "mem") mem++; else fd++ } END { printf "mem = %s, fd = %s\n", mem, fd}' mem = 119, fd = 0 ``` The `mem` value shows how many files are mmap'ed, and the `fd` value shows you many file descriptors these files are using. You should check that `fd` is a small number (usually 0 on 64-bit hosts). See the notes in the `SetMaxOpenFiles()` function in `dbwrapper.cc` for more details. ### Consensus Compatibility It is possible for LevelDB changes to inadvertently change consensus compatibility between nodes. This happened in Bitcoin 0.8 (when LevelDB was first introduced). When upgrading LevelDB you should review the upstream changes to check for issues affecting consensus compatibility. For example, if LevelDB had a bug that accidentally prevented a key from being returned in an edge case, and that bug was fixed upstream, the bug "fix" would be an incompatible consensus change. In this situation the correct behavior would be to revert the upstream fix before applying the updates to Bitcoin ABC's copy of LevelDB. In general you should be wary of any upstream changes affecting what data is returned from LevelDB queries. Git and GitHub tips --------------------- - Github is not typically the source of truth for pull requests. See [CONTRIBUTING](../CONTRIBUTING.md) for instructions on setting up your repo correctly. - Similarly, your git remote origin should be set to: `ssh://vcs@reviews.bitcoinabc.org:2221/source/bitcoin-abc.git` instead of github.com. See [CONTRIBUTING](../CONTRIBUTING.md). For git and GitHub productivity tips, see [Productivity Notes](productivity.md). Release notes ------------- Release notes should be written for any PR that: - introduces a notable new feature - fixes a significant bug - changes an API or configuration model - makes any other visible change to the end-user experience. Release notes should be added to the [/doc/release-notes.md](/doc/release-notes.md) file, which is archived and cleared after each release. RPC interface guidelines -------------------------- A few guidelines for introducing and reviewing new RPC interfaces: - Method naming: use consecutive lower-case names such as `getrawtransaction` and `submitblock` - *Rationale*: Consistency with existing interface. - Argument naming: use snake case `fee_delta` (and not, e.g. camel case `feeDelta`) - *Rationale*: Consistency with existing interface. - Use the JSON parser for parsing, don't manually parse integers or strings from arguments unless absolutely necessary. - *Rationale*: Introduces hand-rolled string manipulation code at both the caller and callee sites, which is error prone, and it is easy to get things such as escaping wrong. JSON already supports nested data structures, no need to re-invent the wheel. - *Exception*: AmountFromValue can parse amounts as string. This was introduced because many JSON parsers and formatters hard-code handling decimal numbers as floating point values, resulting in potential loss of precision. This is unacceptable for monetary values. **Always** use `AmountFromValue` and `ValueFromAmount` when inputting or outputting monetary values. The only exceptions to this are `prioritisetransaction` and `getblocktemplate` because their interface is specified as-is in BIP22. - Missing arguments and 'null' should be treated the same: as default values. If there is no default value, both cases should fail in the same way. The easiest way to follow this guideline is detect unspecified arguments with `params[x].isNull()` instead of `params.size() <= x`. The former returns true if the argument is either null or missing, while the latter returns true if is missing, and false if it is null. - *Rationale*: Avoids surprises when switching to name-based arguments. Missing name-based arguments are passed as 'null'. - Try not to overload methods on argument type. E.g. don't make `getblock(true)` and `getblock("hash")` do different things. - *Rationale*: This is impossible to use with `bitcoin-cli`, and can be surprising to users. - *Exception*: Some RPC calls can take both an `int` and `bool`, most notably when a bool was switched to a multi-value, or due to other historical reasons. **Always** have false map to 0 and true to 1 in this case. - Don't forget to fill in the argument names correctly in the RPC command table. - *Rationale*: If not, the call can not be used with name-based arguments. - Set okSafeMode in the RPC command table to a sensible value: safe mode is when the blockchain is regarded to be in a confused state, and the client deems it unsafe to do anything irreversible such as send. Anything that just queries should be permitted. - *Rationale*: Troubleshooting a node in safe mode is difficult if half the RPCs don't work. - Add every non-string RPC argument `(method, idx, name)` to the table `vRPCConvertParams` in `rpc/client.cpp`. - *Rationale*: `bitcoin-cli` and the GUI debug console use this table to determine how to convert a plaintext command line to JSON. If the types don't match, the method can be unusable from there. - A RPC method must either be a wallet method or a non-wallet method. Do not introduce new methods such as `signrawtransaction` that differ in behavior based on presence of a wallet. - *Rationale*: As well as complicating the implementation and interfering with the introduction of multi-wallet, wallet and non-wallet code should be separated to avoid introducing circular dependencies between code units. - Try to make the RPC response a JSON object. - *Rationale*: If a RPC response is not a JSON object then it is harder to avoid API breakage if new data in the response is needed. - Wallet RPCs call BlockUntilSyncedToCurrentChain to maintain consistency with `getblockchaininfo`'s state immediately prior to the call's execution. Wallet RPCs whose behavior does *not* depend on the current chainstate may omit this call. - *Rationale*: In previous versions of Bitcoin Core, the wallet was always in-sync with the chainstate (by virtue of them all being updated in the same cs_main lock). In order to maintain the behavior that wallet RPCs return results as of at least the highest best-known block an RPC client may be aware of prior to entering a wallet RPC call, we must block until the wallet is caught up to the chainstate as of the RPC call's entry. This also makes the API much easier for RPC clients to reason about. - Be aware of RPC method aliases and generally avoid registering the same callback function pointer for different RPCs. - *Rationale*: RPC methods registered with the same function pointer will be considered aliases and only the first method name will show up in the `help` RPC command list. - *Exception*: Using RPC method aliases may be appropriate in cases where a new RPC is replacing a deprecated RPC, to avoid both RPCs confusingly showing up in the command list. - Use the `UNIX_EPOCH_TIME` constant when describing UNIX epoch time or timestamps in the documentation. - *Rationale*: User-facing consistency. Internal interface guidelines ----------------------------- Internal interfaces between parts of the codebase that are meant to be independent (node, wallet, GUI), are defined in [`src/interfaces/`](../src/interfaces/). The main interface classes defined there are [`interfaces::Chain`](../src/interfaces/chain.h), used by wallet to access the node's latest chain state, [`interfaces::Node`](../src/interfaces/node.h), used by the GUI to control the node, and [`interfaces::Wallet`](../src/interfaces/wallet.h), used by the GUI to control an individual wallet. There are also more specialized interface types like [`interfaces::Handler`](../src/interfaces/handler.h) [`interfaces::ChainClient`](../src/interfaces/chain.h) passed to and from various interface methods. Interface classes are written in a particular style so node, wallet, and GUI code doesn't need to run in the same process, and so the class declarations work more easily with tools and libraries supporting interprocess communication: - Interface classes should be abstract and have methods that are [pure virtual](https://en.cppreference.com/w/cpp/language/abstract_class). This allows multiple implementations to inherit from the same interface class, particularly so one implementation can execute functionality in the local process, and other implementations can forward calls to remote processes. - Interface method definitions should wrap existing functionality instead of implementing new functionality. Any substantial new node or wallet functionality should be implemented in [`src/node/`](../src/node/) or [`src/wallet/`](../src/wallet/) and just exposed in [`src/interfaces/`](../src/interfaces/) instead of being implemented there, so it can be more modular and accessible to unit tests. - Interface method parameter and return types should either be serializable or be other interface classes. Interface methods shouldn't pass references to objects that can't be serialized or accessed from another process. Examples: ```c++ // Good: takes string argument and returns interface class pointer virtual unique_ptr loadWallet(std::string filename) = 0; // Bad: returns CWallet reference that can't be used from another process virtual CWallet& loadWallet(std::string filename) = 0; ``` ```c++ // Good: accepts and returns primitive types virtual bool findBlock(const uint256& hash, int& out_height, int64_t& out_time) = 0; // Bad: returns pointer to internal node in a linked list inaccessible to // other processes virtual const CBlockIndex* findBlock(const uint256& hash) = 0; ``` ```c++ // Good: takes plain callback type and returns interface pointer using TipChangedFn = std::function; virtual std::unique_ptr handleTipChanged(TipChangedFn fn) = 0; // Bad: returns boost connection specific to local process using TipChangedFn = std::function; virtual boost::signals2::scoped_connection connectTipChanged(TipChangedFn fn) = 0; ``` - For consistency and friendliness to code generation tools, interface method input and inout parameters should be ordered first and output parameters should come last. Example: ```c++ // Good: error output param is last virtual bool broadcastTransaction(const CTransactionRef& tx, CAmount max_fee, std::string& error) = 0; // Bad: error output param is between input params virtual bool broadcastTransaction(const CTransactionRef& tx, std::string& error, CAmount max_fee) = 0; ``` - For friendliness to code generation tools, interface methods should not be overloaded: Example: ```c++ // Good: method names are unique virtual bool disconnectByAddress(const CNetAddr& net_addr) = 0; virtual bool disconnectById(NodeId id) = 0; // Bad: methods are overloaded by type virtual bool disconnect(const CNetAddr& net_addr) = 0; virtual bool disconnect(NodeId id) = 0; ``` - For consistency and friendliness to code generation tools, interface method names should be `lowerCamelCase` and standalone function names should be `UpperCamelCase`. Examples: ```c++ // Good: lowerCamelCase method name virtual void blockConnected(const CBlock& block, int height) = 0; // Bad: uppercase class method virtual void BlockConnected(const CBlock& block, int height) = 0; ``` ```c++ // Good: UpperCamelCase standalone function name std::unique_ptr MakeNode(LocalInit& init); // Bad: lowercase standalone function std::unique_ptr makeNode(LocalInit& init); ``` Note: This last convention isn't generally followed outside of [`src/interfaces/`](../src/interfaces/), though it did come up for discussion before in [#14635](https://github.com/bitcoin/bitcoin/pull/14635). diff --git a/src/logging.cpp b/src/logging.cpp index 12219a22c..9bfd8e78d 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -1,359 +1,362 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include bool fLogIPs = DEFAULT_LOGIPS; const char *const DEFAULT_DEBUGLOGFILE = "debug.log"; BCLog::Logger &LogInstance() { /** * NOTE: the logger instance is leaked on exit. This is ugly, but will be * cleaned up by the OS/libc. Defining a logger as a global object doesn't * work since the order of destruction of static/global objects is * undefined. Consider if the logger gets destroyed, and then some later * destructor calls LogPrintf, maybe indirectly, and you get a core dump at * shutdown trying to access the logger. When the shutdown sequence is fully * audited and tested, explicit destruction of these objects can be * implemented by changing this from a raw pointer to a std::unique_ptr. * Since the ~Logger() destructor is never called, the Logger class and all * its subclasses must have implicitly-defined destructors. * * This method of initialization was originally introduced in * ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c. */ static BCLog::Logger *g_logger{new BCLog::Logger()}; return *g_logger; } static int FileWriteStr(const std::string &str, FILE *fp) { return fwrite(str.data(), 1, str.size(), fp); } bool BCLog::Logger::StartLogging() { StdLockGuard scoped_lock(m_cs); assert(m_buffering); assert(m_fileout == nullptr); if (m_print_to_file) { assert(!m_file_path.empty()); m_fileout = fsbridge::fopen(m_file_path, "a"); if (!m_fileout) { return false; } // Unbuffered. setbuf(m_fileout, nullptr); // Add newlines to the logfile to distinguish this execution from the // last one. FileWriteStr("\n\n\n\n\n", m_fileout); } // Dump buffered messages from before we opened the log. m_buffering = false; while (!m_msgs_before_open.empty()) { const std::string &s = m_msgs_before_open.front(); if (m_print_to_file) { FileWriteStr(s, m_fileout); } if (m_print_to_console) { fwrite(s.data(), 1, s.size(), stdout); } for (const auto &cb : m_print_callbacks) { cb(s); } m_msgs_before_open.pop_front(); } if (m_print_to_console) { fflush(stdout); } return true; } void BCLog::Logger::DisconnectTestLogger() { StdLockGuard scoped_lock(m_cs); m_buffering = true; if (m_fileout != nullptr) { fclose(m_fileout); } m_fileout = nullptr; m_print_callbacks.clear(); } struct CLogCategoryDesc { BCLog::LogFlags flag; std::string category; }; const CLogCategoryDesc LogCategories[] = { {BCLog::NONE, "0"}, {BCLog::NONE, "none"}, {BCLog::NET, "net"}, {BCLog::TOR, "tor"}, {BCLog::MEMPOOL, "mempool"}, {BCLog::HTTP, "http"}, {BCLog::BENCH, "bench"}, {BCLog::ZMQ, "zmq"}, {BCLog::WALLETDB, "walletdb"}, {BCLog::RPC, "rpc"}, {BCLog::ESTIMATEFEE, "estimatefee"}, {BCLog::ADDRMAN, "addrman"}, {BCLog::SELECTCOINS, "selectcoins"}, {BCLog::REINDEX, "reindex"}, {BCLog::CMPCTBLOCK, "cmpctblock"}, {BCLog::RAND, "rand"}, {BCLog::PRUNE, "prune"}, {BCLog::PROXY, "proxy"}, {BCLog::MEMPOOLREJ, "mempoolrej"}, {BCLog::LIBEVENT, "libevent"}, {BCLog::COINDB, "coindb"}, {BCLog::QT, "qt"}, {BCLog::LEVELDB, "leveldb"}, {BCLog::VALIDATION, "validation"}, {BCLog::AVALANCHE, "avalanche"}, {BCLog::I2P, "i2p"}, {BCLog::CHRONIK, "chronik"}, +#ifdef DEBUG_LOCKCONTENTION + {BCLog::LOCK, "lock"}, +#endif {BCLog::BLOCKSTORE, "blockstorage"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; bool GetLogCategory(BCLog::LogFlags &flag, const std::string &str) { if (str == "") { flag = BCLog::ALL; return true; } for (const CLogCategoryDesc &category_desc : LogCategories) { if (category_desc.category == str) { flag = category_desc.flag; return true; } } return false; } std::vector BCLog::Logger::LogCategoriesList() const { // Sort log categories by alphabetical order. std::array categories; std::copy(std::begin(LogCategories), std::end(LogCategories), categories.begin()); std::sort(categories.begin(), categories.end(), [](auto a, auto b) { return a.category < b.category; }); std::vector ret; for (const CLogCategoryDesc &category_desc : categories) { if (category_desc.flag == BCLog::NONE || category_desc.flag == BCLog::ALL) { continue; } LogCategory catActive; catActive.category = category_desc.category; catActive.active = WillLogCategory(category_desc.flag); ret.push_back(catActive); } return ret; } BCLog::Logger::~Logger() { if (m_fileout) { fclose(m_fileout); } } std::string BCLog::Logger::LogTimestampStr(const std::string &str) { std::string strStamped; if (!m_log_timestamps) { return str; } if (m_started_new_line) { int64_t nTimeMicros = GetTimeMicros(); strStamped = FormatISO8601DateTime(nTimeMicros / 1000000); if (m_log_time_micros) { strStamped.pop_back(); strStamped += strprintf(".%06dZ", nTimeMicros % 1000000); } int64_t mocktime = GetMockTime(); if (mocktime) { strStamped += " (mocktime: " + FormatISO8601DateTime(mocktime) + ")"; } strStamped += ' ' + str; } else { strStamped = str; } return strStamped; } namespace BCLog { /** Belts and suspenders: make sure outgoing log messages don't contain * potentially suspicious characters, such as terminal control codes. * * This escapes control characters except newline ('\n') in C syntax. * It escapes instead of removes them to still allow for troubleshooting * issues where they accidentally end up in strings. */ std::string LogEscapeMessage(const std::string &str) { std::string ret; for (char ch_in : str) { uint8_t ch = (uint8_t)ch_in; if ((ch >= 32 || ch == '\n') && ch != '\x7f') { ret += ch_in; } else { ret += strprintf("\\x%02x", ch); } } return ret; } } // namespace BCLog void BCLog::Logger::LogPrintStr(const std::string &str, const std::string &logging_function, const std::string &source_file, const int source_line) { StdLockGuard scoped_lock(m_cs); std::string str_prefixed = LogEscapeMessage(str); if (m_log_sourcelocations && m_started_new_line) { str_prefixed.insert(0, "[" + RemovePrefix(source_file, "./") + ":" + ToString(source_line) + "] [" + logging_function + "] "); } if (m_log_threadnames && m_started_new_line) { str_prefixed.insert(0, "[" + util::ThreadGetInternalName() + "] "); } str_prefixed = LogTimestampStr(str_prefixed); m_started_new_line = !str.empty() && str[str.size() - 1] == '\n'; if (m_buffering) { // buffer if we haven't started logging yet m_msgs_before_open.push_back(str_prefixed); return; } if (m_print_to_console) { // Print to console. fwrite(str_prefixed.data(), 1, str_prefixed.size(), stdout); fflush(stdout); } for (const auto &cb : m_print_callbacks) { cb(str_prefixed); } if (m_print_to_file) { assert(m_fileout != nullptr); // Reopen the log file, if requested. if (m_reopen_file) { m_reopen_file = false; FILE *new_fileout = fsbridge::fopen(m_file_path, "a"); if (new_fileout) { // unbuffered. setbuf(m_fileout, nullptr); fclose(m_fileout); m_fileout = new_fileout; } } FileWriteStr(str_prefixed, m_fileout); } } void BCLog::Logger::ShrinkDebugFile() { // Amount of debug.log to save at end when shrinking (must fit in memory) constexpr size_t RECENT_DEBUG_HISTORY_SIZE = 10 * 1000000; assert(!m_file_path.empty()); // Scroll debug.log if it's getting too big. FILE *file = fsbridge::fopen(m_file_path, "r"); // Special files (e.g. device nodes) may not have a size. size_t log_size = 0; try { log_size = fs::file_size(m_file_path); } catch (const fs::filesystem_error &) { } // If debug.log file is more than 10% bigger the RECENT_DEBUG_HISTORY_SIZE // trim it down by saving only the last RECENT_DEBUG_HISTORY_SIZE bytes. if (file && log_size > 11 * (RECENT_DEBUG_HISTORY_SIZE / 10)) { // Restart the file with some of the end. std::vector vch(RECENT_DEBUG_HISTORY_SIZE, 0); if (fseek(file, -((long)vch.size()), SEEK_END)) { LogPrintf("Failed to shrink debug log file: fseek(...) failed\n"); fclose(file); return; } int nBytes = fread(vch.data(), 1, vch.size(), file); fclose(file); file = fsbridge::fopen(m_file_path, "w"); if (file) { fwrite(vch.data(), 1, nBytes, file); fclose(file); } } else if (file != nullptr) { fclose(file); } } void BCLog::Logger::EnableCategory(LogFlags category) { m_categories |= category; } bool BCLog::Logger::EnableCategory(const std::string &str) { BCLog::LogFlags flag; if (!GetLogCategory(flag, str)) { return false; } EnableCategory(flag); return true; } void BCLog::Logger::DisableCategory(LogFlags category) { m_categories &= ~category; } bool BCLog::Logger::DisableCategory(const std::string &str) { BCLog::LogFlags flag; if (!GetLogCategory(flag, str)) { return false; } DisableCategory(flag); return true; } bool BCLog::Logger::WillLogCategory(LogFlags category) const { // ALL is not meant to be used as a logging category, but only as a mask // representing all categories. if (category == BCLog::NONE || category == BCLog::ALL) { LogPrintf("Error trying to log using a category mask instead of an " "explicit category.\n"); return true; } return (m_categories.load(std::memory_order_relaxed) & category) != 0; } bool BCLog::Logger::DefaultShrinkDebugFile() const { return m_categories != BCLog::NONE; } diff --git a/src/logging.h b/src/logging.h index b6d58069d..36e73fa3b 100644 --- a/src/logging.h +++ b/src/logging.h @@ -1,220 +1,223 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_LOGGING_H #define BITCOIN_LOGGING_H #include #include #include #include #include #include #include #include #include static const bool DEFAULT_LOGTIMEMICROS = false; static const bool DEFAULT_LOGIPS = false; static const bool DEFAULT_LOGTIMESTAMPS = true; static const bool DEFAULT_LOGTHREADNAMES = false; static const bool DEFAULT_LOGSOURCELOCATIONS = false; extern bool fLogIPs; extern const char *const DEFAULT_DEBUGLOGFILE; struct LogCategory { std::string category; bool active; }; namespace BCLog { enum LogFlags : uint32_t { NONE = 0, NET = (1 << 0), TOR = (1 << 1), MEMPOOL = (1 << 2), HTTP = (1 << 3), BENCH = (1 << 4), ZMQ = (1 << 5), WALLETDB = (1 << 6), RPC = (1 << 7), ESTIMATEFEE = (1 << 8), ADDRMAN = (1 << 9), SELECTCOINS = (1 << 10), REINDEX = (1 << 11), CMPCTBLOCK = (1 << 12), RAND = (1 << 13), PRUNE = (1 << 14), PROXY = (1 << 15), MEMPOOLREJ = (1 << 16), LIBEVENT = (1 << 17), COINDB = (1 << 18), QT = (1 << 19), LEVELDB = (1 << 20), VALIDATION = (1 << 21), AVALANCHE = (1 << 22), I2P = (1 << 23), CHRONIK = (1 << 24), +#ifdef DEBUG_LOCKCONTENTION + LOCK = (1 << 25), +#endif BLOCKSTORE = (1 << 26), ALL = ~uint32_t(0), }; class Logger { private: // Can not use Mutex from sync.h because in debug mode it would cause a // deadlock when a potential deadlock was detected mutable StdMutex m_cs; FILE *m_fileout GUARDED_BY(m_cs) = nullptr; std::list m_msgs_before_open GUARDED_BY(m_cs); //! Buffer messages before logging can be started. bool m_buffering GUARDED_BY(m_cs) = true; /** * m_started_new_line is a state variable that will suppress printing of the * timestamp when multiple calls are made that don't end in a newline. */ std::atomic_bool m_started_new_line{true}; /** * Log categories bitfield. */ std::atomic m_categories{0}; std::string LogTimestampStr(const std::string &str); /** Slots that connect to the print signal */ std::list> m_print_callbacks GUARDED_BY(m_cs){}; public: bool m_print_to_console = false; bool m_print_to_file = false; bool m_log_timestamps = DEFAULT_LOGTIMESTAMPS; bool m_log_time_micros = DEFAULT_LOGTIMEMICROS; bool m_log_threadnames = DEFAULT_LOGTHREADNAMES; bool m_log_sourcelocations = DEFAULT_LOGSOURCELOCATIONS; fs::path m_file_path; std::atomic m_reopen_file{false}; ~Logger(); /** Send a string to the log output */ void LogPrintStr(const std::string &str, const std::string &logging_function, const std::string &source_file, const int source_line); /** Returns whether logs will be written to any output */ bool Enabled() const { StdLockGuard scoped_lock(m_cs); return m_buffering || m_print_to_console || m_print_to_file || !m_print_callbacks.empty(); } /** Connect a slot to the print signal and return the connection */ std::list>::iterator PushBackCallback(std::function fun) { StdLockGuard scoped_lock(m_cs); m_print_callbacks.push_back(std::move(fun)); return --m_print_callbacks.end(); } /** Delete a connection */ void DeleteCallback( std::list>::iterator it) { StdLockGuard scoped_lock(m_cs); m_print_callbacks.erase(it); } /** Start logging (and flush all buffered messages) */ bool StartLogging(); /** Only for testing */ void DisconnectTestLogger(); void ShrinkDebugFile(); uint32_t GetCategoryMask() const { return m_categories.load(); } void EnableCategory(LogFlags category); bool EnableCategory(const std::string &str); void DisableCategory(LogFlags category); bool DisableCategory(const std::string &str); /** Return true if log accepts specified category */ bool WillLogCategory(LogFlags category) const; /** Returns a vector of the log categories in alphabetical order. */ std::vector LogCategoriesList() const; /** Returns a string with the log categories in alphabetical order. */ std::string LogCategoriesString() const { return Join(LogCategoriesList(), ", ", [&](const LogCategory &i) { return i.category; }); }; /** Default for whether ShrinkDebugFile should be run */ bool DefaultShrinkDebugFile() const; }; } // namespace BCLog BCLog::Logger &LogInstance(); /** Return true if log accepts specified category */ static inline bool LogAcceptCategory(BCLog::LogFlags category) { return LogInstance().WillLogCategory(category); } /** Return true if str parses as a log category and set the flag */ bool GetLogCategory(BCLog::LogFlags &flag, const std::string &str); // Be conservative when using LogPrintf/error or other things which // unconditionally log to debug.log! It should not be the case that an inbound // peer can fill up a user's disk with debug.log entries. template static inline void LogPrintf_(const std::string &logging_function, const std::string &source_file, const int source_line, const char *fmt, const Args &...args) { if (LogInstance().Enabled()) { std::string log_msg; try { log_msg = tfm::format(fmt, args...); } catch (tinyformat::format_error &fmterr) { /** * Original format string will have newline so don't add one here */ log_msg = "Error \"" + std::string(fmterr.what()) + "\" while formatting log message: " + fmt; } LogInstance().LogPrintStr(log_msg, logging_function, source_file, source_line); } } #define LogPrintf(...) LogPrintf_(__func__, __FILE__, __LINE__, __VA_ARGS__) // Use a macro instead of a function for conditional logging to prevent // evaluating arguments when logging for the category is not enabled. #define LogPrint(category, ...) \ do { \ if (LogAcceptCategory((category))) { \ LogPrintf(__VA_ARGS__); \ } \ } while (0) /** * These are aliases used to explicitly state that the message should not end * with a newline character. It allows for detecting the missing newlines that * could make the logs hard to read. */ #define LogPrintfToBeContinued LogPrintf #define LogPrintToBeContinued LogPrint #endif // BITCOIN_LOGGING_H diff --git a/src/logging/timer.h b/src/logging/timer.h index a97a16827..1424ca709 100644 --- a/src/logging/timer.h +++ b/src/logging/timer.h @@ -1,89 +1,93 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_LOGGING_TIMER_H #define BITCOIN_LOGGING_TIMER_H #include #include #include +#include #include #include namespace BCLog { //! RAII-style object that outputs timing information to logs. template class Timer { public: //! If log_category is left as the default, end_msg will log unconditionally //! (instead of being filtered by category). Timer(std::string prefix, std::string end_msg, BCLog::LogFlags log_category = BCLog::LogFlags::ALL) : m_prefix(std::move(prefix)), m_title(std::move(end_msg)), m_log_category(log_category) { this->Log(strprintf("%s started", m_title)); m_start_t = GetTime(); } ~Timer() { this->Log(strprintf("%s completed", m_title)); } void Log(const std::string &msg) { const std::string full_msg = this->LogMsg(msg); if (m_log_category == BCLog::LogFlags::ALL) { LogPrintf("%s\n", full_msg); } else { LogPrint(m_log_category, "%s\n", full_msg); } } std::string LogMsg(const std::string &msg) { const auto end_time = GetTime() - m_start_t; if (m_start_t.count() <= 0) { return strprintf("%s: %s", m_prefix, msg); } - std::string units = ""; - float divisor = 1; - - if (std::is_same::value) { - units = "μs"; - } else if (std::is_same::value) { - units = "ms"; - divisor = 1000.; - } else if (std::is_same::value) { - units = "s"; - divisor = 1000. * 1000.; + if constexpr (std::is_same::value) { + return strprintf("%s: %s (%iμs)", m_prefix, msg, end_time.count()); + } else if constexpr (std::is_same::value) { + return strprintf("%s: %s (%.2fms)", m_prefix, msg, + end_time.count() * 0.001); + } else if constexpr (std::is_same::value) { + return strprintf("%s: %s (%.2fs)", m_prefix, msg, + end_time.count() * 0.000001); + } else { + static_assert(ALWAYS_FALSE, + "Error: unexpected time type"); } - - const float time_ms = end_time.count() / divisor; - return strprintf("%s: %s (%.2f%s)", m_prefix, msg, time_ms, units); } private: std::chrono::microseconds m_start_t{}; //! Log prefix; usually the name of the function this was created in. const std::string m_prefix{}; //! A descriptive message of what is being timed. const std::string m_title{}; //! Forwarded on to LogPrint if specified - has the effect of only //! outputing the timing log when a particular debug= category is specified. const BCLog::LogFlags m_log_category{}; }; } // namespace BCLog +#define LOG_TIME_MICROS_WITH_CATEGORY(end_msg, log_category) \ + BCLog::Timer PASTE2( \ + logging_timer, __COUNTER__)(__func__, end_msg, log_category) #define LOG_TIME_MILLIS_WITH_CATEGORY(end_msg, log_category) \ BCLog::Timer PASTE2( \ logging_timer, __COUNTER__)(__func__, end_msg, log_category) #define LOG_TIME_SECONDS(end_msg) \ BCLog::Timer PASTE2(logging_timer, \ __COUNTER__)(__func__, end_msg) #endif // BITCOIN_LOGGING_TIMER_H diff --git a/src/net.h b/src/net.h index 045f3b96c..e42251df5 100644 --- a/src/net.h +++ b/src/net.h @@ -1,1520 +1,1522 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_NET_H #define BITCOIN_NET_H #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include // For cs_main #include #include #include #include +#include #include #include #include #include class BanMan; class Config; class CNode; class CScheduler; struct bilingual_str; /** Default for -whitelistrelay. */ static const bool DEFAULT_WHITELISTRELAY = true; /** Default for -whitelistforcerelay. */ static const bool DEFAULT_WHITELISTFORCERELAY = false; /** * Time after which to disconnect, after waiting for a ping response (or * inactivity). */ static constexpr std::chrono::minutes TIMEOUT_INTERVAL{20}; /** Run the feeler connection loop once every 2 minutes. **/ static constexpr auto FEELER_INTERVAL = 2min; /** Run the extra block-relay-only connection loop once every 5 minutes. **/ static constexpr auto EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 5min; /** Maximum length of the user agent string in `version` message */ static const unsigned int MAX_SUBVERSION_LENGTH = 256; /** * Maximum number of automatic outgoing nodes over which we'll relay everything * (blocks, tx, addrs, etc) */ static const int MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 16; /** Maximum number of addnode outgoing nodes */ static const int MAX_ADDNODE_CONNECTIONS = 8; /** Maximum number of block-relay-only outgoing connections */ static const int MAX_BLOCK_RELAY_ONLY_CONNECTIONS = 2; /** * Maximum number of avalanche enabled outgoing connections by default. * Can be overridden with the -maxavalancheoutbound option. */ static const int DEFAULT_MAX_AVALANCHE_OUTBOUND_CONNECTIONS = 300; /** Maximum number of feeler connections */ static const int MAX_FEELER_CONNECTIONS = 1; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ #ifdef USE_UPNP static const bool DEFAULT_UPNP = USE_UPNP; #else static const bool DEFAULT_UPNP = false; #endif /** * The maximum number of peer connections to maintain. * This quantity might not be reachable on some systems, especially on platforms * that do not provide a working poll() interface. */ static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 4096; /** The default for -maxuploadtarget. 0 = Unlimited */ static constexpr uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0; /** Default for blocks only*/ static const bool DEFAULT_BLOCKSONLY = false; /** -peertimeout default */ static const int64_t DEFAULT_PEER_CONNECT_TIMEOUT = 60; static const bool DEFAULT_FORCEDNSSEED = false; static const bool DEFAULT_DNSSEED = true; static const bool DEFAULT_FIXEDSEEDS = true; static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000; static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000; /** Refresh period for the avalanche statistics computation */ static constexpr std::chrono::minutes AVALANCHE_STATISTICS_REFRESH_PERIOD{10}; /** Time constant for the avalanche statistics computation */ static constexpr std::chrono::minutes AVALANCHE_STATISTICS_TIME_CONSTANT{10}; /** * Pre-computed decay factor for the avalanche statistics computation. * There is currently no constexpr variant of std::exp, so use a const. */ static const double AVALANCHE_STATISTICS_DECAY_FACTOR = 1. - std::exp(-1. * AVALANCHE_STATISTICS_REFRESH_PERIOD.count() / AVALANCHE_STATISTICS_TIME_CONSTANT.count()); struct AddedNodeInfo { std::string strAddedNode; CService resolvedAddress; bool fConnected; bool fInbound; }; struct CNodeStats; class CClientUIInterface; struct CSerializedNetMsg { CSerializedNetMsg() = default; CSerializedNetMsg(CSerializedNetMsg &&) = default; CSerializedNetMsg &operator=(CSerializedNetMsg &&) = default; // No copying, only moves. CSerializedNetMsg(const CSerializedNetMsg &msg) = delete; CSerializedNetMsg &operator=(const CSerializedNetMsg &) = delete; std::vector data; std::string m_type; }; const std::vector CONNECTION_TYPE_DOC{ "outbound-full-relay (default automatic connections)", "block-relay-only (does not relay transactions or addresses)", "inbound (initiated by the peer)", "manual (added via addnode RPC or -addnode/-connect configuration options)", "addr-fetch (short-lived automatic connection for soliciting addresses)", "feeler (short-lived automatic connection for testing addresses)"}; /** * Different types of connections to a peer. This enum encapsulates the * information we have available at the time of opening or accepting the * connection. Aside from INBOUND, all types are initiated by us. */ enum class ConnectionType { /** * Inbound connections are those initiated by a peer. This is the only * property we know at the time of connection, until P2P messages are * exchanged. */ INBOUND, /** * These are the default connections that we use to connect with the * network. There is no restriction on what is relayed- by default we relay * blocks, addresses & transactions. We automatically attempt to open * MAX_OUTBOUND_FULL_RELAY_CONNECTIONS using addresses from our AddrMan. */ OUTBOUND_FULL_RELAY, /** * We open manual connections to addresses that users explicitly inputted * via the addnode RPC, or the -connect command line argument. Even if a * manual connection is misbehaving, we do not automatically disconnect or * add it to our discouragement filter. */ MANUAL, /** * Feeler connections are short-lived connections made to check that a node * is alive. They can be useful for: * - test-before-evict: if one of the peers is considered for eviction from * our AddrMan because another peer is mapped to the same slot in the * tried table, evict only if this longer-known peer is offline. * - move node addresses from New to Tried table, so that we have more * connectable addresses in our AddrMan. * Note that in the literature ("Eclipse Attacks on Bitcoin’s Peer-to-Peer * Network") only the latter feature is referred to as "feeler connections", * although in our codebase feeler connections encompass test-before-evict * as well. * We make these connections approximately every FEELER_INTERVAL: * first we resolve previously found collisions if they exist * (test-before-evict), otherwise connect to a node from the new table. */ FEELER, /** * We use block-relay-only connections to help prevent against partition * attacks. By not relaying transactions or addresses, these connections * are harder to detect by a third party, thus helping obfuscate the * network topology. We automatically attempt to open * MAX_BLOCK_RELAY_ONLY_ANCHORS using addresses from our anchors.dat. Then * addresses from our AddrMan if MAX_BLOCK_RELAY_ONLY_CONNECTIONS * isn't reached yet. */ BLOCK_RELAY, /** * AddrFetch connections are short lived connections used to solicit * addresses from peers. These are initiated to addresses submitted via the * -seednode command line argument, or under certain conditions when the * AddrMan is empty. */ ADDR_FETCH, /** * Special case of connection to a full relay outbound with avalanche * service enabled. */ AVALANCHE_OUTBOUND, }; void Discover(); void StartMapPort(); void InterruptMapPort(); void StopMapPort(); uint16_t GetListenPort(); enum { // unknown LOCAL_NONE, // address a local interface listens on LOCAL_IF, // address explicit bound to LOCAL_BIND, // address reported by UPnP LOCAL_UPNP, // address explicitly specified (-externalip=) LOCAL_MANUAL, LOCAL_MAX }; bool IsPeerAddrLocalGood(CNode *pnode); /** Returns a local address that we should advertise to this peer */ std::optional GetLocalAddrForPeer(CNode *pnode); /** * Mark a network as reachable or unreachable (no automatic connects to it) * @note Networks are reachable by default */ void SetReachable(enum Network net, bool reachable); /** @returns true if the network is reachable, false otherwise */ bool IsReachable(enum Network net); /** @returns true if the address is in a reachable network, false otherwise */ bool IsReachable(const CNetAddr &addr); bool AddLocal(const CService &addr, int nScore = LOCAL_NONE); bool AddLocal(const CNetAddr &addr, int nScore = LOCAL_NONE); void RemoveLocal(const CService &addr); bool SeenLocal(const CService &addr); bool IsLocal(const CService &addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr); CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices); extern bool fDiscover; extern bool fListen; struct LocalServiceInfo { int nScore; uint16_t nPort; }; extern RecursiveMutex cs_mapLocalHost; extern std::map mapLocalHost GUARDED_BY(cs_mapLocalHost); extern const std::string NET_MESSAGE_COMMAND_OTHER; // Command, total bytes typedef std::map mapMsgCmdSize; /** * POD that contains various stats about a node. * Usually constructed from CConman::GetNodeStats. Stats are filled from the * node using CNode::copyStats. */ struct CNodeStats { NodeId nodeid; ServiceFlags nServices; bool fRelayTxes; std::chrono::seconds m_last_send; std::chrono::seconds m_last_recv; std::chrono::seconds m_last_tx_time; std::chrono::seconds m_last_proof_time; std::chrono::seconds m_last_block_time; std::chrono::seconds m_connected; int64_t nTimeOffset; std::string addrName; int nVersion; std::string cleanSubVer; bool fInbound; bool m_manual_connection; bool m_bip152_highbandwidth_to; bool m_bip152_highbandwidth_from; int m_starting_height; uint64_t nSendBytes; mapMsgCmdSize mapSendBytesPerMsgCmd; uint64_t nRecvBytes; mapMsgCmdSize mapRecvBytesPerMsgCmd; NetPermissionFlags m_permissionFlags; bool m_legacyWhitelisted; std::chrono::microseconds m_last_ping_time; std::chrono::microseconds m_min_ping_time; Amount minFeeFilter; // Our address, as reported by the peer std::string addrLocal; // Address of this peer CAddress addr; // Bind address of our side of the connection CAddress addrBind; // Network the peer connected through Network m_network; uint32_t m_mapped_as; std::string m_conn_type_string; std::optional m_availabilityScore; }; /** * Transport protocol agnostic message container. * Ideally it should only contain receive time, payload, * command and size. */ class CNetMessage { public: //! received message data CDataStream m_recv; //! time of message receipt std::chrono::microseconds m_time{0}; bool m_valid_netmagic = false; bool m_valid_header = false; bool m_valid_checksum = false; //! size of the payload uint32_t m_message_size{0}; //! used wire size of the message (including header/checksum) uint32_t m_raw_message_size{0}; std::string m_command; CNetMessage(CDataStream &&recv_in) : m_recv(std::move(recv_in)) {} void SetVersion(int nVersionIn) { m_recv.SetVersion(nVersionIn); } }; /** * The TransportDeserializer takes care of holding and deserializing the * network receive buffer. It can deserialize the network buffer into a * transport protocol agnostic CNetMessage (command & payload) */ class TransportDeserializer { public: // returns true if the current deserialization is complete virtual bool Complete() const = 0; // set the serialization context version virtual void SetVersion(int version) = 0; /** read and deserialize data, advances msg_bytes data pointer */ virtual int Read(const Config &config, Span &msg_bytes) = 0; // decomposes a message from the context virtual CNetMessage GetMessage(const Config &config, std::chrono::microseconds time) = 0; virtual ~TransportDeserializer() {} }; class V1TransportDeserializer final : public TransportDeserializer { private: mutable CHash256 hasher; mutable uint256 data_hash; // Parsing header (false) or data (true) bool in_data; // Partially received header. CDataStream hdrbuf; // Complete header. CMessageHeader hdr; // Received message data. CDataStream vRecv; uint32_t nHdrPos; uint32_t nDataPos; const uint256 &GetMessageHash() const; int readHeader(const Config &config, Span msg_bytes); int readData(Span msg_bytes); void Reset() { vRecv.clear(); hdrbuf.clear(); hdrbuf.resize(24); in_data = false; nHdrPos = 0; nDataPos = 0; data_hash.SetNull(); hasher.Reset(); } public: V1TransportDeserializer( const CMessageHeader::MessageMagic &pchMessageStartIn, int nTypeIn, int nVersionIn) : hdrbuf(nTypeIn, nVersionIn), hdr(pchMessageStartIn), vRecv(nTypeIn, nVersionIn) { Reset(); } bool Complete() const override { if (!in_data) { return false; } return (hdr.nMessageSize == nDataPos); } void SetVersion(int nVersionIn) override { hdrbuf.SetVersion(nVersionIn); vRecv.SetVersion(nVersionIn); } int Read(const Config &config, Span &msg_bytes) override { int ret = in_data ? readData(msg_bytes) : readHeader(config, msg_bytes); if (ret < 0) { Reset(); } else { msg_bytes = msg_bytes.subspan(ret); } return ret; } CNetMessage GetMessage(const Config &config, std::chrono::microseconds time) override; }; /** * The TransportSerializer prepares messages for the network transport */ class TransportSerializer { public: // prepare message for transport (header construction, error-correction // computation, payload encryption, etc.) virtual void prepareForTransport(const Config &config, CSerializedNetMsg &msg, std::vector &header) = 0; virtual ~TransportSerializer() {} }; class V1TransportSerializer : public TransportSerializer { public: void prepareForTransport(const Config &config, CSerializedNetMsg &msg, std::vector &header) override; }; /** Information about a peer */ class CNode { friend class CConnman; friend struct ConnmanTestMsg; public: std::unique_ptr m_deserializer; std::unique_ptr m_serializer; // socket std::atomic nServices{NODE_NONE}; SOCKET hSocket GUARDED_BY(cs_hSocket); // Total size of all vSendMsg entries. size_t nSendSize{0}; // Offset inside the first vSendMsg already sent. size_t nSendOffset{0}; uint64_t nSendBytes GUARDED_BY(cs_vSend){0}; std::deque> vSendMsg GUARDED_BY(cs_vSend); Mutex cs_vSend; Mutex cs_hSocket; Mutex cs_vRecv; RecursiveMutex cs_vProcessMsg; std::list vProcessMsg GUARDED_BY(cs_vProcessMsg); size_t nProcessQueueSize{0}; RecursiveMutex cs_sendProcessing; uint64_t nRecvBytes GUARDED_BY(cs_vRecv){0}; std::atomic m_last_send{0s}; std::atomic m_last_recv{0s}; //! Unix epoch time at peer connection const std::chrono::seconds m_connected; std::atomic nTimeOffset{0}; // Address of this peer const CAddress addr; // Bind address of our side of the connection const CAddress addrBind; //! Whether this peer is an inbound onion, i.e. connected via our Tor onion //! service. const bool m_inbound_onion; std::atomic nVersion{0}; // The nonce provided by the remote host. uint64_t nRemoteHostNonce{0}; // The extra entropy provided by the remote host. uint64_t nRemoteExtraEntropy{0}; /** * cleanSubVer is a sanitized string of the user agent byte array we read * from the wire. This cleaned string can safely be logged or displayed. */ RecursiveMutex cs_SubVer; std::string cleanSubVer GUARDED_BY(cs_SubVer){}; // This peer is preferred for eviction. bool m_prefer_evict{false}; bool HasPermission(NetPermissionFlags permission) const { return NetPermissions::HasFlag(m_permissionFlags, permission); } // This boolean is unusued in actual processing, only present for backward // compatibility at RPC/QT level bool m_legacyWhitelisted{false}; // set by version message bool fClient{false}; // after BIP159, set by version message bool m_limited_node{false}; std::atomic_bool fSuccessfullyConnected{false}; // Setting fDisconnect to true will cause the node to be disconnected the // next time DisconnectNodes() runs std::atomic_bool fDisconnect{false}; CSemaphoreGrant grantOutbound; std::atomic nRefCount{0}; const uint64_t nKeyedNetGroup; std::atomic_bool fPauseRecv{false}; std::atomic_bool fPauseSend{false}; bool IsOutboundOrBlockRelayConn() const { switch (m_conn_type) { case ConnectionType::OUTBOUND_FULL_RELAY: case ConnectionType::BLOCK_RELAY: case ConnectionType::AVALANCHE_OUTBOUND: return true; case ConnectionType::INBOUND: case ConnectionType::MANUAL: case ConnectionType::ADDR_FETCH: case ConnectionType::FEELER: return false; } // no default case, so the compiler can warn about missing cases assert(false); } bool IsFullOutboundConn() const { return m_conn_type == ConnectionType::OUTBOUND_FULL_RELAY || m_conn_type == ConnectionType::AVALANCHE_OUTBOUND; } bool IsManualConn() const { return m_conn_type == ConnectionType::MANUAL; } bool IsBlockOnlyConn() const { return m_conn_type == ConnectionType::BLOCK_RELAY; } bool IsFeelerConn() const { return m_conn_type == ConnectionType::FEELER; } bool IsAddrFetchConn() const { return m_conn_type == ConnectionType::ADDR_FETCH; } bool IsInboundConn() const { return m_conn_type == ConnectionType::INBOUND; } bool IsAvalancheOutboundConnection() const { return m_conn_type == ConnectionType::AVALANCHE_OUTBOUND; } bool ExpectServicesFromConn() const { switch (m_conn_type) { case ConnectionType::INBOUND: case ConnectionType::MANUAL: case ConnectionType::FEELER: return false; case ConnectionType::OUTBOUND_FULL_RELAY: case ConnectionType::BLOCK_RELAY: case ConnectionType::ADDR_FETCH: case ConnectionType::AVALANCHE_OUTBOUND: return true; } // no default case, so the compiler can warn about missing cases assert(false); } /** * Get network the peer connected through. * * Returns Network::NET_ONION for *inbound* onion connections, * and CNetAddr::GetNetClass() otherwise. The latter cannot be used directly * because it doesn't detect the former, and it's not the responsibility of * the CNetAddr class to know the actual network a peer is connected * through. * * @return network the peer connected through. */ Network ConnectedThroughNetwork() const; protected: mapMsgCmdSize mapSendBytesPerMsgCmd; mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv); public: // We selected peer as (compact blocks) high-bandwidth peer (BIP152) std::atomic m_bip152_highbandwidth_to{false}; // Peer selected us as (compact blocks) high-bandwidth peer (BIP152) std::atomic m_bip152_highbandwidth_from{false}; struct TxRelay { mutable RecursiveMutex cs_filter; // We use fRelayTxes for two purposes - // a) it allows us to not relay tx invs before receiving the peer's // version message. // b) the peer may tell us in its version message that we should not // relay tx invs unless it loads a bloom filter. bool fRelayTxes GUARDED_BY(cs_filter){false}; std::unique_ptr pfilter PT_GUARDED_BY(cs_filter) GUARDED_BY(cs_filter){nullptr}; mutable RecursiveMutex cs_tx_inventory; CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_tx_inventory){ 50000, 0.000001}; // Set of transaction ids we still have to announce. // They are sorted by the mempool before relay, so the order is not // important. std::set setInventoryTxToSend GUARDED_BY(cs_tx_inventory); // Used for BIP35 mempool sending bool fSendMempool GUARDED_BY(cs_tx_inventory){false}; // Last time a "MEMPOOL" request was serviced. std::atomic m_last_mempool_req{0s}; std::chrono::microseconds nNextInvSend{0}; RecursiveMutex cs_feeFilter; // Minimum fee rate with which to filter inv's to this node Amount minFeeFilter GUARDED_BY(cs_feeFilter){Amount::zero()}; Amount lastSentFeeFilter{Amount::zero()}; std::chrono::microseconds m_next_send_feefilter{0}; }; // m_tx_relay == nullptr if we're not relaying transactions with this peer const std::unique_ptr m_tx_relay; struct ProofRelay { mutable RecursiveMutex cs_proof_inventory; std::set setInventoryProofToSend GUARDED_BY(cs_proof_inventory); // Prevent sending proof invs if the peer already knows about them CRollingBloomFilter filterProofKnown GUARDED_BY(cs_proof_inventory){ 10000, 0.000001}; std::chrono::microseconds nextInvSend{0}; RadixTree sharedProofs; std::atomic lastSharedProofsUpdate{0s}; std::atomic compactproofs_requested{false}; }; // m_proof_relay == nullptr if we're not relaying proofs with this peer const std::unique_ptr m_proof_relay; class AvalancheState { /** * The inventories polled and voted couters since last score * computation, stored as a pair of uint32_t with the poll counter * being the 32 lowest bits and the vote counter the 32 highest bits. */ std::atomic invCounters; /** The last computed score */ std::atomic availabilityScore; /** * Protect the sequence of operations required for updating the * statistics. */ Mutex cs_statistics; public: CPubKey pubkey; AvalancheState() : invCounters(0), availabilityScore(0.) {} /** The node was polled for count invs */ void invsPolled(uint32_t count); /** The node voted for count invs */ void invsVoted(uint32_t count); /** * The availability score is calculated using an exponentially weighted * average. * This has several interesting properties: * - The most recent polls/responses have more weight than the previous * ones. A node that recently stopped answering will see its ratio * decrease quickly. * - This is a low-pass filter, so it causes delay. This means that a * node needs to have a track record for the ratio to be high. A node * that has been little requested will have a lower ratio than a node * that failed to answer a few polls but answered a lot of them. * - It is cheap to compute. * * This is expected to be called at a fixed interval of * AVALANCHE_STATISTICS_REFRESH_PERIOD. */ void updateAvailabilityScore(); double getAvailabilityScore() const; }; // m_avalanche_state == nullptr if we're not using avalanche with this peer std::unique_ptr m_avalanche_state; // Store the next time we will consider a getavaaddr message from this peer std::chrono::seconds m_nextGetAvaAddr{0}; /** * UNIX epoch time of the last block received from this peer that we had * not yet seen (e.g. not already received from another peer), that passed * preliminary validity checks and was saved to disk, even if we don't * connect the block or it eventually fails connection. Used as an inbound * peer eviction criterium in CConnman::AttemptToEvictConnection. */ std::atomic m_last_block_time{0s}; /** * UNIX epoch time of the last transaction received from this peer that we * had not yet seen (e.g. not already received from another peer) and that * was accepted into our mempool. Used as an inbound peer eviction criterium * in CConnman::AttemptToEvictConnection. */ std::atomic m_last_tx_time{0s}; /** * UNIX epoch time of the last proof received from this peer that we * had not yet seen (e.g. not already received from another peer) and that * was accepted into our proof pool. Used as an inbound peer eviction * criterium in CConnman::AttemptToEvictConnection. */ std::atomic m_last_proof_time{0s}; /** Last measured round-trip time. Used only for RPC/GUI stats/debugging.*/ std::atomic m_last_ping_time{0us}; /** * Lowest measured round-trip time. Used as an inbound peer eviction * criterium in CConnman::AttemptToEvictConnection. */ std::atomic m_min_ping_time{ std::chrono::microseconds::max()}; CNode(NodeId id, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, uint64_t nLocalExtraEntropyIn, const CAddress &addrBindIn, const std::string &addrNameIn, ConnectionType conn_type_in, bool inbound_onion); ~CNode(); CNode(const CNode &) = delete; CNode &operator=(const CNode &) = delete; /** * A ping-pong round trip has completed successfully. Update latest and * minimum ping times. */ void PongReceived(std::chrono::microseconds ping_time) { m_last_ping_time = ping_time; m_min_ping_time = std::min(m_min_ping_time.load(), ping_time); } private: const NodeId id; const uint64_t nLocalHostNonce; const uint64_t nLocalExtraEntropy; const ConnectionType m_conn_type; std::atomic m_greatest_common_version{INIT_PROTO_VERSION}; //! Services offered to this peer. //! //! This is supplied by the parent CConnman during peer connection //! (CConnman::ConnectNode()) from its attribute of the same name. //! //! This is const because there is no protocol defined for renegotiating //! services initially offered to a peer. The set of local services we //! offer should not change after initialization. //! //! An interesting example of this is NODE_NETWORK and initial block //! download: a node which starts up from scratch doesn't have any blocks //! to serve, but still advertises NODE_NETWORK because it will eventually //! fulfill this role after IBD completes. P2P code is written in such a //! way that it can gracefully handle peers who don't make good on their //! service advertisements. const ServiceFlags nLocalServices; NetPermissionFlags m_permissionFlags{PF_NONE}; // Used only by SocketHandler thread std::list vRecvMsg; mutable RecursiveMutex cs_addrName; std::string addrName GUARDED_BY(cs_addrName); // Our address, as reported by the peer CService addrLocal GUARDED_BY(cs_addrLocal); mutable RecursiveMutex cs_addrLocal; public: NodeId GetId() const { return id; } uint64_t GetLocalNonce() const { return nLocalHostNonce; } uint64_t GetLocalExtraEntropy() const { return nLocalExtraEntropy; } int GetRefCount() const { assert(nRefCount >= 0); return nRefCount; } /** * Receive bytes from the buffer and deserialize them into messages. * * @param[in] msg_bytes The raw data * @param[out] complete Set True if at least one message has been * deserialized and is ready to be processed * @return True if the peer should stay connected, * False if the peer should be disconnected from. */ bool ReceiveMsgBytes(const Config &config, Span msg_bytes, bool &complete); void SetCommonVersion(int greatest_common_version) { Assume(m_greatest_common_version == INIT_PROTO_VERSION); m_greatest_common_version = greatest_common_version; } int GetCommonVersion() const { return m_greatest_common_version; } CService GetAddrLocal() const; //! May not be called more than once void SetAddrLocal(const CService &addrLocalIn); CNode *AddRef() { nRefCount++; return this; } void Release() { nRefCount--; } void AddKnownTx(const TxId &txid) { if (m_tx_relay != nullptr) { LOCK(m_tx_relay->cs_tx_inventory); m_tx_relay->filterInventoryKnown.insert(txid); } } void PushTxInventory(const TxId &txid) { if (m_tx_relay == nullptr) { return; } LOCK(m_tx_relay->cs_tx_inventory); if (!m_tx_relay->filterInventoryKnown.contains(txid)) { m_tx_relay->setInventoryTxToSend.insert(txid); } } void AddKnownProof(const avalanche::ProofId &proofid) { if (m_proof_relay != nullptr) { LOCK(m_proof_relay->cs_proof_inventory); m_proof_relay->filterProofKnown.insert(proofid); } } void PushProofInventory(const avalanche::ProofId &proofid) { if (m_proof_relay == nullptr) { return; } LOCK(m_proof_relay->cs_proof_inventory); if (!m_proof_relay->filterProofKnown.contains(proofid)) { m_proof_relay->setInventoryProofToSend.insert(proofid); } } void CloseSocketDisconnect(); void copyStats(CNodeStats &stats); ServiceFlags GetLocalServices() const { return nLocalServices; } std::string GetAddrName() const; //! Sets the addrName only if it was not previously set void MaybeSetAddrName(const std::string &addrNameIn); std::string ConnectionTypeAsString() const; }; /** * Interface for message handling */ class NetEventsInterface { public: /** Initialize a peer (setup state, queue any initial messages) */ virtual void InitializeNode(const Config &config, CNode *pnode) = 0; /** Handle removal of a peer (clear state) */ virtual void FinalizeNode(const Config &config, const CNode &node, bool &update_connection_time) = 0; /** * Process protocol messages received from a given node * * @param[in] config The applicable configuration object. * @param[in] pnode The node which we have received messages * from. * @param[in] interrupt Interrupt condition for processing threads * @return True if there is more work to be done */ virtual bool ProcessMessages(const Config &config, CNode *pnode, std::atomic &interrupt) = 0; /** * Send queued protocol messages to a given node. * * @param[in] config The applicable configuration object. * @param[in] pnode The node which we are sending messages to. * @return True if there is more work to be done */ virtual bool SendMessages(const Config &config, CNode *pnode) EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_sendProcessing) = 0; protected: /** * Protected destructor so that instances can only be deleted by derived * classes. If that restriction is no longer desired, this should be made * public and virtual. */ ~NetEventsInterface() = default; }; namespace { struct CConnmanTest; } class NetEventsInterface; class CConnman { public: enum NumConnections { CONNECTIONS_NONE = 0, CONNECTIONS_IN = (1U << 0), CONNECTIONS_OUT = (1U << 1), CONNECTIONS_ALL = (CONNECTIONS_IN | CONNECTIONS_OUT), }; struct Options { ServiceFlags nLocalServices = NODE_NONE; int nMaxConnections = 0; int m_max_outbound_full_relay = 0; int m_max_outbound_block_relay = 0; int m_max_avalanche_outbound = 0; int nMaxAddnode = 0; int nMaxFeeler = 0; CClientUIInterface *uiInterface = nullptr; std::vector m_msgproc; BanMan *m_banman = nullptr; unsigned int nSendBufferMaxSize = 0; unsigned int nReceiveFloodSize = 0; uint64_t nMaxOutboundLimit = 0; int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT; std::vector vSeedNodes; std::vector vWhitelistedRange; std::vector vWhiteBinds; std::vector vBinds; std::vector onion_binds; bool m_use_addrman_outgoing = true; std::vector m_specified_outgoing; std::vector m_added_nodes; bool m_i2p_accept_incoming = true; }; void Init(const Options &connOptions) { nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing; nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; { // Lock cs_main to prevent a potential race with the peer validation // logic thread. LOCK(::cs_main); m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); m_max_avalanche_outbound = connOptions.m_max_avalanche_outbound; m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay; m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler + m_max_avalanche_outbound; } clientInterface = connOptions.uiInterface; m_banman = connOptions.m_banman; m_msgproc = connOptions.m_msgproc; nSendBufferMaxSize = connOptions.nSendBufferMaxSize; nReceiveFloodSize = connOptions.nReceiveFloodSize; m_peer_connect_timeout = std::chrono::seconds{connOptions.m_peer_connect_timeout}; { LOCK(cs_totalBytesSent); nMaxOutboundLimit = connOptions.nMaxOutboundLimit; } vWhitelistedRange = connOptions.vWhitelistedRange; { LOCK(cs_vAddedNodes); vAddedNodes = connOptions.m_added_nodes; } m_onion_binds = connOptions.onion_binds; } CConnman(const Config &configIn, uint64_t seed0, uint64_t seed1, bool network_active = true); ~CConnman(); bool Start(CScheduler &scheduler, const Options &options); void StopThreads(); void StopNodes(); void Stop() { StopThreads(); StopNodes(); }; void Interrupt(); bool GetNetworkActive() const { return fNetworkActive; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; void SetNetworkActive(bool active); void OpenNetworkConnection(const CAddress &addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *strDest, ConnectionType conn_type); bool CheckIncomingNonce(uint64_t nonce); bool ForNode(NodeId id, std::function func); void PushMessage(CNode *pnode, CSerializedNetMsg &&msg); using NodeFn = std::function; void ForEachNode(const NodeFn &func) { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { func(node); } } }; void ForEachNode(const NodeFn &func) const { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { func(node); } } }; // Addrman functions void SetServices(const CService &addr, ServiceFlags nServices); void MarkAddressGood(const CAddress &addr); bool AddNewAddresses(const std::vector &vAddr, const CAddress &addrFrom, int64_t nTimePenalty = 0); /** * Return all or many randomly selected addresses, optionally by network. * * @param[in] max_addresses Maximum number of addresses to return * (0 = all). * @param[in] max_pct Maximum percentage of addresses to return * (0 = all). * @param[in] network Select only addresses of this network * (nullopt = all). */ std::vector GetAddresses(size_t max_addresses, size_t max_pct, std::optional network); /** * Cache is used to minimize topology leaks, so it should * be used for all non-trusted calls, for example, p2p. * A non-malicious call (from RPC or a peer with addr permission) should * call the function without a parameter to avoid using the cache. */ std::vector GetAddresses(CNode &requestor, size_t max_addresses, size_t max_pct); // This allows temporarily exceeding m_max_outbound_full_relay, with the // goal of finding a peer that is better than all our current peers. void SetTryNewOutboundPeer(bool flag); bool GetTryNewOutboundPeer(); void StartExtraBlockRelayPeers() { LogPrint(BCLog::NET, "net: enabling extra block-relay-only peers\n"); m_start_extra_block_relay_peers = true; } // Return the number of outbound peers we have in excess of our target (eg, // if we previously called SetTryNewOutboundPeer(true), and have since set // to false, we may have extra peers that we wish to disconnect). This may // return a value less than (num_outbound_connections - num_outbound_slots) // in cases where some outbound connections are not yet fully connected, or // not yet fully disconnected. int GetExtraFullOutboundCount(); // Count the number of block-relay-only peers we have over our limit. int GetExtraBlockRelayCount(); bool AddNode(const std::string &node); bool RemoveAddedNode(const std::string &node); std::vector GetAddedNodeInfo(); /** * Attempts to open a connection. Currently only used from tests. * * @param[in] address Address of node to try connecting to * @param[in] conn_type ConnectionType::OUTBOUND, * ConnectionType::BLOCK_RELAY, * ConnectionType::ADDR_FETCH, or * ConnectionType::FEELER * @return bool Returns false if there are no available * slots for this connection: * - conn_type not a supported ConnectionType * - Max total outbound connection capacity filled * - Max connection capacity for type is filled */ bool AddConnection(const std::string &address, ConnectionType conn_type); size_t GetNodeCount(NumConnections num); void GetNodeStats(std::vector &vstats); bool DisconnectNode(const std::string &node); bool DisconnectNode(const CSubNet &subnet); bool DisconnectNode(const CNetAddr &addr); bool DisconnectNode(NodeId id); //! Used to convey which local services we are offering peers during node //! connection. //! //! The data returned by this is used in CNode construction, //! which is used to advertise which services we are offering //! that peer during `net_processing.cpp:PushNodeVersion()`. ServiceFlags GetLocalServices() const; uint64_t GetMaxOutboundTarget(); std::chrono::seconds GetMaxOutboundTimeframe(); //! check if the outbound target is reached. If param //! historicalBlockServingLimit is set true, the function will response true //! if the limit for serving historical blocks has been reached. bool OutboundTargetReached(bool historicalBlockServingLimit); //! response the bytes left in the current max outbound cycle in case of no //! limit, it will always response 0 uint64_t GetOutboundTargetBytesLeft(); //! returns the time in second left in the current max outbound cycle in //! case of no limit, it will always return 0 std::chrono::seconds GetMaxOutboundTimeLeftInCycle(); uint64_t GetTotalBytesRecv(); uint64_t GetTotalBytesSent(); /** Get a unique deterministic randomizer. */ CSipHasher GetDeterministicRandomizer(uint64_t id) const; unsigned int GetReceiveFloodSize() const; void WakeMessageHandler(); /** * Attempts to obfuscate tx time through exponentially distributed emitting. * Works assuming that a single interval is used. * Variable intervals will result in privacy decrease. */ std::chrono::microseconds PoissonNextSendInbound(std::chrono::microseconds now, std::chrono::seconds average_interval); void SetAsmap(std::vector asmap) { addrman.m_asmap = std::move(asmap); } /** * Return true if we should disconnect the peer for failing an inactivity * check. */ bool ShouldRunInactivityChecks(const CNode &node, std::chrono::seconds now) const; private: struct ListenSocket { public: SOCKET socket; inline void AddSocketPermissionFlags(NetPermissionFlags &flags) const { NetPermissions::AddFlag(flags, m_permissions); } ListenSocket(SOCKET socket_, NetPermissionFlags permissions_) : socket(socket_), m_permissions(permissions_) {} private: NetPermissionFlags m_permissions; }; bool BindListenPort(const CService &bindAddr, bilingual_str &strError, NetPermissionFlags permissions); bool Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions); bool InitBinds(const std::vector &binds, const std::vector &whiteBinds, const std::vector &onion_binds); void ThreadOpenAddedConnections(); void AddAddrFetch(const std::string &strDest); void ProcessAddrFetch(); void ThreadOpenConnections(std::vector connect, std::function mockOpenConnection); void ThreadMessageHandler(); void ThreadI2PAcceptIncoming(); void AcceptConnection(const ListenSocket &hListenSocket); /** * Create a `CNode` object from a socket that has just been accepted and add * the node to the `vNodes` member. * @param[in] hSocket Connected socket to communicate with the peer. * @param[in] permissionFlags The peer's permissions. * @param[in] addr_bind The address and port at our side of the connection. * @param[in] addr The address and port at the peer's side of the connection */ void CreateNodeFromAcceptedSocket(SOCKET hSocket, NetPermissionFlags permissionFlags, const CAddress &addr_bind, const CAddress &addr); void DisconnectNodes(); void NotifyNumConnectionsChanged(); /** Return true if the peer is inactive and should be disconnected. */ bool InactivityCheck(const CNode &node) const; bool GenerateSelectSet(std::set &recv_set, std::set &send_set, std::set &error_set); void SocketEvents(std::set &recv_set, std::set &send_set, std::set &error_set); void SocketHandler(); void ThreadSocketHandler(); void ThreadDNSAddressSeed(); uint64_t CalculateKeyedNetGroup(const CAddress &ad) const; CNode *FindNode(const CNetAddr &ip); CNode *FindNode(const CSubNet &subNet); CNode *FindNode(const std::string &addrName); CNode *FindNode(const CService &addr); /** * Determine whether we're already connected to a given address, in order to * avoid initiating duplicate connections. */ bool AlreadyConnectedToAddress(const CAddress &addr); bool AttemptToEvictConnection(); CNode *ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type); void AddWhitelistPermissionFlags(NetPermissionFlags &flags, const CNetAddr &addr) const; void DeleteNode(CNode *pnode); NodeId GetNewNodeId(); size_t SocketSendData(CNode &node) const EXCLUSIVE_LOCKS_REQUIRED(node.cs_vSend); void DumpAddresses(); // Network stats void RecordBytesRecv(uint64_t bytes); void RecordBytesSent(uint64_t bytes); /** * Return vector of current BLOCK_RELAY peers. */ std::vector GetCurrentBlockRelayOnlyConns() const; // Whether the node should be passed out in ForEach* callbacks static bool NodeFullyConnected(const CNode *pnode); const Config *config; // Network usage totals RecursiveMutex cs_totalBytesRecv; RecursiveMutex cs_totalBytesSent; uint64_t nTotalBytesRecv GUARDED_BY(cs_totalBytesRecv){0}; uint64_t nTotalBytesSent GUARDED_BY(cs_totalBytesSent){0}; // outbound limit & stats uint64_t nMaxOutboundTotalBytesSentInCycle GUARDED_BY(cs_totalBytesSent){0}; std::chrono::seconds nMaxOutboundCycleStartTime GUARDED_BY(cs_totalBytesSent){0}; uint64_t nMaxOutboundLimit GUARDED_BY(cs_totalBytesSent); // P2P timeout in seconds std::chrono::seconds m_peer_connect_timeout; // Whitelisted ranges. Any node connecting from these is automatically // whitelisted (as well as those connecting to whitelisted binds). std::vector vWhitelistedRange; unsigned int nSendBufferMaxSize{0}; unsigned int nReceiveFloodSize{0}; std::vector vhListenSocket; std::atomic fNetworkActive{true}; bool fAddressesInitialized{false}; CAddrMan addrman; std::deque m_addr_fetches GUARDED_BY(m_addr_fetches_mutex); RecursiveMutex m_addr_fetches_mutex; std::vector vAddedNodes GUARDED_BY(cs_vAddedNodes); RecursiveMutex cs_vAddedNodes; std::vector vNodes GUARDED_BY(cs_vNodes); std::list vNodesDisconnected; mutable RecursiveMutex cs_vNodes; std::atomic nLastNodeId{0}; unsigned int nPrevNodeCount{0}; /** * Cache responses to addr requests to minimize privacy leak. * Attack example: scraping addrs in real-time may allow an attacker * to infer new connections of the victim by detecting new records * with fresh timestamps (per self-announcement). */ struct CachedAddrResponse { std::vector m_addrs_response_cache; std::chrono::microseconds m_cache_entry_expiration{0}; }; /** * Addr responses stored in different caches * per (network, local socket) prevent cross-network node identification. * If a node for example is multi-homed under Tor and IPv6, * a single cache (or no cache at all) would let an attacker * to easily detect that it is the same node by comparing responses. * Indexing by local socket prevents leakage when a node has multiple * listening addresses on the same network. * * The used memory equals to 1000 CAddress records (or around 40 bytes) per * distinct Network (up to 5) we have/had an inbound peer from, * resulting in at most ~196 KB. Every separate local socket may * add up to ~196 KB extra. */ std::map m_addr_response_caches; /** * Services this instance offers. * * This data is replicated in each CNode instance we create during peer * connection (in ConnectNode()) under a member also called * nLocalServices. * * This data is not marked const, but after being set it should not * change. See the note in CNode::nLocalServices documentation. * * \sa CNode::nLocalServices */ ServiceFlags nLocalServices; std::unique_ptr semOutbound; std::unique_ptr semAddnode; int nMaxConnections; // How many full-relay (tx, block, addr) outbound peers we want int m_max_outbound_full_relay; // How many block-relay only outbound peers we want // We do not relay tx or addr messages with these peers int m_max_outbound_block_relay; // How many avalanche enabled outbound peers we want int m_max_avalanche_outbound; int nMaxAddnode; int nMaxFeeler; int m_max_outbound; bool m_use_addrman_outgoing; CClientUIInterface *clientInterface; // FIXME m_msgproc is a terrible name std::vector m_msgproc; /** * Pointer to this node's banman. May be nullptr - check existence before * dereferencing. */ BanMan *m_banman; /** * Addresses that were saved during the previous clean shutdown. We'll * attempt to make block-relay-only connections to them. */ std::vector m_anchors; /** SipHasher seeds for deterministic randomness */ const uint64_t nSeed0, nSeed1; /** flag for waking the message processor. */ bool fMsgProcWake GUARDED_BY(mutexMsgProc); std::condition_variable condMsgProc; Mutex mutexMsgProc; std::atomic flagInterruptMsgProc{false}; /** * This is signaled when network activity should cease. * A pointer to it is saved in `m_i2p_sam_session`, so make sure that * the lifetime of `interruptNet` is not shorter than * the lifetime of `m_i2p_sam_session`. */ CThreadInterrupt interruptNet; /** * I2P SAM session. * Used to accept incoming and make outgoing I2P connections. */ std::unique_ptr m_i2p_sam_session; std::thread threadDNSAddressSeed; std::thread threadSocketHandler; std::thread threadOpenAddedConnections; std::thread threadOpenConnections; std::thread threadMessageHandler; std::thread threadI2PAcceptIncoming; /** * flag for deciding to connect to an extra outbound peer, in excess of * m_max_outbound_full_relay. This takes the place of a feeler connection. */ std::atomic_bool m_try_another_outbound_peer; /** * flag for initiating extra block-relay-only peer connections. * this should only be enabled after initial chain sync has occurred, * as these connections are intended to be short-lived and low-bandwidth. */ std::atomic_bool m_start_extra_block_relay_peers{false}; std::atomic m_next_send_inv_to_incoming{0us}; /** * A vector of -bind=
:=onion arguments each of which is * an address and port that are designated for incoming Tor connections. */ std::vector m_onion_binds; friend struct ::CConnmanTest; friend struct ConnmanTestMsg; }; /** * Return a timestamp in the future (in microseconds) for exponentially * distributed events. */ std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval); std::string getSubVersionEB(uint64_t MaxBlockSize); std::string userAgent(const Config &config); struct NodeEvictionCandidate { NodeId id; std::chrono::seconds m_connected; std::chrono::microseconds m_min_ping_time; std::chrono::seconds m_last_block_time; std::chrono::seconds m_last_proof_time; std::chrono::seconds m_last_tx_time; bool fRelevantServices; bool fRelayTxes; bool fBloomFilter; uint64_t nKeyedNetGroup; bool prefer_evict; bool m_is_local; Network m_network; double availabilityScore; }; /** * Select an inbound peer to evict after filtering out (protecting) peers having * distinct, difficult-to-forge characteristics. The protection logic picks out * fixed numbers of desirable peers per various criteria, followed by (mostly) * ratios of desirable or disadvantaged peers. If any eviction candidates * remain, the selection logic chooses a peer to evict. */ [[nodiscard]] std::optional SelectNodeToEvict(std::vector &&vEvictionCandidates); /** * Protect desirable or disadvantaged inbound peers from eviction by ratio. * * This function protects half of the peers which have been connected the * longest, to replicate the non-eviction implicit behavior and preclude attacks * that start later. * * Half of these protected spots (1/4 of the total) are reserved for the * following categories of peers, sorted by longest uptime, even if they're not * longest uptime overall: * * - onion peers connected via our tor control service * * - localhost peers, as manually configured hidden services not using * `-bind=addr[:port]=onion` will not be detected as inbound onion connections * * - I2P peers * * This helps protect these privacy network peers, which tend to be otherwise * disadvantaged under our eviction criteria for their higher min ping times * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. */ void ProtectEvictionCandidatesByRatio( std::vector &vEvictionCandidates); #endif // BITCOIN_NET_H diff --git a/src/sync.cpp b/src/sync.cpp index f544d06bf..ea998adf0 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -1,302 +1,295 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include -#ifdef DEBUG_LOCKCONTENTION -void PrintLockContention(const char *pszName, const char *pszFile, int nLine) { - LogPrintf("LOCKCONTENTION: %s\n", pszName); - LogPrintf("Locker: %s:%d\n", pszFile, nLine); -} -#endif /* DEBUG_LOCKCONTENTION */ - #ifdef DEBUG_LOCKORDER // // Early deadlock detection. // Problem being solved: // Thread 1 locks A, then B, then C // Thread 2 locks D, then C, then A // --> may result in deadlock between the two threads, depending on when // they run. // Solution implemented here: // Keep track of pairs of locks: (A before B), (A before C), etc. // Complain if any thread tries to lock in a different order. // struct CLockLocation { CLockLocation(const char *pszName, const char *pszFile, int nLine, bool fTryIn, const std::string &thread_name) : fTry(fTryIn), mutexName(pszName), sourceFile(pszFile), m_thread_name(thread_name), sourceLine(nLine) {} std::string ToString() const { return strprintf("'%s' in %s:%s%s (in thread '%s')", mutexName, sourceFile, sourceLine, (fTry ? " (TRY)" : ""), m_thread_name); } std::string Name() const { return mutexName; } private: bool fTry; std::string mutexName; std::string sourceFile; const std::string &m_thread_name; int sourceLine; }; using LockStackItem = std::pair; using LockStack = std::vector; using LockStacks = std::unordered_map; using LockPair = std::pair; using LockOrders = std::map; using InvLockOrders = std::set; struct LockData { LockStacks m_lock_stacks; LockOrders lockorders; InvLockOrders invlockorders; std::mutex dd_mutex; }; LockData &GetLockData() { // This approach guarantees that the object is not destroyed until after its // last use. The operating system automatically reclaims all the memory in a // program's heap when that program exits. // Since the ~LockData() destructor is never called, the LockData class and // all its subclasses must have implicitly-defined destructors. static LockData &lock_data = *new LockData(); return lock_data; } static void potential_deadlock_detected(const LockPair &mismatch, const LockStack &s1, const LockStack &s2) { LogPrintf("POTENTIAL DEADLOCK DETECTED\n"); LogPrintf("Previous lock order was:\n"); for (const LockStackItem &i : s1) { if (i.first == mismatch.first) { LogPrintfToBeContinued(" (1)"); } if (i.first == mismatch.second) { LogPrintfToBeContinued(" (2)"); } LogPrintf(" %s\n", i.second.ToString()); } std::string mutex_a, mutex_b; LogPrintf("Current lock order is:\n"); for (const LockStackItem &i : s2) { if (i.first == mismatch.first) { LogPrintfToBeContinued(" (1)"); mutex_a = i.second.Name(); } if (i.first == mismatch.second) { LogPrintfToBeContinued(" (2)"); mutex_b = i.second.Name(); } LogPrintf(" %s\n", i.second.ToString()); } if (g_debug_lockorder_abort) { tfm::format( std::cerr, "Assertion failed: detected inconsistent lock order for %s, " "details in debug log.\n", s2.back().second.ToString()); abort(); } throw std::logic_error( strprintf("potential deadlock detected: %s -> %s -> %s", mutex_b, mutex_a, mutex_b)); } static void push_lock(void *c, const CLockLocation &locklocation) { LockData &lockdata = GetLockData(); std::lock_guard lock(lockdata.dd_mutex); LockStack &lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()]; lock_stack.emplace_back(c, locklocation); for (const LockStackItem &i : lock_stack) { if (i.first == c) { break; } const LockPair p1 = std::make_pair(i.first, c); if (lockdata.lockorders.count(p1)) { continue; } const LockPair p2 = std::make_pair(c, i.first); if (lockdata.lockorders.count(p2)) { auto lock_stack_copy = lock_stack; lock_stack.pop_back(); potential_deadlock_detected(p1, lockdata.lockorders[p2], lock_stack_copy); // potential_deadlock_detected() does not return. } lockdata.lockorders.emplace(p1, lock_stack); lockdata.invlockorders.insert(p2); } } static void pop_lock() { LockData &lockdata = GetLockData(); std::lock_guard lock(lockdata.dd_mutex); LockStack &lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()]; lock_stack.pop_back(); if (lock_stack.empty()) { lockdata.m_lock_stacks.erase(std::this_thread::get_id()); } } void EnterCritical(const char *pszName, const char *pszFile, int nLine, void *cs, bool fTry) { push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry, util::ThreadGetInternalName())); } void CheckLastCritical(void *cs, std::string &lockname, const char *guardname, const char *file, int line) { LockData &lockdata = GetLockData(); std::lock_guard lock(lockdata.dd_mutex); const LockStack &lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()]; if (!lock_stack.empty()) { const auto &lastlock = lock_stack.back(); if (lastlock.first == cs) { lockname = lastlock.second.Name(); return; } } LogPrintf("INCONSISTENT LOCK ORDER DETECTED\n"); LogPrintf("Current lock order (least recent first) is:\n"); for (const LockStackItem &i : lock_stack) { LogPrintf(" %s\n", i.second.ToString()); } if (g_debug_lockorder_abort) { tfm::format(std::cerr, "%s:%s %s was not most recent critical section locked, " "details in debug log.\n", file, line, guardname); abort(); } throw std::logic_error( strprintf("%s was not most recent critical section locked", guardname)); } void LeaveCritical() { pop_lock(); } std::string LocksHeld() { LockData &lockdata = GetLockData(); std::lock_guard lock(lockdata.dd_mutex); const LockStack &lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()]; std::string result; for (const LockStackItem &i : lock_stack) { result += i.second.ToString() + std::string("\n"); } return result; } static bool LockHeld(void *mutex) { LockData &lockdata = GetLockData(); std::lock_guard lock(lockdata.dd_mutex); const LockStack &lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()]; for (const LockStackItem &i : lock_stack) { if (i.first == mutex) { return true; } } return false; } template void AssertLockHeldInternal(const char *pszName, const char *pszFile, int nLine, MutexType *cs) { if (LockHeld(cs)) { return; } tfm::format(std::cerr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld()); abort(); } template void AssertLockHeldInternal(const char *, const char *, int, Mutex *); template void AssertLockHeldInternal(const char *, const char *, int, RecursiveMutex *); template void AssertLockNotHeldInternal(const char *pszName, const char *pszFile, int nLine, MutexType *cs) { if (!LockHeld(cs)) { return; } tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld()); abort(); } template void AssertLockNotHeldInternal(const char *, const char *, int, Mutex *); template void AssertLockNotHeldInternal(const char *, const char *, int, RecursiveMutex *); void DeleteLock(void *cs) { LockData &lockdata = GetLockData(); std::lock_guard lock(lockdata.dd_mutex); const LockPair item = std::make_pair(cs, nullptr); LockOrders::iterator it = lockdata.lockorders.lower_bound(item); while (it != lockdata.lockorders.end() && it->first.first == cs) { const LockPair invitem = std::make_pair(it->first.second, it->first.first); lockdata.invlockorders.erase(invitem); lockdata.lockorders.erase(it++); } InvLockOrders::iterator invit = lockdata.invlockorders.lower_bound(item); while (invit != lockdata.invlockorders.end() && invit->first == cs) { const LockPair invinvitem = std::make_pair(invit->second, invit->first); lockdata.lockorders.erase(invinvitem); lockdata.invlockorders.erase(invit++); } } bool LockStackEmpty() { LockData &lockdata = GetLockData(); std::lock_guard lock(lockdata.dd_mutex); const auto it = lockdata.m_lock_stacks.find(std::this_thread::get_id()); if (it == lockdata.m_lock_stacks.end()) { return true; } return it->second.empty(); } bool g_debug_lockorder_abort = true; #endif /* DEBUG_LOCKORDER */ diff --git a/src/sync.h b/src/sync.h index 9788bd837..16c35b11e 100644 --- a/src/sync.h +++ b/src/sync.h @@ -1,366 +1,368 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_SYNC_H #define BITCOIN_SYNC_H +#ifdef DEBUG_LOCKCONTENTION +#include +#include +#endif + #include #include #include #include #include #include ///////////////////////////////////////////////// // // // THE SIMPLE DEFINITION, EXCLUDING DEBUG CODE // // // ///////////////////////////////////////////////// /* RecursiveMutex mutex; std::recursive_mutex mutex; LOCK(mutex); std::unique_lock criticalblock(mutex); LOCK2(mutex1, mutex2); std::unique_lock criticalblock1(mutex1); std::unique_lock criticalblock2(mutex2); TRY_LOCK(mutex, name); std::unique_lock name(mutex, std::try_to_lock_t); ENTER_CRITICAL_SECTION(mutex); // no RAII mutex.lock(); LEAVE_CRITICAL_SECTION(mutex); // no RAII mutex.unlock(); */ /////////////////////////////// // // // THE ACTUAL IMPLEMENTATION // // // /////////////////////////////// #ifdef DEBUG_LOCKORDER void EnterCritical(const char *pszName, const char *pszFile, int nLine, void *cs, bool fTry = false); void LeaveCritical(); void CheckLastCritical(void *cs, std::string &lockname, const char *guardname, const char *file, int line); std::string LocksHeld(); template void AssertLockHeldInternal(const char *pszName, const char *pszFile, int nLine, MutexType *cs) EXCLUSIVE_LOCKS_REQUIRED(cs); template void AssertLockNotHeldInternal(const char *pszName, const char *pszFile, int nLine, MutexType *cs) LOCKS_EXCLUDED(cs); void DeleteLock(void *cs); bool LockStackEmpty(); /** * Call abort() if a potential lock order deadlock bug is detected, instead of * just logging information and throwing a logic_error. Defaults to true, and * set to false in DEBUG_LOCKORDER unit tests. */ extern bool g_debug_lockorder_abort; #else inline void EnterCritical(const char *pszName, const char *pszFile, int nLine, void *cs, bool fTry = false) {} inline void LeaveCritical() {} inline void CheckLastCritical(void *cs, std::string &lockname, const char *guardname, const char *file, int line) {} template inline void AssertLockHeldInternal(const char *pszName, const char *pszFile, int nLine, MutexType *cs) EXCLUSIVE_LOCKS_REQUIRED(cs) {} template void AssertLockNotHeldInternal(const char *pszName, const char *pszFile, int nLine, MutexType *cs) LOCKS_EXCLUDED(cs) {} inline void DeleteLock(void *cs) {} inline bool LockStackEmpty() { return true; } #endif #define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs) #define AssertLockNotHeld(cs) \ AssertLockNotHeldInternal(#cs, __FILE__, __LINE__, &cs) /** * Template mixin that adds -Wthread-safety locking annotations and lock order * checking to a subset of the mutex API. */ template class LOCKABLE AnnotatedMixin : public PARENT { public: ~AnnotatedMixin() { DeleteLock((void *)this); } void lock() EXCLUSIVE_LOCK_FUNCTION() { PARENT::lock(); } void unlock() UNLOCK_FUNCTION() { PARENT::unlock(); } bool try_lock() EXCLUSIVE_TRYLOCK_FUNCTION(true) { return PARENT::try_lock(); } using UniqueLock = std::unique_lock; #ifdef __clang__ //! For negative capabilities in the Clang Thread Safety Analysis. //! A negative requirement uses the EXCLUSIVE_LOCKS_REQUIRED attribute, in //! conjunction with the ! operator, to indicate that a mutex should not be //! held. const AnnotatedMixin &operator!() const { return *this; } #endif // __clang__ }; /** * Wrapped mutex: supports recursive locking, but no waiting * TODO: We should move away from using the recursive lock by default. */ using RecursiveMutex = AnnotatedMixin; /** Wrapped mutex: supports waiting but not recursive locking */ typedef AnnotatedMixin Mutex; -#ifdef DEBUG_LOCKCONTENTION -void PrintLockContention(const char *pszName, const char *pszFile, int nLine); -#endif - /** Wrapper around std::unique_lock style lock for Mutex. */ template class SCOPED_LOCKABLE UniqueLock : public Base { private: void Enter(const char *pszName, const char *pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, (void *)(Base::mutex())); #ifdef DEBUG_LOCKCONTENTION - if (!Base::try_lock()) { - PrintLockContention(pszName, pszFile, nLine); -#endif - Base::lock(); -#ifdef DEBUG_LOCKCONTENTION + if (Base::try_lock()) { + return; } + LOG_TIME_MICROS_WITH_CATEGORY( + strprintf("lock contention %s, %s:%d", pszName, pszFile, nLine), + BCLog::LOCK); #endif + Base::lock(); } bool TryEnter(const char *pszName, const char *pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, (void *)(Base::mutex()), true); Base::try_lock(); if (!Base::owns_lock()) { LeaveCritical(); } return Base::owns_lock(); } public: UniqueLock(Mutex &mutexIn, const char *pszName, const char *pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : Base(mutexIn, std::defer_lock) { if (fTry) { TryEnter(pszName, pszFile, nLine); } else { Enter(pszName, pszFile, nLine); } } UniqueLock(Mutex *pmutexIn, const char *pszName, const char *pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn) { if (!pmutexIn) { return; } *static_cast(this) = Base(*pmutexIn, std::defer_lock); if (fTry) { TryEnter(pszName, pszFile, nLine); } else { Enter(pszName, pszFile, nLine); } } ~UniqueLock() UNLOCK_FUNCTION() { if (Base::owns_lock()) { LeaveCritical(); } } operator bool() { return Base::owns_lock(); } protected: // needed for reverse_lock UniqueLock() {} public: /** * An RAII-style reverse lock. Unlocks on construction and locks on * destruction. */ class reverse_lock { public: explicit reverse_lock(UniqueLock &_lock, const char *_guardname, const char *_file, int _line) : lock(_lock), file(_file), line(_line) { CheckLastCritical((void *)lock.mutex(), lockname, _guardname, _file, _line); lock.unlock(); LeaveCritical(); lock.swap(templock); } ~reverse_lock() { templock.swap(lock); EnterCritical(lockname.c_str(), file.c_str(), line, (void *)lock.mutex()); lock.lock(); } private: reverse_lock(reverse_lock const &); reverse_lock &operator=(reverse_lock const &); UniqueLock &lock; UniqueLock templock; std::string lockname; const std::string file; const int line; }; friend class reverse_lock; }; #define REVERSE_LOCK(g) \ typename std::decay::type::reverse_lock PASTE2( \ revlock, __COUNTER__)(g, #g, __FILE__, __LINE__) template using DebugLock = UniqueLock::type>::type>; #define LOCK(cs) \ DebugLock PASTE2(criticalblock, \ __COUNTER__)(cs, #cs, __FILE__, __LINE__) #define LOCK2(cs1, cs2) \ DebugLock criticalblock1(cs1, #cs1, __FILE__, __LINE__); \ DebugLock criticalblock2(cs2, #cs2, __FILE__, __LINE__); #define TRY_LOCK(cs, name) \ DebugLock name(cs, #cs, __FILE__, __LINE__, true) #define WAIT_LOCK(cs, name) \ DebugLock name(cs, #cs, __FILE__, __LINE__) #define ENTER_CRITICAL_SECTION(cs) \ { \ EnterCritical(#cs, __FILE__, __LINE__, (void *)(&cs)); \ (cs).lock(); \ } #define LEAVE_CRITICAL_SECTION(cs) \ { \ std::string lockname; \ CheckLastCritical((void *)(&cs), lockname, #cs, __FILE__, __LINE__); \ (cs).unlock(); \ LeaveCritical(); \ } //! Run code while locking a mutex. //! //! Examples: //! //! WITH_LOCK(cs, shared_val = shared_val + 1); //! //! int val = WITH_LOCK(cs, return shared_val); //! #define WITH_LOCK(cs, code) \ [&] { \ LOCK(cs); \ code; \ }() class CSemaphore { private: std::condition_variable condition; std::mutex mutex; int value; public: explicit CSemaphore(int init) : value(init) {} void wait() { std::unique_lock lock(mutex); condition.wait(lock, [&]() { return value >= 1; }); value--; } bool try_wait() { std::lock_guard lock(mutex); if (value < 1) { return false; } value--; return true; } void post() { { std::lock_guard lock(mutex); value++; } condition.notify_one(); } }; /** RAII-style semaphore lock */ class CSemaphoreGrant { private: CSemaphore *sem; bool fHaveGrant; public: void Acquire() { if (fHaveGrant) { return; } sem->wait(); fHaveGrant = true; } void Release() { if (!fHaveGrant) { return; } sem->post(); fHaveGrant = false; } bool TryAcquire() { if (!fHaveGrant && sem->try_wait()) { fHaveGrant = true; } return fHaveGrant; } void MoveTo(CSemaphoreGrant &grant) { grant.Release(); grant.sem = sem; grant.fHaveGrant = fHaveGrant; fHaveGrant = false; } CSemaphoreGrant() : sem(nullptr), fHaveGrant(false) {} explicit CSemaphoreGrant(CSemaphore &sema, bool fTry = false) : sem(&sema), fHaveGrant(false) { if (fTry) { TryAcquire(); } else { Acquire(); } } ~CSemaphoreGrant() { Release(); } operator bool() const { return fHaveGrant; } }; #endif // BITCOIN_SYNC_H diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index 943432cf6..ceaa09675 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -1,405 +1,423 @@ // Copyright (c) 2012-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include -BOOST_FIXTURE_TEST_SUITE(checkqueue_tests, TestingSetup) +/** + * Identical to TestingSetup but excludes lock contention logging if + * `DEBUG_LOCKCONTENTION` is defined, as some of these tests are designed to be + * heavily contested to trigger race conditions or other issues. + */ +struct NoLockLoggingTestingSetup : public TestingSetup { + NoLockLoggingTestingSetup() +#ifdef DEBUG_LOCKCONTENTION + : TestingSetup{CBaseChainParams::MAIN, /*extra_args=*/{ + "-debugexclude=lock" + }} { + } +#else + : TestingSetup{CBaseChainParams::MAIN} { + } +#endif +}; + +BOOST_FIXTURE_TEST_SUITE(checkqueue_tests, NoLockLoggingTestingSetup) static const unsigned int QUEUE_BATCH_SIZE = 128; static const int SCRIPT_CHECK_THREADS = 3; struct FakeCheck { bool operator()() const { return true; } void swap(FakeCheck &x){}; }; struct FakeCheckCheckCompletion { static std::atomic n_calls; bool operator()() { n_calls.fetch_add(1, std::memory_order_relaxed); return true; } void swap(FakeCheckCheckCompletion &x){}; }; struct FailingCheck { bool fails; FailingCheck(bool _fails) : fails(_fails){}; FailingCheck() : fails(true){}; bool operator()() const { return !fails; } void swap(FailingCheck &x) { std::swap(fails, x.fails); }; }; struct UniqueCheck { static Mutex m; static std::unordered_multiset results GUARDED_BY(m); size_t check_id; UniqueCheck(size_t check_id_in) : check_id(check_id_in){}; UniqueCheck() : check_id(0){}; bool operator()() { LOCK(m); results.insert(check_id); return true; } void swap(UniqueCheck &x) { std::swap(x.check_id, check_id); }; }; struct MemoryCheck { static std::atomic fake_allocated_memory; bool b{false}; bool operator()() const { return true; } MemoryCheck(){}; MemoryCheck(const MemoryCheck &x) { // We have to do this to make sure that destructor calls are paired // // Really, copy constructor should be deletable, but CCheckQueue breaks // if it is deleted because of internal push_back. fake_allocated_memory.fetch_add(b, std::memory_order_relaxed); }; MemoryCheck(bool b_) : b(b_) { fake_allocated_memory.fetch_add(b, std::memory_order_relaxed); }; ~MemoryCheck() { fake_allocated_memory.fetch_sub(b, std::memory_order_relaxed); }; void swap(MemoryCheck &x) { std::swap(b, x.b); }; }; struct FrozenCleanupCheck { static std::atomic nFrozen; static std::condition_variable cv; static std::mutex m; // Freezing can't be the default initialized behavior given how the queue // swaps in default initialized Checks. bool should_freeze{false}; bool operator()() const { return true; } FrozenCleanupCheck() {} ~FrozenCleanupCheck() { if (should_freeze) { std::unique_lock l(m); nFrozen.store(1, std::memory_order_relaxed); cv.notify_one(); cv.wait( l, [] { return nFrozen.load(std::memory_order_relaxed) == 0; }); } } void swap(FrozenCleanupCheck &x) { std::swap(should_freeze, x.should_freeze); }; }; // Static Allocations std::mutex FrozenCleanupCheck::m{}; std::atomic FrozenCleanupCheck::nFrozen{0}; std::condition_variable FrozenCleanupCheck::cv{}; Mutex UniqueCheck::m; std::unordered_multiset UniqueCheck::results; std::atomic FakeCheckCheckCompletion::n_calls{0}; std::atomic MemoryCheck::fake_allocated_memory{0}; // Queue Typedefs typedef CCheckQueue Correct_Queue; typedef CCheckQueue Standard_Queue; typedef CCheckQueue Failing_Queue; typedef CCheckQueue Unique_Queue; typedef CCheckQueue Memory_Queue; typedef CCheckQueue FrozenCleanup_Queue; /** This test case checks that the CCheckQueue works properly * with each specified size_t Checks pushed. */ static void Correct_Queue_range(std::vector range) { auto small_queue = std::make_unique(QUEUE_BATCH_SIZE); small_queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); // Make vChecks here to save on malloc (this test can be slow...) std::vector vChecks; for (const size_t i : range) { size_t total = i; FakeCheckCheckCompletion::n_calls = 0; CCheckQueueControl control(small_queue.get()); while (total) { vChecks.resize(std::min(total, (size_t)InsecureRandRange(10))); total -= vChecks.size(); control.Add(vChecks); } BOOST_REQUIRE(control.Wait()); if (FakeCheckCheckCompletion::n_calls != i) { BOOST_REQUIRE_EQUAL(FakeCheckCheckCompletion::n_calls, i); } } small_queue->StopWorkerThreads(); } /** Test that 0 checks is correct */ BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_Zero) { std::vector range; range.push_back((size_t)0); Correct_Queue_range(range); } /** Test that 1 check is correct */ BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_One) { std::vector range; range.push_back((size_t)1); Correct_Queue_range(range); } /** Test that MAX check is correct */ BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_Max) { std::vector range; range.push_back(100000); Correct_Queue_range(range); } /** Test that random numbers of checks are correct */ BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_Random) { std::vector range; range.reserve(100000 / 1000); for (size_t i = 2; i < 100000; i += std::max((size_t)1, (size_t)InsecureRandRange(std::min( (size_t)1000, ((size_t)100000) - i)))) { range.push_back(i); } Correct_Queue_range(range); } /** Test that failing checks are caught */ BOOST_AUTO_TEST_CASE(test_CheckQueue_Catches_Failure) { auto fail_queue = std::make_unique(QUEUE_BATCH_SIZE); fail_queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); for (size_t i = 0; i < 1001; ++i) { CCheckQueueControl control(fail_queue.get()); size_t remaining = i; while (remaining) { size_t r = InsecureRandRange(10); std::vector vChecks; vChecks.reserve(r); for (size_t k = 0; k < r && remaining; k++, remaining--) { vChecks.emplace_back(remaining == 1); } control.Add(vChecks); } bool success = control.Wait(); if (i > 0) { BOOST_REQUIRE(!success); } else if (i == 0) { BOOST_REQUIRE(success); } } fail_queue->StopWorkerThreads(); } // Test that a block validation which fails does not interfere with // future blocks, ie, the bad state is cleared. BOOST_AUTO_TEST_CASE(test_CheckQueue_Recovers_From_Failure) { auto fail_queue = std::make_unique(QUEUE_BATCH_SIZE); fail_queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); for (auto times = 0; times < 10; ++times) { for (const bool end_fails : {true, false}) { CCheckQueueControl control(fail_queue.get()); { std::vector vChecks; vChecks.resize(100, false); vChecks[99] = end_fails; control.Add(vChecks); } bool r = control.Wait(); BOOST_REQUIRE(r != end_fails); } } fail_queue->StopWorkerThreads(); } // Test that unique checks are actually all called individually, rather than // just one check being called repeatedly. Test that checks are not called // more than once as well BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck) { auto queue = std::make_unique(QUEUE_BATCH_SIZE); queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); size_t COUNT = 100000; size_t total = COUNT; { CCheckQueueControl control(queue.get()); while (total) { size_t r = InsecureRandRange(10); std::vector vChecks; for (size_t k = 0; k < r && total; k++) { vChecks.emplace_back(--total); } control.Add(vChecks); } } { LOCK(UniqueCheck::m); bool r = true; BOOST_REQUIRE_EQUAL(UniqueCheck::results.size(), COUNT); for (size_t i = 0; i < COUNT; ++i) { r = r && UniqueCheck::results.count(i) == 1; } BOOST_REQUIRE(r); } queue->StopWorkerThreads(); } // Test that blocks which might allocate lots of memory free their memory // aggressively. // // This test attempts to catch a pathological case where by lazily freeing // checks might mean leaving a check un-swapped out, and decreasing by 1 each // time could leave the data hanging across a sequence of blocks. BOOST_AUTO_TEST_CASE(test_CheckQueue_Memory) { auto queue = std::make_unique(QUEUE_BATCH_SIZE); queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); for (size_t i = 0; i < 1000; ++i) { size_t total = i; { CCheckQueueControl control(queue.get()); while (total) { size_t r = InsecureRandRange(10); std::vector vChecks; for (size_t k = 0; k < r && total; k++) { total--; // Each iteration leaves data at the front, back, and middle // to catch any sort of deallocation failure vChecks.emplace_back(total == 0 || total == i || total == i / 2); } control.Add(vChecks); } } BOOST_REQUIRE_EQUAL(MemoryCheck::fake_allocated_memory, 0U); } queue->StopWorkerThreads(); } // Test that a new verification cannot occur until all checks // have been destructed BOOST_AUTO_TEST_CASE(test_CheckQueue_FrozenCleanup) { auto queue = std::make_unique(QUEUE_BATCH_SIZE); bool fails = false; queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); std::thread t0([&]() { CCheckQueueControl control(queue.get()); std::vector vChecks(1); // Freezing can't be the default initialized behavior given how the // queue // swaps in default initialized Checks (otherwise freezing destructor // would get called twice). vChecks[0].should_freeze = true; control.Add(vChecks); // Hangs here bool waitResult = control.Wait(); assert(waitResult); }); { std::unique_lock l(FrozenCleanupCheck::m); // Wait until the queue has finished all jobs and frozen FrozenCleanupCheck::cv.wait( l, []() { return FrozenCleanupCheck::nFrozen == 1; }); } // Try to get control of the queue a bunch of times for (auto x = 0; x < 100 && !fails; ++x) { fails = queue->m_control_mutex.try_lock(); } { // Unfreeze (we need lock n case of spurious wakeup) std::unique_lock l(FrozenCleanupCheck::m); FrozenCleanupCheck::nFrozen = 0; } // Awaken frozen destructor FrozenCleanupCheck::cv.notify_one(); // Wait for control to finish t0.join(); BOOST_REQUIRE(!fails); queue->StopWorkerThreads(); } /** Test that CCheckQueueControl is threadsafe */ BOOST_AUTO_TEST_CASE(test_CheckQueueControl_Locks) { auto queue = std::make_unique(QUEUE_BATCH_SIZE); { std::vector tg; std::atomic nThreads{0}; std::atomic fails{0}; for (size_t i = 0; i < 3; ++i) { tg.emplace_back([&] { CCheckQueueControl control(queue.get()); // While sleeping, no other thread should execute to this point auto observed = ++nThreads; UninterruptibleSleep(std::chrono::milliseconds{10}); fails += observed != nThreads; }); } for (auto &thread : tg) { if (thread.joinable()) { thread.join(); } } BOOST_REQUIRE_EQUAL(fails, 0); } { std::vector tg; std::mutex m; std::condition_variable cv; bool has_lock{false}; bool has_tried{false}; bool done{false}; bool done_ack{false}; { std::unique_lock l(m); tg.emplace_back([&] { CCheckQueueControl control(queue.get()); std::unique_lock ll(m); has_lock = true; cv.notify_one(); cv.wait(ll, [&] { return has_tried; }); done = true; cv.notify_one(); // Wait until the done is acknowledged // cv.wait(ll, [&] { return done_ack; }); }); // Wait for thread to get the lock cv.wait(l, [&]() { return has_lock; }); bool fails = false; for (auto x = 0; x < 100 && !fails; ++x) { fails = queue->m_control_mutex.try_lock(); } has_tried = true; cv.notify_one(); cv.wait(l, [&]() { return done; }); // Acknowledge the done done_ack = true; cv.notify_one(); BOOST_REQUIRE(!fails); } for (auto &thread : tg) { if (thread.joinable()) { thread.join(); } } } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp index 728b1a394..c6ed443e4 100644 --- a/src/test/logging_tests.cpp +++ b/src/test/logging_tests.cpp @@ -1,37 +1,37 @@ // Copyright (c) 2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include BOOST_FIXTURE_TEST_SUITE(logging_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(logging_timer) { SetMockTime(1); - auto sec_timer = BCLog::Timer("tests", "end_msg"); + auto micro_timer = + BCLog::Timer("tests", "end_msg"); SetMockTime(2); - BOOST_CHECK_EQUAL(sec_timer.LogMsg("test secs"), - "tests: test secs (1.00s)"); + BOOST_CHECK_EQUAL(micro_timer.LogMsg("test micros"), + "tests: test micros (1000000μs)"); SetMockTime(1); auto ms_timer = BCLog::Timer("tests", "end_msg"); SetMockTime(2); BOOST_CHECK_EQUAL(ms_timer.LogMsg("test ms"), "tests: test ms (1000.00ms)"); SetMockTime(1); - auto micro_timer = - BCLog::Timer("tests", "end_msg"); + auto sec_timer = BCLog::Timer("tests", "end_msg"); SetMockTime(2); - BOOST_CHECK_EQUAL(micro_timer.LogMsg("test micros"), - "tests: test micros (1000000.00μs)"); + BOOST_CHECK_EQUAL(sec_timer.LogMsg("test secs"), + "tests: test secs (1.00s)"); SetMockTime(0); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/types.h b/src/util/types.h new file mode 100644 index 000000000..25df7983b --- /dev/null +++ b/src/util/types.h @@ -0,0 +1,10 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_TYPES_H +#define BITCOIN_UTIL_TYPES_H + +template inline constexpr bool ALWAYS_FALSE{false}; + +#endif // BITCOIN_UTIL_TYPES_H diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py index 278f23e21..6b7f63819 100755 --- a/test/functional/rpc_misc.py +++ b/test/functional/rpc_misc.py @@ -1,111 +1,113 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test RPC misc output.""" import xml.etree.ElementTree as ET from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_greater_than_or_equal, assert_raises_rpc_error, ) class RpcMiscTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.supports_cli = False def run_test(self): node = self.nodes[0] self.log.info("test CHECK_NONFATAL") assert_raises_rpc_error( -1, 'Internal bug detected: \'request.params[9].get_str() != "trigger_internal_bug"\'', lambda: node.echo(arg9='trigger_internal_bug'), ) self.log.info("test getmemoryinfo") memory = node.getmemoryinfo()['locked'] assert_greater_than(memory['used'], 0) assert_greater_than(memory['free'], 0) assert_greater_than(memory['total'], 0) # assert_greater_than_or_equal() for locked in case locking pages # failed at some point assert_greater_than_or_equal(memory['locked'], 0) assert_greater_than(memory['chunks_used'], 0) assert_greater_than(memory['chunks_free'], 0) assert_equal(memory['used'] + memory['free'], memory['total']) self.log.info("test mallocinfo") try: mallocinfo = node.getmemoryinfo(mode="mallocinfo") self.log.info('getmemoryinfo(mode="mallocinfo") call succeeded') tree = ET.fromstring(mallocinfo) assert_equal(tree.tag, 'malloc') except JSONRPCException: self.log.info('getmemoryinfo(mode="mallocinfo") not available') assert_raises_rpc_error(-8, 'mallocinfo is only available when compiled with glibc 2.10+', node.getmemoryinfo, mode="mallocinfo") assert_raises_rpc_error(-8, "unknown mode foobar", node.getmemoryinfo, mode="foobar") self.log.info("test logging rpc and help") # Test logging RPC returns the expected number of logging categories. - assert_equal(len(node.logging()), 26) + # Use check if it is greater or equal because some logging categories + # are behind a preprocessor directive. + assert_greater_than_or_equal(len(node.logging()), 26) # Test toggling a logging category on/off/on with the logging RPC. assert_equal(node.logging()['qt'], True) node.logging(exclude=['qt']) assert_equal(node.logging()['qt'], False) node.logging(include=['qt']) assert_equal(node.logging()['qt'], True) # Test logging RPC returns the logging categories in alphabetical # order. sorted_logging_categories = sorted(node.logging()) assert_equal(list(node.logging()), sorted_logging_categories) # Test logging help returns the logging categories string in # alphabetical order. categories = ', '.join(sorted_logging_categories) logging_help = self.nodes[0].help('logging') assert f"valid logging categories are: {categories}" in logging_help self.log.info("test getindexinfo") # Without any indices running the RPC returns an empty object assert_equal(node.getindexinfo(), {}) # Restart the node with indices and wait for them to sync self.restart_node( 0, ["-txindex", "-blockfilterindex", "-coinstatsindex"]) self.wait_until( lambda: all(i["synced"] for i in node.getindexinfo().values())) # Returns a list of all running indices by default values = {"synced": True, "best_block_height": 200} assert_equal( node.getindexinfo(), { "txindex": values, "basic block filter index": values, "coinstatsindex": values, } ) # Specifying an index by name returns only the status of that index for i in {"txindex", "basic block filter index", "coinstatsindex"}: assert_equal(node.getindexinfo(i), {i: values}) # Specifying an unknown index name returns an empty result assert_equal(node.getindexinfo("foo"), {}) if __name__ == '__main__': RpcMiscTest().main()