Skip to content

Commit b1f44ec

Browse files
committed
Merge bitcoin#25737: rpc: treat univalue type check error as RPC_TYPE_ERROR, not RPC_MISC_ERROR
e68d380 rpc: remove unneeded RPCTypeCheckArgument checks (furszy) 5556663 rpc: treat univalue type check error as RPC_TYPE_ERROR, not RPC_MISC_ERROR (furszy) Pull request description: Same rationale as bitcoin#26039, tackling another angle of the problem. #### Context We have the same univalue type error checking code spread/duplicated few times: `RPCTypeCheckObj`, `RPCTypeCheckArgument`, `UniValue::checkType`. In the first two functions, we are properly returning an `RPC_TYPE_ERROR` while in `UniValue::checkType` we are throwing an `std::runtime_error` which is caught by the RPC server request handler, who invalidly treats it as `RPC_MISC_ERROR` (which is a generic error return code that provides no information to the user). #### Proposed Changes Throw a custom exception from `Univalue::checkType` (instead of a plain `std::runtime_error`) and catch it on the RPC server request handler. So we properly return `RPC_TYPE_ERROR` (-3) on every arg type error and not the general `RPC_MISC_ERROR` (-1). This will allow us to remove all the `RPCTypeCheckArgument` calls. As them are redundant since bitcoin#25629. Top commit has no ACKs. Tree-SHA512: 4e4c41851fd4e2b01a2d8b94e71513f9831f810768ebd89684caca4901e87d3677980003949bcce441f9ca607a1b38a5894839b6c492f5947b8bab8cd9423ba6
2 parents 3c537f1 + e68d380 commit b1f44ec

15 files changed

+30
-39
lines changed

src/rpc/fees.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ static RPCHelpMan estimatesmartfee()
6464
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
6565
{
6666
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR});
67-
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
6867

6968
CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
7069
const NodeContext& node = EnsureAnyNodeContext(request.context);
@@ -157,7 +156,6 @@ static RPCHelpMan estimaterawfee()
157156
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
158157
{
159158
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true);
160-
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
161159

162160
CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
163161

