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).*
HiddenServiceDir /var/lib/tor/dashcore-service/
HiddenServicePort 9999 127.0.0.1:9999
HiddenServicePort 19999 127.0.0.1:19999
HiddenServicePort 9999 127.0.0.1:9996
HiddenServicePort 19999 127.0.0.1:19996
The directory can be different of course, but (both) port numbers should be equal to
your dashd's P2P listen port (9999 by default).
The directory can be different of course, but virtual port numbers should be equal to
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
this option, and this can be a .onion address. Given the above

View File

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

View File

@ -11,6 +11,7 @@
#include <chainparamsbase.h>
#include <clientversion.h>
#include <rpc/client.h>
#include <rpc/mining.h>
#include <rpc/protocol.h>
#include <rpc/request.h>
#include <stacktraces.h>
@ -39,6 +40,9 @@ static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
static const bool DEFAULT_NAMED=false;
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)
{
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("-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("-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("-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("-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);
@ -239,6 +245,7 @@ public:
const int ID_NETWORKINFO = 0;
const int ID_BLOCKCHAININFO = 1;
const int ID_WALLETINFO = 2;
const int ID_BALANCES = 3;
/** Create a simulated `getinfo` request. */
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("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO));
result.push_back(JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO));
result.push_back(JSONRPCRequestObj("getbalances", NullUniValue, ID_BALANCES));
return result;
}
@ -257,9 +265,9 @@ public:
UniValue ProcessReply(const UniValue &batch_in) override
{
UniValue result(UniValue::VOBJ);
std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in, 3);
// Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass them on
// getwalletinfo() is allowed to fail in case there is no wallet.
const std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in);
// Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass them on;
// getwalletinfo() and getbalances() are allowed to fail if there is no wallet.
if (!batch[ID_NETWORKINFO]["error"].isNull()) {
return batch[ID_NETWORKINFO];
}
@ -276,7 +284,6 @@ public:
result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);
result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"]));
if (!batch[ID_WALLETINFO]["result"].isNull()) {
result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]);
result.pushKV("coinjoin_balance", batch[ID_WALLETINFO]["result"]["coinjoin_balance"]);
result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]);
if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {
@ -284,12 +291,207 @@ public:
}
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("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
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 */
class DefaultRequestHandler: public BaseRequestHandler {
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;
// 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
std::string endpoint = "/";
if (!gArgs.GetArgs("-rpcwallet").empty()) {
std::string walletName = gArgs.GetArg("-rpcwallet", "");
char *encodedURI = evhttp_uriencode(walletName.data(), walletName.size(), false);
if (rpcwallet) {
char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
if (encodedURI) {
endpoint = "/wallet/"+ std::string(encodedURI);
endpoint = "/wallet/" + std::string(encodedURI);
free(encodedURI);
}
else {
} else {
throw CConnectionFailed("uri-encode failed");
}
}
@ -427,6 +627,121 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co
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[])
{
std::string strPrint;
@ -483,9 +798,19 @@ static int CommandLineRPC(int argc, char *argv[])
}
std::unique_ptr<BaseRequestHandler> rh;
std::string method;
if (gArgs.GetBoolArg("-getinfo", false)) {
if (gArgs.IsArgSet("-getinfo")) {
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 {
rh.reset(new DefaultRequestHandler());
if (args.size() < 1) {
@ -494,62 +819,28 @@ static int CommandLineRPC(int argc, char *argv[])
method = args[0];
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
const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
do {
try {
const UniValue reply = CallRPC(rh.get(), method, args);
// 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);
// Parse reply
UniValue result = find_value(reply, "result");
const UniValue& error = find_value(reply, "error");
if (error.isNull()) {
if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
GetWalletBalances(result); // fetch multiwallet balances and append to result
}
// Connection succeeded, no need to retry.
break;
ParseResult(result, strPrint);
} else {
ParseError(error, strPrint, nRet);
}
catch (const CConnectionFailed&) {
if (fWait)
UninterruptibleSleep(std::chrono::milliseconds{1000});
else
throw;
}
} while (fWait);
}
catch (const std::exception& e) {
}
} catch (const std::exception& e) {
strPrint = std::string("error: ") + e.what();
nRet = EXIT_FAILURE;
}
catch (...) {
} catch (...) {
PrintExceptionContinue(std::current_exception(), "CommandLineRPC()");
throw;
}

View File

@ -49,16 +49,20 @@ const CBaseChainParams& BaseParams()
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)
{
if (chain == CBaseChainParams::MAIN)
return std::make_unique<CBaseChainParams>("", 9998);
return std::make_unique<CBaseChainParams>("", 9998, 9996);
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)
return std::make_unique<CBaseChainParams>(gArgs.GetDevNetName(), 19798);
return std::make_unique<CBaseChainParams>(gArgs.GetDevNetName(), 19798, 19796);
else if (chain == CBaseChainParams::REGTEST)
return std::make_unique<CBaseChainParams>("regtest", 19898);
return std::make_unique<CBaseChainParams>("regtest", 19898, 19896);
else
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; }
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(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:
uint16_t nRPCPort;
const uint16_t m_rpc_port;
const uint16_t m_onion_service_target_port;
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("-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("-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("-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);
@ -2480,8 +2480,6 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
}
}
LogPrintf("::ChainActive().Height() = %d\n", chain_active_height);
if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION))
StartTorControl();
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.m_peer_connect_timeout = peer_connect_timeout;
for (const std::string& strBind : args.GetArgs("-bind")) {
CService addrBind;
if (!Lookup(strBind, addrBind, GetListenPort(), false)) {
return InitError(ResolveErrMsg("bind", strBind));
for (const std::string& bind_arg : args.GetArgs("-bind")) {
CService bind_addr;
const size_t index = bind_arg.rfind('=');
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")) {
NetWhitebindPermissions whitebind;
bilingual_str error;

View File

@ -54,6 +54,7 @@
#include <sys/event.h>
#endif
#include <algorithm>
#include <cstdint>
#include <unordered_map>
@ -99,6 +100,11 @@ enum BindFlags {
BF_NONE = 0,
BF_EXPLICIT = (1U << 0),
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
@ -580,6 +586,11 @@ std::string CNode::GetLogString() const
return fLogIPs ? addr.ToString() : strprintf("%d", id);
}
Network CNode::ConnectedThroughNetwork() const
{
return fInbound && m_inbound_onion ? NET_ONION : addr.GetNetClass();
}
#undef X
#define X(name) stats.name = name
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(addr);
X(addrBind);
stats.m_network = GetNetworkName(ConnectedThroughNetwork());
stats.m_mapped_as = addr.GetMappedAS(m_asmap);
if (!m_block_relay_only_peer) {
LOCK(m_tx_relay->cs_filter);
@ -1199,7 +1211,9 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) {
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->m_permissionFlags = permissionFlags;
// 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));
if (addrBind.IsRoutable() && fDiscover && (permissions & PF_NOBAN) == 0)
AddLocal(addrBind, LOCAL_BIND);
return true;
}
@ -2974,10 +2985,18 @@ bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags
}
return false;
}
if (addr.IsRoutable() && fDiscover && !(flags & BF_DONT_ADVERTISE) && !(permissions & PF_NOBAN)) {
AddLocal(addr, LOCAL_BIND);
}
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;
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()) {
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;
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);
}
for (const auto& addr_bind : onion_binds) {
fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::PF_NONE);
}
return fBound;
}
@ -3031,7 +3055,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
}
#endif
if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) {
if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds, connOptions.onion_binds)) {
if (clientInterface) {
clientInterface->ThreadSafeMessageBox(
_("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; }
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()),
addr(addrIn),
addrBind(addrBindIn),
@ -3752,7 +3776,8 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
id(idIn),
nLocalHostNonce(nLocalHostNonceIn),
nLocalServices(nLocalServicesIn),
nMyStartingHeight(nMyStartingHeightIn)
nMyStartingHeight(nMyStartingHeightIn),
m_inbound_onion(inbound_onion)
{
hSocket = hSocketIn;
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;

View File

@ -178,6 +178,7 @@ public:
std::vector<NetWhitelistPermissions> vWhitelistedRange;
std::vector<NetWhitebindPermissions> vWhiteBinds;
std::vector<CService> vBinds;
std::vector<CService> onion_binds;
bool m_use_addrman_outgoing = true;
std::vector<std::string> m_specified_outgoing;
std::vector<std::string> m_added_nodes;
@ -211,6 +212,7 @@ public:
vAddedNodes = connOptions.m_added_nodes;
}
socketEventsMode = connOptions.socketEventsMode;
m_onion_binds = connOptions.onion_binds;
}
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 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 AddOneShot(const std::string& strDest);
void ProcessOneShot();
@ -675,6 +681,12 @@ private:
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 ConnmanTestMsg;
};
@ -797,6 +809,8 @@ public:
CAddress addr;
// Bind address of our side of the connection
CAddress addrBind;
// Name of the network the peer connected through
std::string m_network;
uint32_t m_mapped_as;
// In case this is a verified MN, this value is the proTx of the MN
uint256 verifiedProRegTxHash;
@ -1010,6 +1024,18 @@ public:
std::atomic_bool fHasRecvData{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:
mapMsgCmdSize mapSendBytesPerMsgCmd;
mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv);
@ -1112,7 +1138,7 @@ public:
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(const CNode&) = delete;
CNode& operator=(const CNode&) = delete;
@ -1150,6 +1176,9 @@ private:
CService addrLocal GUARDED_BY(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)
mutable CCriticalSection cs_mnauth;
uint256 sentMNAuthChallenge GUARDED_BY(cs_mnauth);

View File

@ -668,7 +668,7 @@ uint32_t CNetAddr::GetLinkedIPv4() const
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.

View File

@ -200,7 +200,7 @@ class CNetAddr
std::string ToStringIP(bool fUseGetnameinfo = true) const;
uint64_t GetHash() 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.
uint32_t GetLinkedIPv4() const;

View File

@ -250,7 +250,7 @@ UniValue ParseNonRFCJSONValue(const std::string& strVal)
UniValue jVal;
if (!jVal.read(std::string("[")+strVal+std::string("]")) ||
!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];
}

