Skip to content

Commit 7ed57d3

Browse files
committed
Merge #11050: Avoid treating null RPC arguments different from missing arguments
745d2e3 Clean up getbalance RPC parameter handling (Russell Yanofsky) fd5d71e Update developer notes after params.size() cleanup (Russell Yanofsky) e067673 Avoid treating null RPC arguments different from missing arguments (Russell Yanofsky) e666efc Get rid of redundant RPC params.size() checks (Russell Yanofsky) Pull request description: This is a followup to #10783. - The first commit doesn't change behavior at all, just simplifies code. - The second commit just changes RPC methods to treat null arguments the same as missing arguments instead of throwing type errors. - The third commit updates developer notes after the cleanup. - The forth commit does some additional code cleanup in `getbalance`. Followup changes that should happen in future PRs: - [ ] Replace uses of `.isTrue()` with calls to `.get_bool()` so numbers, objects, and strings cause type errors instead of being interpreted as false. bitcoin/bitcoin#11050 (comment) - [ ] Add braces around if statements. bitcoin/bitcoin#11050 (comment) - [ ] Maybe improve UniValue type error exceptions and eliminate RPCTypeCheck and RPCTypeCheckArgument functions. bitcoin/bitcoin#11050 (comment) Tree-SHA512: e72f696011d20acc0778e996659e41f9426bffce387b29ff63bf59ad1163d5146761e4445b2b9b9e069a80596a57c7f4402b75a15d5d20f69f775ae558cf67e9
2 parents ea3ac59 + 745d2e3 commit 7ed57d3

File tree

7 files changed

+71
-61
lines changed

7 files changed

+71
-61
lines changed

doc/developer-notes.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -572,16 +572,14 @@ A few guidelines for introducing and reviewing new RPC interfaces:
572572
is specified as-is in BIP22.
573573

574574
- Missing arguments and 'null' should be treated the same: as default values. If there is no
575-
default value, both cases should fail in the same way.
575+
default value, both cases should fail in the same way. The easiest way to follow this
576+
guideline is detect unspecified arguments with `params[x].isNull()` instead of
577+
`params.size() <= x`. The former returns true if the argument is either null or missing,
578+
while the latter returns true if is missing, and false if it is null.
576579

577580
- *Rationale*: Avoids surprises when switching to name-based arguments. Missing name-based arguments
578581
are passed as 'null'.
579582

580-
- *Exception*: Many legacy exceptions to this exist, one of the worst ones is
581-
`getbalance` which follows a completely different code path based on the
582-
number of arguments. We are still in the process of cleaning these up. Do not introduce
583-
new ones.
584-
585583
- Try not to overload methods on argument type. E.g. don't make `getblock(true)` and `getblock("hash")`
586584
do different things.
587585

src/rpc/blockchain.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,11 +1489,11 @@ UniValue getchaintxstats(const JSONRPCRequest& request)
14891489
const CBlockIndex* pindex;
14901490
int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month
14911491