src/rpc/mempool.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -605,8 +605,7 @@ static RPCHelpMan gettxspendingprevout()
605605
},
606606
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
607607
{
608-
RPCTypeCheckArgument(request.params[0], UniValue::VARR);
609-
const UniValue& output_params = request.params[0];
608+
const UniValue& output_params = request.params[0].get_array();
610609
if (output_params.empty()) {
611610
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing");
612611
}

src/rpc/server.cpp

+4-5
Original file line numberDiff line numberDiff line change
@@ -466,18 +466,17 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const
466466

467467
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler)
468468
{
469-
try
470-
{
469+
try {
471470
RPCCommandExecution execution(request.strMethod);
472471
// Execute, convert arguments to array if necessary
473472
if (request.params.isObject()) {
474473
return command.actor(transformNamedArguments(request, command.argNames), result, last_handler);
475474
} else {
476475
return command.actor(request, result, last_handler);
477476
}
478-
}
479-
catch (const std::exception& e)
480-
{
477+
} catch (const UniValue::type_error& e) {
478+
throw JSONRPCError(RPC_TYPE_ERROR, e.what());
479+
} catch (const std::exception& e) {
481480
throw JSONRPCError(RPC_MISC_ERROR, e.what());
482481
}
483482
}

src/univalue/include/univalue.h

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ class UniValue {
1919
public:
2020
enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, };
2121

22+
class type_error : public std::runtime_error
23+
{
24+
using std::runtime_error::runtime_error;
25+
};
26+
2227
UniValue() { typ = VNULL; }
2328
UniValue(UniValue::VType initialType, const std::string& initialStr = "") {
2429
typ = initialType;

src/univalue/lib/univalue.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ const UniValue& UniValue::operator[](size_t index) const
210210
void UniValue::checkType(const VType& expected) const
211211
{
212212
if (typ != expected) {
213-
throw std::runtime_error{"JSON value of type " + std::string{uvTypeName(typ)} + " is not of expected type " +
213+
throw type_error{"JSON value of type " + std::string{uvTypeName(typ)} + " is not of expected type " +
214214
std::string{uvTypeName(expected)}};
215215
}
216216
}

src/wallet/rpc/coins.cpp

+1-9
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,6 @@ RPCHelpMan lockunspent()
290290

291291
LOCK(pwallet->cs_wallet);
292292

293-
RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
294-
295293
bool fUnlock = request.params[0].get_bool();
296294

297295
const bool persistent{request.params[2].isNull() ? false : request.params[2].get_bool()};
@@ -304,9 +302,7 @@ RPCHelpMan lockunspent()
304302
return true;
305303
}
306304

307-
RPCTypeCheckArgument(request.params[1], UniValue::VARR);
308-
309-
const UniValue& output_params = request.params[1];
305+
const UniValue& output_params = request.params[1].get_array();
310306

311307
// Create and validate the COutPoints first.
312308

@@ -566,19 +562,16 @@ RPCHelpMan listunspent()
566562

567563
int nMinDepth = 1;
568564
if (!request.params[0].isNull()) {
569-
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
570565
nMinDepth = request.params[0].getInt<int>();
571566
}
572567

573568
int nMaxDepth = 9999999;
574569
if (!request.params[1].isNull()) {
575-
RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
576570
nMaxDepth = request.params[1].getInt<int>();
577571
}
578572

579573
std::set<CTxDestination> destinations;
580574
if (!request.params[2].isNull()) {
581-
RPCTypeCheckArgument(request.params[2], UniValue::VARR);
582575
UniValue inputs = request.params[2].get_array();
583576
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
584577
const UniValue& input = inputs[idx];
@@ -594,7 +587,6 @@ RPCHelpMan listunspent()
594587

595588
bool include_unsafe = true;
596589
if (!request.params[3].isNull()) {
597-
RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
598590
include_unsafe = request.params[3].get_bool();
599591
}
600592

src/wallet/rpc/spend.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,6 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
503503
coinControl.fAllowWatchOnly = options.get_bool();
504504
}
505505
else {
506-
RPCTypeCheckArgument(options, UniValue::VOBJ);
507506
RPCTypeCheckObj(options,
508507
{
509508
{"add_inputs", UniValueType(UniValue::VBOOL)},
@@ -1653,7 +1652,6 @@ RPCHelpMan walletcreatefundedpsbt()
16531652
bool rbf{wallet.m_signal_rbf};
16541653
const UniValue &replaceable_arg = options["replaceable"];
16551654
if (!replaceable_arg.isNull()) {
1656-
RPCTypeCheckArgument(replaceable_arg, UniValue::VBOOL);
16571655
rbf = replaceable_arg.isTrue();
16581656
}
16591657
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);

test/functional/mining_prioritisetransaction.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ def run_test(self):
122122

123123
# Test `prioritisetransaction` invalid `dummy`
124124
txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000'
125-
assert_raises_rpc_error(-1, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid, 'foo', 0)
125+
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid, 'foo', 0)
126126
assert_raises_rpc_error(-8, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0.", self.nodes[0].prioritisetransaction, txid, 1, 0)
127127

128128
# Test `prioritisetransaction` invalid `fee_delta`
129-
assert_raises_rpc_error(-1, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo')
129+
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo')
130130

131131
self.test_diamond()
132132

test/functional/rpc_blockchain.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -262,12 +262,12 @@ def _test_getchaintxstats(self):
262262
assert_raises_rpc_error(-1, 'getchaintxstats', self.nodes[0].getchaintxstats, 0, '', 0)
263263

264264
# Test `getchaintxstats` invalid `nblocks`
265-
assert_raises_rpc_error(-1, "JSON value of type string is not of expected type number", self.nodes[0].getchaintxstats, '')
265+
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getchaintxstats, '')
266266
assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, -1)
267267
assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, self.nodes[0].getblockcount())
268268

269269
# Test `getchaintxstats` invalid `blockhash`
270-
assert_raises_rpc_error(-1, "JSON value of type number is not of expected type string", self.nodes[0].getchaintxstats, blockhash=0)
270+
assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getchaintxstats, blockhash=0)
271271
assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 1, for '0')", self.nodes[0].getchaintxstats, blockhash='0')
272272
assert_raises_rpc_error(-8, "blockhash must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getchaintxstats, blockhash='ZZZ0000000000000000000000000000000000000000000000000000000000000')
273273
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getchaintxstats, blockhash='0000000000000000000000000000000000000000000000000000000000000000')
@@ -547,7 +547,7 @@ def assert_vin_does_not_contain_prevout(verbosity):
547547
datadir = get_datadir_path(self.options.tmpdir, 0)
548548

549549
self.log.info("Test getblock with invalid verbosity type returns proper error message")
550-
assert_raises_rpc_error(-1, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2")
550+
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2")
551551

552552
def move_block_file(old, new):
553553
old_path = os.path.join(datadir, self.chain, 'blocks', old)

test/functional/rpc_fundrawtransaction.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def test_change_type(self):
302302
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
303303
outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) }
304304
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
305-
assert_raises_rpc_error(-1, "JSON value of type null is not of expected type string", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None})
305+
assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None})
306306
assert_raises_rpc_error(-5, "Unknown change type ''", self.nodes[2].fundrawtransaction, rawtx, {'change_type': ''})
307307
rawtx = self.nodes[2].fundrawtransaction(rawtx, {'change_type': 'bech32'})
308308
dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex'])

test/functional/rpc_help.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def test_categories(self):
9292
assert_raises_rpc_error(-1, 'help', node.help, 'foo', 'bar')
9393

9494
# invalid argument
95-
assert_raises_rpc_error(-1, "JSON value of type number is not of expected type string", node.help, 0)
95+
assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", node.help, 0)
9696

