diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 2dce9e1c96..3185917107 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2492,7 +2492,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata", { {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, - {"range", RPCArg::Type::NUM, /* default */ "1000", "Up to what child index HD chains should be explored"}, + {"range", RPCArg::Type::RANGE, /* default */ "1000", "The range of HD chain indexes to explore (either end or [begin,end])"}, }, }, }, @@ -2549,7 +2549,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) // loop through the scan objects for (const UniValue& scanobject : request.params[1].get_array().getValues()) { std::string desc_str; - int range = 1000; + std::pair range = {0, 1000}; if (scanobject.isStr()) { desc_str = scanobject.get_str(); } else if (scanobject.isObject()) { @@ -2558,8 +2558,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) desc_str = desc_uni.get_str(); UniValue range_uni = find_value(scanobject, "range"); if (!range_uni.isNull()) { - range = range_uni.get_int(); - if (range < 0 || range > 1000000) throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range"); + range = ParseDescriptorRange(range_uni); } } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object"); @@ -2570,8 +2569,11 @@ UniValue scantxoutset(const JSONRPCRequest& request) if (!desc) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str)); } - if (!desc->IsRange()) range = 0; - for (int i = 0; i <= range; ++i) { + if (!desc->IsRange()) { + range.first = 0; + range.second = 0; + } + for (int i = range.first; i <= range.second; ++i) { std::vector scripts; if (!desc->Expand(i, provider, scripts, provider)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str)); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 5e1b20ed06..df43c88b6d 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -30,6 +30,7 @@ #include #include +#include #ifdef HAVE_MALLOC_INFO #include #endif @@ -341,7 +342,7 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request) UniValue deriveaddresses(const JSONRPCRequest& request) { - if (request.fHelp || request.params.empty() || request.params.size() > 3) { + if (request.fHelp || request.params.empty() || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{"deriveaddresses", "\nDerives one or more addresses corresponding to an output descriptor.\n" @@ -354,8 +355,7 @@ UniValue deriveaddresses(const JSONRPCRequest& request) "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n", { {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor"}, - {"begin", RPCArg::Type::NUM, /* default */ "", "If a ranged descriptor is used, this specifies the beginning of the range to import"}, - {"end", RPCArg::Type::NUM, /* default */ "", "If a ranged descriptor is used, this specifies the end of the range to import"} + {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."}, }, RPCResult{ "\"address\" (array) A json array of the derived addresses\n" @@ -365,29 +365,19 @@ UniValue deriveaddresses(const JSONRPCRequest& request) }, RPCExamples{ "\nFirst three receive addresses\n" - + HelpExampleCli("deriveaddresses", "\"pkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#trd0mf0l\" 0 2") + + HelpExampleCli("deriveaddresses", "\"pkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#trd0mf0l\" \"[0,2]\"") } }.ToString()); } - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VNUM, UniValue::VNUM}); + RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later const std::string desc_str = request.params[0].get_str(); - int range_begin = 0; - int range_end = 0; + int64_t range_begin = 0; + int64_t range_end = 0; - if (request.params.size() >= 2) { - if (request.params.size() == 2) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing range end parameter"); - } - range_begin = request.params[1].get_int(); - range_end = request.params[2].get_int(); - if (range_begin < 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should be greater or equal than 0"); - } - if (range_begin > range_end) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Range end should be equal to or greater than begin"); - } + if (request.params.size() >= 2 && !request.params[1].isNull()) { + std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]); } FlatSigningProvider key_provider; diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 8dabed3f39..7e726d6df3 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -11,6 +11,8 @@ #include #include +#include + InitInterfaces* g_rpc_interfaces = nullptr; void RPCTypeCheck(const UniValue& params, @@ -293,6 +295,7 @@ struct Sections { case RPCArg::Type::STR: case RPCArg::Type::NUM: case RPCArg::Type::AMOUNT: + case RPCArg::Type::RANGE: case RPCArg::Type::BOOL: { if (outer_type == OuterType::NAMED_ARG) return; // Nothing more to do for non-recursive types on first recursion auto left = indent; @@ -483,6 +486,10 @@ std::string RPCArg::ToDescriptionString() const ret += "numeric or string"; break; } + case Type::RANGE: { + ret += "numeric or array"; + break; + } case Type::BOOL: { ret += "boolean"; break; @@ -542,6 +549,8 @@ std::string RPCArg::ToStringObj(const bool oneline) const return res + "\"hex\""; case Type::NUM: return res + "n"; + case Type::RANGE: + return res + "n or [n,n]"; case Type::AMOUNT: return res + "amount"; case Type::BOOL: @@ -572,6 +581,7 @@ std::string RPCArg::ToString(const bool oneline) const return "\"" + m_name + "\""; } case Type::NUM: + case Type::RANGE: case Type::AMOUNT: case Type::BOOL: { return m_name; @@ -602,6 +612,36 @@ std::string RPCArg::ToString(const bool oneline) const assert(false); } +static std::pair ParseRange(const UniValue& value) +{ + if (value.isNum()) { + return {0, value.get_int64()}; + } + if (value.isArray() && value.size() == 2 && value[0].isNum() && value[1].isNum()) { + int64_t low = value[0].get_int64(); + int64_t high = value[1].get_int64(); + if (low > high) throw JSONRPCError(RPC_INVALID_PARAMETER, "Range specified as [begin,end] must not have begin after end"); + return {low, high}; + } + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified as end or as [begin,end]"); +} + +std::pair ParseDescriptorRange(const UniValue& value) +{ + int64_t low, high; + std::tie(low, high) = ParseRange(value); + if (low < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should be greater or equal than 0"); + } + if ((high >> 31) != 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "End of range is too high"); + } + if (high >= low + 1000000) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range is too large"); + } + return {low, high}; +} + RPCErrorCode RPCErrorFromTransactionError(TransactionError terr) { switch (terr) { diff --git a/src/rpc/util.h b/src/rpc/util.h index 112b160974..dee31ebb16 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -87,6 +87,9 @@ unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target); /** Returns, given services flags, a list of humanly readable (known) network services */ UniValue GetServicesNames(ServiceFlags services); +//! Parse a JSON range specified as int64, or [int64, int64] +std::pair ParseDescriptorRange(const UniValue& value); + struct RPCArg { enum class Type { OBJ, @@ -97,6 +100,7 @@ struct RPCArg { OBJ_USER_KEYS, //!< Special type where the user must set the keys e.g. to define multiple addresses; as opposed to e.g. an options object where the keys are predefined AMOUNT, //!< Special type representing a floating point amount (can be either NUM or STR) STR_HEX, //!< Special type that is a STR with only hex chars + RANGE, //!< Special type that is a NUM or [NUM,NUM] }; enum class Optional { diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index f9c33f12e3..8288136282 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -1297,15 +1298,7 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map