Merge pull request #5329 from kittywhiskers/dashcli_bps

backport: merge bitcoin#18574, #18653, #18691, #18724, #18594, #19991, #19133, #19643, #20002, #19354, partial #19998 (cli backports)
This commit is contained in:
PastaPastaPasta 2023-04-17 09:51:53 -05:00 committed by GitHub
commit 37a4a30b54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 798 additions and 178 deletions

View File

@ -0,0 +1,5 @@
## CLI
The `dash-cli -getinfo` command now displays the wallet name and balance for
each of the loaded wallets when more than one is loaded (e.g. in multiwallet
mode) and a wallet is not specified with `-rpcwallet`. (#18594)

View File

@ -53,11 +53,12 @@ config file): *Needed for Tor version 0.2.7.0 and older versions of Tor only. Fo
versions of Tor see [Section 4](#4-automatically-listen-on-tor).* versions of Tor see [Section 4](#4-automatically-listen-on-tor).*
HiddenServiceDir /var/lib/tor/dashcore-service/ HiddenServiceDir /var/lib/tor/dashcore-service/
HiddenServicePort 9999 127.0.0.1:9999 HiddenServicePort 9999 127.0.0.1:9996
HiddenServicePort 19999 127.0.0.1:19999 HiddenServicePort 19999 127.0.0.1:19996
The directory can be different of course, but (both) port numbers should be equal to The directory can be different of course, but virtual port numbers should be equal to
your dashd's P2P listen port (9999 by default). your dashd's P2P listen port (9999 by default), and target addresses and ports
should be equal to binding address and port for inbound Tor connections (127.0.0.1:9996 by default).
-externalip=X You can tell Dash Core about its publicly reachable address using -externalip=X You can tell Dash Core about its publicly reachable address using
this option, and this can be a .onion address. Given the above this option, and this can be a .onion address. Given the above

View File

@ -263,6 +263,7 @@ BITCOIN_CORE_H = \
reverse_iterator.h \ reverse_iterator.h \
rpc/blockchain.h \ rpc/blockchain.h \
rpc/client.h \ rpc/client.h \
rpc/mining.h \
rpc/protocol.h \ rpc/protocol.h \
rpc/rawtransaction_util.h \ rpc/rawtransaction_util.h \
rpc/register.h \ rpc/register.h \

View File

@ -11,6 +11,7 @@
#include <chainparamsbase.h> #include <chainparamsbase.h>
#include <clientversion.h> #include <clientversion.h>
#include <rpc/client.h> #include <rpc/client.h>
#include <rpc/mining.h>
#include <rpc/protocol.h> #include <rpc/protocol.h>
#include <rpc/request.h> #include <rpc/request.h>
#include <stacktraces.h> #include <stacktraces.h>
@ -39,6 +40,9 @@ static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
static const bool DEFAULT_NAMED=false; static const bool DEFAULT_NAMED=false;
static const int CONTINUE_EXECUTION=-1; static const int CONTINUE_EXECUTION=-1;
/** Default number of blocks to generate for RPC generatetoaddress. */
static const std::string DEFAULT_NBLOCKS = "1";
static void SetupCliArgs(ArgsManager& argsman) static void SetupCliArgs(ArgsManager& argsman)
{ {
SetupHelpOptions(argsman); SetupHelpOptions(argsman);
@ -50,7 +54,9 @@ static void SetupCliArgs(ArgsManager& argsman)
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-generate", strprintf("Generate blocks immediately, equivalent to RPC generatenewaddress followed by RPC generatetoaddress. Optional positional integer arguments are number of blocks to generate (default: %s) and maximum iterations to try (default: %s), equivalent to RPC generatetoaddress nblocks and maxtries arguments. Example: dash-cli -generate 4 1000", DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES), 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("-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).", ArgsManager::ALLOW_INT, OptionsCategory::OPTIONS);
argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, 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("-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); argsman.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@ -239,6 +245,7 @@ public:
const int ID_NETWORKINFO = 0; const int ID_NETWORKINFO = 0;
const int ID_BLOCKCHAININFO = 1; const int ID_BLOCKCHAININFO = 1;
const int ID_WALLETINFO = 2; const int ID_WALLETINFO = 2;
const int ID_BALANCES = 3;
/** Create a simulated `getinfo` request. */ /** Create a simulated `getinfo` request. */
UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
@ -250,6 +257,7 @@ public:
result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO)); result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO)); result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO));
result.push_back(JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO)); result.push_back(JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO));
result.push_back(JSONRPCRequestObj("getbalances", NullUniValue, ID_BALANCES));
return result; return result;
} }
@ -257,9 +265,9 @@ public:
UniValue ProcessReply(const UniValue &batch_in) override UniValue ProcessReply(const UniValue &batch_in) override
{ {
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in, 3); const std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in);
// Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass them on // Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass them on;
// getwalletinfo() is allowed to fail in case there is no wallet. // getwalletinfo() and getbalances() are allowed to fail if there is no wallet.
if (!batch[ID_NETWORKINFO]["error"].isNull()) { if (!batch[ID_NETWORKINFO]["error"].isNull()) {
return batch[ID_NETWORKINFO]; return batch[ID_NETWORKINFO];
} }
@ -276,7 +284,6 @@ public:
result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]); result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);
result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"])); result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"]));
if (!batch[ID_WALLETINFO]["result"].isNull()) { if (!batch[ID_WALLETINFO]["result"].isNull()) {
result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]);
result.pushKV("coinjoin_balance", batch[ID_WALLETINFO]["result"]["coinjoin_balance"]); result.pushKV("coinjoin_balance", batch[ID_WALLETINFO]["result"]["coinjoin_balance"]);
result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]); result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]);
if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) { if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {
@ -284,12 +291,207 @@ public:
} }
result.pushKV("paytxfee", batch[ID_WALLETINFO]["result"]["paytxfee"]); result.pushKV("paytxfee", batch[ID_WALLETINFO]["result"]["paytxfee"]);
} }
if (!batch[ID_BALANCES]["result"].isNull()) {
result.pushKV("balance", batch[ID_BALANCES]["result"]["mine"]["trusted"]);
}
result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]); result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]); result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
return JSONRPCReplyObj(result, NullUniValue, 1); return JSONRPCReplyObj(result, NullUniValue, 1);
} }
}; };
/** Process netinfo requests */
class NetinfoRequestHandler : public BaseRequestHandler
{
private:
static constexpr int8_t UNKNOWN_NETWORK{-1};
static constexpr size_t m_networks_size{3};
const std::array<std::string, m_networks_size> 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)
int8_t NetworkStringToId(const std::string& str) const
{
for (size_t i = 0; i < m_networks_size; ++i) {
if (str == m_networks.at(i)) return i;
}
return UNKNOWN_NETWORK;
}
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; }
bool m_is_asmap_on{false};
size_t m_max_addr_length{0};
size_t m_max_id_length{2};
struct Peer {
int id;
int mapped_as;
int version;
int64_t conn_time;
int64_t last_blck;
int64_t last_recv;
int64_t last_send;
int64_t last_trxn;
double min_ping;
double ping;
std::string addr;
std::string network;
std::string sub_version;
bool is_block_relay;
bool is_outbound;
bool operator<(const Peer& rhs) const { return std::tie(is_outbound, min_ping) < std::tie(rhs.is_outbound, rhs.min_ping); }
};
std::vector<Peer> m_peers;
std::string ChainToString() const
{
if (gArgs.GetChainName() == CBaseChainParams::TESTNET) return " testnet";
if (gArgs.GetChainName() == CBaseChainParams::REGTEST) return " regtest";
return "";
}
const int64_t m_time_now{GetSystemTimeInSeconds()};
public:
static constexpr int ID_PEERINFO = 0;
static constexpr int ID_NETWORKINFO = 1;
UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
{
if (!args.empty()) {
uint8_t n{0};
if (ParseUInt8(args.at(0), &n)) {
m_details_level = n;
}
}
UniValue result(UniValue::VARR);
result.push_back(JSONRPCRequestObj("getpeerinfo", NullUniValue, ID_PEERINFO));
result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
return result;
}
UniValue ProcessReply(const UniValue& batch_in) override
{
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];
const UniValue& networkinfo{batch[ID_NETWORKINFO]["result"]};
if (networkinfo["version"].get_int() < 200000) {
throw std::runtime_error("-netinfo requires dashd server to be running v20.0 and up");
}
// Count peer connection totals, and if DetailsRequested(), store peer data in a vector of structs.
for (const UniValue& peer : batch[ID_PEERINFO]["result"].getValues()) {
const std::string network{peer["network"].get_str()};
const int8_t network_id{NetworkStringToId(network)};
if (network_id == UNKNOWN_NETWORK) continue;
const bool is_outbound{!peer["inbound"].get_bool()};
const bool is_block_relay{!peer["relaytxes"].get_bool()};
++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 (DetailsRequested()) {
// Push data for this peer to the peers vector.
const int peer_id{peer["id"].get_int()};
const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].get_int()};
const int version{peer["version"].get_int()};
const int64_t conn_time{peer["conntime"].get_int64()};
const int64_t last_blck{peer["last_block"].get_int64()};
const int64_t last_recv{peer["lastrecv"].get_int64()};
const int64_t last_send{peer["lastsend"].get_int64()};
const int64_t last_trxn{peer["last_transaction"].get_int64()};
const double min_ping{peer["minping"].isNull() ? -1 : peer["minping"].get_real()};
const double ping{peer["pingtime"].isNull() ? -1 : peer["pingtime"].get_real()};
const std::string addr{peer["addr"].get_str()};
const std::string sub_version{peer["subver"].get_str()};
m_peers.push_back({peer_id, mapped_as, version, conn_time, last_blck, last_recv, last_send, last_trxn, min_ping, ping, addr, network, sub_version, is_block_relay, is_outbound});
m_max_id_length = std::max(ToString(peer_id).length(), m_max_id_length);
m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length);
m_is_asmap_on |= (mapped_as != 0);
}
}
// Generate report header.
std::string result{strprintf("%s %s%s - %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].get_int(), networkinfo["subversion"].get_str())};
// 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 += "Peer connections sorted by direction and min ping\n<-> relay net mping ping send recv txn blk uptime ";
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%6s%7s%5s%5s%5s%5s%7s%*i %*s %-*s%s\n",
peer.is_outbound ? "out" : "in",
peer.is_block_relay ? "block" : "full",
peer.network,
peer.min_ping == -1 ? "" : ToString(round(1000 * peer.min_ping)),
peer.ping == -1 ? "" : ToString(round(1000 * peer.ping)),
peer.last_send == 0 ? "" : ToString(m_time_now - peer.last_send),
peer.last_recv == 0 ? "" : ToString(m_time_now - peer.last_recv),
peer.last_trxn == 0 ? "" : ToString((m_time_now - peer.last_trxn) / 60),
peer.last_blck == 0 ? "" : ToString((m_time_now - peer.last_blck) / 60),
peer.conn_time == 0 ? "" : ToString((m_time_now - peer.conn_time) / 60),
m_is_asmap_on ? 7 : 0, // variable spacing
m_is_asmap_on && peer.mapped_as != 0 ? ToString(peer.mapped_as) : "",
m_max_id_length, // variable spacing
peer.id,
IsAddressSelected() ? m_max_addr_length : 0, // variable spacing
IsAddressSelected() ? peer.addr : "",
IsVersionSelected() && version != "0" ? version : "");
}
result += " ms ms sec sec min min min\n\n";
}
// Report peer connection totals by type.
result += " ipv4 ipv6 onion total block-relay\n";
const std::array<std::string, 3> rows{{"in", "out", "total"}};
for (size_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));
}
// Report local addresses, ports, and scores.
result += "\nLocal addresses";
const UniValue& local_addrs{networkinfo["localaddresses"]};
if (local_addrs.empty()) {
result += ": n/a\n";
} else {
for (const UniValue& addr : local_addrs.getValues()) {
result += strprintf("\n%-40i port %5i score %6i", addr["address"].get_str(), addr["port"].get_int(), addr["score"].get_int());
}
}
return JSONRPCReplyObj(UniValue{result}, NullUniValue, 1);
}
};
/** Process RPC generatetoaddress request. */
class GenerateToAddressRequestHandler : public BaseRequestHandler
{
public:
UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
{
address_str = args.at(1);
UniValue params{RPCConvertValues("generatetoaddress", args)};
return JSONRPCRequestObj("generatetoaddress", params, 1);
}
UniValue ProcessReply(const UniValue &reply) override
{
UniValue result(UniValue::VOBJ);
result.pushKV("address", address_str);
result.pushKV("blocks", reply.get_obj()["result"]);
return JSONRPCReplyObj(result, NullUniValue, 1);
}
protected:
std::string address_str;
};
/** Process default single requests */ /** Process default single requests */
class DefaultRequestHandler: public BaseRequestHandler { class DefaultRequestHandler: public BaseRequestHandler {
public: public:
@ -310,7 +512,7 @@ public:
} }
}; };
static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, const std::vector<std::string>& args) static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::optional<std::string>& rpcwallet = {})
{ {
std::string host; std::string host;
// In preference order, we choose the following for the port: // In preference order, we choose the following for the port:
@ -376,14 +578,12 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co
// check if we should use a special wallet endpoint // check if we should use a special wallet endpoint
std::string endpoint = "/"; std::string endpoint = "/";
if (!gArgs.GetArgs("-rpcwallet").empty()) { if (rpcwallet) {
std::string walletName = gArgs.GetArg("-rpcwallet", ""); char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
char *encodedURI = evhttp_uriencode(walletName.data(), walletName.size(), false);
if (encodedURI) { if (encodedURI) {
endpoint = "/wallet/"+ std::string(encodedURI); endpoint = "/wallet/" + std::string(encodedURI);
free(encodedURI); free(encodedURI);
} } else {
else {
throw CConnectionFailed("uri-encode failed"); throw CConnectionFailed("uri-encode failed");
} }
} }
@ -427,6 +627,121 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co
return reply; return reply;
} }
/**
* ConnectAndCallRPC wraps CallRPC with -rpcwait and an exception handler.
*
* @param[in] rh Pointer to RequestHandler.
* @param[in] strMethod Reference to const string method to forward to CallRPC.
* @param[in] rpcwallet Reference to const optional string wallet name to forward to CallRPC.
* @returns the RPC response as a UniValue object.
* @throws a CConnectionFailed std::runtime_error if connection failed or RPC server still in warmup.
*/
static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::optional<std::string>& rpcwallet = {})
{
UniValue response(UniValue::VOBJ);
// Execute and handle connection failures with -rpcwait.
const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
do {
try {
response = CallRPC(rh, strMethod, args, rpcwallet);
if (fWait) {
const UniValue& error = find_value(response, "error");
if (!error.isNull() && error["code"].get_int() == RPC_IN_WARMUP) {
throw CConnectionFailed("server in warmup");
}
}
break; // Connection succeeded, no need to retry.
} catch (const CConnectionFailed&) {
if (fWait) {
UninterruptibleSleep(std::chrono::milliseconds{1000});
} else {
throw;
}
}
} while (fWait);
return response;
}
/** Parse UniValue result to update the message to print to std::cout. */
static void ParseResult(const UniValue& result, std::string& strPrint)
{
if (result.isNull()) return;
strPrint = result.isStr() ? result.get_str() : result.write(2);
}
/** Parse UniValue error to update the message to print to std::cerr and the code to return. */
static void ParseError(const UniValue& error, std::string& strPrint, int& nRet)
{
if (error.isObject()) {
const UniValue& err_code = find_value(error, "code");
const UniValue& err_msg = find_value(error, "message");
if (!err_code.isNull()) {
strPrint = "error code: " + err_code.getValStr() + "\n";
}
if (err_msg.isStr()) {
strPrint += ("error message:\n" + err_msg.get_str());
}
if (err_code.isNum() && err_code.get_int() == RPC_WALLET_NOT_SPECIFIED) {
strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to dash-cli command line.";
}
} else {
strPrint = "error: " + error.write();
}
nRet = abs(error["code"].get_int());
}
/**
* GetWalletBalances calls listwallets; if more than one wallet is loaded, it then
* fetches mine.trusted balances for each loaded wallet and pushes them to `result`.
*
* @param result Reference to UniValue object the wallet names and balances are pushed to.
*/
static void GetWalletBalances(UniValue& result)
{
std::unique_ptr<BaseRequestHandler> rh{std::make_unique<DefaultRequestHandler>()};
const UniValue listwallets = ConnectAndCallRPC(rh.get(), "listwallets", /* args=*/{});
if (!find_value(listwallets, "error").isNull()) return;
const UniValue& wallets = find_value(listwallets, "result");
if (wallets.size() <= 1) return;
UniValue balances(UniValue::VOBJ);
for (const UniValue& wallet : wallets.getValues()) {
const std::string wallet_name = wallet.get_str();
const UniValue getbalances = ConnectAndCallRPC(rh.get(), "getbalances", /* args=*/{}, wallet_name);
const UniValue& balance = find_value(getbalances, "result")["mine"]["trusted"];
balances.pushKV(wallet_name, balance);
}
result.pushKV("balances", balances);
}
/**
* Call RPC getnewaddress.
* @returns getnewaddress response as a UniValue object.
*/
static UniValue GetNewAddress()
{
std::optional<std::string> wallet_name{};
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
std::unique_ptr<BaseRequestHandler> rh{std::make_unique<DefaultRequestHandler>()};
return ConnectAndCallRPC(rh.get(), "getnewaddress", /* args=*/{}, wallet_name);
}
/**
* Check bounds and set up args for RPC generatetoaddress params: nblocks, address, maxtries.
* @param[in] address Reference to const string address to insert into the args.
* @param args Reference to vector of string args to modify.
*/
static void SetGenerateToAddressArgs(const std::string& address, std::vector<std::string>& args)
{
if (args.size() > 2) throw std::runtime_error("too many arguments (maximum 2 for nblocks and maxtries)");
if (args.size() == 0) {
args.emplace_back(DEFAULT_NBLOCKS);
} else if (args.at(0) == "0") {
throw std::runtime_error("the first argument (number of blocks to generate, default: " + DEFAULT_NBLOCKS + ") must be an integer value greater than zero");
}
args.emplace(args.begin() + 1, address);
}
static int CommandLineRPC(int argc, char *argv[]) static int CommandLineRPC(int argc, char *argv[])
{ {
std::string strPrint; std::string strPrint;
@ -483,9 +798,19 @@ static int CommandLineRPC(int argc, char *argv[])
} }
std::unique_ptr<BaseRequestHandler> rh; std::unique_ptr<BaseRequestHandler> rh;
std::string method; std::string method;
if (gArgs.GetBoolArg("-getinfo", false)) { if (gArgs.IsArgSet("-getinfo")) {
rh.reset(new GetinfoRequestHandler()); rh.reset(new GetinfoRequestHandler());
method = ""; } else if (gArgs.GetBoolArg("-netinfo", false)) {
rh.reset(new NetinfoRequestHandler());
} else if (gArgs.GetBoolArg("-generate", false)) {
const UniValue getnewaddress{GetNewAddress()};
const UniValue& error{find_value(getnewaddress, "error")};
if (error.isNull()) {
SetGenerateToAddressArgs(find_value(getnewaddress, "result").get_str(), args);
rh.reset(new GenerateToAddressRequestHandler());
} else {
ParseError(error, strPrint, nRet);
}
} else { } else {
rh.reset(new DefaultRequestHandler()); rh.reset(new DefaultRequestHandler());
if (args.size() < 1) { if (args.size() < 1) {
@ -494,62 +819,28 @@ static int CommandLineRPC(int argc, char *argv[])
method = args[0]; method = args[0];
args.erase(args.begin()); // Remove trailing method name from arguments vector args.erase(args.begin()); // Remove trailing method name from arguments vector
} }
if (nRet == 0) {
// Perform RPC call
std::optional<std::string> wallet_name{};
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
// Execute and handle connection failures with -rpcwait // Parse reply
const bool fWait = gArgs.GetBoolArg("-rpcwait", false); UniValue result = find_value(reply, "result");
do { const UniValue& error = find_value(reply, "error");
try { if (error.isNull()) {
const UniValue reply = CallRPC(rh.get(), method, args); if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
GetWalletBalances(result); // fetch multiwallet balances and append to result
// Parse reply
const UniValue& result = find_value(reply, "result");
const UniValue& error = find_value(reply, "error");
if (!error.isNull()) {
// Error
int code = error["code"].get_int();
if (fWait && code == RPC_IN_WARMUP)
throw CConnectionFailed("server in warmup");
strPrint = "error: " + error.write();
nRet = abs(code);
if (error.isObject())
{
UniValue errCode = find_value(error, "code");
UniValue errMsg = find_value(error, "message");
strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n";
if (errMsg.isStr())
strPrint += "error message:\n"+errMsg.get_str();
if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to dash-cli command line.";
}
}
} else {
// Result
if (result.isNull())
strPrint = "";
else if (result.isStr())
strPrint = result.get_str();
else
strPrint = result.write(2);
} }
// Connection succeeded, no need to retry. ParseResult(result, strPrint);
break; } else {
ParseError(error, strPrint, nRet);
} }
catch (const CConnectionFailed&) { }
if (fWait) } catch (const std::exception& e) {
UninterruptibleSleep(std::chrono::milliseconds{1000});
else
throw;
}
} while (fWait);
}
catch (const std::exception& e) {
strPrint = std::string("error: ") + e.what(); strPrint = std::string("error: ") + e.what();
nRet = EXIT_FAILURE; nRet = EXIT_FAILURE;
} } catch (...) {
catch (...) {
PrintExceptionContinue(std::current_exception(), "CommandLineRPC()"); PrintExceptionContinue(std::current_exception(), "CommandLineRPC()");
throw; throw;
} }