View File

@ -23,6 +23,7 @@
#include <policy/fees.h>
#include <pow.h>
#include <rpc/blockchain.h>
#include <rpc/mining.h>
#include <rpc/server.h>
#include <rpc/util.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."},
{"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::Type::ARR, "", "",
@ -235,7 +236,7 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
.Check(request);
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;
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."},
{"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::Type::ARR, "", "hashes of blocks generated",
@ -272,11 +273,8 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
+ HelpExampleCli("getnewaddress", "")},
}.Check(request);
int nGenerate = request.params[0].get_int();
uint64_t nMaxTries = 1000000;
if (!request.params[2].isNull()) {
nMaxTries = request.params[2].get_int();
}
const int num_blocks{request.params[0].get_int()};
const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
CTxDestination destination = DecodeDestination(request.params[1].get_str());
if (!IsValidDestination(destination)) {
@ -290,7 +288,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
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)
@ -389,7 +387,7 @@ static UniValue generateblock(const JSONRPCRequest& request)
}
uint256 block_hash;
uint64_t max_tries{1000000};
uint64_t max_tries{DEFAULT_MAX_TRIES};
unsigned int extra_nonce{0};
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, "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, "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_HEX, "services", "The services offered"},
{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);
obj.pushKV("id", stats.nodeid);
obj.pushKV("addr", stats.addrName);
if (!(stats.addrLocal.empty()))
obj.pushKV("addrlocal", stats.addrLocal);
if (stats.addrBind.IsValid())
if (stats.addrBind.IsValid()) {
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) {
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()) {
throw std::runtime_error("Batch must be an array");
}
const size_t num {in.size()};
std::vector<UniValue> batch(num);
for (size_t i=0; i<in.size(); ++i) {
const UniValue &rec = in[i];
for (const UniValue& rec : in.getValues()) {
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();
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;
}

View File

@ -24,7 +24,7 @@ bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */
void DeleteAuthCookie();
/** 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
{

View File

@ -47,6 +47,7 @@ FUZZ_TARGET_INIT(net, initialize_net)
*address_bind,
fuzzed_data_provider.ConsumeRandomLengthString(32),
fuzzed_data_provider.ConsumeBool(),
fuzzed_data_provider.ConsumeBool(),
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}) :
static_cast<NetPermissionFlags>(fuzzed_data_provider.ConsumeIntegral<uint32_t>());
(void)node.HasPermission(net_permission_flags);
(void)node.ConnectedThroughNetwork();
}

View File

@ -3,14 +3,17 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.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 <netaddress.h>
#include <netbase.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <util/time.h>
#include <crypto/hmac_sha256.h>
#include <vector>
#include <deque>
@ -79,12 +82,12 @@ public:
/**
* 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.
* disconnected is a handler that is called when the connection is broken.
* 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.
@ -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)
Disconnect();
// Parse target address:port
// Parse tor_control_center address:port
struct sockaddr_storage 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) {
LogPrintf("tor: Error parsing socket address %s\n", target);
LogPrintf("tor: Error parsing socket address %s\n", tor_control_center);
return false;
}
@ -213,9 +216,9 @@ bool TorControlConnection::Connect(const std::string &target, const ConnectionCB
this->connected = _connected;
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) {
LogPrintf("tor: Error connecting to address %s\n", target);
LogPrintf("tor: Error connecting to address %s\n", tor_control_center);
return false;
}
return true;
@ -408,7 +411,7 @@ static bool WriteBinaryFile(const fs::path &filename, const std::string &data)
class TorController
{
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();
/** Get name of file to store private key in */
@ -418,7 +421,7 @@ public:
void Reconnect();
private:
struct event_base* base;
std::string target;
const std::string m_tor_control_center;
TorControlConnection conn;
std::string private_key;
std::string service_id;
@ -426,6 +429,7 @@ private:
struct event *reconnect_ev;
float reconnect_timeout;
CService service;
const CService m_target;
/** Cookie for SAFECOOKIE auth */
std::vector<uint8_t> cookie;
/** ClientNonce for SAFECOOKIE auth */
@ -448,18 +452,19 @@ private:
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),
target(_target), conn(base), reconnect(true), reconnect_ev(0),
reconnect_timeout(RECONNECT_TIMEOUT_START)
m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_ev(0),
reconnect_timeout(RECONNECT_TIMEOUT_START),
m_target(target)
{
reconnect_ev = event_new(base, -1, 0, reconnect_cb, this);
if (!reconnect_ev)
LogPrintf("tor: Failed to create event for reconnection: out of memory?\n");
// 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) )) {
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
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.
// 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));
} else {
LogPrintf("tor: Authentication failed\n");
@ -697,7 +702,7 @@ void TorController::disconnected_cb(TorControlConnection& _conn)
if (!reconnect)
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.
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
* 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) )) {
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 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);
}
void StartTorControl()
void StartTorControl(CService onion_service_target)
{
assert(!gBase);
#ifdef WIN32
@ -753,7 +758,9 @@ void StartTorControl()
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()
@ -774,3 +781,10 @@ void StopTorControl()
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
#define BITCOIN_TORCONTROL_H
#include <string>
class CService;
extern const std::string DEFAULT_TOR_CONTROL;
static const bool DEFAULT_LISTEN_ONION = true;
void StartTorControl();
void StartTorControl(CService onion_service_target);
void InterruptTorControl();
void StopTorControl();
CService DefaultOnionServiceTarget();
#endif /* BITCOIN_TORCONTROL_H */

View File

@ -18,8 +18,9 @@ Test plan:
- proxy on IPv6
- Create various proxies (as threads)
- Create dashds that connect to them
- Manipulate the dashds using addnode (onetry) an observe effects
- Create nodes that connect to them
- 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 IPv6
@ -40,6 +41,12 @@ from test_framework.util import (
from test_framework.netutil import test_ipv6_local
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):
def set_test_params(self):
@ -90,10 +97,16 @@ class ProxyTest(BitcoinTestFramework):
self.add_nodes(self.num_nodes, extra_args=args)
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):
rv = []
# Test: outgoing IPv4 connection through node
node.addnode("15.61.23.23:1234", "onetry")
addr = "15.61.23.23:1234"
self.log.debug("Test: outgoing IPv4 connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[0].queue.get()
assert isinstance(cmd, Socks5Command)
# 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.password, None)
rv.append(cmd)
self.network_test(node, addr, network=NET_IPV4)
if self.have_ipv6:
# Test: outgoing IPv6 connection through node
node.addnode("[1233:3432:2434:2343:3234:2345:6546:4534]:5443", "onetry")
addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443"
self.log.debug("Test: outgoing IPv6 connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[1].queue.get()
assert isinstance(cmd, Socks5Command)
# 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.password, None)
rv.append(cmd)
self.network_test(node, addr, network=NET_IPV6)
if test_onion:
# Test: outgoing onion connection through node
node.addnode("bitcoinostk4e4re.onion:8333", "onetry")
addr = "bitcoinostk4e4re.onion:8333"
self.log.debug("Test: outgoing onion connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[2].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@ -131,9 +148,11 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
self.network_test(node, addr, network=NET_ONION)
# Test: outgoing DNS name connection through node
node.addnode("node.noumenon:8333", "onetry")
addr = "node.noumenon:8333"
self.log.debug("Test: outgoing DNS name connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[3].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@ -143,6 +162,7 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
self.network_test(node, addr, network=NET_UNROUTABLE)
return rv
@ -197,5 +217,6 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
if __name__ == '__main__':
ProxyTest().main()

View File

@ -3,30 +3,41 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test dash-cli"""
from decimal import Decimal
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):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
if self.is_wallet_compiled():
self.requires_wallet = True
def skip_test_if_missing_module(self):
self.skip_if_no_cli()
def run_test(self):
"""Main test logic"""
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.nodes[0].generate(BLOCKS)
self.log.info("Compare responses from getblockchaininfo RPC and `dash-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)
self.log.info("Test -stdinrpcpass option")
assert_equal(0, self.nodes[0].cli('-rpcuser=%s' % 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_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={}'.format(user), '-stdinrpcpass', input='foo').echo)
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_raises_process_error(1, "Incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input="foo").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={}'.format(user), '-stdin', '-stdinrpcpass', input='foo').echo)
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)
@ -49,30 +60,185 @@ class TestBitcoinCli(BitcoinTestFramework):
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)
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)
self.log.info("Compare responses from `dash-cli -getinfo` and the RPCs data is retrieved from.")
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
self.log.info("Test -getinfo returns expected network and blockchain info")
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()
blockchain_info = self.nodes[0].getblockchaininfo()
assert_equal(cli_get_info['version'], network_info['version'])
assert_equal(cli_get_info['blocks'], blockchain_info['blocks'])
assert_equal(cli_get_info['headers'], blockchain_info['headers'])
assert_equal(cli_get_info['timeoffset'], network_info['timeoffset'])
assert_equal(cli_get_info['connections'], network_info['connections'])
assert_equal(cli_get_info['proxy'], network_info['networks'][0]['proxy'])
assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty'])
assert_equal(cli_get_info['chain'], blockchain_info['chain'])
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['keypoolsize'], wallet_info['keypoolsize'])
assert_equal(cli_get_info['unlocked_until'], wallet_info['unlocked_until'])
assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee'])
assert_equal(cli_get_info['relayfee'], network_info['relayfee'])
# 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__':

View File

@ -27,6 +27,7 @@ from .util import (
MAX_NODES,
append_config,
delete_cookie_file,
get_auth_cookie,
get_rpc_proxy,
rpc_url,
wait_until,
@ -280,12 +281,27 @@ class TestNode():
pass # Port not yet open?
else:
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):
raise
time.sleep(1.0 / poll_per_s)
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):
self.log.debug("TestNode.generate() dispatches `generate` call to `generatetoaddress`")
return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries)