mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
Merge #6034: backport: merge bitcoin#21261, #20877, #21832, #22547, #22544, #22959, #23324, partial bitcoin#20764 (cli backports: part 2)
bde72a41fe
merge bitcoin#23324: print peer counts for all reachable networks in -netinfo (Kittywhiskers Van Gogh)4b245441a0
merge bitcoin#22959: Display all proxies in -getinfo (Kittywhiskers Van Gogh)30b0fcf4a6
merge bitcoin#22544: drop torv2; torv3 becomes onion per GetNetworkName() (Kittywhiskers Van Gogh)b6ca36edda
merge bitcoin#22547: Add progress bar for -getinfo (Kittywhiskers Van Gogh)1f89bfd176
merge bitcoin#21832: Implement human readable -getinfo (Kittywhiskers Van Gogh)2200b78a15
merge bitcoin#20877: user help and argument parsing improvements (Kittywhiskers Van Gogh)bd934c71eb
partial bitcoin#20764: cli -netinfo peer connections dashboard updates (Kittywhiskers Van Gogh)b2d865633f
merge bitcoin#21261: update inbound eviction protection for multiple networks, add I2P peers (Kittywhiskers Van Gogh)0b16b50fcb
cli: fix loop counter comparison in `ProcessReply` (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Dependency for https://github.com/dashpay/dash/pull/6035 * Dependency for https://github.com/dashpay/dash/pull/6031 * In [dash#5904](https://github.com/dashpay/dash/pull/5904) ([bitcoin#21595](https://github.com/bitcoin/bitcoin/pull/21595)), one of the loops in `ProcessReply` is supposed to iterate `rows.size()` times (which at the time was hardcoded to `3`), the backport erroneously set the value to `m_networks.size()` (which also evaluated to `3`) as part of increasing `m_networks.size()` usage. As this pull request includes [bitcoin#23324](https://github.com/bitcoin/bitcoin/pull/23324), which changes it over to `rows.size()`, the above has been corrected in a separate commit for documentation purposes. * `-addrinfo` output ![dash-cli addrinfo output](https://github.com/dashpay/dash/assets/63189531/24db46be-729e-4fa8-a268-87f2497cff9a) * `-getinfo` output (diamonds are due to rendering limitations of my terminal and are not indicative of the symbols used) ![dash-cli getinfo output](https://github.com/dashpay/dash/assets/63189531/626fe67f-f505-4a04-931a-76e75146e5a0) * `-netinfo` output ![dash-cli netinfo output](https://github.com/dashpay/dash/assets/63189531/afbff3d0-7127-44e2-bfe7-81b08c0e214e) ## Breaking Changes * CLI `-addrinfo` now returns a single field for the number of `onion` addresses known to the node instead of separate `torv2` and `torv3` fields, as support for TorV2 addresses was removed from Dash Core in 18.0. * `-getinfo` has been updated to return data in a user-friendly format that also reduces vertical space. ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas **(note: N/A)** - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone ACKs for top commit: PastaPastaPasta: utACKbde72a41fe
Tree-SHA512: 921cb45b7e243a321a32c835eb23d5ba8df610ff234a548a9051436a2c21845ce70097fb9a9bb812b77b04373f9f0a9f90264168d97b08da1890be06bfd9f99c
This commit is contained in:
commit
3b3b1b8e00
4
doc/release-notes-21832.md
Normal file
4
doc/release-notes-21832.md
Normal file
@ -0,0 +1,4 @@
|
||||
Tools and Utilities
|
||||
-------------------
|
||||
|
||||
- Update `-getinfo` to return data in a user-friendly format that also reduces vertical space.
|
6
doc/release-notes-22544.md
Normal file
6
doc/release-notes-22544.md
Normal file
@ -0,0 +1,6 @@
|
||||
Tools and Utilities
|
||||
-------------------
|
||||
|
||||
- CLI `-addrinfo` now returns a single field for the number of `onion` addresses
|
||||
known to the node instead of separate `torv2` and `torv3` fields, as support
|
||||
for Tor V2 addresses was removed from Dash Core in 18.0.
|
@ -18,10 +18,9 @@ There are several ways to see your local onion address in Dash Core:
|
||||
You may set the `-debug=tor` config logging option to have additional
|
||||
information in the debug log about your Tor configuration.
|
||||
|
||||
CLI `-addrinfo` returns the number of addresses known to your node per network
|
||||
type, including Tor v2 and v3. This is useful to see how many onion addresses
|
||||
are known to your node for `-onlynet=onion` and how many Tor v3 addresses it
|
||||
knows when upgrading to current and future Tor releases that support Tor v3 only.
|
||||
CLI `-addrinfo` returns the number of addresses known to your node per
|
||||
network. This can be useful to see how many onion peers your node knows,
|
||||
e.g. for `-onlynet=onion`.
|
||||
|
||||
## 1. Run Dash Core behind a Tor proxy
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <chainparamsbase.h>
|
||||
#include <clientversion.h>
|
||||
#include <compat.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <rpc/client.h>
|
||||
#include <rpc/mining.h>
|
||||
#include <rpc/protocol.h>
|
||||
@ -31,6 +32,10 @@
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
#include <support/events.h>
|
||||
@ -51,6 +56,9 @@ static constexpr int8_t UNKNOWN_NETWORK{-1};
|
||||
/** Default number of blocks to generate for RPC generatetoaddress. */
|
||||
static const std::string DEFAULT_NBLOCKS = "1";
|
||||
|
||||
/** Default -color setting. */
|
||||
static const std::string DEFAULT_COLOR_SETTING{"auto"};
|
||||
|
||||
static void SetupCliArgs(ArgsManager& argsman)
|
||||
{
|
||||
SetupHelpOptions(argsman);
|
||||
@ -66,6 +74,8 @@ static void SetupCliArgs(ArgsManager& argsman)
|
||||
argsman.AddArg("-addrinfo", "Get the number of addresses known to the node, per network and total.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-netinfo", "Get network peer connection information from the remote server. An optional integer argument from 0 to 4 can be passed for different peers listings (default: 0). Pass \"help\" for detailed help documentation.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
|
||||
argsman.AddArg("-color=<when>", strprintf("Color setting for CLI output (default: %s). Valid values: always, auto (add color codes when standard output is connected to a terminal and OS is not WIN32), never.", DEFAULT_COLOR_SETTING), ArgsManager::ALLOW_STRING, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
@ -249,7 +259,7 @@ public:
|
||||
class AddrinfoRequestHandler : public BaseRequestHandler
|
||||
{
|
||||
private:
|
||||
static constexpr std::array m_networks{"ipv4", "ipv6", "torv2", "torv3", "i2p"};
|
||||
static constexpr std::array m_networks{"ipv4", "ipv6", "onion", "i2p"};
|
||||
int8_t NetworkStringToId(const std::string& str) const
|
||||
{
|
||||
for (size_t i = 0; i < m_networks.size(); ++i) {
|
||||
@ -275,13 +285,10 @@ public:
|
||||
if (!nodes.empty() && nodes.at(0)["network"].isNull()) {
|
||||
throw std::runtime_error("-addrinfo requires dashd server to be running v21.0 and up");
|
||||
}
|
||||
// Count the number of peers we know by network, including torv2 versus torv3.
|
||||
// Count the number of peers known to our node, by network.
|
||||
std::array<uint64_t, m_networks.size()> counts{{}};
|
||||
for (const UniValue& node : nodes) {
|
||||
std::string network_name{node["network"].get_str()};
|
||||
if (network_name == "onion") {
|
||||
network_name = node["address"].get_str().size() > 22 ? "torv3" : "torv2";
|
||||
}
|
||||
const int8_t network_id{NetworkStringToId(network_name)};
|
||||
if (network_id == UNKNOWN_NETWORK) continue;
|
||||
++counts.at(network_id);
|
||||
@ -350,12 +357,14 @@ public:
|
||||
connections.pushKV("mn_total", batch[ID_NETWORKINFO]["result"]["connections_mn"]);
|
||||
result.pushKV("connections", connections);
|
||||
|
||||
result.pushKV("proxy", batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]);
|
||||
result.pushKV("networks", batch[ID_NETWORKINFO]["result"]["networks"]);
|
||||
result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);
|
||||
result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"]));
|
||||
if (!batch[ID_WALLETINFO]["result"].isNull()) {
|
||||
result.pushKV("coinjoin_balance", batch[ID_WALLETINFO]["result"]["coinjoin_balance"]);
|
||||
result.pushKV("has_wallet", true);
|
||||
result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]);
|
||||
result.pushKV("walletname", batch[ID_WALLETINFO]["result"]["walletname"]);
|
||||
if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {
|
||||
result.pushKV("unlocked_until", batch[ID_WALLETINFO]["result"]["unlocked_until"]);
|
||||
}
|
||||
@ -375,8 +384,10 @@ class NetinfoRequestHandler : public BaseRequestHandler
|
||||
{
|
||||
private:
|
||||
static constexpr uint8_t MAX_DETAIL_LEVEL{4};
|
||||
static constexpr std::array m_networks{"ipv4", "ipv6", "onion"};
|
||||
std::array<std::array<uint16_t, m_networks.size() + 2>, 3> m_counts{{{}}}; //!< Peer counts by (in/out/total, networks/total/block-relay)
|
||||
static constexpr std::array m_networks{"ipv4", "ipv6", "onion", "i2p"};
|
||||
std::array<std::array<uint16_t, m_networks.size() + 1>, 3> m_counts{{{}}}; //!< Peer counts by (in/out/total, networks/total)
|
||||
uint8_t m_block_relay_peers_count{0};
|
||||
uint8_t m_manual_peers_count{0};
|
||||
int8_t NetworkStringToId(const std::string& str) const
|
||||
{
|
||||
for (size_t i = 0; i < m_networks.size(); ++i) {
|
||||
@ -384,8 +395,7 @@ private:
|
||||
}
|
||||
return UNKNOWN_NETWORK;
|
||||
}
|
||||
uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level
|
||||
bool m_is_help_requested{false}; //!< Optional user-supplied arg to print help documentation
|
||||
uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level
|
||||
bool DetailsRequested() const { return m_details_level > 0 && m_details_level < 5; }
|
||||
bool IsAddressSelected() const { return m_details_level == 2 || m_details_level == 4; }
|
||||
bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; }
|
||||
@ -396,6 +406,7 @@ private:
|
||||
struct Peer {
|
||||
std::string addr;
|
||||
std::string sub_version;
|
||||
std::string conn_type;
|
||||
std::string network;
|
||||
std::string age;
|
||||
double min_ping;
|
||||
@ -425,61 +436,13 @@ private:
|
||||
const double milliseconds{round(1000 * seconds)};
|
||||
return milliseconds > 999999 ? "-" : ToString(milliseconds);
|
||||
}
|
||||
const UniValue NetinfoHelp()
|
||||
std::string ConnectionTypeForNetinfo(const std::string& conn_type) const
|
||||
{
|
||||
return std::string{
|
||||
"-netinfo level|\"help\" \n\n"
|
||||
"Returns a network peer connections dashboard with information from the remote server.\n"
|
||||
"Under the hood, -netinfo fetches the data by calling getpeerinfo and getnetworkinfo.\n"
|
||||
"An optional integer argument from 0 to 4 can be passed for different peers listings.\n"
|
||||
"Pass \"help\" to see this detailed help documentation.\n"
|
||||
"If more than one argument is passed, only the first one is read and parsed.\n"
|
||||
"Suggestion: use with the Linux watch(1) command for a live dashboard; see example below.\n\n"
|
||||
"Arguments:\n"
|
||||
"1. level (integer 0-4, optional) Specify the info level of the peers dashboard (default 0):\n"
|
||||
" 0 - Connection counts and local addresses\n"
|
||||
" 1 - Like 0 but with a peers listing (without address or version columns)\n"
|
||||
" 2 - Like 1 but with an address column\n"
|
||||
" 3 - Like 1 but with a version column\n"
|
||||
" 4 - Like 1 but with both address and version columns\n"
|
||||
"2. help (string \"help\", optional) Print this help documentation instead of the dashboard.\n\n"
|
||||
"Result:\n\n"
|
||||
"* The peers listing in levels 1-4 displays all of the peers sorted by direction and minimum ping time:\n\n"
|
||||
" Column Description\n"
|
||||
" ------ -----------\n"
|
||||
" <-> Direction\n"
|
||||
" \"in\" - inbound connections are those initiated by the peer\n"
|
||||
" \"out\" - outbound connections are those initiated by us\n"
|
||||
" type Type of peer connection\n"
|
||||
" \"full\" - full relay, the default\n"
|
||||
" \"block\" - block relay; like full relay but does not relay transactions or addresses\n"
|
||||
" net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n"
|
||||
" mping Minimum observed ping time, in milliseconds (ms)\n"
|
||||
" ping Last observed ping time, in milliseconds (ms)\n"
|
||||
" send Time since last message sent to the peer, in seconds\n"
|
||||
" recv Time since last message received from the peer, in seconds\n"
|
||||
" txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n"
|
||||
" blk Time since last novel block passing initial validity checks received from the peer, in minutes\n"
|
||||
" age Duration of connection to the peer, in minutes\n"
|
||||
" asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n"
|
||||
" peer selection (only displayed if the -asmap config option is set)\n"
|
||||
" id Peer index, in increasing order of peer connections since node startup\n"
|
||||
" address IP address and port of the peer\n"
|
||||
" version Peer version and subversion concatenated, e.g. \"70016/Satoshi:21.0.0/\"\n\n"
|
||||
"* The connection counts table displays the number of peers by direction, network, and the totals\n"
|
||||
" for each, as well as a column for block relay peers.\n\n"
|
||||
"* The local addresses table lists each local address broadcast by the node, the port, and the score.\n\n"
|
||||
"Examples:\n\n"
|
||||
"Connection counts and local addresses only\n"
|
||||
"> dash-cli -netinfo\n\n"
|
||||
"Compact peers listing\n"
|
||||
"> dash-cli -netinfo 1\n\n"
|
||||
"Full dashboard\n"
|
||||
"> dash-cli -netinfo 4\n\n"
|
||||
"Full live dashboard, adjust --interval or --no-title as needed (Linux)\n"
|
||||
"> watch --interval 1 --no-title dash-cli -netinfo 4\n\n"
|
||||
"See this help\n"
|
||||
"> dash-cli -netinfo help\n"};
|
||||
if (conn_type == "outbound-full-relay") return "full";
|
||||
if (conn_type == "block-relay-only") return "block";
|
||||
if (conn_type == "manual" || conn_type == "feeler") return conn_type;
|
||||
if (conn_type == "addr-fetch") return "addr";
|
||||
return "";
|
||||
}
|
||||
const int64_t m_time_now{GetTimeSeconds()};
|
||||
|
||||
@ -493,10 +456,8 @@ public:
|
||||
uint8_t n{0};
|
||||
if (ParseUInt8(args.at(0), &n)) {
|
||||
m_details_level = std::min(n, MAX_DETAIL_LEVEL);
|
||||
} else if (args.at(0) == "help") {
|
||||
m_is_help_requested = true;
|
||||
} else {
|
||||
throw std::runtime_error(strprintf("invalid -netinfo argument: %s", args.at(0)));
|
||||
throw std::runtime_error(strprintf("invalid -netinfo argument: %s\nFor more information, run: dash-cli -netinfo help", args.at(0)));
|
||||
}
|
||||
}
|
||||
UniValue result(UniValue::VARR);
|
||||
@ -507,9 +468,6 @@ public:
|
||||
|
||||
UniValue ProcessReply(const UniValue& batch_in) override
|
||||
{
|
||||
if (m_is_help_requested) {
|
||||
return JSONRPCReplyObj(NetinfoHelp(), NullUniValue, 1);
|
||||
}
|
||||
const std::vector<UniValue> batch{JSONRPCProcessBatchReply(batch_in)};
|
||||
if (!batch[ID_PEERINFO]["error"].isNull()) return batch[ID_PEERINFO];
|
||||
if (!batch[ID_NETWORKINFO]["error"].isNull()) return batch[ID_NETWORKINFO];
|
||||
@ -526,14 +484,13 @@ public:
|
||||
if (network_id == UNKNOWN_NETWORK) continue;
|
||||
const bool is_outbound{!peer["inbound"].get_bool()};
|
||||
const bool is_block_relay{peer["relaytxes"].isNull() ? false : !peer["relaytxes"].get_bool()};
|
||||
const std::string conn_type{peer["connection_type"].get_str()};
|
||||
++m_counts.at(is_outbound).at(network_id); // in/out by network
|
||||
++m_counts.at(is_outbound).at(m_networks.size()); // in/out overall
|
||||
++m_counts.at(2).at(network_id); // total by network
|
||||
++m_counts.at(2).at(m_networks.size()); // total overall
|
||||
if (is_block_relay) {
|
||||
++m_counts.at(is_outbound).at(m_networks.size() + 1); // in/out block-relay
|
||||
++m_counts.at(2).at(m_networks.size() + 1); // total block-relay
|
||||
}
|
||||
if (is_block_relay) ++m_block_relay_peers_count;
|
||||
if (conn_type == "manual") ++m_manual_peers_count;
|
||||
if (DetailsRequested()) {
|
||||
// Push data for this peer to the peers vector.
|
||||
const int peer_id{peer["id"].get_int()};
|
||||
@ -549,7 +506,7 @@ public:
|
||||
const std::string addr{peer["addr"].get_str()};
|
||||
const std::string age{conn_time == 0 ? "" : ToString((m_time_now - conn_time) / 60)};
|
||||
const std::string sub_version{peer["subver"].get_str()};
|
||||
m_peers.push_back({addr, sub_version, network, age, min_ping, ping, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_block_relay, is_outbound});
|
||||
m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_block_relay, is_outbound});
|
||||
m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length);
|
||||
m_max_age_length = std::max(age.length(), m_max_age_length);
|
||||
m_max_id_length = std::max(ToString(peer_id).length(), m_max_id_length);
|
||||
@ -563,15 +520,15 @@ public:
|
||||
// Report detailed peer connections list sorted by direction and minimum ping time.
|
||||
if (DetailsRequested() && !m_peers.empty()) {
|
||||
std::sort(m_peers.begin(), m_peers.end());
|
||||
result += strprintf("<-> relay net mping ping send recv txn blk %*s ", m_max_age_length, "age");
|
||||
result += strprintf("<-> type net mping ping send recv txn blk %*s ", m_max_age_length, "age");
|
||||
if (m_is_asmap_on) result += " asmap ";
|
||||
result += strprintf("%*s %-*s%s\n", m_max_id_length, "id", IsAddressSelected() ? m_max_addr_length : 0, IsAddressSelected() ? "address" : "", IsVersionSelected() ? "version" : "");
|
||||
for (const Peer& peer : m_peers) {
|
||||
std::string version{ToString(peer.version) + peer.sub_version};
|
||||
result += strprintf(
|
||||
"%3s %5s %5s%7s%7s%5s%5s%5s%5s %*s%*i %*s %-*s%s\n",
|
||||
"%3s %6s %5s%7s%7s%5s%5s%5s%5s %*s%*i %*s %-*s%s\n",
|
||||
peer.is_outbound ? "out" : "in",
|
||||
peer.is_block_relay ? "block" : "full",
|
||||
ConnectionTypeForNetinfo(peer.conn_type),
|
||||
peer.network,
|
||||
PingTimeToString(peer.min_ping),
|
||||
PingTimeToString(peer.ping),
|
||||
@ -589,18 +546,39 @@ public:
|
||||
IsAddressSelected() ? peer.addr : "",
|
||||
IsVersionSelected() && version != "0" ? version : "");
|
||||
}
|
||||
result += strprintf(" ms ms sec sec min min %*s\n\n", m_max_age_length, "min");
|
||||
result += strprintf(" ms ms sec sec min min %*s\n\n", m_max_age_length, "min");
|
||||
}
|
||||
|
||||
// Report peer connection totals by type.
|
||||
result += " ipv4 ipv6 onion total block-relay\n";
|
||||
result += " ";
|
||||
std::vector<int8_t> reachable_networks;
|
||||
for (const UniValue& network : networkinfo["networks"].getValues()) {
|
||||
if (network["reachable"].get_bool()) {
|
||||
const std::string& network_name{network["name"].get_str()};
|
||||
const int8_t network_id{NetworkStringToId(network_name)};
|
||||
if (network_id == UNKNOWN_NETWORK) continue;
|
||||
result += strprintf("%8s", network_name); // column header
|
||||
reachable_networks.push_back(network_id);
|
||||
}
|
||||
};
|
||||
result += " total block";
|
||||
if (m_manual_peers_count) result += " manual";
|
||||
|
||||
const std::array rows{"in", "out", "total"};
|
||||
for (uint8_t i = 0; i < m_networks.size(); ++i) {
|
||||
result += strprintf("%-5s %5i %5i %5i %5i %5i\n", rows.at(i), m_counts.at(i).at(0), m_counts.at(i).at(1), m_counts.at(i).at(2), m_counts.at(i).at(m_networks.size()), m_counts.at(i).at(m_networks.size() + 1));
|
||||
for (size_t i = 0; i < rows.size(); ++i) {
|
||||
result += strprintf("\n%-5s", rows[i]); // row header
|
||||
for (int8_t n : reachable_networks) {
|
||||
result += strprintf("%8i", m_counts.at(i).at(n)); // network peers count
|
||||
}
|
||||
result += strprintf(" %5i", m_counts.at(i).at(m_networks.size())); // total peers count
|
||||
if (i == 1) { // the outbound row has two extra columns for block relay and manual peer counts
|
||||
result += strprintf(" %5i", m_block_relay_peers_count);
|
||||
if (m_manual_peers_count) result += strprintf(" %5i", m_manual_peers_count);
|
||||
}
|
||||
}
|
||||
|
||||
// Report local addresses, ports, and scores.
|
||||
result += "\nLocal addresses";
|
||||
result += "\n\nLocal addresses";
|
||||
const std::vector<UniValue>& local_addrs{networkinfo["localaddresses"].getValues()};
|
||||
if (local_addrs.empty()) {
|
||||
result += ": n/a\n";
|
||||
@ -616,6 +594,64 @@ public:
|
||||
|
||||
return JSONRPCReplyObj(UniValue{result}, NullUniValue, 1);
|
||||
}
|
||||
|
||||
const std::string m_help_doc{
|
||||
"-netinfo level|\"help\" \n\n"
|
||||
"Returns a network peer connections dashboard with information from the remote server.\n"
|
||||
"This human-readable interface will change regularly and is not intended to be a stable API.\n"
|
||||
"Under the hood, -netinfo fetches the data by calling getpeerinfo and getnetworkinfo.\n"
|
||||
+ strprintf("An optional integer argument from 0 to %d can be passed for different peers listings; %d to 255 are parsed as %d.\n", MAX_DETAIL_LEVEL, MAX_DETAIL_LEVEL, MAX_DETAIL_LEVEL) +
|
||||
"Pass \"help\" to see this detailed help documentation.\n"
|
||||
"If more than one argument is passed, only the first one is read and parsed.\n"
|
||||
"Suggestion: use with the Linux watch(1) command for a live dashboard; see example below.\n\n"
|
||||
"Arguments:\n"
|
||||
+ strprintf("1. level (integer 0-%d, optional) Specify the info level of the peers dashboard (default 0):\n", MAX_DETAIL_LEVEL) +
|
||||
" 0 - Connection counts and local addresses\n"
|
||||
" 1 - Like 0 but with a peers listing (without address or version columns)\n"
|
||||
" 2 - Like 1 but with an address column\n"
|
||||
" 3 - Like 1 but with a version column\n"
|
||||
" 4 - Like 1 but with both address and version columns\n"
|
||||
"2. help (string \"help\", optional) Print this help documentation instead of the dashboard.\n\n"
|
||||
"Result:\n\n"
|
||||
+ strprintf("* The peers listing in levels 1-%d displays all of the peers sorted by direction and minimum ping time:\n\n", MAX_DETAIL_LEVEL) +
|
||||
" Column Description\n"
|
||||
" ------ -----------\n"
|
||||
" <-> Direction\n"
|
||||
" \"in\" - inbound connections are those initiated by the peer\n"
|
||||
" \"out\" - outbound connections are those initiated by us\n"
|
||||
" type Type of peer connection\n"
|
||||
" \"full\" - full relay, the default\n"
|
||||
" \"block\" - block relay; like full relay but does not relay transactions or addresses\n"
|
||||
" \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n"
|
||||
" \"feeler\" - short-lived connection for testing addresses\n"
|
||||
" \"addr\" - address fetch; short-lived connection for requesting addresses\n"
|
||||
" net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n"
|
||||
" mping Minimum observed ping time, in milliseconds (ms)\n"
|
||||
" ping Last observed ping time, in milliseconds (ms)\n"
|
||||
" send Time since last message sent to the peer, in seconds\n"
|
||||
" recv Time since last message received from the peer, in seconds\n"
|
||||
" txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n"
|
||||
" blk Time since last novel block passing initial validity checks received from the peer, in minutes\n"
|
||||
" age Duration of connection to the peer, in minutes\n"
|
||||
" asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n"
|
||||
" peer selection (only displayed if the -asmap config option is set)\n"
|
||||
" id Peer index, in increasing order of peer connections since node startup\n"
|
||||
" address IP address and port of the peer\n"
|
||||
" version Peer version and subversion concatenated, e.g. \"70016/Satoshi:21.0.0/\"\n\n"
|
||||
"* The connection counts table displays the number of peers by direction, network, and the totals\n"
|
||||
" for each, as well as two special outbound columns for block relay peers and manual peers.\n\n"
|
||||
"* The local addresses table lists each local address broadcast by the node, the port, and the score.\n\n"
|
||||
"Examples:\n\n"
|
||||
"Connection counts and local addresses only\n"
|
||||
"> dash-cli -netinfo\n\n"
|
||||
"Compact peers listing\n"
|
||||
"> dash-cli -netinfo 1\n\n"
|
||||
"Full dashboard\n"
|
||||
+ strprintf("> dash-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) +
|
||||
"Full live dashboard, adjust --interval or --no-title as needed (Linux)\n"
|
||||
+ strprintf("> watch --interval 1 --no-title dash-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) +
|
||||
"See this help\n"
|
||||
"> dash-cli -netinfo help\n"};
|
||||
};
|
||||
|
||||
/** Process RPC generatetoaddress request. */
|
||||
@ -866,6 +902,156 @@ static void GetWalletBalances(UniValue& result)
|
||||
result.pushKV("balances", balances);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetProgressBar contructs a progress bar with 5% intervals.
|
||||
*
|
||||
* @param[in] progress The proportion of the progress bar to be filled between 0 and 1.
|
||||
* @param[out] progress_bar String representation of the progress bar.
|
||||
*/
|
||||
static void GetProgressBar(double progress, std::string& progress_bar)
|
||||
{
|
||||
if (progress < 0 || progress > 1) return;
|
||||
|
||||
static constexpr double INCREMENT{0.05};
|
||||
static const std::string COMPLETE_BAR{"\u2592"};
|
||||
static const std::string INCOMPLETE_BAR{"\u2591"};
|
||||
|
||||
for (int i = 0; i < progress / INCREMENT; ++i) {
|
||||
progress_bar += COMPLETE_BAR;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (1 - progress) / INCREMENT; ++i) {
|
||||
progress_bar += INCOMPLETE_BAR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ParseGetInfoResult takes in -getinfo result in UniValue object and parses it
|
||||
* into a user friendly UniValue string to be printed on the console.
|
||||
* @param[out] result Reference to UniValue result containing the -getinfo output.
|
||||
*/
|
||||
static void ParseGetInfoResult(UniValue& result)
|
||||
{
|
||||
if (!find_value(result, "error").isNull()) return;
|
||||
|
||||
std::string RESET, GREEN, BLUE, YELLOW, MAGENTA, CYAN;
|
||||
bool should_colorize = false;
|
||||
|
||||
#ifndef WIN32
|
||||
if (isatty(fileno(stdout))) {
|
||||
// By default, only print colored text if OS is not WIN32 and stdout is connected to a terminal.
|
||||
should_colorize = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gArgs.IsArgSet("-color")) {
|
||||
const std::string color{gArgs.GetArg("-color", DEFAULT_COLOR_SETTING)};
|
||||
if (color == "always") {
|
||||
should_colorize = true;
|
||||
} else if (color == "never") {
|
||||
should_colorize = false;
|
||||
} else if (color != "auto") {
|
||||
throw std::runtime_error("Invalid value for -color option. Valid values: always, auto, never.");
|
||||
}
|
||||
}
|
||||
|
||||
if (should_colorize) {
|
||||
RESET = "\x1B[0m";
|
||||
GREEN = "\x1B[32m";
|
||||
BLUE = "\x1B[34m";
|
||||
YELLOW = "\x1B[33m";
|
||||
MAGENTA = "\x1B[35m";
|
||||
CYAN = "\x1B[36m";
|
||||
}
|
||||
|
||||
std::string result_string = strprintf("%sChain: %s%s\n", BLUE, result["chain"].getValStr(), RESET);
|
||||
result_string += strprintf("Blocks: %s\n", result["blocks"].getValStr());
|
||||
result_string += strprintf("Headers: %s\n", result["headers"].getValStr());
|
||||
|
||||
const double ibd_progress{result["verificationprogress"].get_real()};
|
||||
std::string ibd_progress_bar;
|
||||
// Display the progress bar only if IBD progress is less than 99%
|
||||
if (ibd_progress < 0.99) {
|
||||
GetProgressBar(ibd_progress, ibd_progress_bar);
|
||||
// Add padding between progress bar and IBD progress
|
||||
ibd_progress_bar += " ";
|
||||
}
|
||||
|
||||
result_string += strprintf("Verification progress: %s%.4f%%\n", ibd_progress_bar, ibd_progress * 100);
|
||||
result_string += strprintf("Difficulty: %s\n\n", result["difficulty"].getValStr());
|
||||
|
||||
result_string += strprintf(
|
||||
"%sNetwork: in %s, out %s, total %s, mn_in %s, mn_out %s, mn_total %s%s\n",
|
||||
GREEN,
|
||||
result["connections"]["in"].getValStr(),
|
||||
result["connections"]["out"].getValStr(),
|
||||
result["connections"]["total"].getValStr(),
|
||||
result["connections"]["mn_in"].getValStr(),
|
||||
result["connections"]["mn_out"].getValStr(),
|
||||
result["connections"]["mn_total"].getValStr(),
|
||||
RESET);
|
||||
result_string += strprintf("Version: %s\n", result["version"].getValStr());
|
||||
result_string += strprintf("Time offset (s): %s\n", result["timeoffset"].getValStr());
|
||||
|
||||
// proxies
|
||||
std::map<std::string, std::vector<std::string>> proxy_networks;
|
||||
std::vector<std::string> ordered_proxies;
|
||||
|
||||
for (const UniValue& network : result["networks"].getValues()) {
|
||||
const std::string proxy = network["proxy"].getValStr();
|
||||
if (proxy.empty()) continue;
|
||||
// Add proxy to ordered_proxy if has not been processed
|
||||
if (proxy_networks.find(proxy) == proxy_networks.end()) ordered_proxies.push_back(proxy);
|
||||
|
||||
proxy_networks[proxy].push_back(network["name"].getValStr());
|
||||
}
|
||||
|
||||
std::vector<std::string> formatted_proxies;
|
||||
for (const std::string& proxy : ordered_proxies) {
|
||||
formatted_proxies.emplace_back(strprintf("%s (%s)", proxy, Join(proxy_networks.find(proxy)->second, ", ")));
|
||||
}
|
||||
result_string += strprintf("Proxies: %s\n", formatted_proxies.empty() ? "n/a" : Join(formatted_proxies, ", "));
|
||||
|
||||
result_string += strprintf("Min tx relay fee rate (%s/kB): %s\n\n", CURRENCY_UNIT, result["relayfee"].getValStr());
|
||||
|
||||
if (!result["has_wallet"].isNull()) {
|
||||
const std::string walletname = result["walletname"].getValStr();
|
||||
result_string += strprintf("%sWallet: %s%s\n", MAGENTA, walletname.empty() ? "\"\"" : walletname, RESET);
|
||||
|
||||
result_string += strprintf("%sCoinJoin balance:%s %s\n", CYAN, RESET, result["coinjoin_balance"].getValStr());
|
||||
|
||||
result_string += strprintf("Keypool size: %s\n", result["keypoolsize"].getValStr());
|
||||
if (!result["unlocked_until"].isNull()) {
|
||||
result_string += strprintf("Unlocked until: %s\n", result["unlocked_until"].getValStr());
|
||||
}
|
||||
result_string += strprintf("Transaction fee rate (-paytxfee) (%s/kB): %s\n\n", CURRENCY_UNIT, result["paytxfee"].getValStr());
|
||||
}
|
||||
if (!result["balance"].isNull()) {
|
||||
result_string += strprintf("%sBalance:%s %s\n\n", CYAN, RESET, result["balance"].getValStr());
|
||||
}
|
||||
|
||||
if (!result["balances"].isNull()) {
|
||||
result_string += strprintf("%sBalances%s\n", CYAN, RESET);
|
||||
|
||||
size_t max_balance_length{10};
|
||||
|
||||
for (const std::string& wallet : result["balances"].getKeys()) {
|
||||
max_balance_length = std::max(result["balances"][wallet].getValStr().length(), max_balance_length);
|
||||
}
|
||||
|
||||
for (const std::string& wallet : result["balances"].getKeys()) {
|
||||
result_string += strprintf("%*s %s\n",
|
||||
max_balance_length,
|
||||
result["balances"][wallet].getValStr(),
|
||||
wallet.empty() ? "\"\"" : wallet);
|
||||
}
|
||||
result_string += "\n";
|
||||
}
|
||||
|
||||
result_string += strprintf("%sWarnings:%s %s", YELLOW, RESET, result["warnings"].getValStr());
|
||||
result.setStr(result_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call RPC getnewaddress.
|
||||
* @returns getnewaddress response as a UniValue object.
|
||||
@ -953,6 +1139,10 @@ static int CommandLineRPC(int argc, char *argv[])
|
||||
if (gArgs.IsArgSet("-getinfo")) {
|
||||
rh.reset(new GetinfoRequestHandler());
|
||||
} else if (gArgs.GetBoolArg("-netinfo", false)) {
|
||||
if (!args.empty() && args.at(0) == "help") {
|
||||
tfm::format(std::cout, "%s\n", NetinfoRequestHandler().m_help_doc);
|
||||
return 0;
|
||||
}
|
||||
rh.reset(new NetinfoRequestHandler());
|
||||
} else if (gArgs.GetBoolArg("-generate", false)) {
|
||||
const UniValue getnewaddress{GetNewAddress()};
|
||||
@ -983,9 +1173,13 @@ static int CommandLineRPC(int argc, char *argv[])
|
||||
UniValue result = find_value(reply, "result");
|
||||
const UniValue& error = find_value(reply, "error");
|
||||
if (error.isNull()) {
|
||||
if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
|
||||
GetWalletBalances(result); // fetch multiwallet balances and append to result
|
||||
if (gArgs.GetBoolArg("-getinfo", false)) {
|
||||
if (!gArgs.IsArgSet("-rpcwallet")) {
|
||||
GetWalletBalances(result); // fetch multiwallet balances and append to result
|
||||
}
|
||||
ParseGetInfoResult(result);
|
||||
}
|
||||
|
||||
ParseResult(result, strPrint);
|
||||
} else {
|
||||
ParseError(error, strPrint, nRet);
|
||||
|
116
src/net.cpp
116
src/net.cpp
@ -62,6 +62,7 @@
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
@ -913,18 +914,6 @@ static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate& a, cons
|
||||
return a.nTimeConnected > b.nTimeConnected;
|
||||
}
|
||||
|
||||
static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
|
||||
{
|
||||
if (a.m_is_local != b.m_is_local) return b.m_is_local;
|
||||
return a.nTimeConnected > b.nTimeConnected;
|
||||
}
|
||||
|
||||
static bool CompareOnionTimeConnected(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b)
|
||||
{
|
||||
if (a.m_is_onion != b.m_is_onion) return b.m_is_onion;
|
||||
return a.nTimeConnected > b.nTimeConnected;
|
||||
}
|
||||
|
||||
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
|
||||
return a.nKeyedNetGroup < b.nKeyedNetGroup;
|
||||
}
|
||||
@ -955,6 +944,26 @@ static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const
|
||||
return a.nTimeConnected > b.nTimeConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort eviction candidates by network/localhost and connection uptime.
|
||||
* Candidates near the beginning are more likely to be evicted, and those
|
||||
* near the end are more likely to be protected, e.g. less likely to be evicted.
|
||||
* - First, nodes that are not `is_local` and that do not belong to `network`,
|
||||
* sorted by increasing uptime (from most recently connected to connected longer).
|
||||
* - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime.
|
||||
*/
|
||||
struct CompareNodeNetworkTime {
|
||||
const bool m_is_local;
|
||||
const Network m_network;
|
||||
CompareNodeNetworkTime(bool is_local, Network network) : m_is_local(is_local), m_network(network) {}
|
||||
bool operator()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const
|
||||
{
|
||||
if (m_is_local && a.m_is_local != b.m_is_local) return b.m_is_local;
|
||||
if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network;
|
||||
return a.nTimeConnected > b.nTimeConnected;
|
||||
};
|
||||
};
|
||||
|
||||
//! Sort an array by the specified comparator, then erase the last K elements where predicate is true.
|
||||
template <typename T, typename Comparator>
|
||||
static void EraseLastKElements(
|
||||
@ -966,40 +975,72 @@ static void EraseLastKElements(
|
||||
elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end());
|
||||
}
|
||||
|
||||
void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates)
|
||||
void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& eviction_candidates)
|
||||
{
|
||||
// Protect the half of the remaining nodes which have been connected the longest.
|
||||
// This replicates the non-eviction implicit behavior, and precludes attacks that start later.
|
||||
// To favorise the diversity of our peer connections, reserve up to (half + 2) of
|
||||
// these protected spots for onion and localhost peers, if any, even if they're not
|
||||
// longest uptime overall. This helps protect tor peers, which tend to be otherwise
|
||||
// To favorise the diversity of our peer connections, reserve up to half of these protected
|
||||
// spots for Tor/onion, localhost and I2P peers, even if they're not longest uptime overall.
|
||||
// This helps protect these higher-latency peers that tend to be otherwise
|
||||
// disadvantaged under our eviction criteria.
|
||||
const size_t initial_size = vEvictionCandidates.size();
|
||||
size_t total_protect_size = initial_size / 2;
|
||||
const size_t onion_protect_size = total_protect_size / 2;
|
||||
const size_t initial_size = eviction_candidates.size();
|
||||
const size_t total_protect_size{initial_size / 2};
|
||||
|
||||
if (onion_protect_size) {
|
||||
// Pick out up to 1/4 peers connected via our onion service, sorted by longest uptime.
|
||||
EraseLastKElements(vEvictionCandidates, CompareOnionTimeConnected, onion_protect_size,
|
||||
[](const NodeEvictionCandidate& n) { return n.m_is_onion; });
|
||||
// Disadvantaged networks to protect: I2P, localhost, Tor/onion. In case of equal counts, earlier
|
||||
// array members have first opportunity to recover unused slots from the previous iteration.
|
||||
struct Net { bool is_local; Network id; size_t count; };
|
||||
std::array<Net, 3> networks{
|
||||
{{false, NET_I2P, 0}, {/* localhost */ true, NET_MAX, 0}, {false, NET_ONION, 0}}};
|
||||
|
||||
// Count and store the number of eviction candidates per network.
|
||||
for (Net& n : networks) {
|
||||
n.count = std::count_if(eviction_candidates.cbegin(), eviction_candidates.cend(),
|
||||
[&n](const NodeEvictionCandidate& c) {
|
||||
return n.is_local ? c.m_is_local : c.m_network == n.id;
|
||||
});
|
||||
}
|
||||
// Sort `networks` by ascending candidate count, to give networks having fewer candidates
|
||||
// the first opportunity to recover unused protected slots from the previous iteration.
|
||||
std::stable_sort(networks.begin(), networks.end(), [](Net a, Net b) { return a.count < b.count; });
|
||||
|
||||
const size_t localhost_min_protect_size{2};
|
||||
if (onion_protect_size >= localhost_min_protect_size) {
|
||||
// Allocate any remaining slots of the 1/4, or minimum 2 additional slots,
|
||||
// to localhost peers, sorted by longest uptime, as manually configured
|
||||
// hidden services not using `-bind=addr[:port]=onion` will not be detected
|
||||
// as inbound onion connections.
|
||||
const size_t remaining_tor_slots{onion_protect_size - (initial_size - vEvictionCandidates.size())};
|
||||
const size_t localhost_protect_size{std::max(remaining_tor_slots, localhost_min_protect_size)};
|
||||
EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, localhost_protect_size,
|
||||
[](const NodeEvictionCandidate& n) { return n.m_is_local; });
|
||||
// Protect up to 25% of the eviction candidates by disadvantaged network.
|
||||
const size_t max_protect_by_network{total_protect_size / 2};
|
||||
size_t num_protected{0};
|
||||
|
||||
while (num_protected < max_protect_by_network) {
|
||||
const size_t disadvantaged_to_protect{max_protect_by_network - num_protected};
|
||||
const size_t protect_per_network{
|
||||
std::max(disadvantaged_to_protect / networks.size(), static_cast<size_t>(1))};
|
||||
|
||||
// Early exit flag if there are no remaining candidates by disadvantaged network.
|
||||
bool protected_at_least_one{false};
|
||||
|
||||
for (const Net& n : networks) {
|
||||
if (n.count == 0) continue;
|
||||
const size_t before = eviction_candidates.size();
|
||||
EraseLastKElements(eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id),
|
||||
protect_per_network, [&n](const NodeEvictionCandidate& c) {
|
||||
return n.is_local ? c.m_is_local : c.m_network == n.id;
|
||||
});
|
||||
const size_t after = eviction_candidates.size();
|
||||
if (before > after) {
|
||||
protected_at_least_one = true;
|
||||
num_protected += before - after;
|
||||
if (num_protected >= max_protect_by_network) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!protected_at_least_one) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many we removed, and update our total number of peers that
|
||||
// we want to protect based on uptime accordingly.
|
||||
total_protect_size -= initial_size - vEvictionCandidates.size();
|
||||
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size);
|
||||
assert(num_protected == initial_size - eviction_candidates.size());
|
||||
const size_t remaining_to_protect{total_protect_size - num_protected};
|
||||
EraseLastKElements(eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
|
||||
@ -1016,8 +1057,7 @@ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvict
|
||||
// An attacker cannot manipulate this metric without performing useful work.
|
||||
EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
|
||||
// Protect up to 8 non-tx-relay peers that have sent us novel blocks.
|
||||
const size_t erase_size = std::min(size_t(8), vEvictionCandidates.size());
|
||||
EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, erase_size,
|
||||
EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8,
|
||||
[](const NodeEvictionCandidate& n) { return !n.m_relay_txs && n.fRelevantServices; });
|
||||
|
||||
// Protect 4 nodes that most recently sent us novel blocks.
|
||||
@ -1109,7 +1149,7 @@ bool CConnman::AttemptToEvictConnection()
|
||||
HasAllDesirableServiceFlags(node->nServices),
|
||||
node->m_relays_txs.load(), node->m_bloom_filter_loaded.load(),
|
||||
node->nKeyedNetGroup, node->m_prefer_evict, node->addr.IsLocal(),
|
||||
node->m_inbound_onion};
|
||||
node->ConnectedThroughNetwork()};
|
||||
vEvictionCandidates.push_back(candidate);
|
||||
}
|
||||
}
|
||||
|
26
src/net.h
26
src/net.h
@ -1583,7 +1583,7 @@ struct NodeEvictionCandidate
|
||||
uint64_t nKeyedNetGroup;
|
||||
bool prefer_evict;
|
||||
bool m_is_local;
|
||||
bool m_is_onion;
|
||||
Network m_network;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1613,20 +1613,20 @@ size_t GetRequestedObjectCount(NodeId nodeId) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
* 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 onion peers
|
||||
* connected via our tor control service, if any, sorted by longest uptime, even
|
||||
* if they're not longest uptime overall. Any remaining slots of the 1/4 are
|
||||
* then allocated to protect localhost peers, if any (or up to 2 localhost peers
|
||||
* if no slots remain and 2 or more onion peers were protected), sorted by
|
||||
* longest uptime, as manually configured hidden services not using
|
||||
* `-bind=addr[:port]=onion` will not be detected as inbound onion connections.
|
||||
* 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:
|
||||
*
|
||||
* This helps protect onion peers, which tend to be otherwise disadvantaged
|
||||
* under our eviction criteria for their higher min ping times relative to IPv4
|
||||
* and IPv6 peers, and favorise the diversity of peer connections.
|
||||
* - onion peers connected via our tor control service
|
||||
*
|
||||
* This function was extracted from SelectNodeToEvict() to be able to test the
|
||||
* ratio-based protection logic deterministically.
|
||||
* - 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<NodeEvictionCandidate>& vEvictionCandidates);
|
||||
|
||||
|
@ -31,7 +31,7 @@ FUZZ_TARGET(node_eviction)
|
||||
/* nKeyedNetGroup */ fuzzed_data_provider.ConsumeIntegral<uint64_t>(),
|
||||
/* prefer_evict */ fuzzed_data_provider.ConsumeBool(),
|
||||
/* m_is_local */ fuzzed_data_provider.ConsumeBool(),
|
||||
/* m_is_onion */ fuzzed_data_provider.ConsumeBool(),
|
||||
/* m_network */ fuzzed_data_provider.PickValueInArray(ALL_NETWORKS),
|
||||
});
|
||||
}
|
||||
// Make a copy since eviction_candidates may be in some valid but otherwise
|
||||
|
@ -2,7 +2,9 @@
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <netaddress.h>
|
||||
#include <net.h>
|
||||
#include <test/util/net.h>
|
||||
#include <test/util/setup_common.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
@ -15,11 +17,6 @@
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup)
|
||||
|
||||
namespace {
|
||||
constexpr int NODE_EVICTION_TEST_ROUNDS{10};
|
||||
constexpr int NODE_EVICTION_TEST_UP_TO_N_NODES{200};
|
||||
} // namespace
|
||||
|
||||
std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_candidates, FastRandomContext& random_context)
|
||||
{
|
||||
std::vector<NodeEvictionCandidate> candidates;
|
||||
@ -36,7 +33,7 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_c
|
||||
/* nKeyedNetGroup */ random_context.randrange(100),
|
||||
/* prefer_evict */ random_context.randbool(),
|
||||
/* m_is_local */ random_context.randbool(),
|
||||
/* m_is_onion */ random_context.randbool(),
|
||||
/* m_network */ ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())],
|
||||
});
|
||||
}
|
||||
return candidates;
|
||||
@ -94,7 +91,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_onion = c.m_is_local = false;
|
||||
c.m_is_local = false;
|
||||
c.m_network = NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 4, 5},
|
||||
/* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11},
|
||||
@ -104,129 +102,359 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [num_peers](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = num_peers - c.id;
|
||||
c.m_is_onion = c.m_is_local = false;
|
||||
c.m_is_local = false;
|
||||
c.m_network = NET_IPV6;
|
||||
},
|
||||
/* protected_peer_ids */ {6, 7, 8, 9, 10, 11},
|
||||
/* unprotected_peer_ids */ {0, 1, 2, 3, 4, 5},
|
||||
random_context));
|
||||
|
||||
// Test protection of onion and localhost peers...
|
||||
// Test protection of onion, localhost, and I2P peers...
|
||||
|
||||
// Expect 1/4 onion peers to be protected from eviction,
|
||||
// independently of other characteristics.
|
||||
// if no localhost or I2P peers.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.m_is_onion = (c.id == 3 || c.id == 8 || c.id == 9);
|
||||
c.m_is_local = false;
|
||||
c.m_network = (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {3, 8, 9},
|
||||
/* unprotected_peer_ids */ {},
|
||||
random_context));
|
||||
|
||||
// Expect 1/4 onion peers and 1/4 of the others to be protected
|
||||
// from eviction, sorted by longest uptime (lowest nTimeConnected).
|
||||
// Expect 1/4 onion peers and 1/4 of the other peers to be protected,
|
||||
// sorted by longest uptime (lowest nTimeConnected), if no localhost or I2P peers.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = false;
|
||||
c.m_is_onion = (c.id == 3 || c.id > 7);
|
||||
c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 8, 9},
|
||||
/* unprotected_peer_ids */ {4, 5, 6, 7, 10, 11},
|
||||
random_context));
|
||||
|
||||
// Expect 1/4 localhost peers to be protected from eviction,
|
||||
// if no onion peers.
|
||||
// if no onion or I2P peers.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.m_is_onion = false;
|
||||
c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
|
||||
c.m_network = NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {1, 9, 11},
|
||||
/* unprotected_peer_ids */ {},
|
||||
random_context));
|
||||
|
||||
// Expect 1/4 localhost peers and 1/4 of the other peers to be protected,
|
||||
// sorted by longest uptime (lowest nTimeConnected), if no onion peers.
|
||||
// sorted by longest uptime (lowest nTimeConnected), if no onion or I2P peers.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_onion = false;
|
||||
c.m_is_local = (c.id > 6);
|
||||
c.m_network = NET_IPV6;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 7, 8, 9},
|
||||
/* unprotected_peer_ids */ {3, 4, 5, 6, 10, 11},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect 1/4 onion and 2 localhost peers to be protected
|
||||
// from eviction, sorted by longest uptime.
|
||||
// Expect 1/4 I2P peers to be protected from eviction,
|
||||
// if no onion or localhost peers.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.m_is_local = false;
|
||||
c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {2, 7, 10},
|
||||
/* unprotected_peer_ids */ {},
|
||||
random_context));
|
||||
|
||||
// Expect 1/4 I2P peers and 1/4 of the other peers to be protected,
|
||||
// sorted by longest uptime (lowest nTimeConnected), if no onion or localhost peers.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_onion = (c.id == 0 || c.id == 5 || c.id == 10);
|
||||
c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
|
||||
c.m_is_local = false;
|
||||
c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 5, 9, 10},
|
||||
/* unprotected_peer_ids */ {3, 4, 6, 7, 8, 11},
|
||||
/* protected_peer_ids */ {0, 1, 2, 4, 9, 10},
|
||||
/* unprotected_peer_ids */ {3, 5, 6, 7, 8, 11},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having only 1 onion to allow allocating the
|
||||
// remaining 2 of the 1/4 to localhost peers, sorted by longest uptime.
|
||||
// Tests with 2 networks...
|
||||
|
||||
// Combined test: expect having 1 localhost and 1 onion peer out of 4 to
|
||||
// protect 1 localhost, 0 onion and 1 other peer, sorted by longest uptime;
|
||||
// stable sort breaks tie with array order of localhost first.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers + 4, [](NodeEvictionCandidate& c) {
|
||||
4, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_onion = (c.id == 15);
|
||||
c.m_is_local = (c.id > 6 && c.id < 11);
|
||||
c.m_is_local = (c.id == 4);
|
||||
c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15},
|
||||
/* unprotected_peer_ids */ {4, 5, 6, 10, 11, 12, 13, 14},
|
||||
/* protected_peer_ids */ {0, 4},
|
||||
/* unprotected_peer_ids */ {1, 2},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect 2 onions (< 1/4) to allow allocating the minimum 2
|
||||
// localhost peers, sorted by longest uptime.
|
||||
// Combined test: expect having 1 localhost and 1 onion peer out of 7 to
|
||||
// protect 1 localhost, 0 onion, and 2 other peers (3 total), sorted by
|
||||
// uptime; stable sort breaks tie with array order of localhost first.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
7, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_onion = (c.id == 7 || c.id == 9);
|
||||
c.m_is_local = (c.id == 6 || c.id == 11);
|
||||
c.m_is_local = (c.id == 6);
|
||||
c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 6, 7, 9, 11},
|
||||
/* unprotected_peer_ids */ {2, 3, 4, 5, 8, 10},
|
||||
/* protected_peer_ids */ {0, 1, 6},
|
||||
/* unprotected_peer_ids */ {2, 3, 4, 5},
|
||||
random_context));
|
||||
|
||||
// Combined test: when > 1/4, expect max 1/4 onion and 2 localhost peers
|
||||
// to be protected from eviction, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_onion = (c.id > 3 && c.id < 8);
|
||||
c.m_is_local = (c.id > 7);
|
||||
},
|
||||
/* protected_peer_ids */ {0, 4, 5, 6, 8, 9},
|
||||
/* unprotected_peer_ids */ {1, 2, 3, 7, 10, 11},
|
||||
random_context));
|
||||
|
||||
// Combined test: idem > 1/4 with only 8 peers: expect 2 onion and 2
|
||||
// localhost peers (1/4 + 2) to be protected, sorted by longest uptime.
|
||||
// Combined test: expect having 1 localhost and 1 onion peer out of 8 to
|
||||
// protect protect 1 localhost, 1 onion and 2 other peers (4 total), sorted
|
||||
// by uptime; stable sort breaks tie with array order of localhost first.
|
||||
BOOST_CHECK(IsProtected(
|
||||
8, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_onion = (c.id > 1 && c.id < 5);
|
||||
c.m_is_local = (c.id > 4);
|
||||
c.m_is_local = (c.id == 6);
|
||||
c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {2, 3, 5, 6},
|
||||
/* unprotected_peer_ids */ {0, 1, 4, 7},
|
||||
/* protected_peer_ids */ {0, 1, 5, 6},
|
||||
/* unprotected_peer_ids */ {2, 3, 4, 7},
|
||||
random_context));
|
||||
|
||||
// Combined test: idem > 1/4 with only 6 peers: expect 1 onion peer and no
|
||||
// localhost peers (1/4 + 0) to be protected, sorted by longest uptime.
|
||||
// Combined test: expect having 3 localhost and 3 onion peers out of 12 to
|
||||
// protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest
|
||||
// uptime; stable sort breaks ties with the array order of localhost first.
|
||||
BOOST_CHECK(IsProtected(
|
||||
6, [](NodeEvictionCandidate& c) {
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_onion = (c.id == 4 || c.id == 5);
|
||||
c.m_is_local = (c.id == 3);
|
||||
c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11);
|
||||
c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 4},
|
||||
/* unprotected_peer_ids */ {2, 3, 5},
|
||||
/* protected_peer_ids */ {0, 1, 2, 6, 7, 9},
|
||||
/* unprotected_peer_ids */ {3, 4, 5, 8, 10, 11},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 4 localhost and 1 onion peer out of 12 to
|
||||
// protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id > 4 && c.id < 9);
|
||||
c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 5, 6, 10},
|
||||
/* unprotected_peer_ids */ {3, 4, 7, 8, 9, 11},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 4 localhost and 2 onion peers out of 16 to
|
||||
// protect 2 localhost and 2 onions, plus 4 other peers, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
16, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12);
|
||||
c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 6, 8, 9, 10},
|
||||
/* unprotected_peer_ids */ {4, 5, 7, 11, 12, 13, 14, 15},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 5 localhost and 1 onion peer out of 16 to
|
||||
// protect 3 localhost (recovering the unused onion slot), 1 onion, and 4
|
||||
// others, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
16, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id > 10);
|
||||
c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 10, 11, 12, 13},
|
||||
/* unprotected_peer_ids */ {4, 5, 6, 7, 8, 9, 14, 15},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 1 localhost and 4 onion peers out of 16 to
|
||||
// protect 1 localhost and 3 onions (recovering the unused localhost slot),
|
||||
// plus 4 others, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
16, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 15);
|
||||
c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6;
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15},
|
||||
/* unprotected_peer_ids */ {5, 6, 10, 11, 12, 13, 14},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 2 onion and 4 I2P out of 12 peers to protect
|
||||
// 2 onion (prioritized for having fewer candidates) and 1 I2P, plus 3
|
||||
// others, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
num_peers, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = false;
|
||||
if (c.id == 8 || c.id == 10) {
|
||||
c.m_network = NET_ONION;
|
||||
} else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) {
|
||||
c.m_network = NET_I2P;
|
||||
} else {
|
||||
c.m_network = NET_IPV4;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 6, 8, 10},
|
||||
/* unprotected_peer_ids */ {3, 4, 5, 7, 9, 11},
|
||||
random_context));
|
||||
|
||||
// Tests with 3 networks...
|
||||
|
||||
// Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 4
|
||||
// to protect 1 I2P, 0 localhost, 0 onion and 1 other peer (2 total), sorted
|
||||
// by longest uptime; stable sort breaks tie with array order of I2P first.
|
||||
BOOST_CHECK(IsProtected(
|
||||
4, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 3);
|
||||
if (c.id == 4) {
|
||||
c.m_network = NET_I2P;
|
||||
} else if (c.id == 2) {
|
||||
c.m_network = NET_ONION;
|
||||
} else {
|
||||
c.m_network = NET_IPV6;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 4},
|
||||
/* unprotected_peer_ids */ {1, 2},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 7
|
||||
// to protect 1 I2P, 0 localhost, 0 onion and 2 other peers (3 total) sorted
|
||||
// by longest uptime; stable sort breaks tie with array order of I2P first.
|
||||
BOOST_CHECK(IsProtected(
|
||||
7, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 4);
|
||||
if (c.id == 6) {
|
||||
c.m_network = NET_I2P;
|
||||
} else if (c.id == 5) {
|
||||
c.m_network = NET_ONION;
|
||||
} else {
|
||||
c.m_network = NET_IPV6;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 6},
|
||||
/* unprotected_peer_ids */ {2, 3, 4, 5},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 8
|
||||
// to protect 1 I2P, 1 localhost, 0 onion and 2 other peers (4 total) sorted
|
||||
// by uptime; stable sort breaks tie with array order of I2P then localhost.
|
||||
BOOST_CHECK(IsProtected(
|
||||
8, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 6);
|
||||
if (c.id == 5) {
|
||||
c.m_network = NET_I2P;
|
||||
} else if (c.id == 4) {
|
||||
c.m_network = NET_ONION;
|
||||
} else {
|
||||
c.m_network = NET_IPV6;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 5, 6},
|
||||
/* unprotected_peer_ids */ {2, 3, 4, 7},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 4 localhost, 2 I2P, and 2 onion peers out of
|
||||
// 16 to protect 1 localhost, 2 I2P, and 1 onion (4/16 total), plus 4 others
|
||||
// for 8 total, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
16, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 6 || c.id > 11);
|
||||
if (c.id == 7 || c.id == 11) {
|
||||
c.m_network = NET_I2P;
|
||||
} else if (c.id == 9 || c.id == 10) {
|
||||
c.m_network = NET_ONION;
|
||||
} else {
|
||||
c.m_network = NET_IPV4;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 6, 7, 9, 11},
|
||||
/* unprotected_peer_ids */ {4, 5, 8, 10, 12, 13, 14, 15},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 1 localhost, 8 I2P and 1 onion peer out of
|
||||
// 24 to protect 1, 4, and 1 (6 total), plus 6 others for 12/24 total,
|
||||
// sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
24, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 12);
|
||||
if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2
|
||||
c.m_network = NET_I2P;
|
||||
} else if (c.id == 23) {
|
||||
c.m_network = NET_ONION;
|
||||
} else {
|
||||
c.m_network = NET_IPV6;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23},
|
||||
/* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 1 localhost, 3 I2P and 6 onion peers out of
|
||||
// 24 to protect 1, 3, and 2 (6 total, I2P has fewer candidates and so gets the
|
||||
// unused localhost slot), plus 6 others for 12/24 total, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
24, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 15);
|
||||
if (c.id == 12 || c.id == 14 || c.id == 17) {
|
||||
c.m_network = NET_I2P;
|
||||
} else if (c.id > 17) { // 4 protected instead of usual 2
|
||||
c.m_network = NET_ONION;
|
||||
} else {
|
||||
c.m_network = NET_IPV4;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19},
|
||||
/* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 1 localhost, 7 I2P and 4 onion peers out of
|
||||
// 24 to protect 1 localhost, 2 I2P, and 3 onions (6 total), plus 6 others
|
||||
// for 12/24 total, sorted by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
24, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id == 13);
|
||||
if (c.id > 16) {
|
||||
c.m_network = NET_I2P;
|
||||
} else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
|
||||
c.m_network = NET_ONION;
|
||||
} else {
|
||||
c.m_network = NET_IPV6;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18},
|
||||
/* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23},
|
||||
random_context));
|
||||
|
||||
// Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of
|
||||
// 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted
|
||||
// by longest uptime.
|
||||
BOOST_CHECK(IsProtected(
|
||||
24, [](NodeEvictionCandidate& c) {
|
||||
c.nTimeConnected = c.id;
|
||||
c.m_is_local = (c.id > 15);
|
||||
if (c.id > 10 && c.id < 15) {
|
||||
c.m_network = NET_I2P;
|
||||
} else if (c.id > 6 && c.id < 10) {
|
||||
c.m_network = NET_ONION;
|
||||
} else {
|
||||
c.m_network = NET_IPV4;
|
||||
}
|
||||
},
|
||||
/* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17},
|
||||
/* unprotected_peer_ids */ {6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23},
|
||||
random_context));
|
||||
}
|
||||
|
||||
@ -257,91 +485,89 @@ BOOST_AUTO_TEST_CASE(peer_eviction_test)
|
||||
{
|
||||
FastRandomContext random_context{true};
|
||||
|
||||
for (int i = 0; i < NODE_EVICTION_TEST_ROUNDS; ++i) {
|
||||
for (int number_of_nodes = 0; number_of_nodes < NODE_EVICTION_TEST_UP_TO_N_NODES; ++number_of_nodes) {
|
||||
// Four nodes with the highest keyed netgroup values should be
|
||||
// protected from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
|
||||
},
|
||||
{0, 1, 2, 3}, random_context));
|
||||
for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) {
|
||||
// Four nodes with the highest keyed netgroup values should be
|
||||
// protected from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
|
||||
},
|
||||
{0, 1, 2, 3}, random_context));
|
||||
|
||||
// Eight nodes with the lowest minimum ping time should be protected
|
||||
// from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [](NodeEvictionCandidate& candidate) {
|
||||
candidate.m_min_ping_time = std::chrono::microseconds{candidate.id};
|
||||
},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7}, random_context));
|
||||
// Eight nodes with the lowest minimum ping time should be protected
|
||||
// from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [](NodeEvictionCandidate& candidate) {
|
||||
candidate.m_min_ping_time = std::chrono::microseconds{candidate.id};
|
||||
},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7}, random_context));
|
||||
|
||||
// Four nodes that most recently sent us novel transactions accepted
|
||||
// into our mempool should be protected from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nLastTXTime = number_of_nodes - candidate.id;
|
||||
},
|
||||
{0, 1, 2, 3}, random_context));
|
||||
// Four nodes that most recently sent us novel transactions accepted
|
||||
// into our mempool should be protected from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nLastTXTime = number_of_nodes - candidate.id;
|
||||
},
|
||||
{0, 1, 2, 3}, random_context));
|
||||
|
||||
// Up to eight non-tx-relay peers that most recently sent us novel
|
||||
// blocks should be protected from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nLastBlockTime = number_of_nodes - candidate.id;
|
||||
if (candidate.id <= 7) {
|
||||
candidate.m_relay_txs = false;
|
||||
candidate.fRelevantServices = true;
|
||||
}
|
||||
},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7}, random_context));
|
||||
// Up to eight non-tx-relay peers that most recently sent us novel
|
||||
// blocks should be protected from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nLastBlockTime = number_of_nodes - candidate.id;
|
||||
if (candidate.id <= 7) {
|
||||
candidate.m_relay_txs = false;
|
||||
candidate.fRelevantServices = true;
|
||||
}
|
||||
},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7}, random_context));
|
||||
|
||||
// Four peers that most recently sent us novel blocks should be
|
||||
// protected from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nLastBlockTime = number_of_nodes - candidate.id;
|
||||
},
|
||||
{0, 1, 2, 3}, random_context));
|
||||
// Four peers that most recently sent us novel blocks should be
|
||||
// protected from eviction.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nLastBlockTime = number_of_nodes - candidate.id;
|
||||
},
|
||||
{0, 1, 2, 3}, random_context));
|
||||
|
||||
// Combination of the previous two tests.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nLastBlockTime = number_of_nodes - candidate.id;
|
||||
if (candidate.id <= 7) {
|
||||
candidate.m_relay_txs = false;
|
||||
candidate.fRelevantServices = true;
|
||||
}
|
||||
},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));
|
||||
// Combination of the previous two tests.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nLastBlockTime = number_of_nodes - candidate.id;
|
||||
if (candidate.id <= 7) {
|
||||
candidate.m_relay_txs = false;
|
||||
candidate.fRelevantServices = true;
|
||||
}
|
||||
},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));
|
||||
|
||||
// Combination of all tests above.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected
|
||||
candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected
|
||||
candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected
|
||||
candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected
|
||||
},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context));
|
||||
// Combination of all tests above.
|
||||
BOOST_CHECK(!IsEvicted(
|
||||
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
|
||||
candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected
|
||||
candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected
|
||||
candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected
|
||||
candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected
|
||||
},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context));
|
||||
|
||||
// An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most
|
||||
// four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay
|
||||
// peers by last novel block time, and four more peers by last novel block time.
|
||||
if (number_of_nodes >= 29) {
|
||||
BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
|
||||
}
|
||||
|
||||
// No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least
|
||||
// four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last
|
||||
// novel block time.
|
||||
if (number_of_nodes <= 20) {
|
||||
BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
|
||||
}
|
||||
|
||||
// Cases left to test:
|
||||
// * "If any remaining peers are preferred for eviction consider only them. [...]"
|
||||
// * "Identify the network group with the most connections and youngest member. [...]"
|
||||
// An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most
|
||||
// four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay
|
||||
// peers by last novel block time, and four more peers by last novel block time.
|
||||
if (number_of_nodes >= 29) {
|
||||
BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
|
||||
}
|
||||
|
||||
// No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least
|
||||
// four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last
|
||||
// novel block time.
|
||||
if (number_of_nodes <= 20) {
|
||||
BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
|
||||
}
|
||||
|
||||
// Cases left to test:
|
||||
// * "If any remaining peers are preferred for eviction consider only them. [...]"
|
||||
// * "Identify the network group with the most connections and youngest member. [...]"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,11 @@
|
||||
#define BITCOIN_TEST_UTIL_NET_H
|
||||
|
||||
#include <compat.h>
|
||||
#include <netaddress.h>
|
||||
#include <net.h>
|
||||
#include <util/sock.h>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
@ -73,6 +75,16 @@ constexpr ConnectionType ALL_CONNECTION_TYPES[]{
|
||||
ConnectionType::ADDR_FETCH,
|
||||
};
|
||||
|
||||
constexpr auto ALL_NETWORKS = std::array{
|
||||
Network::NET_UNROUTABLE,
|
||||
Network::NET_IPV4,
|
||||
Network::NET_IPV6,
|
||||
Network::NET_ONION,
|
||||
Network::NET_I2P,
|
||||
Network::NET_CJDNS,
|
||||
Network::NET_INTERNAL,
|
||||
};
|
||||
|
||||
/**
|
||||
* A mocked Sock alternative that returns a statically contained data upon read and succeeds
|
||||
* and ignores all writes. The data to be returned is given to the constructor and when it is
|
||||
|
@ -5,6 +5,7 @@
|
||||
"""Test dash-cli"""
|
||||
|
||||
from decimal import Decimal
|
||||
import re
|
||||
|
||||
from test_framework.blocktools import COINBASE_MATURITY
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
@ -29,6 +30,41 @@ TOO_MANY_ARGS = 'error: too many arguments (maximum 2 for nblocks and maxtries)'
|
||||
WALLET_NOT_LOADED = 'Requested wallet does not exist or is not loaded'
|
||||
WALLET_NOT_SPECIFIED = 'Wallet file not specified'
|
||||
|
||||
|
||||
def cli_get_info_string_to_dict(cli_get_info_string):
|
||||
"""Helper method to convert human-readable -getinfo into a dictionary"""
|
||||
cli_get_info = {}
|
||||
lines = cli_get_info_string.splitlines()
|
||||
line_idx = 0
|
||||
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
||||
while line_idx < len(lines):
|
||||
# Remove ansi colour code
|
||||
line = ansi_escape.sub('', lines[line_idx])
|
||||
if "Balances" in line:
|
||||
# When "Balances" appears in a line, all of the following lines contain "balance: wallet" until an empty line
|
||||
cli_get_info["Balances"] = {}
|
||||
while line_idx < len(lines) and not (lines[line_idx + 1] == ''):
|
||||
line_idx += 1
|
||||
balance, wallet = lines[line_idx].strip().split(" ")
|
||||
# Remove right justification padding
|
||||
wallet = wallet.strip()
|
||||
if wallet == '""':
|
||||
# Set default wallet("") to empty string
|
||||
wallet = ''
|
||||
cli_get_info["Balances"][wallet] = balance.strip()
|
||||
elif ": " in line:
|
||||
key, value = line.split(": ")
|
||||
if key == 'Wallet' and value == '""':
|
||||
# Set default wallet("") to empty string
|
||||
value = ''
|
||||
if key == "Proxies" and value == "n/a":
|
||||
# Set N/A to empty string to represent no proxy
|
||||
value = ''
|
||||
cli_get_info[key.strip()] = value.strip()
|
||||
line_idx += 1
|
||||
return cli_get_info
|
||||
|
||||
|
||||
class TestBitcoinCli(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
@ -72,41 +108,51 @@ class TestBitcoinCli(BitcoinTestFramework):
|
||||
self.log.info("Test -getinfo with arguments fails")
|
||||
assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)
|
||||
|
||||
self.log.info("Test -getinfo with -color=never does not return ANSI escape codes")
|
||||
assert "\u001b[0m" not in self.nodes[0].cli('-getinfo', '-color=never').send_cli()
|
||||
|
||||
self.log.info("Test -getinfo with -color=always returns ANSI escape codes")
|
||||
assert "\u001b[0m" in self.nodes[0].cli('-getinfo', '-color=always').send_cli()
|
||||
|
||||
self.log.info("Test -getinfo with invalid value for -color option")
|
||||
assert_raises_process_error(1, "Invalid value for -color option. Valid values: always, auto, never.", self.nodes[0].cli('-getinfo', '-color=foo').send_cli)
|
||||
|
||||
self.log.info("Test -getinfo returns expected network and blockchain info")
|
||||
if self.is_wallet_compiled():
|
||||
self.nodes[0].encryptwallet(password)
|
||||
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
|
||||
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
|
||||
|
||||
network_info = self.nodes[0].getnetworkinfo()
|
||||
blockchain_info = self.nodes[0].getblockchaininfo()
|
||||
assert_equal(cli_get_info['version'], network_info['version'])
|
||||
assert_equal(cli_get_info['blocks'], blockchain_info['blocks'])
|
||||
assert_equal(cli_get_info['headers'], blockchain_info['headers'])
|
||||
assert_equal(cli_get_info['timeoffset'], network_info['timeoffset'])
|
||||
assert_equal(
|
||||
cli_get_info['connections'],
|
||||
{
|
||||
'in': network_info['connections_in'],
|
||||
'out': network_info['connections_out'],
|
||||
'total': network_info['connections'],
|
||||
'mn_in': network_info['connections_mn_in'],
|
||||
'mn_out': network_info['connections_mn_out'],
|
||||
'mn_total': network_info['connections_mn'],
|
||||
}
|
||||
)
|
||||
assert_equal(cli_get_info['proxy'], network_info['networks'][0]['proxy'])
|
||||
assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty'])
|
||||
assert_equal(cli_get_info['chain'], blockchain_info['chain'])
|
||||
assert_equal(int(cli_get_info['Version']), network_info['version'])
|
||||
assert_equal(cli_get_info['Verification progress'], "%.4f%%" % (blockchain_info['verificationprogress'] * 100))
|
||||
assert_equal(int(cli_get_info['Blocks']), blockchain_info['blocks'])
|
||||
assert_equal(int(cli_get_info['Headers']), blockchain_info['headers'])
|
||||
assert_equal(int(cli_get_info['Time offset (s)']), network_info['timeoffset'])
|
||||
expected_network_info = f"in {network_info['connections_in']}, out {network_info['connections_out']}, total {network_info['connections']}, mn_in {network_info['connections_mn_in']}, mn_out {network_info['connections_mn_out']}, mn_total {network_info['connections_mn']}"
|
||||
assert_equal(cli_get_info["Network"], expected_network_info)
|
||||
assert_equal(cli_get_info['Proxies'], network_info['networks'][0]['proxy'])
|
||||
assert_equal(Decimal(cli_get_info['Difficulty']), blockchain_info['difficulty'])
|
||||
assert_equal(cli_get_info['Chain'], blockchain_info['chain'])
|
||||
|
||||
self.log.info("Test -getinfo and dash-cli return all proxies")
|
||||
self.restart_node(0, extra_args=["-proxy=127.0.0.1:9050", "-i2psam=127.0.0.1:7656"])
|
||||
network_info = self.nodes[0].getnetworkinfo()
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
|
||||
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
|
||||
assert_equal(cli_get_info["Proxies"], "127.0.0.1:9050 (ipv4, ipv6, onion), 127.0.0.1:7656 (i2p)")
|
||||
|
||||
if self.is_wallet_compiled():
|
||||
self.log.info("Test -getinfo and dash-cli getwalletinfo return expected wallet info")
|
||||
assert_equal(cli_get_info['balance'], BALANCE)
|
||||
assert 'balances' not in cli_get_info.keys()
|
||||
assert_equal(Decimal(cli_get_info['Balance']), BALANCE)
|
||||
assert 'Balances' not in cli_get_info_string
|
||||
wallet_info = self.nodes[0].getwalletinfo()
|
||||
assert_equal(cli_get_info['coinjoin_balance'], wallet_info['coinjoin_balance'])
|
||||
assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize'])
|
||||
assert_equal(cli_get_info['unlocked_until'], wallet_info['unlocked_until'])
|
||||
assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee'])
|
||||
assert_equal(cli_get_info['relayfee'], network_info['relayfee'])
|
||||
assert_equal(Decimal(cli_get_info['CoinJoin balance']), wallet_info['coinjoin_balance'])
|
||||
assert_equal(int(cli_get_info['Keypool size']), wallet_info['keypoolsize'])
|
||||
assert_equal(int(cli_get_info['Unlocked until']), wallet_info['unlocked_until'])
|
||||
assert_equal(Decimal(cli_get_info['Transaction fee rate (-paytxfee) (DASH/kB)']), wallet_info['paytxfee'])
|
||||
assert_equal(Decimal(cli_get_info['Min tx relay fee rate (DASH/kB)']), network_info['relayfee'])
|
||||
assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info)
|
||||
|
||||
# Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets.
|
||||
@ -129,44 +175,57 @@ class TestBitcoinCli(BitcoinTestFramework):
|
||||
|
||||
self.log.info("Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance")
|
||||
for i in range(len(wallets)):
|
||||
cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli()
|
||||
assert 'balances' not in cli_get_info.keys()
|
||||
assert_equal(cli_get_info['balance'], amounts[i])
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli()
|
||||
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
|
||||
assert 'Balances' not in cli_get_info_string
|
||||
assert_equal(cli_get_info["Wallet"], wallets[i])
|
||||
assert_equal(Decimal(cli_get_info['Balance']), amounts[i])
|
||||
|
||||
self.log.info("Test -getinfo with multiple wallets and -rpcwallet=non-existing-wallet returns no balances")
|
||||
cli_get_info_keys = self.nodes[0].cli('-getinfo', '-rpcwallet=does-not-exist').send_cli().keys()
|
||||
assert 'balance' not in cli_get_info_keys
|
||||
assert 'balances' not in cli_get_info_keys
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo', '-rpcwallet=does-not-exist').send_cli()
|
||||
assert 'Balance' not in cli_get_info_string
|
||||
assert 'Balances' not in cli_get_info_string
|
||||
|
||||
self.log.info("Test -getinfo with multiple wallets returns all loaded wallet names and balances")
|
||||
assert_equal(set(self.nodes[0].listwallets()), set(wallets))
|
||||
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
|
||||
assert 'balance' not in cli_get_info.keys()
|
||||
assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets, amounts)})
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
|
||||
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
|
||||
assert 'Balance' not in cli_get_info
|
||||
for k, v in zip(wallets, amounts):
|
||||
assert_equal(Decimal(cli_get_info['Balances'][k]), v)
|
||||
|
||||
# Unload the default wallet and re-verify.
|
||||
self.nodes[0].unloadwallet(wallets[0])
|
||||
assert wallets[0] not in self.nodes[0].listwallets()
|
||||
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
|
||||
assert 'balance' not in cli_get_info.keys()
|
||||
assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets[1:], amounts[1:])})
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
|
||||
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
|
||||
assert 'Balance' not in cli_get_info
|
||||
assert 'Balances' in cli_get_info_string
|
||||
for k, v in zip(wallets[1:], amounts[1:]):
|
||||
assert_equal(Decimal(cli_get_info['Balances'][k]), v)
|
||||
assert wallets[0] not in cli_get_info
|
||||
|
||||
self.log.info("Test -getinfo after unloading all wallets except a non-default one returns its balance")
|
||||
self.nodes[0].unloadwallet(wallets[2])
|
||||
assert_equal(self.nodes[0].listwallets(), [wallets[1]])
|
||||
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
|
||||
assert 'balances' not in cli_get_info.keys()
|
||||
assert_equal(cli_get_info['balance'], amounts[1])
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
|
||||
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
|
||||
assert 'Balances' not in cli_get_info_string
|
||||
assert_equal(cli_get_info['Wallet'], wallets[1])
|
||||
assert_equal(Decimal(cli_get_info['Balance']), amounts[1])
|
||||
|
||||
self.log.info("Test -getinfo with -rpcwallet=remaining-non-default-wallet returns only its balance")
|
||||
cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli()
|
||||
assert 'balances' not in cli_get_info.keys()
|
||||
assert_equal(cli_get_info['balance'], amounts[1])
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli()
|
||||
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
|
||||
assert 'Balances' not in cli_get_info_string
|
||||
assert_equal(cli_get_info['Wallet'], wallets[1])
|
||||
assert_equal(Decimal(cli_get_info['Balance']), amounts[1])
|
||||
|
||||
self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balances")
|
||||
cli_get_info_keys = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli().keys()
|
||||
assert 'balance' not in cli_get_info_keys
|
||||
assert 'balances' not in cli_get_info_keys
|
||||
cli_get_info_string = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli()
|
||||
cli_get_info_keys = cli_get_info_string_to_dict(cli_get_info_string)
|
||||
assert 'Balance' not in cli_get_info_keys
|
||||
assert 'Balances' not in cli_get_info_string
|
||||
|
||||
# Test bitcoin-cli -generate.
|
||||
n1 = 3
|
||||
|
Loading…
Reference in New Issue
Block a user