Skip to content

Commit 47a30ef

Browse files
committed
Merge bitcoin#19133: rpc, cli, test: add bitcoin-cli -generate command
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
2 parents e6f807f + 22cb303 commit 47a30ef

File tree

6 files changed

+223
-49
lines changed

6 files changed

+223
-49
lines changed

src/Makefile.am

+1
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ BITCOIN_CORE_H = \
184184
reverse_iterator.h \
185185
rpc/blockchain.h \
186186
rpc/client.h \
187+
rpc/mining.h \
187188
rpc/protocol.h \
188189
rpc/rawtransaction_util.h \
189190
rpc/register.h \

src/bitcoin-cli.cpp

+106-32
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <clientversion.h>
1212
#include <optional.h>
1313
#include <rpc/client.h>
14+
#include <rpc/mining.h>
1415
#include <rpc/protocol.h>
1516
#include <rpc/request.h>
1617
#include <util/strencodings.h>
@@ -39,6 +40,9 @@ static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
3940
static const bool DEFAULT_NAMED=false;
4041
static const int CONTINUE_EXECUTION=-1;
4142

43+
/** Default number of blocks to generate for RPC generatetoaddress. */
44+
static const std::string DEFAULT_NBLOCKS = "1";
45+
4246
static void SetupCliArgs()
4347
{
4448
SetupHelpOptions(gArgs);
@@ -50,6 +54,7 @@ static void SetupCliArgs()
5054
gArgs.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
5155
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);
5256
gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
57+
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);
5358
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);
5459
SetupChainParamsBaseOptions();
5560
gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -286,6 +291,28 @@ class GetinfoRequestHandler: public BaseRequestHandler
286291
}
287292
};
288293

294+
/** Process RPC generatetoaddress request. */
295+
class GenerateToAddressRequestHandler : public BaseRequestHandler
296+
{
297+
public:
298+
UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
299+
{
300+
address_str = args.at(1);
301+
UniValue params{RPCConvertValues("generatetoaddress", args)};
302+
return JSONRPCRequestObj("generatetoaddress", params, 1);
303+
}
304+
305+
UniValue ProcessReply(const UniValue &reply) override
306+
{
307+
UniValue result(UniValue::VOBJ);
308+
result.pushKV("address", address_str);
309+
result.pushKV("blocks", reply.get_obj()["result"]);
310+
return JSONRPCReplyObj(result, NullUniValue, 1);
311+
}
312+
protected:
313+
std::string address_str;
314+
};
315+
289316
/** Process default single requests */
290317
class DefaultRequestHandler: public BaseRequestHandler {
291318
public:
@@ -453,6 +480,34 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str
453480
return response;
454481
}
455482