View File

@ -49,16 +49,20 @@ const CBaseChainParams& BaseParams()
return *globalChainBaseParams; return *globalChainBaseParams;
} }
/**
* Port numbers for incoming Tor connections (9996, 19996, 19796, 19896) have
* been chosen arbitrarily to keep ranges of used ports tight.
*/
std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const std::string& chain) std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const std::string& chain)
{ {
if (chain == CBaseChainParams::MAIN) if (chain == CBaseChainParams::MAIN)
return std::make_unique<CBaseChainParams>("", 9998); return std::make_unique<CBaseChainParams>("", 9998, 9996);
else if (chain == CBaseChainParams::TESTNET) else if (chain == CBaseChainParams::TESTNET)
return std::make_unique<CBaseChainParams>("testnet3", 19998); return std::make_unique<CBaseChainParams>("testnet3", 19998, 19996);
else if (chain == CBaseChainParams::DEVNET) else if (chain == CBaseChainParams::DEVNET)
return std::make_unique<CBaseChainParams>(gArgs.GetDevNetName(), 19798); return std::make_unique<CBaseChainParams>(gArgs.GetDevNetName(), 19798, 19796);
else if (chain == CBaseChainParams::REGTEST) else if (chain == CBaseChainParams::REGTEST)
return std::make_unique<CBaseChainParams>("regtest", 19898); return std::make_unique<CBaseChainParams>("regtest", 19898, 19896);
else else
throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain));
} }

