Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/release-notes-7099.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Updated RPCs
------------

* Dash RPCs will no longer permit submitting strings to boolean input fields in line with validation
enforced on upstream RPCs, where this is already the case. Requests must now use unquoted `true`
or `false`.

* The following RPCs are affected, `bls generate`, `bls fromsecret`, `coinjoinsalt generate`,
`coinjoinsalt set`, `masternode connect`, `protx diff`, `protx list`, `protx register`,
`protx register_legacy`, `protx register_evo`, `protx register_fund`, `protx register_fund_legacy`,
`protx register_fund_evo`, `protx revoke`, `protx update_registrar`, `protx update_registrar_legacy`,
`protx update_service`, `protx update_service_evo`, `quorum info`, `quorum platformsign`,
`quorum rotationinfo`, `quorum sign`.

* This restriction can be relaxed by setting `-deprecatedrpc=permissive_bool` at runtime
but is liable to be removed in future versions of Dash Core.
56 changes: 56 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,25 +256,81 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "verifyislock", 3, "maxHeight" },
{ "submitchainlock", 2, "blockHeight" },
{ "mnauth", 0, "nodeId" },
// Compound RPCs (note: index position is offset by one to account for subcommand)
{ "bls generate", 1, "legacy" },
{ "bls fromsecret", 2, "legacy" },
{ "coinjoinsalt generate", 1, "overwrite" },
{ "coinjoinsalt set", 2, "overwrite" },
{ "gobject list-prepared", 1, "count" },
{ "gobject prepare", 2, "revision" },
{ "gobject prepare", 3, "time" },
{ "gobject prepare", 7, "outputIndex" },
{ "gobject submit", 2, "revision" },
{ "gobject submit", 3, "time" },
{ "masternode connect", 2, "v2transport" },
{ "masternode payments", 2, "count" },
{ "masternode winners", 1, "count" },
{ "protx diff", 3, "extended" },
{ "protx list", 2, "detailed" },
{ "protx list", 3, "height" },
{ "protx register", 2, "collateralIndex" },
{ "protx register", 3, "coreP2PAddrs", true },
{ "protx register", 10, "submit" },
{ "protx register_legacy", 2, "collateralIndex" },
{ "protx register_legacy", 3, "coreP2PAddrs", true },
{ "protx register_legacy", 10, "submit" },
{ "protx register_evo", 2, "collateralIndex" },
{ "protx register_evo", 3, "coreP2PAddrs", true },
{ "protx register_evo", 10, "platformP2PAddrs", true },
{ "protx register_evo", 11, "platformHTTPSAddrs", true },
{ "protx register_evo", 13, "submit" },
{ "protx register_fund", 2, "coreP2PAddrs", true },
{ "protx register_fund", 9, "submit" },
{ "protx register_fund_legacy", 2, "coreP2PAddrs", true },
{ "protx register_fund_legacy", 9, "submit" },
{ "protx register_fund_evo", 2, "coreP2PAddrs", true },
{ "protx register_fund_evo", 9, "platformP2PAddrs", true },
{ "protx register_fund_evo", 10, "platformHTTPSAddrs", true },
{ "protx register_fund_evo", 12, "submit" },
{ "protx register_prepare", 2, "collateralIndex" },
{ "protx register_prepare", 3, "coreP2PAddrs", true },
{ "protx register_prepare_legacy", 2, "collateralIndex" },
{ "protx register_prepare_legacy", 3, "coreP2PAddrs", true },
{ "protx register_prepare_evo", 2, "collateralIndex" },
{ "protx register_prepare_evo", 3, "coreP2PAddrs", true },
{ "protx register_prepare_evo", 10, "platformP2PAddrs", true },
{ "protx register_prepare_evo", 11, "platformHTTPSAddrs", true },
{ "protx revoke", 3, "reason" },
{ "protx revoke", 5, "submit" },
{ "protx update_registrar", 6, "submit" },
{ "protx update_registrar_legacy", 6, "submit" },
{ "protx update_service", 2, "coreP2PAddrs", true },
{ "protx update_service", 6, "submit" },
{ "protx update_service_evo", 2, "coreP2PAddrs", true },
{ "protx update_service_evo", 5, "platformP2PAddrs", true },
{ "protx update_service_evo", 6, "platformHTTPSAddrs", true },
{ "protx update_service_evo", 9, "submit" },
{ "quorum dkgsimerror", 2, "rate" },
{ "quorum dkgstatus", 1, "detail_level" },
{ "quorum getdata", 1, "nodeId" },
{ "quorum getdata", 2, "llmqType" },
{ "quorum getdata", 4, "dataMask" },
{ "quorum getrecsig", 1, "llmqType" },
{ "quorum hasrecsig", 1, "llmqType" },
{ "quorum info", 1, "llmqType" },
{ "quorum info", 3, "includeSkShare" },
{ "quorum isconflicting", 1, "llmqType" },
{ "quorum listextended", 1, "height" },
{ "quorum list", 1, "count" },
{ "quorum memberof", 2, "scanQuorumsCount" },
{ "quorum platformsign", 4, "submit" },
{ "quorum rotationinfo", 2, "extraShare" },
{ "quorum rotationinfo", 3, "baseBlockHashes" },
{ "quorum selectquorum", 1, "llmqType" },
{ "quorum sign", 1, "llmqType" },
{ "quorum sign", 5, "submit" },
{ "quorum verify", 1, "llmqType" },
{ "quorum verify", 6, "signHeight" },
};
// clang-format on