9797
# help of unknown command
9898
assert_equal(node.help('foo'), 'help: unknown command: foo')

test/functional/rpc_rawtransaction.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,13 @@ def getrawtransaction_tests(self):
124124

125125
# 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose
126126
for value in ["True", "False"]:
127-
assert_raises_rpc_error(-1, "not of expected type bool", self.nodes[n].getrawtransaction, txid=txId, verbose=value)
127+
assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txid=txId, verbose=value)
128128

129129
# 7. invalid parameters - supply txid and empty array
130-
assert_raises_rpc_error(-1, "not of expected type bool", self.nodes[n].getrawtransaction, txId, [])
130+
assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, [])
131131

132132
# 8. invalid parameters - supply txid and empty dict
133-
assert_raises_rpc_error(-1, "not of expected type bool", self.nodes[n].getrawtransaction, txId, {})
133+
assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, {})
134134

135135
# Make a tx by sending, then generate 2 blocks; block1 has the tx in it
136136
tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid']
@@ -152,7 +152,7 @@ def getrawtransaction_tests(self):
152152
# We should not get the tx if we provide an unrelated block
153153
assert_raises_rpc_error(-5, "No such transaction found", self.nodes[n].getrawtransaction, txid=tx, blockhash=block2)
154154
# An invalid block hash should raise the correct errors
155-
assert_raises_rpc_error(-1, "JSON value of type bool is not of expected type string", self.nodes[n].getrawtransaction, txid=tx, blockhash=True)
155+
assert_raises_rpc_error(-3, "JSON value of type bool is not of expected type string", self.nodes[n].getrawtransaction, txid=tx, blockhash=True)
156156
assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 6, for 'foobar')", self.nodes[n].getrawtransaction, txid=tx, blockhash="foobar")
157157
assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 8, for 'abcd1234')", self.nodes[n].getrawtransaction, txid=tx, blockhash="abcd1234")
158158
foo = "ZZZ0000000000000000000000000000000000000000000000000000000000000"
@@ -181,8 +181,8 @@ def createrawtransaction_tests(self):
181181

182182
# Test `createrawtransaction` invalid `inputs`
183183
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, 'foo', {})
184-
assert_raises_rpc_error(-1, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {})
185-
assert_raises_rpc_error(-1, "JSON value of type null is not of expected type string", self.nodes[0].createrawtransaction, [{}], {})
184+
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {})
185+
assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].createrawtransaction, [{}], {})
186186
assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].createrawtransaction, [{'txid': 'foo'}], {})
187187
txid = "ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844"
188188
assert_raises_rpc_error(-8, f"txid must be hexadecimal string (not '{txid}')", self.nodes[0].createrawtransaction, [{'txid': txid}], {})
@@ -207,7 +207,7 @@ def createrawtransaction_tests(self):
207207

208208
# Test `createrawtransaction` invalid `outputs`
209209
address = getnewdestination()[2]
210-
assert_raises_rpc_error(-1, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, [], 'foo')
210+
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, [], 'foo')
211211
self.nodes[0].createrawtransaction(inputs=[], outputs={}) # Should not throw for backwards compatibility
212212
self.nodes[0].createrawtransaction(inputs=[], outputs=[])
213213
assert_raises_rpc_error(-8, "Data must be hexadecimal string", self.nodes[0].createrawtransaction, [], {'data': 'foo'})

test/functional/wallet_basic.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def run_test(self):
415415
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
416416

417417
# This will raise an exception since generate does not accept a string
418-
assert_raises_rpc_error(-1, "not of expected type number", self.generate, self.nodes[0], "2")
418+
assert_raises_rpc_error(-3, "not of expected type number", self.generate, self.nodes[0], "2")
419419

420420
if not self.options.descriptors:
421421

test/functional/wallet_hd.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ def run_test(self):
177177
# Sethdseed parameter validity
178178
assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0)
179179
assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif")
180-
assert_raises_rpc_error(-1, "JSON value of type string is not of expected type bool", self.nodes[1].sethdseed, "Not_bool")
181-
assert_raises_rpc_error(-1, "JSON value of type bool is not of expected type string", self.nodes[1].sethdseed, False, True)
180+
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[1].sethdseed, "Not_bool")
181+
assert_raises_rpc_error(-3, "JSON value of type bool is not of expected type string", self.nodes[1].sethdseed, False, True)
182182
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed)
183183
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress()))
184184

test/functional/wallet_multiwallet.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ def wallet_file(name):
356356
self.log.info("Test dynamic wallet unloading")
357357

358358
# Test `unloadwallet` errors
359-
assert_raises_rpc_error(-1, "JSON value of type null is not of expected type string", self.nodes[0].unloadwallet)
359+
assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].unloadwallet)
360360
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy")
361361
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet)
362362
assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", w1.unloadwallet, "w2"),

0 commit comments

Comments
 (0)