View File

@ -26,13 +26,16 @@ public:
///@} ///@}
const std::string& DataDir() const { return strDataDir; } const std::string& DataDir() const { return strDataDir; }
uint16_t RPCPort() const { return nRPCPort; } uint16_t RPCPort() const { return m_rpc_port; }
uint16_t OnionServiceTargetPort() const { return m_onion_service_target_port; }
CBaseChainParams() = delete; CBaseChainParams() = delete;
CBaseChainParams(const std::string& data_dir, int rpc_port) : nRPCPort(rpc_port), strDataDir(data_dir) {} CBaseChainParams(const std::string& data_dir, uint16_t rpc_port, uint16_t onion_service_target_port)
: m_rpc_port(rpc_port), m_onion_service_target_port(onion_service_target_port), strDataDir(data_dir) {}
private: private:
uint16_t nRPCPort; const uint16_t m_rpc_port;
const uint16_t m_onion_service_target_port;
std::string strDataDir; std::string strDataDir;
}; };

View File

@ -552,7 +552,7 @@ void SetupServerArgs(NodeContext& node)
argsman.AddArg("-allowprivatenet", strprintf("Allow RFC1918 addresses to be relayed and connected to (default: %u)", DEFAULT_ALLOWPRIVATENET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-allowprivatenet", strprintf("Allow RFC1918 addresses to be relayed and connected to (default: %u)", DEFAULT_ALLOWPRIVATENET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting and discouraging misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting and discouraging misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bind=<addr>", "Bind to given address and always listen on it. Use [host]:port notation for IPv6", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@ -2480,8 +2480,6 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
} }
} }
LogPrintf("::ChainActive().Height() = %d\n", chain_active_height); LogPrintf("::ChainActive().Height() = %d\n", chain_active_height);
if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION))
StartTorControl();
Discover(); Discover();
@ -2506,13 +2504,39 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
connOptions.nMaxOutboundLimit = 1024 * 1024 * args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET); connOptions.nMaxOutboundLimit = 1024 * 1024 * args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET);
connOptions.m_peer_connect_timeout = peer_connect_timeout; connOptions.m_peer_connect_timeout = peer_connect_timeout;
for (const std::string& strBind : args.GetArgs("-bind")) { for (const std::string& bind_arg : args.GetArgs("-bind")) {
CService addrBind; CService bind_addr;
if (!Lookup(strBind, addrBind, GetListenPort(), false)) { const size_t index = bind_arg.rfind('=');
return InitError(ResolveErrMsg("bind", strBind)); if (index == std::string::npos) {
if (Lookup(bind_arg, bind_addr, GetListenPort(), false)) {
connOptions.vBinds.push_back(bind_addr);
continue;
}
} else {
const std::string network_type = bind_arg.substr(index + 1);
if (network_type == "onion") {
const std::string truncated_bind_arg = bind_arg.substr(0, index);
if (Lookup(truncated_bind_arg, bind_addr, BaseParams().OnionServiceTargetPort(), false)) {
connOptions.onion_binds.push_back(bind_addr);
continue;
}
}
} }
connOptions.vBinds.push_back(addrBind); return InitError(ResolveErrMsg("bind", bind_arg));
} }
if (connOptions.onion_binds.empty()) {
connOptions.onion_binds.push_back(DefaultOnionServiceTarget());
}
if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
const auto bind_addr = connOptions.onion_binds.front();
if (connOptions.onion_binds.size() > 1) {
InitWarning(strprintf(_("More than one onion bind address is provided. Using %s for the automatically created Tor onion service."), bind_addr.ToStringIPPort()));
}
StartTorControl(bind_addr);
}
for (const std::string& strBind : args.GetArgs("-whitebind")) { for (const std::string& strBind : args.GetArgs("-whitebind")) {
NetWhitebindPermissions whitebind; NetWhitebindPermissions whitebind;
bilingual_str error; bilingual_str error;

View File

@ -54,6 +54,7 @@
#include <sys/event.h> #include <sys/event.h>
#endif #endif
#include <algorithm>
#include <cstdint> #include <cstdint>
#include <unordered_map> #include <unordered_map>
@ -99,6 +100,11 @@ enum BindFlags {
BF_NONE = 0, BF_NONE = 0,
BF_EXPLICIT = (1U << 0), BF_EXPLICIT = (1U << 0),
BF_REPORT_ERROR = (1U << 1), BF_REPORT_ERROR = (1U << 1),
/**
* Do not call AddLocal() for our special addresses, e.g., for incoming
* Tor connections, to prevent gossiping them over the network.
*/
BF_DONT_ADVERTISE = (1U << 2),
}; };
#ifndef USE_WAKEUP_PIPE #ifndef USE_WAKEUP_PIPE
@ -580,6 +586,11 @@ std::string CNode::GetLogString() const
return fLogIPs ? addr.ToString() : strprintf("%d", id); return fLogIPs ? addr.ToString() : strprintf("%d", id);
} }
Network CNode::ConnectedThroughNetwork() const
{
return fInbound && m_inbound_onion ? NET_ONION : addr.GetNetClass();
}
#undef X #undef X
#define X(name) stats.name = name #define X(name) stats.name = name
void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap)
@ -588,6 +599,7 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap)
X(nServices); X(nServices);
X(addr); X(addr);
X(addrBind); X(addrBind);
stats.m_network = GetNetworkName(ConnectedThroughNetwork());
stats.m_mapped_as = addr.GetMappedAS(m_asmap); stats.m_mapped_as = addr.GetMappedAS(m_asmap);
if (!m_block_relay_only_peer) { if (!m_block_relay_only_peer) {
LOCK(m_tx_relay->cs_filter); LOCK(m_tx_relay->cs_filter);
@ -1199,7 +1211,9 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) { if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) {
nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM); nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM);
} }
CNode* pnode = new CNode(id, nodeServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true);
const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end();
CNode* pnode = new CNode(id, nodeServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true, inbound_onion);
pnode->AddRef(); pnode->AddRef();
pnode->m_permissionFlags = permissionFlags; pnode->m_permissionFlags = permissionFlags;
// If this flag is present, the user probably expect that RPC and QT report it as whitelisted (backward compatibility) // If this flag is present, the user probably expect that RPC and QT report it as whitelisted (backward compatibility)
@ -2875,9 +2889,6 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError,
vhListenSocket.push_back(ListenSocket(sock->Release(), permissions)); vhListenSocket.push_back(ListenSocket(sock->Release(), permissions));
if (addrBind.IsRoutable() && fDiscover && (permissions & PF_NOBAN) == 0)
AddLocal(addrBind, LOCAL_BIND);
return true; return true;
} }
@ -2974,10 +2985,18 @@ bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags
} }
return false; return false;
} }
if (addr.IsRoutable() && fDiscover && !(flags & BF_DONT_ADVERTISE) && !(permissions & PF_NOBAN)) {
AddLocal(addr, LOCAL_BIND);
}
return true; return true;
} }
bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds) bool CConnman::InitBinds(
const std::vector<CService>& binds,
const std::vector<NetWhitebindPermissions>& whiteBinds,
const std::vector<CService>& onion_binds)
{ {
bool fBound = false; bool fBound = false;
for (const auto& addrBind : binds) { for (const auto& addrBind : binds) {
@ -2988,11 +3007,16 @@ bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<N
} }
if (binds.empty() && whiteBinds.empty()) { if (binds.empty() && whiteBinds.empty()) {
struct in_addr inaddr_any; struct in_addr inaddr_any;
inaddr_any.s_addr = INADDR_ANY; inaddr_any.s_addr = htonl(INADDR_ANY);
struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT;
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::PF_NONE); fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::PF_NONE);
fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::PF_NONE); fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::PF_NONE);
} }
for (const auto& addr_bind : onion_binds) {
fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::PF_NONE);
}
return fBound; return fBound;
} }
@ -3031,7 +3055,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
} }
#endif #endif
if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) { if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds, connOptions.onion_binds)) {
if (clientInterface) { if (clientInterface) {
clientInterface->ThreadSafeMessageBox( clientInterface->ThreadSafeMessageBox(
_("Failed to listen on any port. Use -listen=0 if you want this."), _("Failed to listen on any port. Use -listen=0 if you want this."),
@ -3741,7 +3765,7 @@ int CConnman::GetBestHeight() const
unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; }
CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn, bool block_relay_only) CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn, bool block_relay_only, bool inbound_onion)
: nTimeConnected(GetSystemTimeInSeconds()), : nTimeConnected(GetSystemTimeInSeconds()),
addr(addrIn), addr(addrIn),
addrBind(addrBindIn), addrBind(addrBindIn),
@ -3752,7 +3776,8 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
id(idIn), id(idIn),
nLocalHostNonce(nLocalHostNonceIn), nLocalHostNonce(nLocalHostNonceIn),
nLocalServices(nLocalServicesIn), nLocalServices(nLocalServicesIn),
nMyStartingHeight(nMyStartingHeightIn) nMyStartingHeight(nMyStartingHeightIn),
m_inbound_onion(inbound_onion)
{ {
hSocket = hSocketIn; hSocket = hSocketIn;
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;

View File

@ -178,6 +178,7 @@ public:
std::vector<NetWhitelistPermissions> vWhitelistedRange; std::vector<NetWhitelistPermissions> vWhitelistedRange;
std::vector<NetWhitebindPermissions> vWhiteBinds; std::vector<NetWhitebindPermissions> vWhiteBinds;
std::vector<CService> vBinds; std::vector<CService> vBinds;
std::vector<CService> onion_binds;
bool m_use_addrman_outgoing = true; bool m_use_addrman_outgoing = true;
std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_specified_outgoing;
std::vector<std::string> m_added_nodes; std::vector<std::string> m_added_nodes;
@ -211,6 +212,7 @@ public:
vAddedNodes = connOptions.m_added_nodes; vAddedNodes = connOptions.m_added_nodes;
} }
socketEventsMode = connOptions.socketEventsMode; socketEventsMode = connOptions.socketEventsMode;
m_onion_binds = connOptions.onion_binds;
} }
CConnman(uint64_t seed0, uint64_t seed1, CAddrMan& addrman); CConnman(uint64_t seed0, uint64_t seed1, CAddrMan& addrman);
@ -496,7 +498,11 @@ private:
bool BindListenPort(const CService& bindAddr, bilingual_str& strError, NetPermissionFlags permissions); bool BindListenPort(const CService& bindAddr, bilingual_str& strError, NetPermissionFlags permissions);
bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions);
bool InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds); bool InitBinds(
const std::vector<CService>& binds,
const std::vector<NetWhitebindPermissions>& whiteBinds,
const std::vector<CService>& onion_binds);
void ThreadOpenAddedConnections(); void ThreadOpenAddedConnections();
void AddOneShot(const std::string& strDest); void AddOneShot(const std::string& strDest);
void ProcessOneShot(); void ProcessOneShot();
@ -675,6 +681,12 @@ private:
std::atomic<int64_t> m_next_send_inv_to_incoming{0}; std::atomic<int64_t> m_next_send_inv_to_incoming{0};
/**
* A vector of -bind=<address>:<port>=onion arguments each of which is
* an address and port that are designated for incoming Tor connections.
*/
std::vector<CService> m_onion_binds;
friend struct CConnmanTest; friend struct CConnmanTest;
friend struct ConnmanTestMsg; friend struct ConnmanTestMsg;
}; };
@ -797,6 +809,8 @@ public:
CAddress addr; CAddress addr;
// Bind address of our side of the connection // Bind address of our side of the connection
CAddress addrBind; CAddress addrBind;
// Name of the network the peer connected through
std::string m_network;
uint32_t m_mapped_as; uint32_t m_mapped_as;
// In case this is a verified MN, this value is the proTx of the MN // In case this is a verified MN, this value is the proTx of the MN
uint256 verifiedProRegTxHash; uint256 verifiedProRegTxHash;
@ -1010,6 +1024,18 @@ public:
std::atomic_bool fHasRecvData{false}; std::atomic_bool fHasRecvData{false};
std::atomic_bool fCanSendData{false}; std::atomic_bool fCanSendData{false};
/**
* Get network the peer connected through.
*
* Returns Network::NET_ONION for *inbound* onion connections,
* and CNetAddr::GetNetClass() otherwise. The latter cannot be used directly
* because it doesn't detect the former, and it's not the responsibility of
* the CNetAddr class to know the actual network a peer is connected through.
*
* @return network the peer connected through.
*/
Network ConnectedThroughNetwork() const;
protected: protected:
mapMsgCmdSize mapSendBytesPerMsgCmd; mapMsgCmdSize mapSendBytesPerMsgCmd;
mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv); mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv);
@ -1112,7 +1138,7 @@ public:
std::set<uint256> orphan_work_set; std::set<uint256> orphan_work_set;
CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false, bool block_relay_only = false); CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false, bool block_relay_only = false, bool inbound_onion = false);
~CNode(); ~CNode();
CNode(const CNode&) = delete; CNode(const CNode&) = delete;
CNode& operator=(const CNode&) = delete; CNode& operator=(const CNode&) = delete;
@ -1150,6 +1176,9 @@ private:
CService addrLocal GUARDED_BY(cs_addrLocal); CService addrLocal GUARDED_BY(cs_addrLocal);
mutable CCriticalSection cs_addrLocal; mutable CCriticalSection cs_addrLocal;
//! Whether this peer connected via our Tor onion service.
const bool m_inbound_onion{false};
// Challenge sent in VERSION to be answered with MNAUTH (only happens between MNs) // Challenge sent in VERSION to be answered with MNAUTH (only happens between MNs)
mutable CCriticalSection cs_mnauth; mutable CCriticalSection cs_mnauth;
uint256 sentMNAuthChallenge GUARDED_BY(cs_mnauth); uint256 sentMNAuthChallenge GUARDED_BY(cs_mnauth);