483+
/** Parse UniValue result to update the message to print to std::cout. */
484+
static void ParseResult(const UniValue& result, std::string& strPrint)
485+
{
486+
if (result.isNull()) return;
487+
strPrint = result.isStr() ? result.get_str() : result.write(2);
488+
}
489+
490+
/** Parse UniValue error to update the message to print to std::cerr and the code to return. */
491+
static void ParseError(const UniValue& error, std::string& strPrint, int& nRet)
492+
{
493+
if (error.isObject()) {
494+
const UniValue& err_code = find_value(error, "code");
495+
const UniValue& err_msg = find_value(error, "message");
496+
if (!err_code.isNull()) {
497+
strPrint = "error code: " + err_code.getValStr() + "\n";
498+
}
499+
if (err_msg.isStr()) {
500+
strPrint += ("error message:\n" + err_msg.get_str());
501+
}
502+
if (err_code.isNum() && err_code.get_int() == RPC_WALLET_NOT_SPECIFIED) {
503+
strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line.";
504+
}
505+
} else {
506+
strPrint = "error: " + error.write();
507+
}
508+
nRet = abs(error["code"].get_int());
509+
}
510+
456511
/**
457512
* GetWalletBalances calls listwallets; if more than one wallet is loaded, it then
458513
* fetches mine.trusted balances for each loaded wallet and pushes them to `result`.
@@ -477,6 +532,34 @@ static void GetWalletBalances(UniValue& result)
477532
result.pushKV("balances", balances);
478533
}
479534

535+
/**
536+
* Call RPC getnewaddress.
537+
* @returns getnewaddress response as a UniValue object.
538+
*/
539+
static UniValue GetNewAddress()
540+
{
541+
Optional<std::string> wallet_name{};
542+
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
543+
std::unique_ptr<BaseRequestHandler> rh{MakeUnique<DefaultRequestHandler>()};
544+
return ConnectAndCallRPC(rh.get(), "getnewaddress", /* args=*/{}, wallet_name);
545+
}
546+
547+
/**
548+
* Check bounds and set up args for RPC generatetoaddress params: nblocks, address, maxtries.
549+
* @param[in] address Reference to const string address to insert into the args.
550+
* @param args Reference to vector of string args to modify.
551+
*/
552+
static void SetGenerateToAddressArgs(const std::string& address, std::vector<std::string>& args)
553+
{
554+
if (args.size() > 2) throw std::runtime_error("too many arguments (maximum 2 for nblocks and maxtries)");
555+
if (args.size() == 0) {
556+
args.emplace_back(DEFAULT_NBLOCKS);
557+
} else if (args.at(0) == "0") {
558+
throw std::runtime_error("the first argument (number of blocks to generate, default: " + DEFAULT_NBLOCKS + ") must be an integer value greater than zero");
559+
}
560+
args.emplace(args.begin() + 1, address);
561+
}
562+
480563
static int CommandLineRPC(int argc, char *argv[])
481564
{
482565
std::string strPrint;
@@ -535,6 +618,15 @@ static int CommandLineRPC(int argc, char *argv[])
535618
std::string method;
536619
if (gArgs.IsArgSet("-getinfo")) {
537620
rh.reset(new GetinfoRequestHandler());
621+
} else if (gArgs.GetBoolArg("-generate", false)) {
622+
const UniValue getnewaddress{GetNewAddress()};
623+
const UniValue& error{find_value(getnewaddress, "error")};
624+
if (error.isNull()) {
625+
SetGenerateToAddressArgs(find_value(getnewaddress, "result").get_str(), args);
626+
rh.reset(new GenerateToAddressRequestHandler());
627+
} else {
628+
ParseError(error, strPrint, nRet);
629+
}
538630
} else {
539631
rh.reset(new DefaultRequestHandler());
540632
if (args.size() < 1) {
@@ -543,40 +635,22 @@ static int CommandLineRPC(int argc, char *argv[])
543635
method = args[0];
544636
args.erase(args.begin()); // Remove trailing method name from arguments vector
545637
}
546-
Optional<std::string> wallet_name{};
547-
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
548-
const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
549-
550-
// Parse reply
551-
UniValue result = find_value(reply, "result");
552-
const UniValue& error = find_value(reply, "error");
553-
if (!error.isNull()) {
554-
// Error
555-
strPrint = "error: " + error.write();
556-
nRet = abs(error["code"].get_int());
557-
if (error.isObject()) {
558-
const UniValue& errCode = find_value(error, "code");
559-
const UniValue& errMsg = find_value(error, "message");
560-
strPrint = errCode.isNull() ? "" : ("error code: " + errCode.getValStr() + "\n");
561-
562-
if (errMsg.isStr()) {
563-
strPrint += ("error message:\n" + errMsg.get_str());
564-
}
565-
if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
566-
strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line.";
638+
if (nRet == 0) {
639+
// Perform RPC call
640+
Optional<std::string> wallet_name{};
641+
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
642+
const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
643+
644+
// Parse reply
645+
UniValue result = find_value(reply, "result");
646+
const UniValue& error = find_value(reply, "error");
647+
if (error.isNull()) {
648+
if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
649+
GetWalletBalances(result); // fetch multiwallet balances and append to result
567650
}
568-
}
569-
} else {
570-
if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
571-
GetWalletBalances(result); // fetch multiwallet balances and append to result
572-
}
573-
// Result
574-
if (result.isNull()) {
575-
strPrint = "";
576-
} else if (result.isStr()) {
577-
strPrint = result.get_str();
651+
ParseResult(result, strPrint);
578652
} else {
579-
strPrint = result.write(2);
653+
ParseError(error, strPrint, nRet);
580654
}
581655
}
582656
} catch (const std::exception& e) {

src/rpc/client.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ UniValue ParseNonRFCJSONValue(const std::string& strVal)
217217
UniValue jVal;
218218
if (!jVal.read(std::string("[")+strVal+std::string("]")) ||
219219
!jVal.isArray() || jVal.size()!=1)
220-
throw std::runtime_error(std::string("Error parsing JSON:")+strVal);
220+
throw std::runtime_error(std::string("Error parsing JSON: ") + strVal);
221221
return jVal[0];
222222
}
223223

src/rpc/mining.cpp

+8-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <policy/fees.h>
1818
#include <pow.h>
1919
#include <rpc/blockchain.h>
20+
#include <rpc/mining.h>
2021
#include <rpc/server.h>
2122
#include <rpc/util.h>
2223
#include <script/descriptor.h>
@@ -207,7 +208,7 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
207208
{
208209
{"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."},
209210
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor to send the newly generated bitcoin to."},
210-
{"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."},
211+
{"maxtries", RPCArg::Type::NUM, /* default */ ToString(DEFAULT_MAX_TRIES), "How many iterations to try."},
211212
},
212213
RPCResult{
213214
RPCResult::Type::ARR, "", "hashes of blocks generated",
@@ -221,7 +222,7 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
221222
.Check(request);
222223

223224
const int num_blocks{request.params[0].get_int()};
224-
const int64_t max_tries{request.params[2].isNull() ? 1000000 : request.params[2].get_int()};
225+
const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
225226

226227
CScript coinbase_script;
227228
std::string error;
@@ -242,7 +243,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
242243
{
243244
{"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."},
244245
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."},
245-
{"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."},
246+
{"maxtries", RPCArg::Type::NUM, /* default */ ToString(DEFAULT_MAX_TRIES), "How many iterations to try."},
246247
},
247248
RPCResult{
248249
RPCResult::Type::ARR, "", "hashes of blocks generated",
@@ -257,11 +258,8 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
257258
},
258259
}.Check(request);
259260

260-
int nGenerate = request.params[0].get_int();
261-
uint64_t nMaxTries = 1000000;
262-
if (!request.params[2].isNull()) {
263-
nMaxTries = request.params[2].get_int();
264-
}
261+
const int num_blocks{request.params[0].get_int()};
262+
const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
265263

266264
CTxDestination destination = DecodeDestination(request.params[1].get_str());
267265
if (!IsValidDestination(destination)) {
@@ -273,7 +271,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
273271

274272
CScript coinbase_script = GetScriptForDestination(destination);
275273

276-
return generateBlocks(chainman, mempool, coinbase_script, nGenerate, nMaxTries);
274+
return generateBlocks(chainman, mempool, coinbase_script, num_blocks, max_tries);
277275
}
278276

279277
static UniValue generateblock(const JSONRPCRequest& request)
@@ -371,7 +369,7 @@ static UniValue generateblock(const JSONRPCRequest& request)
371369
}
372370

373371
uint256 block_hash;
374-
uint64_t max_tries{1000000};
372+
uint64_t max_tries{DEFAULT_MAX_TRIES};
375373
unsigned int extra_nonce{0};
376374

377375
if (!GenerateBlock(EnsureChainman(request.context), block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {

src/rpc/mining.h

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

0 commit comments

Comments
 (0)