1492-
if (request.params.size() > 0 && !request.params[0].isNull()) {
1492+
if (!request.params[0].isNull()) {
14931493
blockcount = request.params[0].get_int();
14941494
}
14951495

1496-
bool havehash = request.params.size() > 1 && !request.params[1].isNull();
1496+
bool havehash = !request.params[1].isNull();
14971497
uint256 hash;
14981498
if (havehash) {
14991499
hash = uint256S(request.params[1].get_str());

src/rpc/mining.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -842,7 +842,7 @@ UniValue estimatesmartfee(const JSONRPCRequest& request)
842842
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
843843
unsigned int conf_target = ParseConfirmTarget(request.params[0]);
844844
bool conservative = true;
845-
if (request.params.size() > 1 && !request.params[1].isNull()) {
845+
if (!request.params[1].isNull()) {
846846
FeeEstimateMode fee_mode;
847847
if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
848848
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");

src/rpc/misc.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ UniValue getmemoryinfo(const JSONRPCRequest& request)
552552
+ HelpExampleRpc("getmemoryinfo", "")
553553
);
554554

555-
std::string mode = (request.params.size() < 1 || request.params[0].isNull()) ? "stats" : request.params[0].get_str();
555+
std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str();
556556
if (mode == "stats") {
557557
UniValue obj(UniValue::VOBJ);
558558
obj.push_back(Pair("locked", RPCLockedMemoryInfo()));
@@ -603,11 +603,11 @@ UniValue logging(const JSONRPCRequest& request)
603603
}
604604

605605
uint32_t originalLogCategories = logCategories;
606-
if (request.params.size() > 0 && request.params[0].isArray()) {
606+
if (request.params[0].isArray()) {
607607
logCategories |= getCategoryMask(request.params[0]);
608608
}
609609

610-
if (request.params.size() > 1 && request.params[1].isArray()) {
610+
if (request.params[1].isArray()) {
611611
logCategories &= ~getCategoryMask(request.params[1]);
612612
}
613613

src/rpc/net.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ UniValue getpeerinfo(const JSONRPCRequest& request)
193193
UniValue addnode(const JSONRPCRequest& request)
194194
{
195195
std::string strCommand;
196-
if (request.params.size() == 2)
196+
if (!request.params[1].isNull())
197197
strCommand = request.params[1].get_str();
198198
if (request.fHelp || request.params.size() != 2 ||
199199
(strCommand != "onetry" && strCommand != "add" && strCommand != "remove"))
@@ -258,7 +258,7 @@ UniValue disconnectnode(const JSONRPCRequest& request)
258258

259259
bool success;
260260
const UniValue &address_arg = request.params[0];
261-
const UniValue &id_arg = request.params.size() < 2 ? NullUniValue : request.params[1];
261+
const UniValue &id_arg = request.params[1];
262262

263263
if (!address_arg.isNull() && id_arg.isNull()) {
264264
/* handle disconnect-by-address */
@@ -311,7 +311,7 @@ UniValue getaddednodeinfo(const JSONRPCRequest& request)
311311

312312
std::vector<AddedNodeInfo> vInfo = g_connman->GetAddedNodeInfo();
313313

314-
if (request.params.size() == 1 && !request.params[0].isNull()) {
314+
if (!request.params[0].isNull()) {
315315
bool found = false;
316316
for (const AddedNodeInfo& info : vInfo) {
317317
if (info.strAddedNode == request.params[0].get_str()) {
@@ -490,7 +490,7 @@ UniValue getnetworkinfo(const JSONRPCRequest& request)
490490
UniValue setban(const JSONRPCRequest& request)
491491
{
492492
std::string strCommand;
493-
if (request.params.size() >= 2)
493+
if (!request.params[1].isNull())
494494
strCommand = request.params[1].get_str();
495495
if (request.fHelp || request.params.size() < 2 ||
496496
(strCommand != "add" && strCommand != "remove"))
@@ -534,11 +534,11 @@ UniValue setban(const JSONRPCRequest& request)
534534
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned");
535535

536536
int64_t banTime = 0; //use standard bantime if not specified
537-
if (request.params.size() >= 3 && !request.params[2].isNull())
537+
if (!request.params[2].isNull())
538538
banTime = request.params[2].get_int64();
539539

540540
bool absolute = false;
541-
if (request.params.size() == 4 && request.params[3].isTrue())
541+
if (request.params[3].isTrue())
542542
absolute = true;
543543

544544
isSubnet ? g_connman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute) : g_connman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute);

src/rpc/rawtransaction.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -336,14 +336,14 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
336336

337337
CMutableTransaction rawTx;
338338

339-
if (request.params.size() > 2 && !request.params[2].isNull()) {
339+
if (!request.params[2].isNull()) {
340340
int64_t nLockTime = request.params[2].get_int64();
341341
if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max())
342342
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
343343
rawTx.nLockTime = nLockTime;
344344
}
345345

346-
bool rbfOptIn = request.params.size() > 3 ? request.params[3].isTrue() : false;
346+
bool rbfOptIn = request.params[3].isTrue();
347347

348348
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
349349
const UniValue& input = inputs[idx];
@@ -732,7 +732,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
732732

733733
bool fGivenKeys = false;
734734
CBasicKeyStore tempKeystore;
735-
if (request.params.size() > 2 && !request.params[2].isNull()) {
735+
if (!request.params[2].isNull()) {
736736
fGivenKeys = true;
737737
UniValue keys = request.params[2].get_array();
738738
for (unsigned int idx = 0; idx < keys.size(); idx++) {
@@ -754,7 +754,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
754754
#endif
755755

756756
// Add previous txouts given in the RPC call:
757-
if (request.params.size() > 1 && !request.params[1].isNull()) {
757+
if (!request.params[1].isNull()) {
758758
UniValue prevTxs = request.params[1].get_array();
759759
for (unsigned int idx = 0; idx < prevTxs.size(); idx++) {
760760
const UniValue& p = prevTxs[idx];
@@ -825,7 +825,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
825825
#endif
826826

827827
int nHashType = SIGHASH_ALL;
828-
if (request.params.size() > 3 && !request.params[3].isNull()) {
828+
if (!request.params[3].isNull()) {
829829
static std::map<std::string, int> mapSigHashValues = {
830830
{std::string("ALL"), int(SIGHASH_ALL)},
831831
{std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)},
@@ -919,7 +919,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
919919
const uint256& hashTx = tx->GetHash();
920920

921921
CAmount nMaxRawTxFee = maxTxFee;
922-
if (request.params.size() > 1 && request.params[1].get_bool())
922+
if (!request.params[1].isNull() && request.params[1].get_bool())
923923
nMaxRawTxFee = 0;
924924

925925
CCoinsViewCache &view = *pcoinsTip;

src/wallet/rpcwallet.cpp

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ UniValue setaccount(const JSONRPCRequest& request)
280280
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
281281

282282
std::string strAccount;
283-
if (request.params.size() > 1)
283+
if (!request.params[1].isNull())
284284
strAccount = AccountFromValue(request.params[1]);
285285

286286
// Only add the account if the address is yours.
@@ -462,26 +462,26 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
462462

463463
// Wallet comments
464464
CWalletTx wtx;
465-
if (request.params.size() > 2 && !request.params[2].isNull() && !request.params[2].get_str().empty())
465+
if (!request.params[2].isNull() && !request.params[2].get_str().empty())
466466
wtx.mapValue["comment"] = request.params[2].get_str();
467-
if (request.params.size() > 3 && !request.params[3].isNull() && !request.params[3].get_str().empty())
467+
if (!request.params[3].isNull() && !request.params[3].get_str().empty())
468468
wtx.mapValue["to"] = request.params[3].get_str();
469469

470470
bool fSubtractFeeFromAmount = false;
471-
if (request.params.size() > 4 && !request.params[4].isNull()) {
471+
if (!request.params[4].isNull()) {
472472
fSubtractFeeFromAmount = request.params[4].get_bool();
473473
}
474474

475475
CCoinControl coin_control;
476-
if (request.params.size() > 5 && !request.params[5].isNull()) {
476+
if (!request.params[5].isNull()) {
477477
coin_control.signalRbf = request.params[5].get_bool();
478478
}
479479

480-
if (request.params.size() > 6 && !request.params[6].isNull()) {
480+
if (!request.params[6].isNull()) {
481481
coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]);
482482
}
483483

484-
if (request.params.size() > 7 && !request.params[7].isNull()) {
484+
if (!request.params[7].isNull()) {
485485
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
486486
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
487487
}
@@ -768,18 +768,31 @@ UniValue getbalance(const JSONRPCRequest& request)
768768

769769
LOCK2(cs_main, pwallet->cs_wallet);
770770

771-
if (request.params.size() == 0)
772-
return ValueFromAmount(pwallet->GetBalance());
771+
const UniValue& account_value = request.params[0];
772+
const UniValue& minconf = request.params[1];
773+
const UniValue& include_watchonly = request.params[2];
774+
775+
if (account_value.isNull()) {
776+
if (!minconf.isNull()) {
777+
throw JSONRPCError(RPC_INVALID_PARAMETER,
778+
"getbalance minconf option is only currently supported if an account is specified");
779+
}
780+
if (!include_watchonly.isNull()) {
781+
throw JSONRPCError(RPC_INVALID_PARAMETER,
782+
"getbalance include_watchonly option is only currently supported if an account is specified");
783+
}
784+
return ValueFromAmount(pwallet->GetBalance());
785+
}
773786

774-
const std::string& account_param = request.params[0].get_str();
787+
const std::string& account_param = account_value.get_str();
775788
const std::string* account = account_param != "*" ? &account_param : nullptr;
776789

777790
int nMinDepth = 1;
778-
if (!request.params[1].isNull())
779-
nMinDepth = request.params[1].get_int();
791+
if (!minconf.isNull())
792+
nMinDepth = minconf.get_int();
780793
isminefilter filter = ISMINE_SPENDABLE;
781-
if(!request.params[2].isNull())
782-
if(request.params[2].get_bool())
794+
if(!include_watchonly.isNull())
795+
if(include_watchonly.get_bool())
783796
filter = filter | ISMINE_WATCH_ONLY;
784797

785798
return ValueFromAmount(pwallet->GetLegacyBalance(filter, nMinDepth, account));
@@ -838,11 +851,11 @@ UniValue movecmd(const JSONRPCRequest& request)
838851
CAmount nAmount = AmountFromValue(request.params[2]);
839852
if (nAmount <= 0)
840853
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
841-
if (request.params.size() > 3)
854+
if (!request.params[3].isNull())
842855
// unused parameter, used to be nMinDepth, keep type-checking it though
843856
(void)request.params[3].get_int();
844857
std::string strComment;
845-
if (request.params.size() > 4)
858+
if (!request.params[4].isNull())
846859
strComment = request.params[4].get_str();
847860

848861
if (!pwallet->AccountMove(strFrom, strTo, nAmount, strComment)) {
@@ -899,14 +912,14 @@ UniValue sendfrom(const JSONRPCRequest& request)
899912
if (nAmount <= 0)
900913
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
901914
int nMinDepth = 1;
902-
if (request.params.size() > 3)
915+
if (!request.params[3].isNull())
903916
nMinDepth = request.params[3].get_int();
904917

905918
CWalletTx wtx;
906919
wtx.strFromAccount = strAccount;
907-
if (request.params.size() > 4 && !request.params[4].isNull() && !request.params[4].get_str().empty())
920+
if (!request.params[4].isNull() && !request.params[4].get_str().empty())
908921
wtx.mapValue["comment"] = request.params[4].get_str();
909-
if (request.params.size() > 5 && !request.params[5].isNull() && !request.params[5].get_str().empty())
922+
if (!request.params[5].isNull() && !request.params[5].get_str().empty())
910923
wtx.mapValue["to"] = request.params[5].get_str();
911924

912925
EnsureWalletIsUnlocked(pwallet);
@@ -986,23 +999,23 @@ UniValue sendmany(const JSONRPCRequest& request)
986999

9871000
CWalletTx wtx;
9881001
wtx.strFromAccount = strAccount;
989-
if (request.params.size() > 3 && !request.params[3].isNull() && !request.params[3].get_str().empty())
1002+
if (!request.params[3].isNull() && !request.params[3].get_str().empty())
9901003
wtx.mapValue["comment"] = request.params[3].get_str();
9911004

9921005
UniValue subtractFeeFromAmount(UniValue::VARR);
993-
if (request.params.size() > 4 && !request.params[4].isNull())
1006+
if (!request.params[4].isNull())
9941007
subtractFeeFromAmount = request.params[4].get_array();
9951008

9961009
CCoinControl coin_control;
997-
if (request.params.size() > 5 && !request.params[5].isNull()) {
1010+
if (!request.params[5].isNull()) {
9981011
coin_control.signalRbf = request.params[5].get_bool();
9991012
}
10001013

1001-
if (request.params.size() > 6 && !request.params[6].isNull()) {
1014+
if (!request.params[6].isNull()) {
10021015
coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]);
10031016
}
10041017

1005-
if (request.params.size() > 7 && !request.params[7].isNull()) {
1018+
if (!request.params[7].isNull()) {
10061019
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
10071020
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
10081021
}
@@ -1105,7 +1118,7 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
11051118
LOCK2(cs_main, pwallet->cs_wallet);
11061119

11071120
std::string strAccount;
1108-
if (request.params.size() > 2)
1121+
if (!request.params[2].isNull())
11091122
strAccount = AccountFromValue(request.params[2]);
11101123

11111124
// Construct using pay-to-script-hash:
@@ -1711,10 +1724,10 @@ UniValue listaccounts(const JSONRPCRequest& request)
17111724
LOCK2(cs_main, pwallet->cs_wallet);
17121725

17131726
int nMinDepth = 1;
1714-
if (request.params.size() > 0)
1727+
if (!request.params[0].isNull())
17151728
nMinDepth = request.params[0].get_int();
17161729
isminefilter includeWatchonly = ISMINE_SPENDABLE;
1717-
if(request.params.size() > 1)
1730+
if(!request.params[1].isNull())
17181731
if(request.params[1].get_bool())
17191732
includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY;
17201733

@@ -2363,19 +2376,18 @@ UniValue lockunspent(const JSONRPCRequest& request)
23632376

23642377
LOCK2(cs_main, pwallet->cs_wallet);
23652378

2366-
if (request.params.size() == 1)
2367-
RPCTypeCheck(request.params, {UniValue::VBOOL});
2368-
else
2369-
RPCTypeCheck(request.params, {UniValue::VBOOL, UniValue::VARR});
2379+
RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
23702380

23712381
bool fUnlock = request.params[0].get_bool();
23722382

2373-
if (request.params.size() == 1) {
2383+
if (request.params[1].isNull()) {
23742384
if (fUnlock)
23752385
pwallet->UnlockAllCoins();
23762386
return true;
23772387
}
23782388

2389+
RPCTypeCheckArgument(request.params[1], UniValue::VARR);
2390+
23792391
UniValue outputs = request.params[1].get_array();
23802392
for (unsigned int idx = 0; idx < outputs.size(); idx++) {
23812393
const UniValue& output = outputs[idx];
@@ -2672,19 +2684,19 @@ UniValue listunspent(const JSONRPCRequest& request)
26722684
);
26732685

26742686
int nMinDepth = 1;
2675-
if (request.params.size() > 0 && !request.params[0].isNull()) {
2687+
if (!request.params[0].isNull()) {
26762688
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
26772689
nMinDepth = request.params[0].get_int();
26782690
}
26792691

26802692
int nMaxDepth = 9999999;
2681-
if (request.params.size() > 1 && !request.params[1].isNull()) {
2693+
if (!request.params[1].isNull()) {
26822694
RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
26832695
nMaxDepth = request.params[1].get_int();
26842696
}
26852697

26862698
std::set<CBitcoinAddress> setAddress;
2687-
if (request.params.size() > 2 && !request.params[2].isNull()) {
2699+
if (!request.params[2].isNull()) {
26882700
RPCTypeCheckArgument(request.params[2], UniValue::VARR);
26892701
UniValue inputs = request.params[2].get_array();
26902702
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
@@ -2699,7 +2711,7 @@ UniValue listunspent(const JSONRPCRequest& request)
26992711
}
27002712

27012713
bool include_unsafe = true;
2702-
if (request.params.size() > 3 && !request.params[3].isNull()) {
2714+
if (!request.params[3].isNull()) {
27032715
RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
27042716
include_unsafe = request.params[3].get_bool();
27052717
}
@@ -3114,7 +3126,7 @@ UniValue generate(const JSONRPCRequest& request)
31143126

31153127
int num_generate = request.params[0].get_int();
31163128
uint64_t max_tries = 1000000;
3117-
if (request.params.size() > 1 && !request.params[1].isNull()) {
3129+
if (!request.params[1].isNull()) {
31183130
max_tries = request.params[1].get_int();
31193131
}
31203132

0 commit comments

Comments
 (0)