View File

@ -668,7 +668,7 @@ uint32_t CNetAddr::GetLinkedIPv4() const
assert(false); assert(false);
} }
uint32_t CNetAddr::GetNetClass() const Network CNetAddr::GetNetClass() const
{ {
// Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers expect that. // Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers expect that.

View File

@ -200,7 +200,7 @@ class CNetAddr
std::string ToStringIP(bool fUseGetnameinfo = true) const; std::string ToStringIP(bool fUseGetnameinfo = true) const;
uint64_t GetHash() const; uint64_t GetHash() const;
bool GetInAddr(struct in_addr* pipv4Addr) const; bool GetInAddr(struct in_addr* pipv4Addr) const;
uint32_t GetNetClass() const; Network GetNetClass() const;
//! For IPv4, mapped IPv4, SIIT translated IPv4, Teredo, 6to4 tunneled addresses, return the relevant IPv4 address as a uint32. //! For IPv4, mapped IPv4, SIIT translated IPv4, Teredo, 6to4 tunneled addresses, return the relevant IPv4 address as a uint32.
uint32_t GetLinkedIPv4() const; uint32_t GetLinkedIPv4() const;

View File

@ -250,7 +250,7 @@ UniValue ParseNonRFCJSONValue(const std::string& strVal)
UniValue jVal; UniValue jVal;
if (!jVal.read(std::string("[")+strVal+std::string("]")) || if (!jVal.read(std::string("[")+strVal+std::string("]")) ||
!jVal.isArray() || jVal.size()!=1) !jVal.isArray() || jVal.size()!=1)
throw std::runtime_error(std::string("Error parsing JSON:")+strVal); throw std::runtime_error(std::string("Error parsing JSON: ") + strVal);
return jVal[0]; return jVal[0];
} }

View File

@ -23,6 +23,7 @@
#include <policy/fees.h> #include <policy/fees.h>
#include <pow.h> #include <pow.h>
#include <rpc/blockchain.h> #include <rpc/blockchain.h>
#include <rpc/mining.h>
#include <rpc/server.h> #include <rpc/server.h>
#include <rpc/util.h> #include <rpc/util.h>
#include <script/descriptor.h> #include <script/descriptor.h>
@ -221,7 +222,7 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
{ {
{"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, {"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."},
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor to send the newly generated bitcoin to."}, {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor to send the newly generated bitcoin to."},
{"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."}, {"maxtries", RPCArg::Type::NUM, /* default */ ToString(DEFAULT_MAX_TRIES), "How many iterations to try."},
}, },
RPCResult{ RPCResult{
RPCResult::Type::ARR, "", "", RPCResult::Type::ARR, "", "",
@ -235,7 +236,7 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
.Check(request); .Check(request);
const int num_blocks{request.params[0].get_int()}; const int num_blocks{request.params[0].get_int()};
const int64_t max_tries{request.params[2].isNull() ? 1000000 : request.params[2].get_int()}; const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
CScript coinbase_script; CScript coinbase_script;
std::string error; std::string error;
@ -258,7 +259,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
{ {
{"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."},
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated Dash to."}, {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated Dash to."},
{"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."}, {"maxtries", RPCArg::Type::NUM, /* default */ ToString(DEFAULT_MAX_TRIES), "How many iterations to try."},
}, },
RPCResult{ RPCResult{
RPCResult::Type::ARR, "", "hashes of blocks generated", RPCResult::Type::ARR, "", "hashes of blocks generated",
@ -272,11 +273,8 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
+ HelpExampleCli("getnewaddress", "")}, + HelpExampleCli("getnewaddress", "")},
}.Check(request); }.Check(request);
int nGenerate = request.params[0].get_int(); const int num_blocks{request.params[0].get_int()};
uint64_t nMaxTries = 1000000; const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
if (!request.params[2].isNull()) {
nMaxTries = request.params[2].get_int();
}
CTxDestination destination = DecodeDestination(request.params[1].get_str()); CTxDestination destination = DecodeDestination(request.params[1].get_str());
if (!IsValidDestination(destination)) { if (!IsValidDestination(destination)) {
@ -290,7 +288,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
CScript coinbase_script = GetScriptForDestination(destination); CScript coinbase_script = GetScriptForDestination(destination);
return generateBlocks(chainman, mempool, *node_context.evodb, *llmq_ctx.quorum_block_processor, *llmq_ctx.clhandler, *llmq_ctx.isman, coinbase_script, nGenerate, nMaxTries); return generateBlocks(chainman, mempool, *node_context.evodb, *llmq_ctx.quorum_block_processor, *llmq_ctx.clhandler, *llmq_ctx.isman, coinbase_script, num_blocks, max_tries);
} }
static UniValue generateblock(const JSONRPCRequest& request) static UniValue generateblock(const JSONRPCRequest& request)
@ -389,7 +387,7 @@ static UniValue generateblock(const JSONRPCRequest& request)
} }
uint256 block_hash; uint256 block_hash;
uint64_t max_tries{1000000}; uint64_t max_tries{DEFAULT_MAX_TRIES};
unsigned int extra_nonce{0}; unsigned int extra_nonce{0};
if (!GenerateBlock(EnsureChainman(request.context), block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) { if (!GenerateBlock(EnsureChainman(request.context), block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {

11
src/rpc/mining.h Normal file
View File

@ -0,0 +1,11 @@
// Copyright (c) 2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_RPC_MINING_H
#define BITCOIN_RPC_MINING_H
/** Default max iterations to try in RPC generatetodescriptor, generatetoaddress, and generateblock. */
static const uint64_t DEFAULT_MAX_TRIES{1000000};
#endif // BITCOIN_RPC_MINING_H

View File

@ -90,6 +90,7 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
{RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"}, {RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"},
{RPCResult::Type::STR, "addrbind", "(ip:port) Bind address of the connection to the peer"}, {RPCResult::Type::STR, "addrbind", "(ip:port) Bind address of the connection to the peer"},
{RPCResult::Type::STR, "addrlocal", "(ip:port) Local address as reported by the peer"}, {RPCResult::Type::STR, "addrlocal", "(ip:port) Local address as reported by the peer"},
{RPCResult::Type::STR, "network", "Network (ipv4, ipv6, or onion) the peer connected through"},
{RPCResult::Type::STR, "mapped_as", "The AS in the BGP route to the peer used for diversifying peer selection"}, {RPCResult::Type::STR, "mapped_as", "The AS in the BGP route to the peer used for diversifying peer selection"},
{RPCResult::Type::STR_HEX, "services", "The services offered"}, {RPCResult::Type::STR_HEX, "services", "The services offered"},
{RPCResult::Type::STR_HEX, "verified_proregtx_hash", true /*optional*/, "Only present when the peer is a masternode and successfully " {RPCResult::Type::STR_HEX, "verified_proregtx_hash", true /*optional*/, "Only present when the peer is a masternode and successfully "
@ -161,10 +162,13 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
bool fStateStats = GetNodeStateStats(stats.nodeid, statestats); bool fStateStats = GetNodeStateStats(stats.nodeid, statestats);
obj.pushKV("id", stats.nodeid); obj.pushKV("id", stats.nodeid);
obj.pushKV("addr", stats.addrName); obj.pushKV("addr", stats.addrName);
if (!(stats.addrLocal.empty())) if (stats.addrBind.IsValid()) {
obj.pushKV("addrlocal", stats.addrLocal);
if (stats.addrBind.IsValid())
obj.pushKV("addrbind", stats.addrBind.ToString()); obj.pushKV("addrbind", stats.addrBind.ToString());
}
if (!(stats.addrLocal.empty())) {
obj.pushKV("addrlocal", stats.addrLocal);
}
obj.pushKV("network", stats.m_network);
if (stats.m_mapped_as != 0) { if (stats.m_mapped_as != 0) {
obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as)); obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
} }

View File

@ -130,20 +130,20 @@ void DeleteAuthCookie()
} }
} }
std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num) std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in)
{ {
if (!in.isArray()) { if (!in.isArray()) {
throw std::runtime_error("Batch must be an array"); throw std::runtime_error("Batch must be an array");
} }
const size_t num {in.size()};
std::vector<UniValue> batch(num); std::vector<UniValue> batch(num);
for (size_t i=0; i<in.size(); ++i) { for (const UniValue& rec : in.getValues()) {
const UniValue &rec = in[i];
if (!rec.isObject()) { if (!rec.isObject()) {
throw std::runtime_error("Batch member must be object"); throw std::runtime_error("Batch member must be an object");
} }
size_t id = rec["id"].get_int(); size_t id = rec["id"].get_int();
if (id >= num) { if (id >= num) {
throw std::runtime_error("Batch member id larger than size"); throw std::runtime_error("Batch member id is larger than batch size");
} }
batch[id] = rec; batch[id] = rec;
} }

View File

@ -24,7 +24,7 @@ bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */ /** Delete RPC authentication cookie from disk */
void DeleteAuthCookie(); void DeleteAuthCookie();
/** Parse JSON-RPC batch reply into a vector */ /** Parse JSON-RPC batch reply into a vector */
std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num); std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in);
class JSONRPCRequest class JSONRPCRequest
{ {

View File

@ -47,6 +47,7 @@ FUZZ_TARGET_INIT(net, initialize_net)
*address_bind, *address_bind,
fuzzed_data_provider.ConsumeRandomLengthString(32), fuzzed_data_provider.ConsumeRandomLengthString(32),
fuzzed_data_provider.ConsumeBool(), fuzzed_data_provider.ConsumeBool(),
fuzzed_data_provider.ConsumeBool(),
fuzzed_data_provider.ConsumeBool() fuzzed_data_provider.ConsumeBool()
}; };
while (fuzzed_data_provider.ConsumeBool()) { while (fuzzed_data_provider.ConsumeBool()) {
@ -148,4 +149,5 @@ FUZZ_TARGET_INIT(net, initialize_net)
fuzzed_data_provider.PickValueInArray<NetPermissionFlags>({NetPermissionFlags::PF_NONE, NetPermissionFlags::PF_BLOOMFILTER, NetPermissionFlags::PF_RELAY, NetPermissionFlags::PF_FORCERELAY, NetPermissionFlags::PF_NOBAN, NetPermissionFlags::PF_MEMPOOL, NetPermissionFlags::PF_ISIMPLICIT, NetPermissionFlags::PF_ALL}) : fuzzed_data_provider.PickValueInArray<NetPermissionFlags>({NetPermissionFlags::PF_NONE, NetPermissionFlags::PF_BLOOMFILTER, NetPermissionFlags::PF_RELAY, NetPermissionFlags::PF_FORCERELAY, NetPermissionFlags::PF_NOBAN, NetPermissionFlags::PF_MEMPOOL, NetPermissionFlags::PF_ISIMPLICIT, NetPermissionFlags::PF_ALL}) :
static_cast<NetPermissionFlags>(fuzzed_data_provider.ConsumeIntegral<uint32_t>()); static_cast<NetPermissionFlags>(fuzzed_data_provider.ConsumeIntegral<uint32_t>());
(void)node.HasPermission(net_permission_flags); (void)node.HasPermission(net_permission_flags);
(void)node.ConnectedThroughNetwork();
} }

