mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
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:
commit
37a4a30b54
5
doc/release-notes-18594.md
Normal file
5
doc/release-notes-18594.md
Normal 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)
|
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
// Execute and handle connection failures with -rpcwait
|
||||
const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
|
||||
do {
|
||||
try {
|
||||
const UniValue reply = CallRPC(rh.get(), method, args);
|
||||
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);
|
||||
|
||||
// Parse reply
|
||||
const UniValue& result = find_value(reply, "result");
|
||||
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.";
|
||||
}
|
||||
if (error.isNull()) {
|
||||
if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
|
||||
GetWalletBalances(result); // fetch multiwallet balances and append to result
|
||||
}
|
||||
ParseResult(result, strPrint);
|
||||
} else {
|
||||
// Result
|
||||
if (result.isNull())
|
||||
strPrint = "";
|
||||
else if (result.isStr())
|
||||
strPrint = result.get_str();
|
||||
else
|
||||
strPrint = result.write(2);
|
||||
ParseError(error, strPrint, nRet);
|
||||
}
|
||||
// Connection succeeded, no need to retry.
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
40
src/init.cpp
40
src/init.cpp
@ -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;
|
||||
}
|
||||
connOptions.vBinds.push_back(addrBind);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
43
src/net.cpp
43
src/net.cpp
@ -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;
|
||||
|
33
src/net.h
33
src/net.h
@ -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);
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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
11
src/rpc/mining.h
Normal 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
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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()};
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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()
|
||||
|
@ -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__':
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user