Skip to content

Commit

Permalink
Merge bitcoin#19133: rpc, cli, test: add bitcoin-cli -generate command
Browse files Browse the repository at this point in the history
22cb303 rpc: add missing space in JSON parsing error message, update test (Jon Atack)
bf53ebe test: add multiwallet tests for bitcoin-cli -generate (Jon Atack)
4b859cf cli: add multiwallet capability to GetNewAddress and -generate (Jon Atack)
18f9354 test: add tests for bitcoin-cli -generate (Jon Atack)
4818124 cli: create bitcoin-cli -generate command (Jon Atack)
ff41a36 cli: extract ParseResult() and ParseError() (Jon Atack)
f4185b2 cli: create GenerateToAddressRequestHandler class (Harris)
f7c65a3 cli: create GetNewAddress() (Jon Atack)
9be7fd3 rpc: make generatetoaddress locals const (Jon Atack)
cb00510 rpc: create rpc/mining.h, hoist default max tries values to constant (Jon Atack)

Pull request description:

  This PR continues and completes the work begun in bitcoin#17700 working on issue bitcoin#16000 to create a client-side version of RPC `generate`.

  Basically, `bitcoin-cli -generate` wraps calling `generatenewaddress` followed by `generatetoaddress [nblocks] [maxtries]` and prints the following:

  ```
  $ bitcoin-cli -generate
  {
    "address": "bcrt1qn4aszr2y2xvpa70y675a76wsu70wlkwvdyyln6"
    "blocks": [
      "01d2ebcddf663da90b28da7f6805115e2ba7818f16fe747258836646a43a0bb5",
    ]
  }

  $ bitcoin-cli -rpcwallet=wallet-name -generate 3 100
  {
    "address": "bcrt1q4cunfw0gnsj7g7e6mk0v0uuvvau9mwr09dj45l",
    "blocks": [
      "7a6650ca5e0c614992ee64fb148a7e5e022af842e4b6003f81abd8baf1e75136",
      "01d2ebcddf663da90b28da7f6805115e2ba7818f16fe747258836646a43a0bb5",
      "3f8795ec40b1ad812b818c177680841be319a3f6753d4e32dc7dfb5bafe5d00e"
    ]
  }
  ```

  Help doc:
  ```
  $ bitcoin-cli -h | grep -A5 "\-generate"
    -generate
         Generate blocks immediately, equivalent to RPC generatenewaddress
         followed by RPC generatetoaddress. Optional positional arguments
         are number of blocks to generate (default: 1) and maximum
         iterations to try (default: 1000000), equivalent to RPC
         generatetoaddress nblocks and maxtries arguments. Example:
         bitcoin-cli -generate 4 1000
  ```

  Quite a bit of test coverage turned out to be needed to cover the change and the different cases (arguments, multiwallet mode) and error-handling.

  This PR also improves some things that working on these changes brought to light.

  Credit to Harris Brakmić for the initial work in bitcoin#17700.

ACKs for top commit:
  adamjonas:
    utACK 22cb303
  meshcollider:
    utACK 22cb303

Tree-SHA512: 94f67f632fe093d076f614e0ecff09ce7342ac6e424579200d5211a6615260e438d857861767fb788950ec6da0b26ef56dc8268c430012a3b3d4822b24ca6fbf
  • Loading branch information
meshcollider committed Jun 21, 2020
2 parents e6f807f + 22cb303 commit 47a30ef
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 49 deletions.
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,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 \
Expand Down
138 changes: 106 additions & 32 deletions src/bitcoin-cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <clientversion.h>
#include <optional.h>
#include <rpc/client.h>
#include <rpc/mining.h>
#include <rpc/protocol.h>
#include <rpc/request.h>
#include <util/strencodings.h>
Expand Down Expand Up @@ -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()
{
SetupHelpOptions(gArgs);
Expand All @@ -50,6 +54,7 @@ static void SetupCliArgs()
gArgs.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.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);
gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.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: bitcoin-cli -generate 4 1000", DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.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);
SetupChainParamsBaseOptions();
gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
Expand Down Expand Up @@ -286,6 +291,28 @@ class GetinfoRequestHandler: public BaseRequestHandler
}
};

/** 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:
Expand Down Expand Up @@ -453,6 +480,34 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str
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 bitcoin-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`.
Expand All @@ -477,6 +532,34 @@ static void GetWalletBalances(UniValue& result)
result.pushKV("balances", balances);
}

/**
* Call RPC getnewaddress.
* @returns getnewaddress response as a UniValue object.
*/
static UniValue GetNewAddress()
{
Optional<std::string> wallet_name{};
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
std::unique_ptr<BaseRequestHandler> rh{MakeUnique<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;
Expand Down Expand Up @@ -535,6 +618,15 @@ static int CommandLineRPC(int argc, char *argv[])
std::string method;
if (gArgs.IsArgSet("-getinfo")) {
rh.reset(new GetinfoRequestHandler());
} 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) {
Expand All @@ -543,40 +635,22 @@ static int CommandLineRPC(int argc, char *argv[])
method = args[0];
args.erase(args.begin()); // Remove trailing method name from arguments vector
}
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
UniValue result = find_value(reply, "result");
const UniValue& error = find_value(reply, "error");
if (!error.isNull()) {
// Error
strPrint = "error: " + error.write();
nRet = abs(error["code"].get_int());
if (error.isObject()) {
const UniValue& errCode = find_value(error, "code");
const 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 bitcoin-cli command line.";
if (nRet == 0) {
// Perform RPC call
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
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
}
}
} else {
if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
GetWalletBalances(result); // fetch multiwallet balances and append to result
}
// Result
if (result.isNull()) {
strPrint = "";
} else if (result.isStr()) {
strPrint = result.get_str();
ParseResult(result, strPrint);
} else {
strPrint = result.write(2);
ParseError(error, strPrint, nRet);
}
}
} catch (const std::exception& e) {
Expand Down
2 changes: 1 addition & 1 deletion src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,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];
}

Expand Down
18 changes: 8 additions & 10 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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>
Expand Down Expand Up @@ -207,7 +208,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, "", "hashes of blocks generated",
Expand All @@ -221,7 +222,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;
Expand All @@ -242,7 +243,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 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, "", "hashes of blocks generated",
Expand All @@ -257,11 +258,8 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
},
}.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)) {
Expand All @@ -273,7 +271,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)

CScript coinbase_script = GetScriptForDestination(destination);

return generateBlocks(chainman, mempool, coinbase_script, nGenerate, nMaxTries);
return generateBlocks(chainman, mempool, coinbase_script, num_blocks, max_tries);
}

static UniValue generateblock(const JSONRPCRequest& request)
Expand Down Expand Up @@ -371,7 +369,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()) {
Expand Down
11 changes: 11 additions & 0 deletions src/rpc/mining.h
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 47a30ef

Please sign in to comment.