View File

@ -3,14 +3,17 @@
// Distributed under the MIT software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
#include <torcontrol.h> #include <torcontrol.h>
#include <util/strencodings.h>
#include <netbase.h> #include <chainparams.h>
#include <chainparamsbase.h>
#include <crypto/hmac_sha256.h>
#include <net.h> #include <net.h>
#include <netaddress.h>
#include <netbase.h>
#include <util/strencodings.h>
#include <util/system.h> #include <util/system.h>
#include <util/time.h> #include <util/time.h>
#include <crypto/hmac_sha256.h>
#include <vector> #include <vector>
#include <deque> #include <deque>
@ -79,12 +82,12 @@ public:
/** /**
* Connect to a Tor control port. * Connect to a Tor control port.
* target is address of the form host:port. * tor_control_center is address of the form host:port.
* connected is the handler that is called when connection is successfully established. * connected is the handler that is called when connection is successfully established.
* disconnected is a handler that is called when the connection is broken. * disconnected is a handler that is called when the connection is broken.
* Return true on success. * Return true on success.
*/ */
bool Connect(const std::string &target, const ConnectionCB& connected, const ConnectionCB& disconnected); bool Connect(const std::string& tor_control_center, const ConnectionCB& connected, const ConnectionCB& disconnected);
/** /**
* Disconnect from Tor control port. * Disconnect from Tor control port.
@ -191,16 +194,16 @@ void TorControlConnection::eventcb(struct bufferevent *bev, short what, void *ct
} }
} }
bool TorControlConnection::Connect(const std::string &target, const ConnectionCB& _connected, const ConnectionCB& _disconnected) bool TorControlConnection::Connect(const std::string& tor_control_center, const ConnectionCB& _connected, const ConnectionCB& _disconnected)
{ {
if (b_conn) if (b_conn)
Disconnect(); Disconnect();
// Parse target address:port // Parse tor_control_center address:port
struct sockaddr_storage connect_to_addr; struct sockaddr_storage connect_to_addr;
int connect_to_addrlen = sizeof(connect_to_addr); int connect_to_addrlen = sizeof(connect_to_addr);
if (evutil_parse_sockaddr_port(target.c_str(), if (evutil_parse_sockaddr_port(tor_control_center.c_str(),
(struct sockaddr*)&connect_to_addr, &connect_to_addrlen)<0) { (struct sockaddr*)&connect_to_addr, &connect_to_addrlen)<0) {
LogPrintf("tor: Error parsing socket address %s\n", target); LogPrintf("tor: Error parsing socket address %s\n", tor_control_center);
return false; return false;
} }
@ -213,9 +216,9 @@ bool TorControlConnection::Connect(const std::string &target, const ConnectionCB
this->connected = _connected; this->connected = _connected;
this->disconnected = _disconnected; this->disconnected = _disconnected;
// Finally, connect to target // Finally, connect to tor_control_center
if (bufferevent_socket_connect(b_conn, (struct sockaddr*)&connect_to_addr, connect_to_addrlen) < 0) { if (bufferevent_socket_connect(b_conn, (struct sockaddr*)&connect_to_addr, connect_to_addrlen) < 0) {
LogPrintf("tor: Error connecting to address %s\n", target); LogPrintf("tor: Error connecting to address %s\n", tor_control_center);
return false; return false;
} }
return true; return true;
@ -408,7 +411,7 @@ static bool WriteBinaryFile(const fs::path &filename, const std::string &data)
class TorController class TorController
{ {
public: public:
TorController(struct event_base* base, const std::string& target); TorController(struct event_base* base, const std::string& tor_control_center, const CService& target);
~TorController(); ~TorController();
/** Get name of file to store private key in */ /** Get name of file to store private key in */
@ -418,7 +421,7 @@ public:
void Reconnect(); void Reconnect();
private: private:
struct event_base* base; struct event_base* base;
std::string target; const std::string m_tor_control_center;
TorControlConnection conn; TorControlConnection conn;
std::string private_key; std::string private_key;
std::string service_id; std::string service_id;
@ -426,6 +429,7 @@ private:
struct event *reconnect_ev; struct event *reconnect_ev;
float reconnect_timeout; float reconnect_timeout;
CService service; CService service;
const CService m_target;
/** Cookie for SAFECOOKIE auth */ /** Cookie for SAFECOOKIE auth */
std::vector<uint8_t> cookie; std::vector<uint8_t> cookie;
/** ClientNonce for SAFECOOKIE auth */ /** ClientNonce for SAFECOOKIE auth */
@ -448,18 +452,19 @@ private:
static void reconnect_cb(evutil_socket_t fd, short what, void *arg); static void reconnect_cb(evutil_socket_t fd, short what, void *arg);
}; };
TorController::TorController(struct event_base* _base, const std::string& _target): TorController::TorController(struct event_base* _base, const std::string& tor_control_center, const CService& target):
base(_base), base(_base),
target(_target), conn(base), reconnect(true), reconnect_ev(0), m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_ev(0),
reconnect_timeout(RECONNECT_TIMEOUT_START) reconnect_timeout(RECONNECT_TIMEOUT_START),
m_target(target)
{ {
reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); reconnect_ev = event_new(base, -1, 0, reconnect_cb, this);
if (!reconnect_ev) if (!reconnect_ev)
LogPrintf("tor: Failed to create event for reconnection: out of memory?\n"); LogPrintf("tor: Failed to create event for reconnection: out of memory?\n");
// Start connection attempts immediately // Start connection attempts immediately
if (!conn.Connect(_target, std::bind(&TorController::connected_cb, this, std::placeholders::_1), if (!conn.Connect(m_tor_control_center, std::bind(&TorController::connected_cb, this, std::placeholders::_1),
std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) { std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) {
LogPrintf("tor: Initiating connection to Tor control port %s failed\n", _target); LogPrintf("tor: Initiating connection to Tor control port %s failed\n", m_tor_control_center);
} }
// Read service private key if cached // Read service private key if cached
std::pair<bool,std::string> pkf = ReadBinaryFile(GetPrivateKeyFile()); std::pair<bool,std::string> pkf = ReadBinaryFile(GetPrivateKeyFile());
@ -535,7 +540,7 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply&
} }
// Request hidden service, redirect port. // Request hidden service, redirect port.
// Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports. // Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports.
_conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, Params().GetDefaultPort(), GetListenPort()), _conn.Command(strprintf("ADD_ONION %s Port=%i,%s", private_key, Params().GetDefaultPort(), m_target.ToStringIPPort()),
std::bind(&TorController::add_onion_cb, this, std::placeholders::_1, std::placeholders::_2)); std::bind(&TorController::add_onion_cb, this, std::placeholders::_1, std::placeholders::_2));
} else { } else {
LogPrintf("tor: Authentication failed\n"); LogPrintf("tor: Authentication failed\n");
@ -697,7 +702,7 @@ void TorController::disconnected_cb(TorControlConnection& _conn)
if (!reconnect) if (!reconnect)
return; return;
LogPrint(BCLog::TOR, "tor: Not connected to Tor control port %s, trying to reconnect\n", target); LogPrint(BCLog::TOR, "tor: Not connected to Tor control port %s, trying to reconnect\n", m_tor_control_center);
// Single-shot timer for reconnect. Use exponential backoff. // Single-shot timer for reconnect. Use exponential backoff.
struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0));
@ -711,9 +716,9 @@ void TorController::Reconnect()
/* Try to reconnect and reestablish if we get booted - for example, Tor /* Try to reconnect and reestablish if we get booted - for example, Tor
* may be restarting. * may be restarting.
*/ */
if (!conn.Connect(target, std::bind(&TorController::connected_cb, this, std::placeholders::_1), if (!conn.Connect(m_tor_control_center, std::bind(&TorController::connected_cb, this, std::placeholders::_1),
std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) { std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) {
LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", target); LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", m_tor_control_center);
} }
} }
@ -732,14 +737,14 @@ void TorController::reconnect_cb(evutil_socket_t fd, short what, void *arg)
static struct event_base *gBase; static struct event_base *gBase;
static std::thread torControlThread; static std::thread torControlThread;
static void TorControlThread() static void TorControlThread(CService onion_service_target)
{ {
TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL)); TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL), onion_service_target);
event_base_dispatch(gBase); event_base_dispatch(gBase);
} }
void StartTorControl() void StartTorControl(CService onion_service_target)
{ {
assert(!gBase); assert(!gBase);
#ifdef WIN32 #ifdef WIN32
@ -753,7 +758,9 @@ void StartTorControl()
return; return;
} }
torControlThread = std::thread(std::bind(&TraceThread<void (*)()>, "torcontrol", &TorControlThread)); torControlThread = std::thread(&TraceThread<std::function<void()>>, "torcontrol", [onion_service_target] {
TorControlThread(onion_service_target);
});
} }
void InterruptTorControl() void InterruptTorControl()
@ -774,3 +781,10 @@ void StopTorControl()
gBase = nullptr; gBase = nullptr;
} }
} }
CService DefaultOnionServiceTarget()
{
struct in_addr onion_service_target;
onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
return {onion_service_target, BaseParams().OnionServiceTargetPort()};
}

View File

@ -8,12 +8,17 @@
#ifndef BITCOIN_TORCONTROL_H #ifndef BITCOIN_TORCONTROL_H
#define BITCOIN_TORCONTROL_H #define BITCOIN_TORCONTROL_H
#include <string>
class CService;
extern const std::string DEFAULT_TOR_CONTROL; extern const std::string DEFAULT_TOR_CONTROL;
static const bool DEFAULT_LISTEN_ONION = true; static const bool DEFAULT_LISTEN_ONION = true;
void StartTorControl(); void StartTorControl(CService onion_service_target);
void InterruptTorControl(); void InterruptTorControl();
void StopTorControl(); void StopTorControl();
CService DefaultOnionServiceTarget();
#endif /* BITCOIN_TORCONTROL_H */ #endif /* BITCOIN_TORCONTROL_H */

View File

@ -18,8 +18,9 @@ Test plan:
- proxy on IPv6 - proxy on IPv6
- Create various proxies (as threads) - Create various proxies (as threads)
- Create dashds that connect to them - Create nodes that connect to them
- Manipulate the dashds using addnode (onetry) an observe effects - Manipulate the peer connections using addnode (onetry) and observe effects
- Test the getpeerinfo `network` field for the peer
addnode connect to IPv4 addnode connect to IPv4
addnode connect to IPv6 addnode connect to IPv6
@ -40,6 +41,12 @@ from test_framework.util import (
from test_framework.netutil import test_ipv6_local from test_framework.netutil import test_ipv6_local
RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports
# From GetNetworkName() in netbase.cpp:
NET_UNROUTABLE = ""
NET_IPV4 = "ipv4"
NET_IPV6 = "ipv6"
NET_ONION = "onion"
class ProxyTest(BitcoinTestFramework): class ProxyTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
@ -90,10 +97,16 @@ class ProxyTest(BitcoinTestFramework):
self.add_nodes(self.num_nodes, extra_args=args) self.add_nodes(self.num_nodes, extra_args=args)
self.start_nodes() self.start_nodes()
def network_test(self, node, addr, network):
for peer in node.getpeerinfo():
if peer["addr"] == addr:
assert_equal(peer["network"], network)
def node_test(self, node, proxies, auth, test_onion=True): def node_test(self, node, proxies, auth, test_onion=True):
rv = [] rv = []
# Test: outgoing IPv4 connection through node addr = "15.61.23.23:1234"
node.addnode("15.61.23.23:1234", "onetry") self.log.debug("Test: outgoing IPv4 connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[0].queue.get() cmd = proxies[0].queue.get()
assert isinstance(cmd, Socks5Command) assert isinstance(cmd, Socks5Command)
# Note: dashd's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 # Note: dashd's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
@ -104,10 +117,12 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None) assert_equal(cmd.username, None)
assert_equal(cmd.password, None) assert_equal(cmd.password, None)
rv.append(cmd) rv.append(cmd)
self.network_test(node, addr, network=NET_IPV4)
if self.have_ipv6: if self.have_ipv6:
# Test: outgoing IPv6 connection through node addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443"
node.addnode("[1233:3432:2434:2343:3234:2345:6546:4534]:5443", "onetry") self.log.debug("Test: outgoing IPv6 connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[1].queue.get() cmd = proxies[1].queue.get()
assert isinstance(cmd, Socks5Command) assert isinstance(cmd, Socks5Command)
# Note: dashd's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 # Note: dashd's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
@ -118,10 +133,12 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None) assert_equal(cmd.username, None)
assert_equal(cmd.password, None) assert_equal(cmd.password, None)
rv.append(cmd) rv.append(cmd)
self.network_test(node, addr, network=NET_IPV6)
if test_onion: if test_onion:
# Test: outgoing onion connection through node addr = "bitcoinostk4e4re.onion:8333"
node.addnode("bitcoinostk4e4re.onion:8333", "onetry") self.log.debug("Test: outgoing onion connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[2].queue.get() cmd = proxies[2].queue.get()
assert isinstance(cmd, Socks5Command) assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME) assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@ -131,9 +148,11 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None) assert_equal(cmd.username, None)
assert_equal(cmd.password, None) assert_equal(cmd.password, None)
rv.append(cmd) rv.append(cmd)
self.network_test(node, addr, network=NET_ONION)
# Test: outgoing DNS name connection through node addr = "node.noumenon:8333"
node.addnode("node.noumenon:8333", "onetry") self.log.debug("Test: outgoing DNS name connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[3].queue.get() cmd = proxies[3].queue.get()
assert isinstance(cmd, Socks5Command) assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME) assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@ -143,6 +162,7 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None) assert_equal(cmd.username, None)
assert_equal(cmd.password, None) assert_equal(cmd.password, None)
rv.append(cmd) rv.append(cmd)
self.network_test(node, addr, network=NET_UNROUTABLE)
return rv return rv
@ -197,5 +217,6 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n3[net]['proxy_randomize_credentials'], False) assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False) assert_equal(n3['onion']['reachable'], False)
if __name__ == '__main__': if __name__ == '__main__':
ProxyTest().main() ProxyTest().main()

View File