Expand Down
30 changes: 15 additions & 15 deletions src/rpc/evo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request,
paramIdx++;
} else {
uint256 collateralHash(ParseHashV(request.params[paramIdx], "collateralHash"));
int32_t collateralIndex = ParseInt32V(request.params[paramIdx + 1], "collateralIndex");
int32_t collateralIndex = request.params[paramIdx + 1].getInt<int>();
if (collateralHash.IsNull() || collateralIndex < 0) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid hash or index: %s-%d", collateralHash.ToString(), collateralIndex));
}
Expand Down Expand Up @@ -1277,7 +1277,7 @@ static RPCHelpMan protx_revoke()
CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[1].get_str(), "operatorKey");

if (!request.params[2].isNull()) {
int32_t nReason = ParseInt32V(request.params[2], "reason");
int32_t nReason = request.params[2].getInt<int>();
if (nReason < 0 || nReason > CProUpRevTx::REASON_LAST) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid reason %d, must be between 0 and %d", nReason, CProUpRevTx::REASON_LAST));
}
Expand Down Expand Up @@ -1462,7 +1462,7 @@ static RPCHelpMan protx_list()
bool detailed = !request.params[1].isNull() ? ParseBoolV(request.params[1], "detailed") : false;

LOCK2(wallet->cs_wallet, ::cs_main);
int height = !request.params[2].isNull() ? ParseInt32V(request.params[2], "height") : chainman.ActiveChain().Height();
int height = !request.params[2].isNull() ? request.params[2].getInt<int>() : chainman.ActiveChain().Height();
if (height < 1 || height > chainman.ActiveChain().Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid height specified");
}
Expand Down Expand Up @@ -1495,7 +1495,7 @@ static RPCHelpMan protx_list()
#else
LOCK(::cs_main);
#endif
int height = !request.params[2].isNull() ? ParseInt32V(request.params[2], "height") : chainman.ActiveChain().Height();
int height = !request.params[2].isNull() ? request.params[2].getInt<int>() : chainman.ActiveChain().Height();
if (height < 1 || height > chainman.ActiveChain().Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid height specified");
}
Expand Down Expand Up @@ -1587,7 +1587,7 @@ static uint256 ParseBlock(const UniValue& v, const ChainstateManager& chainman,
try {
return ParseHashV(v, strName);
} catch (...) {
int h = ParseInt32V(v, strName);
int h = v.isNum() ? v.getInt<int>() : LocaleIndependentAtoi<int>(v.get_str());
if (h < 1 || h > chainman.ActiveChain().Height())
throw std::runtime_error(strprintf("%s must be a block hash or chain height and not %s", strName, v.getValStr()));
return *chainman.ActiveChain()[h]->phashBlock;
Comment on lines 1587 to 1593
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep malformed block identifiers on RPC_INVALID_PARAMETER.

These helpers catch ParseHashV()'s typed RPC error and then rethrow std::runtime_error, so bad baseBlock/block/startBlock/stopBlock inputs lose their structured RPC error code.

💡 Suggested fix
-            throw std::runtime_error(strprintf("%s must be a block hash or chain height and not %s", strName, v.getValStr()));
+            throw JSONRPCError(RPC_INVALID_PARAMETER,
+                               strprintf("%s must be a block hash or chain height and not %s",
+                                         strName, v.getValStr()));

Apply the same change in both helpers.

Also applies to: 1642-1652

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rpc/evo.cpp` around lines 1587 - 1593, The catch blocks that currently
convert ParseHashV exceptions into std::runtime_error lose the original RPC
error code; update the handlers (the catch in the helper that calls ParseHashV
and the analogous one around lines 1642-1652) to throw a JSONRPCError with
RPC_INVALID_PARAMETER and the same formatted message instead of
std::runtime_error (i.e., replace the throw std::runtime_error(...) with throw
JSONRPCError(RPC_INVALID_PARAMETER, strprintf(...))), so malformed block
identifiers retain the structured RPC error code; reference ParseHashV,
RPC_INVALID_PARAMETER, JSONRPCError, and chainman.ActiveChain() when making the
change.

Expand All @@ -1599,8 +1599,8 @@ static RPCHelpMan protx_diff()
return RPCHelpMan{"protx diff",
"\nCalculates a diff between two deterministic masternode lists. The result also contains proof data.\n",
{
{"baseBlock", RPCArg::Type::NUM, RPCArg::Optional::NO, "The starting block height."},
{"block", RPCArg::Type::NUM, RPCArg::Optional::NO, "The ending block height."},
{"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height."},
{"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height."},
{"extended", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Show additional fields."},
},
CSimplifiedMNListDiff::GetJsonHelp(/*key=*/"", /*optional=*/false),
Expand Down Expand Up @@ -1646,9 +1646,9 @@ static const CBlockIndex* ParseBlockIndex(const UniValue& v, const ChainstateMan
throw std::runtime_error(strprintf("Block %s with hash %s not found", strName, v.getValStr()));
return pblockindex;
} catch (...) {
int h = ParseInt32V(v, strName);
int h = v.isNum() ? v.getInt<int>() : LocaleIndependentAtoi<int>(v.get_str());
if (h < 1 || h > chainman.ActiveChain().Height())
Comment on lines 1648 to 1650

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve hash-not-found errors in ParseBlockIndex

ParseBlockIndex now catches every exception and immediately falls back to LocaleIndependentAtoi, which is intentionally atoi-like and accepts numeric prefixes. That means an input that is a valid-looking hash but not found in the block index (or a malformed hash starting with digits) can be silently reinterpreted as a height, so protx listdiff and evodb verify/repair may operate on the wrong block range instead of returning the intended hash error. The fallback should only happen when hash parsing fails, not when hash lookup fails.

Useful? React with 👍 / 👎.

throw std::runtime_error(strprintf("%s must be a chain height and not %s", strName, v.getValStr()));
throw std::runtime_error(strprintf("%s must be a block hash or chain height and not %s", strName, v.getValStr()));
return chainman.ActiveChain()[h];
}
}
Expand All @@ -1658,8 +1658,8 @@ static RPCHelpMan protx_listdiff()
return RPCHelpMan{"protx listdiff",
"\nCalculate a full MN list diff between two masternode lists.\n",
{
{"baseBlock", RPCArg::Type::NUM, RPCArg::Optional::NO, "The starting block height."},
{"block", RPCArg::Type::NUM, RPCArg::Optional::NO, "The ending block height."},
{"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height."},
{"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height."},
},
RPCResult {
RPCResult::Type::OBJ, "", "",
Expand Down Expand Up @@ -1834,8 +1834,8 @@ static RPCHelpMan evodb_verify()
"This is a read-only operation that does not modify the database.\n"
"If no heights are specified, defaults to the full range from DIP0003 activation to chain tip.\n",
{
{"startBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The starting block height (defaults to DIP0003 activation height)."},
{"stopBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The ending block height (defaults to current chain tip)."},
{"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height)."},
{"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip)."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
Expand Down Expand Up @@ -1872,8 +1872,8 @@ static RPCHelpMan evodb_repair()
"If verification fails, recalculates diffs from blockchain data and replaces corrupted records.\n"
"If no heights are specified, defaults to the full range from DIP0003 activation to chain tip.\n",
{
{"startBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The starting block height (defaults to DIP0003 activation height)."},
{"stopBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The ending block height (defaults to current chain tip)."},
{"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height)."},
{"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip)."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
Expand Down
12 changes: 6 additions & 6 deletions src/rpc/governance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ static RPCHelpMan gobject_prepare()
hashParent = ParseHashV(request.params[0], "parent-hash");
}

int nRevision = ParseInt32V(request.params[1], "revision");
int64_t nTime = ParseInt64V(request.params[2], "time");
int nRevision = request.params[1].getInt<int>();
int64_t nTime = request.params[2].getInt<int64_t>();
std::string strDataHex = request.params[3].get_str();

// CREATE A NEW COLLATERAL TRANSACTION FOR THIS SPECIFIC OBJECT
Expand Down Expand Up @@ -225,7 +225,7 @@ static RPCHelpMan gobject_prepare()
outpoint.SetNull();
if (!request.params[5].isNull() && !request.params[6].isNull()) {
uint256 collateralHash(ParseHashV(request.params[5], "outputHash"));
int32_t collateralIndex = ParseInt32V(request.params[6], "outputIndex");
int32_t collateralIndex = request.params[6].getInt<int>();
if (collateralHash.IsNull() || collateralIndex < 0) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid hash or index: %s-%d", collateralHash.ToString(), collateralIndex));
}
Expand Down Expand Up @@ -278,7 +278,7 @@ static RPCHelpMan gobject_list_prepared()
if (!wallet) return UniValue::VNULL;
EnsureWalletIsUnlocked(*wallet);

int64_t nCount = request.params.empty() ? 10 : ParseInt64V(request.params[0], "count");
int64_t nCount = request.params.empty() ? 10 : request.params[0].getInt<int64_t>();
if (nCount < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
}
Expand Down Expand Up @@ -361,8 +361,8 @@ static RPCHelpMan gobject_submit()

// GET THE PARAMETERS FROM USER

int nRevision = ParseInt32V(request.params[1], "revision");
int64_t nTime = ParseInt64V(request.params[2], "time");
int nRevision = request.params[1].getInt<int>();
int64_t nTime = request.params[2].getInt<int64_t>();
std::string strDataHex = request.params[3].get_str();

CGovernanceObject govobj(hashParent, nRevision, nTime, txidFee, strDataHex);
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/masternode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ static RPCHelpMan masternode_winners()
std::string strFilter;

if (!request.params[0].isNull()) {
nCount = LocaleIndependentAtoi<int>(request.params[0].get_str());
nCount = request.params[0].getInt<int>();
}

if (!request.params[1].isNull()) {
Expand Down Expand Up @@ -378,7 +378,7 @@ static RPCHelpMan masternode_payments()
}
}

int64_t nCount = request.params.size() > 1 ? ParseInt64V(request.params[1], "count") : 1;
int64_t nCount = request.params.size() > 1 ? request.params[1].getInt<int64_t>() : 1;

// A temporary vector which is used to sort results properly (there is no "reverse" in/for UniValue)
std::vector<UniValue> vecPayments;
Expand Down
Loading
Loading