diff --git a/.cirrus.yml b/.cirrus.yml index 3f52adde8a..e5a2f36f4d 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -111,7 +111,7 @@ task: FILE_ENV: "./ci/test/00_setup_env_arm.sh" task: - name: 'Win64, unit tests, no gui tests, no boost::process, no functional tests' + name: 'Win64, unit tests, no gui tests, no functional tests' << : *GLOBAL_TASK_TEMPLATE persistent_worker: labels: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5939520ad7..1cd40be541 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: name: 'macOS 13 native, x86_64, no depends, sqlite only, gui' # Use latest image, but hardcode version to avoid silent upgrades (and breaks). # See: https://github.com/actions/runner-images#available-images. - runs-on: macos-13 # Use M1 once available https://github.com/github/roadmap/issues/528 + runs-on: macos-13 # No need to run on the read-only mirror, unless it is a PR. if: github.repository != 'bitcoin-core/gui' || github.event_name == 'pull_request' diff --git a/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj b/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj index 20cdb7bb6e..fe01da28c8 100644 --- a/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj +++ b/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj @@ -58,7 +58,7 @@ Windows $(QtReleaseLibraries);%(AdditionalDependencies) - /ignore:4206 /LTCG:OFF + /LTCG:OFF ..\..\src; @@ -72,7 +72,6 @@ $(QtDebugLibraries);%(AdditionalDependencies) - /ignore:4206 ..\..\src; diff --git a/build_msvc/common.init.vcxproj.in b/build_msvc/common.init.vcxproj.in index 9cce546268..d54e559c9f 100644 --- a/build_msvc/common.init.vcxproj.in +++ b/build_msvc/common.init.vcxproj.in @@ -90,7 +90,7 @@ /utf-8 /Zc:__cplusplus /std:c++20 %(AdditionalOptions) 4018;4244;4267;4715;4805 true - _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;SECP256K1_STATIC;ZMQ_STATIC;NOMINMAX;WIN32;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;_CONSOLE;_WIN32_WINNT=0x0601;_WIN32_IE=0x0501;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) + _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;SECP256K1_STATIC;ZMQ_STATIC;NOMINMAX;WIN32;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_CONSOLE;_WIN32_WINNT=0x0601;_WIN32_IE=0x0501;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) ..\..\src;..\..\src\minisketch\include;..\..\src\univalue\include;..\..\src\secp256k1\include;..\..\src\leveldb\include;..\..\src\leveldb\helpers\memenv;%(AdditionalIncludeDirectories) @@ -98,9 +98,6 @@ Iphlpapi.lib;ws2_32.lib;Shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) true - - /ignore:4221 - diff --git a/build_msvc/libminisketch/libminisketch.vcxproj b/build_msvc/libminisketch/libminisketch.vcxproj index b34593fe5c..60e57caa57 100644 --- a/build_msvc/libminisketch/libminisketch.vcxproj +++ b/build_msvc/libminisketch/libminisketch.vcxproj @@ -28,7 +28,7 @@ - 4060;4065;4146;4244;4267;4554 + 4060;4065;4146;4244;4267 HAVE_CLMUL;DISABLE_DEFAULT_FIELDS;ENABLE_FIELD_32;%(PreprocessorDefinitions) diff --git a/build_msvc/libsecp256k1/libsecp256k1.vcxproj b/build_msvc/libsecp256k1/libsecp256k1.vcxproj index 777515aa3a..7ea4b96534 100644 --- a/build_msvc/libsecp256k1/libsecp256k1.vcxproj +++ b/build_msvc/libsecp256k1/libsecp256k1.vcxproj @@ -17,7 +17,7 @@ ENABLE_MODULE_RECOVERY;ENABLE_MODULE_EXTRAKEYS;ENABLE_MODULE_SCHNORRSIG;ENABLE_MODULE_ELLSWIFT;%(PreprocessorDefinitions) USE_ASM_X86_64;%(UndefinePreprocessorDefinitions) ..\..\src\secp256k1;%(AdditionalIncludeDirectories) - 4146;4244;4267;4334 + 4146;4244;4267 diff --git a/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj b/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj index 3776317fc7..c5a32a9711 100644 --- a/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj +++ b/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj @@ -82,7 +82,7 @@ $(QtLibraryDir)\Qt5Test.lib;$(QtReleaseLibraries);%(AdditionalDependencies) - /ignore:4206 /LTCG:OFF + /LTCG:OFF @@ -92,7 +92,6 @@ $(QtDebugLibraries);%(AdditionalDependencies) - /ignore:4206 diff --git a/ci/test/00_setup_env_native_fuzz.sh b/ci/test/00_setup_env_native_fuzz.sh index 122f044b58..3585b2b417 100755 --- a/ci/test/00_setup_env_native_fuzz.sh +++ b/ci/test/00_setup_env_native_fuzz.sh @@ -18,3 +18,4 @@ export CI_CONTAINER_CAP="--cap-add SYS_PTRACE" # If run with (ASan + LSan), the export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,address,undefined,float-divide-by-zero,integer \ CC='clang-17 -ftrivial-auto-var-init=pattern' CXX='clang++-17 -ftrivial-auto-var-init=pattern'" export CCACHE_MAXSIZE=200M +export LLVM_SYMBOLIZER_PATH="/usr/bin/llvm-symbolizer-17" diff --git a/ci/test/01_base_install.sh b/ci/test/01_base_install.sh index a0b054ab40..b15df4b6cc 100755 --- a/ci/test/01_base_install.sh +++ b/ci/test/01_base_install.sh @@ -67,8 +67,7 @@ if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then fi if [[ "${RUN_TIDY}" == "true" ]]; then - ${CI_RETRY_EXE} git clone https://github.com/include-what-you-use/include-what-you-use -b master /include-what-you-use - git -C /include-what-you-use checkout a138eaac254e5a472464e31d5ec418fe6e6f1fc7 + ${CI_RETRY_EXE} git clone --depth=1 https://github.com/include-what-you-use/include-what-you-use -b clang_"${TIDY_LLVM_V}" /include-what-you-use cmake -B /iwyu-build/ -G 'Unix Makefiles' -DCMAKE_PREFIX_PATH=/usr/lib/llvm-"${TIDY_LLVM_V}" -S /include-what-you-use make -C /iwyu-build/ install "-j$( nproc )" # Use nproc, because MAKEJOBS is the default in docker image builds fi diff --git a/doc/README.md b/doc/README.md index c570432aa4..446684b482 100644 --- a/doc/README.md +++ b/doc/README.md @@ -79,6 +79,7 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th - [Init Scripts (systemd/upstart/openrc)](init.md) - [Managing Wallets](managing-wallets.md) - [Multisig Tutorial](multisig-tutorial.md) +- [Offline Signing Tutorial](offline-signing-tutorial.md) - [P2P bad ports definition and list](p2p-bad-ports.md) - [PSBT support](psbt.md) - [Reduce Memory](reduce-memory.md) diff --git a/doc/offline-signing-tutorial.md b/doc/offline-signing-tutorial.md new file mode 100644 index 0000000000..4761cf256b --- /dev/null +++ b/doc/offline-signing-tutorial.md @@ -0,0 +1,255 @@ +# Offline Signing Tutorial + +This tutorial will describe how to use two instances of Bitcoin Core, one online and one offline, to greatly increase security by not having private keys reside on a networked device. + +Maintaining an air-gap between private keys and any network connections drastically reduces the opportunity for those keys to be exfiltrated from the user. + +This workflow uses [Partially Signed Bitcoin Transactions](https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md) (PSBTs) to transfer the transaction to and from the offline wallet for signing using the private keys. + +> [!NOTE] +> While this tutorial demonstrates the process using `signet` network, you should omit the `-signet` flag in the provided commands when working with `mainnet`. + +## Overview +In this tutorial we have two hosts, both running Bitcoin v25.0 + +* `offline` host which is disconnected from all networks (internet, Tor, wifi, bluetooth etc.) and does not have, or need, a copy of the blockchain. +* `online` host which is a regular online node with a synced blockchain. + +We are going to first create an `offline_wallet` on the offline host. We will then create a `watch_only_wallet` on the online host using public key descriptors exported from the `offline_wallet`. Next we will receive some coins into the wallet. In order to spend these coins we'll create an unsigned PSBT using the `watch_only_wallet`, sign the PSBT using the private keys in the `offline_wallet`, and finally broadcast the signed PSBT using the online host. + +### Requirements +- [jq](https://jqlang.github.io/jq/) installation - This tutorial uses jq to process certain fields from JSON RPC responses, but this convenience is optional. + +### Create and Prepare the `offline_wallet` + +1. On the offline machine create a wallet named `offline_wallet` secured by a wallet `passphrase`. This wallet will contain private keys and must remain unconnected to any networks at all times. + +```sh +[offline]$ ./src/bitcoin-cli -signet -named createwallet \ + wallet_name="offline_wallet" \ + passphrase="** enter passphrase **" + +{ + "name": "offline_wallet" +} +``` + +> [!NOTE] +> The use of a passphrase is crucial to encrypt the wallet.dat file. This encryption ensures that even if an unauthorized individual gains access to the offline host, they won't be able to access the wallet's contents. Further details about securing your wallet can be found in [Managing the Wallet](https://github.com/bitcoin/bitcoin/blob/master/doc/managing-wallets.md#12-encrypting-the-wallet) + +2. Export the public key-only descriptors from the offline host to a JSON file named `descriptors.json`. We use `jq` here to extract the `.descriptors` field from the full RPC response. + +```sh +[offline]$ ./src/bitcoin-cli -signet -rpcwallet="offline_wallet" listdescriptors \ + | jq -r '.descriptors' \ + >> /path/to/descriptors.json +``` + +> [!NOTE] +> The `descriptors.json` file will be transferred to the online machine (e.g. using a USB flash drive) where it can be imported to create a related watch-only wallet. + +### Create the online `watch_only_wallet` + +1. On the online machine create a blank watch-only wallet which has private keys disabled and is named `watch_only_wallet`. This is achieved by using the `createwallet` options: `disable_private_keys=true, blank=true`. + +The `watch_only_wallet` wallet will be used to track and validate incoming transactions, create unsigned PSBTs when spending coins, and broadcast signed and finalized PSBTs. + +> [!NOTE] +> `disable_private_keys` indicates that the wallet should refuse to import private keys, i.e. will be a dedicated watch-only wallet. + +```sh +[online]$ ./src/bitcoin-cli -signet -named createwallet \ + wallet_name="watch_only_wallet" \ + disable_private_keys=true + +{ + "name": "watch_only_wallet" +} +``` + +2. Import the `offline_wallet`s public key descriptors to the online `watch_only_wallet` using the `descriptors.json` file created on the offline wallet. + +```sh +[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" importdescriptors "$(cat /path/to/descriptors.json)" + +[ + { + "success": true + }, + { + "success": true + }, + { + "success": true + }, + { + "success": true + }, + { + "success": true + }, + { + "success": true + }, + { + "success": true + }, + { + "success": true + } +] +``` +> [!NOTE] +> Multiple success values indicate that multiple descriptors, for different address types, have been successfully imported. This allows generating different address types on the `watch_only_wallet`. + +### Fund the `offline_wallet` + +At this point, it's important to understand that both the `offline_wallet` and online `watch_only_wallet` share the same public keys. As a result, they generate the same addresses. Transactions can be created using either wallet, but valid signatures can only be added by the `offline_wallet` as only it has the private keys. + +1. Generate an address to receive coins. You can use _either_ the `offline_wallet` or the online `watch_only_wallet` to generate this address, as they will produce the same addresses. For the sake of this guide, we'll use the online `watch_only_wallet` to generate the address. + +```sh +[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" getnewaddress + +tb1qtu5qgc6ddhmqm5yqjvhg83qgk2t4ewajg0h6yh +``` + +2. Visit a faucet like https://signet.bc-2.jp and enter your address from the previous command to receive a small amount of signet coins to this address. + +3. Confirm that coins were received using the online `watch_only_wallet`. Note that the transaction may take a few moments before being received on your local node, depending on its connectivity. Just re-run the command periodically until the transaction is received. + +```sh +[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" listunspent + +[ + { + "txid": "0f3953dfc3eb8e753cd1633151837c5b9953992914ff32b7de08c47f1f29c762", + "vout": 1, + "address": "tb1qtu5qgc6ddhmqm5yqjvhg83qgk2t4ewajg0h6yh", + "label": "", + "scriptPubKey": "00145f2804634d6df60dd080932e83c408b2975cbbb2", + "amount": 0.01000000, + "confirmations": 4, + "spendable": true, + "solvable": true, + "desc": "wpkh([306c734f/84h/1h/0h/0/0]025932ccee7590158f7e08bb36290d135d30a0b045163da896e1cd7645ec4223a9)#xytvyr4a", + "parent_descs": [ + "wpkh([306c734f/84h/1h/0h]tpubDCJnY92ib4Zu3qd6wrBXEjG436tQdA2tDiJU2iSJYjkNS1darssPWKaBfojhjUF5vMLBcxbN2r93pmFMz2zyTEZuNx9JDo9rWqoHhATW3Uz/0/*)#7mh08dkg" + ], + "safe": true + } +] +``` + +### Create and Export an Unsigned PSBT + +1. Get a destination address for the transaction. In this tutorial we'll be sending funds to the address `tb1q9k5w0nhnhyeh78snpxh0t5t7c3lxdeg3erez32`, but if you don't need the coins for further testing you could send the coins back to the faucet. + +2. Create a funded but unsigned PSBT to the destination address with the online `watch_only_wallet` by using `send [{"address":amount},...]` and export the unsigned PSBT to a file `funded_psbt.txt` for easy portability to the `offline_wallet` for signing: + +```sh +[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" send \ + '{"tb1q9k5w0nhnhyeh78snpxh0t5t7c3lxdeg3erez32": 0.009}' \ + | jq -r '.psbt' \ + >> /path/to/funded_psbt.txt + +[online]$ cat /path/to/funded_psbt.txt + +cHNidP8BAHECAAAAAWLHKR9/xAjetzL/FCmZU5lbfINRMWPRPHWO68PfUzkPAQAAAAD9////AoA4AQAAAAAAFgAULajnzvO5M38eEwmu9dF+xH5m5RGs0g0AAAAAABYAFMaT0f/Wp2DCZzL6dkJ3GhWj4Y9vAAAAAAABAHECAAAAAY+dRPEBrGopkw4ugSzS9npzJDEIrE/bq1XXI0KbYnYrAQAAAAD+////ArKaXgAAAAAAFgAUwEc4LdoxSjbWo/2Ue+HS+QjwfiBAQg8AAAAAABYAFF8oBGNNbfYN0ICTLoPECLKXXLuyYW8CAAEBH0BCDwAAAAAAFgAUXygEY01t9g3QgJMug8QIspdcu7IiBgJZMszudZAVj34IuzYpDRNdMKCwRRY9qJbhzXZF7EIjqRgwbHNPVAAAgAEAAIAAAACAAAAAAAAAAAAAACICA7BlBnyAR4F2UkKuSX9MFhYCsn6j//z9i7lHDm1O0CU0GDBsc09UAACAAQAAgAAAAIABAAAAAAAAAAA= +``` +> [!NOTE] +> Leaving the `input` array empty in the above `walletcreatefundedpsbt` command is permitted and will cause the wallet to automatically select appropriate inputs for the transaction. + +### Decode and Analyze the Unsigned PSBT + +Decode and analyze the unsigned PSBT on the `offline_wallet` using the `funded_psbt.txt` file: + +```sh +[offline]$ ./src/bitcoin-cli -signet decodepsbt $(cat /path/to/funded_psbt.txt) + +{ + ... +} + +[offline]$ ./src/bitcoin-cli -signet analyzepsbt $(cat /path/to/funded_psbt.txt) + +{ + "inputs": [ + { + "has_utxo": true, + "is_final": false, + "next": "signer", + "missing": { + "signatures": [ + "5f2804634d6df60dd080932e83c408b2975cbbb2" + ] + } + } + ], + "estimated_vsize": 141, + "estimated_feerate": 0.00100000, + "fee": 0.00014100, + "next": "signer" +} +``` + +Notice that the analysis of the PSBT shows that "signatures" are missing and should be provided by the private key corresponding to the public key hash (hash160) "5f2804634d6df60dd080932e83c408b2975cbbb2" + +### Process and Sign the PSBT + +1. Unlock the `offline_wallet` with the Passphrase: + +Use the walletpassphrase command to unlock the `offline_wallet` with the passphrase. You should specify the passphrase and a timeout (in seconds) for how long you want the wallet to remain unlocked. + +```sh +[offline]$ ./src/bitcoin-cli -signet -rpcwallet="offline_wallet" walletpassphrase "** enter passphrase **" 60 +``` + +2. Process, sign and finalize the PSBT on the `offline_wallet` using the `walletprocesspsbt` command, saving the output to a file `final_psbt.txt`. + + ```sh +[offline]$ ./src/bitcoin-cli -signet -rpcwallet="offline_wallet" walletprocesspsbt \ + $(cat /path/to/funded_psbt.txt) \ + | jq -r .hex \ + >> /path/to/final_psbt.txt + ``` + +### Broadcast the Signed and Finalized PSBT +Broadcast the funded, signed and finalized PSBT `final_psbt.txt` using `sendrawtransaction` with an online node: + +```sh +[online]$ ./src/bitcoin-cli -signet sendrawtransaction $(cat /path/to/final_psbt.txt) + +c2430a0e46df472b04b0ca887bbcd5c4abf7b2ce2eb71de981444a80e2b96d52 +``` + +### Confirm Wallet Balance + +Confirm the updated balance of the offline wallet using the `watch_only_wallet`. + +```sh +[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" getbalances + +{ + "mine": { + "trusted": 0.00085900, + "untrusted_pending": 0.00000000, + "immature": 0.00000000 + }, + "lastprocessedblock": { + "hash": "0000003065c0669fff27edb4a71928cb48e5a6cfcdf06f491a83fd86822d18a6", + "height": 159592 + } +} +``` + + +You can also show transactions related to the wallet using `listtransactions` + +```sh +[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" listtransactions + +{ + ... +} +``` \ No newline at end of file diff --git a/src/Makefile.test.include b/src/Makefile.test.include index ce2c514b09..89d9f9ef59 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -112,6 +112,7 @@ BITCOIN_TESTS =\ test/miniscript_tests.cpp \ test/minisketch_tests.cpp \ test/multisig_tests.cpp \ + test/net_peer_connection_tests.cpp \ test/net_peer_eviction_tests.cpp \ test/net_tests.cpp \ test/netbase_tests.cpp \ diff --git a/src/consensus/validation.h b/src/consensus/validation.h index d5bf08cd61..bd3a5913c3 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -54,6 +54,8 @@ enum class TxValidationResult { TX_CONFLICT, TX_MEMPOOL_POLICY, //!< violated mempool's fee/size/descendant/RBF/etc limits TX_NO_MEMPOOL, //!< this node does not have a mempool so can't validate the transaction + TX_RECONSIDERABLE, //!< fails some policy, but might be acceptable if submitted in a (different) package + TX_UNKNOWN, //!< transaction was not validated because package failed }; /** A "reason" why a block was invalid, suitable for determining whether the diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 661406e122..c72dbf10bc 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include diff --git a/src/init.cpp b/src/init.cpp index 42331d37e8..62e7e32272 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -343,11 +343,11 @@ void Shutdown(NodeContext& node) node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); - node.kernel.reset(); node.mempool.reset(); node.fee_estimator.reset(); node.chainman.reset(); node.scheduler.reset(); + node.kernel.reset(); try { if (!fs::remove(GetPidFile(*node.args))) { @@ -489,7 +489,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-forcednsseed", strprintf("Always query for peer addresses via DNS lookup (default: %u)", DEFAULT_FORCEDNSSEED), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-listen", strprintf("Accept connections from outside (default: %u if no -proxy, -connect or -maxconnections=0)", DEFAULT_LISTEN), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-listenonion", strprintf("Automatically create Tor onion service (default: %d)", DEFAULT_LISTEN_ONION), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-maxconnections=", strprintf("Maintain at most connections to peers (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-maxconnections=", strprintf("Maintain at most automatic connections to peers (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxreceivebuffer=", strprintf("Maximum per-connection receive buffer, *1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxsendbuffer=", strprintf("Maximum per-connection memory usage for the send buffer, *1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by outbound peers forward or backward by this amount (default: %u seconds).", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1751,11 +1751,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; - connOptions.nMaxConnections = nMaxConnections; - connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections); - connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCK_RELAY_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay); - connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; - connOptions.nMaxFeeler = MAX_FEELER_CONNECTIONS; + connOptions.m_max_automatic_connections = nMaxConnections; connOptions.uiInterface = &uiInterface; connOptions.m_banman = node.banman.get(); connOptions.m_msgproc = node.peerman.get(); diff --git a/src/net.cpp b/src/net.cpp index 16075efdbb..4aee78846a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -417,21 +417,25 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo const uint16_t default_port{pszDest != nullptr ? GetDefaultPort(pszDest) : m_params.GetDefaultPort()}; if (pszDest) { - const std::vector resolved{Lookup(pszDest, default_port, fNameLookup && !HaveNameProxy(), 256)}; + std::vector resolved{Lookup(pszDest, default_port, fNameLookup && !HaveNameProxy(), 256)}; if (!resolved.empty()) { - const CService& rnd{resolved[GetRand(resolved.size())]}; - addrConnect = CAddress{MaybeFlipIPv6toCJDNS(rnd), NODE_NONE}; - if (!addrConnect.IsValid()) { - LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToStringAddrPort(), pszDest); - return nullptr; - } - // It is possible that we already have a connection to the IP/port pszDest resolved to. - // In that case, drop the connection that was just created. - LOCK(m_nodes_mutex); - CNode* pnode = FindNode(static_cast(addrConnect)); - if (pnode) { - LogPrintf("Failed to open new connection, already connected\n"); - return nullptr; + Shuffle(resolved.begin(), resolved.end(), FastRandomContext()); + // If the connection is made by name, it can be the case that the name resolves to more than one address. + // We don't want to connect any more of them if we are already connected to one + for (const auto& r : resolved) { + addrConnect = CAddress{MaybeFlipIPv6toCJDNS(r), NODE_NONE}; + if (!addrConnect.IsValid()) { + LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToStringAddrPort(), pszDest); + return nullptr; + } + // It is possible that we already have a connection to the IP/port pszDest resolved to. + // In that case, drop the connection that was just created. + LOCK(m_nodes_mutex); + CNode* pnode = FindNode(static_cast(addrConnect)); + if (pnode) { + LogPrintf("Not opening a connection to %s, already connected to %s\n", pszDest, addrConnect.ToStringAddrPort()); + return nullptr; + } } } } @@ -1730,7 +1734,6 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr&& sock, const CAddress& addr) { int nInbound = 0; - int nMaxInbound = nMaxConnections - m_max_outbound; AddWhitelistPermissionFlags(permission_flags, addr); if (NetPermissions::HasFlag(permission_flags, NetPermissionFlags::Implicit)) { @@ -1776,13 +1779,13 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr&& sock, // Only accept connections from discouraged peers if our inbound slots aren't (almost) full. bool discouraged = m_banman && m_banman->IsDiscouraged(addr); - if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && nInbound + 1 >= nMaxInbound && discouraged) + if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && nInbound + 1 >= m_max_inbound && discouraged) { LogPrint(BCLog::NET, "connection from %s dropped (discouraged)\n", addr.ToStringAddrPort()); return; } - if (nInbound >= nMaxInbound) + if (nInbound >= m_max_inbound) { if (!AttemptToEvictConnection()) { // No connection to evict, disconnect the new connection @@ -2733,7 +2736,7 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // different netgroups in ipv4/ipv6 networks + all peers in Tor/I2P/CJDNS networks. // Don't record addrman failure attempts when node is offline. This can be identified since all local // network connections (if any) belong in the same netgroup, and the size of `outbound_ipv46_peer_netgroups` would only be 1. - const bool count_failures{((int)outbound_ipv46_peer_netgroups.size() + outbound_privacy_network_peers) >= std::min(nMaxConnections - 1, 2)}; + const bool count_failures{((int)outbound_ipv46_peer_netgroups.size() + outbound_privacy_network_peers) >= std::min(m_max_automatic_connections - 1, 2)}; // Use BIP324 transport when both us and them have NODE_V2_P2P set. const bool use_v2transport(addrConnect.nServices & GetLocalServices() & NODE_P2P_V2); OpenNetworkConnection(addrConnect, count_failures, std::move(grant), /*strDest=*/nullptr, conn_type, use_v2transport); @@ -2754,7 +2757,7 @@ std::vector CConnman::GetCurrentBlockRelayOnlyConns() const return ret; } -std::vector CConnman::GetAddedNodeInfo() const +std::vector CConnman::GetAddedNodeInfo(bool include_connected) const { std::vector ret; @@ -2789,6 +2792,9 @@ std::vector CConnman::GetAddedNodeInfo() const // strAddNode is an IP:port auto it = mapConnected.find(service); if (it != mapConnected.end()) { + if (!include_connected) { + continue; + } addedNode.resolvedAddress = service; addedNode.fConnected = true; addedNode.fInbound = it->second; @@ -2797,6 +2803,9 @@ std::vector CConnman::GetAddedNodeInfo() const // strAddNode is a name auto it = mapConnectedByName.find(addr.m_added_node); if (it != mapConnectedByName.end()) { + if (!include_connected) { + continue; + } addedNode.resolvedAddress = it->second.second; addedNode.fConnected = true; addedNode.fInbound = it->second.first; @@ -2815,21 +2824,19 @@ void CConnman::ThreadOpenAddedConnections() while (true) { CSemaphoreGrant grant(*semAddnode); - std::vector vInfo = GetAddedNodeInfo(); + std::vector vInfo = GetAddedNodeInfo(/*include_connected=*/false); bool tried = false; for (const AddedNodeInfo& info : vInfo) { - if (!info.fConnected) { - if (!grant) { - // If we've used up our semaphore and need a new one, let's not wait here since while we are waiting - // the addednodeinfo state might change. - break; - } - tried = true; - CAddress addr(CService(), NODE_NONE); - OpenNetworkConnection(addr, false, std::move(grant), info.m_params.m_added_node.c_str(), ConnectionType::MANUAL, info.m_params.m_use_v2transport); - if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) return; - grant = CSemaphoreGrant(*semAddnode, /*fTry=*/true); + if (!grant) { + // If we've used up our semaphore and need a new one, let's not wait here since while we are waiting + // the addednodeinfo state might change. + break; } + tried = true; + CAddress addr(CService(), NODE_NONE); + OpenNetworkConnection(addr, false, std::move(grant), info.m_params.m_added_node.c_str(), ConnectionType::MANUAL, info.m_params.m_use_v2transport); + if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) return; + grant = CSemaphoreGrant(*semAddnode, /*fTry=*/true); } // Retry every 60 seconds if a connection was attempted, otherwise two seconds if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2))) @@ -3208,18 +3215,17 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) if (semOutbound == nullptr) { // initialize semaphore - semOutbound = std::make_unique(std::min(m_max_outbound, nMaxConnections)); + semOutbound = std::make_unique(std::min(m_max_automatic_outbound, m_max_automatic_connections)); } if (semAddnode == nullptr) { // initialize semaphore - semAddnode = std::make_unique(nMaxAddnode); + semAddnode = std::make_unique(m_max_addnode); } // // Start threads // assert(m_msgproc); - InterruptSocks5(false); interruptNet.reset(); flagInterruptMsgProc = false; @@ -3291,16 +3297,16 @@ void CConnman::Interrupt() condMsgProc.notify_all(); interruptNet(); - InterruptSocks5(true); + g_socks5_interrupt(); if (semOutbound) { - for (int i=0; ipost(); } } if (semAddnode) { - for (int i=0; ipost(); } } @@ -3426,9 +3432,12 @@ std::vector CConnman::GetAddresses(CNode& requestor, size_t max_addres bool CConnman::AddNode(const AddedNodeParams& add) { + const CService resolved(LookupNumeric(add.m_added_node, GetDefaultPort(add.m_added_node))); + const bool resolved_is_valid{resolved.IsValid()}; + LOCK(m_added_nodes_mutex); for (const auto& it : m_added_node_params) { - if (add.m_added_node == it.m_added_node) return false; + if (add.m_added_node == it.m_added_node || (resolved_is_valid && resolved == LookupNumeric(it.m_added_node, GetDefaultPort(it.m_added_node)))) return false; } m_added_node_params.push_back(add); diff --git a/src/net.h b/src/net.h index 7f48ff5ed6..974c055c53 100644 --- a/src/net.h +++ b/src/net.h @@ -1051,11 +1051,7 @@ class CConnman struct Options { ServiceFlags nLocalServices = NODE_NONE; - int nMaxConnections = 0; - int m_max_outbound_full_relay = 0; - int m_max_outbound_block_relay = 0; - int nMaxAddnode = 0; - int nMaxFeeler = 0; + int m_max_automatic_connections = 0; CClientUIInterface* uiInterface = nullptr; NetEventsInterface* m_msgproc = nullptr; BanMan* m_banman = nullptr; @@ -1082,13 +1078,12 @@ class CConnman AssertLockNotHeld(m_total_bytes_sent_mutex); nLocalServices = connOptions.nLocalServices; - nMaxConnections = connOptions.nMaxConnections; - m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); - m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay; + m_max_automatic_connections = connOptions.m_max_automatic_connections; + m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, m_max_automatic_connections); + m_max_outbound_block_relay = std::min(MAX_BLOCK_RELAY_ONLY_CONNECTIONS, m_max_automatic_connections - m_max_outbound_full_relay); + m_max_automatic_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + m_max_feeler; + m_max_inbound = std::max(0, m_max_automatic_connections - m_max_automatic_outbound); m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing; - nMaxAddnode = connOptions.nMaxAddnode; - nMaxFeeler = connOptions.nMaxFeeler; - m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler; m_client_interface = connOptions.uiInterface; m_banman = connOptions.m_banman; m_msgproc = connOptions.m_msgproc; @@ -1195,7 +1190,7 @@ class CConnman bool AddNode(const AddedNodeParams& add) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); bool RemoveAddedNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); - std::vector GetAddedNodeInfo() const EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); + std::vector GetAddedNodeInfo(bool include_connected) const EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); /** * Attempts to open a connection. Currently only used from tests. @@ -1472,7 +1467,18 @@ class CConnman std::unique_ptr semOutbound; std::unique_ptr semAddnode; - int nMaxConnections; + + /** + * Maximum number of automatic connections permitted, excluding manual + * connections but including inbounds. May be changed by the user and is + * potentially limited by the operating system (number of file descriptors). + */ + int m_max_automatic_connections; + + /* + * Maximum number of peers by connection type. Might vary from defaults + * based on -maxconnections init value. + */ // How many full-relay (tx, block, addr) outbound peers we want int m_max_outbound_full_relay; @@ -1481,9 +1487,11 @@ class CConnman // We do not relay tx or addr messages with these peers int m_max_outbound_block_relay; - int nMaxAddnode; - int nMaxFeeler; - int m_max_outbound; + int m_max_addnode{MAX_ADDNODE_CONNECTIONS}; + int m_max_feeler{MAX_FEELER_CONNECTIONS}; + int m_max_automatic_outbound; + int m_max_inbound; + bool m_use_addrman_outgoing; CClientUIInterface* m_client_interface; NetEventsInterface* m_msgproc; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 181b9d6b49..a5174261f6 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1836,6 +1836,8 @@ bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationStat case TxValidationResult::TX_CONFLICT: case TxValidationResult::TX_MEMPOOL_POLICY: case TxValidationResult::TX_NO_MEMPOOL: + case TxValidationResult::TX_RECONSIDERABLE: + case TxValidationResult::TX_UNKNOWN: break; } return false; diff --git a/src/netbase.cpp b/src/netbase.cpp index 5a609a872e..9fbd9f7dea 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -30,7 +30,7 @@ bool fNameLookup = DEFAULT_NAME_LOOKUP; // Need ample time for negotiation for very slow proxies such as Tor std::chrono::milliseconds g_socks5_recv_timeout = 20s; -static std::atomic interruptSocks5Recv(false); +CThreadInterrupt g_socks5_interrupt; ReachableNets g_reachable_nets; @@ -271,7 +271,7 @@ enum class IntrRecvError { * IntrRecvError::OK only if all of the specified number of bytes were * read. * - * @see This function can be interrupted by calling InterruptSocks5(bool). + * @see This function can be interrupted by calling g_socks5_interrupt(). * Sockets can be made non-blocking with Sock::SetNonBlocking(). */ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, std::chrono::milliseconds timeout, const Sock& sock) @@ -299,8 +299,9 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, std::chrono::m return IntrRecvError::NetworkError; } } - if (interruptSocks5Recv) + if (g_socks5_interrupt) { return IntrRecvError::Interrupted; + } curTime = Now(); } return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout; @@ -333,103 +334,93 @@ static std::string Socks5ErrorString(uint8_t err) bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* auth, const Sock& sock) { - IntrRecvError recvr; - LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); - if (strDest.size() > 255) { - return error("Hostname too long"); - } - // Construct the version identifier/method selection message - std::vector vSocks5Init; - vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol - if (auth) { - vSocks5Init.push_back(0x02); // 2 method identifiers follow... - vSocks5Init.push_back(SOCKS5Method::NOAUTH); - vSocks5Init.push_back(SOCKS5Method::USER_PASS); - } else { - vSocks5Init.push_back(0x01); // 1 method identifier follows... - vSocks5Init.push_back(SOCKS5Method::NOAUTH); - } - ssize_t ret = sock.Send(vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL); - if (ret != (ssize_t)vSocks5Init.size()) { - return error("Error sending to proxy"); - } - uint8_t pchRet1[2]; - if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { - LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port); - return false; - } - if (pchRet1[0] != SOCKSVersion::SOCKS5) { - return error("Proxy failed to initialize"); - } - if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) { - // Perform username/password authentication (as described in RFC1929) - std::vector vAuth; - vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation - if (auth->username.size() > 255 || auth->password.size() > 255) - return error("Proxy username or password too long"); - vAuth.push_back(auth->username.size()); - vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end()); - vAuth.push_back(auth->password.size()); - vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end()); - ret = sock.Send(vAuth.data(), vAuth.size(), MSG_NOSIGNAL); - if (ret != (ssize_t)vAuth.size()) { - return error("Error sending authentication to proxy"); - } - LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); - uint8_t pchRetA[2]; - if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { - return error("Error reading proxy authentication response"); + try { + IntrRecvError recvr; + LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); + if (strDest.size() > 255) { + return error("Hostname too long"); } - if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { - return error("Proxy authentication unsuccessful"); + // Construct the version identifier/method selection message + std::vector vSocks5Init; + vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol + if (auth) { + vSocks5Init.push_back(0x02); // 2 method identifiers follow... + vSocks5Init.push_back(SOCKS5Method::NOAUTH); + vSocks5Init.push_back(SOCKS5Method::USER_PASS); + } else { + vSocks5Init.push_back(0x01); // 1 method identifier follows... + vSocks5Init.push_back(SOCKS5Method::NOAUTH); } - } else if (pchRet1[1] == SOCKS5Method::NOAUTH) { - // Perform no authentication - } else { - return error("Proxy requested wrong authentication method %02x", pchRet1[1]); - } - std::vector vSocks5; - vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version - vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT - vSocks5.push_back(0x00); // RSV Reserved must be 0 - vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME - vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function - vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end()); - vSocks5.push_back((port >> 8) & 0xFF); - vSocks5.push_back((port >> 0) & 0xFF); - ret = sock.Send(vSocks5.data(), vSocks5.size(), MSG_NOSIGNAL); - if (ret != (ssize_t)vSocks5.size()) { - return error("Error sending to proxy"); - } - uint8_t pchRet2[4]; - if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { - if (recvr == IntrRecvError::Timeout) { - /* If a timeout happens here, this effectively means we timed out while connecting - * to the remote node. This is very common for Tor, so do not print an - * error message. */ + sock.SendComplete(vSocks5Init, g_socks5_recv_timeout, g_socks5_interrupt); + uint8_t pchRet1[2]; + if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { + LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port); return false; + } + if (pchRet1[0] != SOCKSVersion::SOCKS5) { + return error("Proxy failed to initialize"); + } + if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) { + // Perform username/password authentication (as described in RFC1929) + std::vector vAuth; + vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation + if (auth->username.size() > 255 || auth->password.size() > 255) + return error("Proxy username or password too long"); + vAuth.push_back(auth->username.size()); + vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end()); + vAuth.push_back(auth->password.size()); + vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end()); + sock.SendComplete(vAuth, g_socks5_recv_timeout, g_socks5_interrupt); + LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); + uint8_t pchRetA[2]; + if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { + return error("Error reading proxy authentication response"); + } + if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { + return error("Proxy authentication unsuccessful"); + } + } else if (pchRet1[1] == SOCKS5Method::NOAUTH) { + // Perform no authentication } else { - return error("Error while reading proxy response"); + return error("Proxy requested wrong authentication method %02x", pchRet1[1]); } - } - if (pchRet2[0] != SOCKSVersion::SOCKS5) { - return error("Proxy failed to accept request"); - } - if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) { - // Failures to connect to a peer that are not proxy errors - LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1])); - return false; - } - if (pchRet2[2] != 0x00) { // Reserved field must be 0 - return error("Error: malformed proxy response"); - } - uint8_t pchRet3[256]; - switch (pchRet2[3]) - { + std::vector vSocks5; + vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version + vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT + vSocks5.push_back(0x00); // RSV Reserved must be 0 + vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME + vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function + vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end()); + vSocks5.push_back((port >> 8) & 0xFF); + vSocks5.push_back((port >> 0) & 0xFF); + sock.SendComplete(vSocks5, g_socks5_recv_timeout, g_socks5_interrupt); + uint8_t pchRet2[4]; + if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { + if (recvr == IntrRecvError::Timeout) { + /* If a timeout happens here, this effectively means we timed out while connecting + * to the remote node. This is very common for Tor, so do not print an + * error message. */ + return false; + } else { + return error("Error while reading proxy response"); + } + } + if (pchRet2[0] != SOCKSVersion::SOCKS5) { + return error("Proxy failed to accept request"); + } + if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) { + // Failures to connect to a peer that are not proxy errors + LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1])); + return false; + } + if (pchRet2[2] != 0x00) { // Reserved field must be 0 + return error("Error: malformed proxy response"); + } + uint8_t pchRet3[256]; + switch (pchRet2[3]) { case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, g_socks5_recv_timeout, sock); break; case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, g_socks5_recv_timeout, sock); break; - case SOCKS5Atyp::DOMAINNAME: - { + case SOCKS5Atyp::DOMAINNAME: { recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock); if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); @@ -439,15 +430,18 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a break; } default: return error("Error: malformed proxy response"); + } + if (recvr != IntrRecvError::OK) { + return error("Error reading from proxy"); + } + if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { + return error("Error reading from proxy"); + } + LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest); + return true; + } catch (const std::runtime_error& e) { + return error("Error during SOCKS5 proxy handshake: %s", e.what()); } - if (recvr != IntrRecvError::OK) { - return error("Error reading from proxy"); - } - if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { - return error("Error reading from proxy"); - } - LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest); - return true; } std::unique_ptr CreateSockTCP(const CService& address_family) @@ -681,11 +675,6 @@ CSubNet LookupSubNet(const std::string& subnet_str) return subnet; } -void InterruptSocks5(bool interrupt) -{ - interruptSocks5Recv = interrupt; -} - bool IsBadPort(uint16_t port) { /* Don't forget to update doc/p2p-bad-ports.md if you change this list. */ diff --git a/src/netbase.h b/src/netbase.h index d51f63fd81..8523f59b4d 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -274,7 +275,10 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT */ bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed); -void InterruptSocks5(bool interrupt); +/** + * Interrupt SOCKS5 reads or writes. + */ +extern CThreadInterrupt g_socks5_interrupt; /** * Connect to a specified destination service through an already connected diff --git a/src/node/mini_miner.cpp b/src/node/mini_miner.cpp index 3d24a3f58e..58422c4439 100644 --- a/src/node/mini_miner.cpp +++ b/src/node/mini_miner.cpp @@ -78,11 +78,11 @@ MiniMiner::MiniMiner(const CTxMemPool& mempool, const std::vector& ou for (const auto& txiter : cluster) { if (!m_to_be_replaced.count(txiter->GetTx().GetHash())) { auto [mapiter, success] = m_entries_by_txid.emplace(txiter->GetTx().GetHash(), - MiniMinerMempoolEntry{/*fee_self=*/txiter->GetModifiedFee(), - /*fee_ancestor=*/txiter->GetModFeesWithAncestors(), + MiniMinerMempoolEntry{/*tx_in=*/txiter->GetSharedTx(), /*vsize_self=*/txiter->GetTxSize(), /*vsize_ancestor=*/txiter->GetSizeWithAncestors(), - /*tx_in=*/txiter->GetSharedTx()}); + /*fee_self=*/txiter->GetModifiedFee(), + /*fee_ancestor=*/txiter->GetModFeesWithAncestors()}); m_entries.push_back(mapiter); } else { auto outpoints_it = m_requested_outpoints_by_txid.find(txiter->GetTx().GetHash()); @@ -154,7 +154,7 @@ MiniMiner::MiniMiner(const std::vector& manual_entries, m_ready_to_calculate = false; return; } - std::vector cached_descendants; + std::vector descendants; for (const auto& desc_txid : desc_txids) { auto desc_it{m_entries_by_txid.find(desc_txid)}; // Descendants should only include transactions with corresponding entries. @@ -162,10 +162,10 @@ MiniMiner::MiniMiner(const std::vector& manual_entries, m_ready_to_calculate = false; return; } else { - cached_descendants.emplace_back(desc_it); + descendants.emplace_back(desc_it); } } - m_descendant_set_by_txid.emplace(txid, cached_descendants); + m_descendant_set_by_txid.emplace(txid, descendants); } Assume(m_to_be_replaced.empty()); Assume(m_requested_outpoints_by_txid.empty()); diff --git a/src/node/mini_miner.h b/src/node/mini_miner.h index 8f86709ae4..de62c0af75 100644 --- a/src/node/mini_miner.h +++ b/src/node/mini_miner.h @@ -34,11 +34,11 @@ class MiniMinerMempoolEntry // methods can be called without holding that lock. public: - explicit MiniMinerMempoolEntry(CAmount fee_self, - CAmount fee_ancestor, + explicit MiniMinerMempoolEntry(const CTransactionRef& tx_in, int64_t vsize_self, int64_t vsize_ancestor, - const CTransactionRef& tx_in): + CAmount fee_self, + CAmount fee_ancestor): tx{tx_in}, vsize_individual{vsize_self}, vsize_with_ancestors{vsize_ancestor}, @@ -137,10 +137,10 @@ class MiniMiner */ MiniMiner(const CTxMemPool& mempool, const std::vector& outpoints); - /** Constructor in which the MiniMinerMempoolEntry entries have been constructed manually, - * presumably because these transactions are not in the mempool (yet). It is assumed that all - * entries are unique and their values are correct, otherwise results computed by MiniMiner may - * be incorrect. Callers should check IsReadyToCalculate() after construction. + /** Constructor in which the MiniMinerMempoolEntry entries have been constructed manually. + * It is assumed that all entries are unique and their values are correct, otherwise results + * computed by MiniMiner may be incorrect. Callers should check IsReadyToCalculate() after + * construction. * @param[in] descendant_caches A map from each transaction to the set of txids of this * transaction's descendant set, including itself. Each tx in * manual_entries must have a corresponding entry in this map, and diff --git a/src/random.cpp b/src/random.cpp index 9bd8ff9f1a..ce4266a567 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -38,6 +38,9 @@ #ifdef HAVE_SYSCTL_ARND #include #endif +#if defined(HAVE_STRONG_GETAUXVAL) && defined(__aarch64__) +#include +#endif [[noreturn]] static void RandFailure() { @@ -173,6 +176,62 @@ static uint64_t GetRdSeed() noexcept #endif } +#elif defined(__aarch64__) && defined(HWCAP2_RNG) + +static bool g_rndr_supported = false; + +static void InitHardwareRand() +{ + if (getauxval(AT_HWCAP2) & HWCAP2_RNG) { + g_rndr_supported = true; + } +} + +static void ReportHardwareRand() +{ + // This must be done in a separate function, as InitHardwareRand() may be indirectly called + // from global constructors, before logging is initialized. + if (g_rndr_supported) { + LogPrintf("Using RNDR and RNDRRS as additional entropy sources\n"); + } +} + +/** Read 64 bits of entropy using rndr. + * + * Must only be called when RNDR is supported. + */ +static uint64_t GetRNDR() noexcept +{ + uint8_t ok; + uint64_t r1; + do { + // https://developer.arm.com/documentation/ddi0601/2022-12/AArch64-Registers/RNDR--Random-Number + __asm__ volatile("mrs %0, s3_3_c2_c4_0; cset %w1, ne;" + : "=r"(r1), "=r"(ok)::"cc"); + if (ok) break; + __asm__ volatile("yield"); + } while (true); + return r1; +} + +/** Read 64 bits of entropy using rndrrs. + * + * Must only be called when RNDRRS is supported. + */ +static uint64_t GetRNDRRS() noexcept +{ + uint8_t ok; + uint64_t r1; + do { + // https://developer.arm.com/documentation/ddi0601/2022-12/AArch64-Registers/RNDRRS--Reseeded-Random-Number + __asm__ volatile("mrs %0, s3_3_c2_c4_1; cset %w1, ne;" + : "=r"(r1), "=r"(ok)::"cc"); + if (ok) break; + __asm__ volatile("yield"); + } while (true); + return r1; +} + #else /* Access to other hardware random number generators could be added here later, * assuming it is sufficiently fast (in the order of a few hundred CPU cycles). @@ -191,6 +250,12 @@ static void SeedHardwareFast(CSHA512& hasher) noexcept { hasher.Write((const unsigned char*)&out, sizeof(out)); return; } +#elif defined(__aarch64__) && defined(HWCAP2_RNG) + if (g_rndr_supported) { + uint64_t out = GetRNDR(); + hasher.Write((const unsigned char*)&out, sizeof(out)); + return; + } #endif } @@ -216,6 +281,14 @@ static void SeedHardwareSlow(CSHA512& hasher) noexcept { } return; } +#elif defined(__aarch64__) && defined(HWCAP2_RNG) + if (g_rndr_supported) { + for (int i = 0; i < 4; ++i) { + uint64_t out = GetRNDRRS(); + hasher.Write((const unsigned char*)&out, sizeof(out)); + } + return; + } #endif } diff --git a/src/rest.cpp b/src/rest.cpp index 13bc109af1..679a92acc8 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 23cef42d30..b387febc1d 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 1a9052e008..c631132df2 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,18 @@ static RPCHelpMan ping() }; } +/** Returns, given services flags, a list of humanly readable (known) network services */ +static UniValue GetServicesNames(ServiceFlags services) +{ + UniValue servicesNames(UniValue::VARR); + + for (const auto& flag : serviceFlagsToStr(services)) { + servicesNames.push_back(flag); + } + + return servicesNames; +} + static RPCHelpMan getpeerinfo() { return RPCHelpMan{ @@ -487,7 +500,7 @@ static RPCHelpMan getaddednodeinfo() NodeContext& node = EnsureAnyNodeContext(request.context); const CConnman& connman = EnsureConnman(node); - std::vector vInfo = connman.GetAddedNodeInfo(); + std::vector vInfo = connman.GetAddedNodeInfo(/*include_connected=*/true); if (!request.params[0].isNull()) { bool found = false; diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 639237132b..63618ad202 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -1304,17 +1304,6 @@ std::vector EvalDescriptorStringOrObject(const UniValue& scanobject, Fl return ret; } -UniValue GetServicesNames(ServiceFlags services) -{ - UniValue servicesNames(UniValue::VARR); - - for (const auto& flag : serviceFlagsToStr(services)) { - servicesNames.push_back(flag); - } - - return servicesNames; -} - /** Convert a vector of bilingual strings to a UniValue::VARR containing their original untranslated values. */ [[nodiscard]] static UniValue BilingualStringsToUniValue(const std::vector& bilingual_strings) { diff --git a/src/rpc/util.h b/src/rpc/util.h index addf9000d0..d2137993de 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -60,7 +59,6 @@ extern const std::string UNIX_EPOCH_TIME; extern const std::string EXAMPLE_ADDRESS[2]; class FillableSigningProvider; -class CPubKey; class CScript; struct Sections; @@ -133,9 +131,6 @@ std::pair ParseDescriptorRange(const UniValue& value); /** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */ std::vector EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider, const bool expand_priv = false); -/** Returns, given services flags, a list of humanly readable (known) network services */ -UniValue GetServicesNames(ServiceFlags services); - /** * Serializing JSON objects depends on the outer type. Only arrays and * dictionaries can be nested in json. The top-level outer type is "NONE". diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 6e740a4f53..0fef8c5906 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -147,9 +147,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS; CConnman::Options options; - options.nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS; - options.m_max_outbound_full_relay = max_outbound_full_relay; - options.nMaxFeeler = MAX_FEELER_CONNECTIONS; + options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS; const auto time_init{GetTime()}; SetMockTime(time_init); @@ -248,9 +246,7 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction) constexpr int max_outbound_block_relay{MAX_BLOCK_RELAY_ONLY_CONNECTIONS}; constexpr int64_t MINIMUM_CONNECT_TIME{30}; CConnman::Options options; - options.nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS; - options.m_max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS; - options.m_max_outbound_block_relay = max_outbound_block_relay; + options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS; connman->Init(options); std::vector vNodes; diff --git a/src/test/fuzz/bloom_filter.cpp b/src/test/fuzz/bloom_filter.cpp index 3e303ecc0f..612f113b14 100644 --- a/src/test/fuzz/bloom_filter.cpp +++ b/src/test/fuzz/bloom_filter.cpp @@ -10,21 +10,22 @@ #include #include -#include +#include #include -#include #include FUZZ_TARGET(bloom_filter) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + bool good_data{true}; CBloomFilter bloom_filter{ fuzzed_data_provider.ConsumeIntegralInRange(1, 10000000), 1.0 / fuzzed_data_provider.ConsumeIntegralInRange(1, std::numeric_limits::max()), fuzzed_data_provider.ConsumeIntegral(), static_cast(fuzzed_data_provider.PickValueInArray({BLOOM_UPDATE_NONE, BLOOM_UPDATE_ALL, BLOOM_UPDATE_P2PUBKEY_ONLY, BLOOM_UPDATE_MASK}))}; - LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 10000) { + LIMITED_WHILE(good_data && fuzzed_data_provider.remaining_bytes() > 0, 10'000) + { CallOneOf( fuzzed_data_provider, [&] { @@ -37,6 +38,7 @@ FUZZ_TARGET(bloom_filter) [&] { const std::optional out_point = ConsumeDeserializable(fuzzed_data_provider); if (!out_point) { + good_data = false; return; } (void)bloom_filter.contains(*out_point); @@ -47,6 +49,7 @@ FUZZ_TARGET(bloom_filter) [&] { const std::optional u256 = ConsumeDeserializable(fuzzed_data_provider); if (!u256) { + good_data = false; return; } (void)bloom_filter.contains(*u256); @@ -57,6 +60,7 @@ FUZZ_TARGET(bloom_filter) [&] { const std::optional mut_tx = ConsumeDeserializable(fuzzed_data_provider); if (!mut_tx) { + good_data = false; return; } const CTransaction tx{*mut_tx}; diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index b088aa0bd7..1a8570ee8f 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -2,26 +2,28 @@ // 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