@ -3,30 +3,41 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test dash-cli""" """Test dash-cli"""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_process_error, get_auth_cookie from test_framework.util import (
assert_equal,
assert_raises_process_error,
assert_raises_rpc_error,
get_auth_cookie,
)
# The block reward of coinbaseoutput.nValue (500) DASH/block matures after
# COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect
# node 0 to have a balance of (BLOCKS - COINBASE_MATURITY) * 500 DASH/block.
BLOCKS = 101
BALANCE = (BLOCKS - 100) * 500
JSON_PARSING_ERROR = 'error: Error parsing JSON: foo'
BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero'
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'
class TestBitcoinCli(BitcoinTestFramework): class TestBitcoinCli(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.setup_clean_chain = True self.setup_clean_chain = True
self.num_nodes = 1 self.num_nodes = 1
if self.is_wallet_compiled():
self.requires_wallet = True
def skip_test_if_missing_module(self): def skip_test_if_missing_module(self):
self.skip_if_no_cli() self.skip_if_no_cli()
def run_test(self): def run_test(self):
"""Main test logic""" """Main test logic"""
self.nodes[0].generate(BLOCKS)
cli_response = self.nodes[0].cli("-version").send_cli()
assert "{} RPC client version".format(self.config['environment']['PACKAGE_NAME']) in cli_response
self.log.info("Compare responses from getwalletinfo RPC and `dash-cli getwalletinfo`")
if self.is_wallet_compiled():
self.nodes[0].createwallet(self.default_wallet_name)
cli_response = self.nodes[0].cli.getwalletinfo()
rpc_response = self.nodes[0].getwalletinfo()
assert_equal(cli_response, rpc_response)
self.log.info("Compare responses from getblockchaininfo RPC and `dash-cli getblockchaininfo`") self.log.info("Compare responses from getblockchaininfo RPC and `dash-cli getblockchaininfo`")
cli_response = self.nodes[0].cli.getblockchaininfo() cli_response = self.nodes[0].cli.getblockchaininfo()
@ -36,12 +47,12 @@ class TestBitcoinCli(BitcoinTestFramework):
user, password = get_auth_cookie(self.nodes[0].datadir, self.chain) user, password = get_auth_cookie(self.nodes[0].datadir, self.chain)
self.log.info("Test -stdinrpcpass option") self.log.info("Test -stdinrpcpass option")
assert_equal(0, self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input=password).getblockcount()) assert_equal(BLOCKS, self.nodes[0].cli('-rpcuser={}'.format(user), '-stdinrpcpass', input=password).getblockcount())
assert_raises_process_error(1, "Incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input="foo").echo) assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword', self.nodes[0].cli('-rpcuser={}'.format(user), '-stdinrpcpass', input='foo').echo)
self.log.info("Test -stdin and -stdinrpcpass") self.log.info("Test -stdin and -stdinrpcpass")
assert_equal(["foo", "bar"], self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input=password + "\nfoo\nbar").echo()) assert_equal(['foo', 'bar'], self.nodes[0].cli('-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input=password + '\nfoo\nbar').echo())
assert_raises_process_error(1, "Incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input="foo").echo) assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword', self.nodes[0].cli('-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input='foo').echo)
self.log.info("Test connecting to a non-existing server") self.log.info("Test connecting to a non-existing server")
assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo) assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo)
@ -49,30 +60,185 @@ class TestBitcoinCli(BitcoinTestFramework):
self.log.info("Test connecting with non-existing RPC cookie file") self.log.info("Test connecting with non-existing RPC cookie file")
assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli('-rpccookiefile=does-not-exist', '-rpcpassword=').echo) assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli('-rpccookiefile=does-not-exist', '-rpcpassword=').echo)
self.log.info("Make sure that -getinfo with arguments fails") self.log.info("Test -getinfo with arguments fails")
assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help) assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)
self.log.info("Compare responses from `dash-cli -getinfo` and the RPCs data is retrieved from.") self.log.info("Test -getinfo returns expected network and blockchain info")
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
if self.is_wallet_compiled(): if self.is_wallet_compiled():
wallet_info = self.nodes[0].getwalletinfo() self.nodes[0].encryptwallet(password)
cli_get_info = self.nodes[0].cli().send_cli('-getinfo')
network_info = self.nodes[0].getnetworkinfo() network_info = self.nodes[0].getnetworkinfo()
blockchain_info = self.nodes[0].getblockchaininfo() blockchain_info = self.nodes[0].getblockchaininfo()
assert_equal(cli_get_info['version'], network_info['version']) assert_equal(cli_get_info['version'], network_info['version'])
assert_equal(cli_get_info['blocks'], blockchain_info['blocks']) 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['timeoffset'], network_info['timeoffset'])
assert_equal(cli_get_info['connections'], network_info['connections']) assert_equal(cli_get_info['connections'], network_info['connections'])
assert_equal(cli_get_info['proxy'], network_info['networks'][0]['proxy']) 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['difficulty'], blockchain_info['difficulty'])
assert_equal(cli_get_info['chain'], blockchain_info['chain']) assert_equal(cli_get_info['chain'], blockchain_info['chain'])
if self.is_wallet_compiled(): if self.is_wallet_compiled():
assert_equal(cli_get_info['balance'], wallet_info['balance']) 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()
wallet_info = self.nodes[0].getwalletinfo()
assert_equal(cli_get_info['coinjoin_balance'], wallet_info['coinjoin_balance']) 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['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['paytxfee'], wallet_info['paytxfee'])
assert_equal(cli_get_info['relayfee'], network_info['relayfee']) assert_equal(cli_get_info['relayfee'], network_info['relayfee'])
# unlocked_until is not tested because the wallet is not encrypted assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info)
# Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets.
wallets = [self.default_wallet_name, 'Encrypted', 'secret']
amounts = [BALANCE + Decimal('459.9999955'), Decimal(9), Decimal(31)]
self.nodes[0].createwallet(wallet_name=wallets[1])
self.nodes[0].createwallet(wallet_name=wallets[2])
w1 = self.nodes[0].get_wallet_rpc(wallets[0])
w2 = self.nodes[0].get_wallet_rpc(wallets[1])
w3 = self.nodes[0].get_wallet_rpc(wallets[2])
rpcwallet2 = '-rpcwallet={}'.format(wallets[1])
rpcwallet3 = '-rpcwallet={}'.format(wallets[2])
w1.walletpassphrase(password, self.rpc_timeout)
w2.encryptwallet(password)
w1.sendtoaddress(w2.getnewaddress(), amounts[1])
w1.sendtoaddress(w3.getnewaddress(), amounts[2])
# Mine a block to confirm; adds a block reward (500 DASH) to the default wallet.
self.nodes[0].generate(1)
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])
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
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)})
# 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:])})
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])
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])
self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balances")
cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli()
assert 'balance' not in cli_get_info_keys
assert 'balances' not in cli_get_info_keys
# Test bitcoin-cli -generate.
n1 = 3
n2 = 4
w2.walletpassphrase(password, self.rpc_timeout)
blocks = self.nodes[0].getblockcount()
self.log.info('Test -generate with no args')
generate = self.nodes[0].cli('-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1)
self.log.info('Test -generate with bad args')
assert_raises_process_error(1, JSON_PARSING_ERROR, self.nodes[0].cli('-generate', 'foo').echo)
assert_raises_process_error(1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli('-generate', 0).echo)
assert_raises_process_error(1, TOO_MANY_ARGS, self.nodes[0].cli('-generate', 1, 2, 3).echo)
self.log.info('Test -generate with nblocks')
generate = self.nodes[0].cli('-generate', n1).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1)
self.log.info('Test -generate with nblocks and maxtries')
generate = self.nodes[0].cli('-generate', n2, 1000000).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n2)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1 + n2)
self.log.info('Test -generate -rpcwallet in single-wallet mode')
generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 2 + n1 + n2)
self.log.info('Test -generate -rpcwallet=unloaded wallet raises RPC error')
assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate').echo)
assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 'foo').echo)
assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 0).echo)
assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 1, 2, 3).echo)
# Test bitcoin-cli -generate with -rpcwallet in multiwallet mode.
self.nodes[0].loadwallet(wallets[2])
n3 = 4
n4 = 10
blocks = self.nodes[0].getblockcount()
self.log.info('Test -generate -rpcwallet with no args')
generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1)
self.log.info('Test -generate -rpcwallet with bad args')
assert_raises_process_error(1, JSON_PARSING_ERROR, self.nodes[0].cli(rpcwallet2, '-generate', 'foo').echo)
assert_raises_process_error(1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli(rpcwallet2, '-generate', 0).echo)
assert_raises_process_error(1, TOO_MANY_ARGS, self.nodes[0].cli(rpcwallet2, '-generate', 1, 2, 3).echo)
self.log.info('Test -generate -rpcwallet with nblocks')
generate = self.nodes[0].cli(rpcwallet2, '-generate', n3).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n3)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3)
self.log.info('Test -generate -rpcwallet with nblocks and maxtries')
generate = self.nodes[0].cli(rpcwallet2, '-generate', n4, 1000000).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n4)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3 + n4)
self.log.info('Test -generate without -rpcwallet in multiwallet mode raises RPC error')
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate').echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 'foo').echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 0).echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 1, 2, 3).echo)
else:
self.log.info("*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped")
self.nodes[0].generate(25) # maintain block parity with the wallet_compiled conditional branch
self.log.info("Test -version with node stopped")
self.stop_node(0)
cli_response = self.nodes[0].cli().send_cli('-version')
assert "{} RPC client version".format(self.config['environment']['PACKAGE_NAME']) in cli_response
self.log.info("Test -rpcwait option successfully waits for RPC connection")
self.nodes[0].start() # start node without RPC connection
self.nodes[0].wait_for_cookie_credentials() # ensure cookie file is available to avoid race condition
blocks = self.nodes[0].cli('-rpcwait').send_cli('getblockcount')
self.nodes[0].wait_for_rpc_connection()
assert_equal(blocks, BLOCKS + 25)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -27,6 +27,7 @@ from .util import (
MAX_NODES, MAX_NODES,
append_config, append_config,
delete_cookie_file, delete_cookie_file,
get_auth_cookie,
get_rpc_proxy, get_rpc_proxy,
rpc_url, rpc_url,
wait_until, wait_until,
@ -280,12 +281,27 @@ class TestNode():
pass # Port not yet open? pass # Port not yet open?
else: else:
raise # unknown OS error raise # unknown OS error
except ValueError as e: # cookie file not found and no rpcuser or rpcassword. dashd still starting except ValueError as e: # cookie file not found and no rpcuser or rpcpassword; dashd is still starting
if "No RPC credentials" not in str(e): if "No RPC credentials" not in str(e):
raise raise
time.sleep(1.0 / poll_per_s) time.sleep(1.0 / poll_per_s)
self._raise_assertion_error("Unable to connect to dashd after {}s".format(self.rpc_timeout)) self._raise_assertion_error("Unable to connect to dashd after {}s".format(self.rpc_timeout))
def wait_for_cookie_credentials(self):
"""Ensures auth cookie credentials can be read, e.g. for testing CLI with -rpcwait before RPC connection is up."""
self.log.debug("Waiting for cookie credentials")
# Poll at a rate of four times per second.
poll_per_s = 4
for _ in range(poll_per_s * self.rpc_timeout):
try:
get_auth_cookie(self.datadir, self.chain)
self.log.debug("Cookie credentials successfully retrieved")
return
except ValueError: # cookie file not found and no rpcuser or rpcpassword; bitcoind is still starting
pass # so we continue polling until RPC credentials are retrieved
time.sleep(1.0 / poll_per_s)
self._raise_assertion_error("Unable to retrieve cookie credentials after {}s".format(self.rpc_timeout))
def generate(self, nblocks, maxtries=1000000): def generate(self, nblocks, maxtries=1000000):
self.log.debug("TestNode.generate() dispatches `generate` call to `generatetoaddress`") self.log.debug("TestNode.generate() dispatches `generate` call to `generatetoaddress`")
return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries) return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries)