From 14a52bf188f7c400923ffe56e5f939a7568610fd Mon Sep 17 00:00:00 2001 From: Ruslan Kasheparov Date: Thu, 25 Jun 2026 17:01:10 +0200 Subject: [PATCH] rpc: use null for optional parameters --- src/rpc/blockchain.cpp | 2 +- src/rpc/output_script.cpp | 2 +- src/rpc/server.cpp | 2 +- src/wallet/rpc/elements.cpp | 22 ++++++++++---------- test/functional/feature_fedpeg.py | 6 ++++++ test/functional/feature_issuance.py | 7 +++++++ test/functional/feature_pak.py | 11 ++++++++++ test/functional/feature_pegin_subsidy.py | 10 +++++++++ test/functional/rpc_deriveaddresses.py | 1 + test/functional/rpc_help.py | 3 +++ test/functional/rpc_scantxoutset.py | 1 + test/functional/wallet_fundrawtransaction.py | 3 +++ 12 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 84ceaafa299..889d2fd1078 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2526,7 +2526,7 @@ static RPCHelpMan scantxoutset() throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); } - if (request.params.size() < 2) { + if (request.params[1].isNull()) { throw JSONRPCError(RPC_MISC_ERROR, "scanobjects argument is required for the start action"); } diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 4605f8129c0..19de77e538e 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -353,7 +353,7 @@ static RPCHelpMan deriveaddresses() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } auto& desc = descs.at(0); - if (!desc->IsRange() && request.params.size() > 1) { + if (!desc->IsRange() && !request.params[1].isNull()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); } diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 34f19df2564..a51908999e3 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -139,7 +139,7 @@ static RPCHelpMan help() [&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue { std::string strCommand; - if (jsonRequest.params.size() > 0) { + if (!jsonRequest.params[0].isNull()) { strCommand = jsonRequest.params[0].get_str(); } if (strCommand == "dump_all_command_conversions") { diff --git a/src/wallet/rpc/elements.cpp b/src/wallet/rpc/elements.cpp index b455c33459a..ec57d114745 100644 --- a/src/wallet/rpc/elements.cpp +++ b/src/wallet/rpc/elements.cpp @@ -325,7 +325,7 @@ RPCHelpMan initpegoutwallet() // Generate a new key that is added to wallet or set from argument CPubKey online_pubkey; - if (request.params.size() < 3) { + if (request.params[2].isNull()) { std::string error; if (!pwallet->GetOnlinePakKey(online_pubkey, error)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); @@ -342,7 +342,7 @@ RPCHelpMan initpegoutwallet() // Parse offline counter int counter = 0; - if (request.params.size() > 1) { + if (!request.params[1].isNull()) { counter = request.params[1].getInt(); if (counter < 0 || counter > 1000000000) { throw JSONRPCError(RPC_INVALID_PARAMETER, "bip32_counter must be between 0 and 1,000,000,000, inclusive."); @@ -499,7 +499,7 @@ RPCHelpMan sendtomainchain_base() throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); bool subtract_fee = false; - if (request.params.size() > 2) { + if (!request.params[2].isNull()) { subtract_fee = request.params[2].get_bool(); } @@ -520,7 +520,7 @@ RPCHelpMan sendtomainchain_base() EnsureWalletIsUnlocked(*pwallet); - bool verbose = request.params[3].isNull() ? false: request.params[3].get_bool(); + bool verbose = request.params[3].isNull() ? false : request.params[3].get_bool(); mapValue_t mapValue; CCoinControl no_coin_control; // This is a deprecated API return SendMoney(*pwallet, no_coin_control, recipients, std::move(mapValue), verbose, true /* ignore_blind_fail */); @@ -612,7 +612,7 @@ RPCHelpMan sendtomainchain_pak() throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount for send, must send more than 0.00100000 BTC"); bool subtract_fee = false; - if (request.params.size() > 2) { + if (!request.params[2].isNull()) { subtract_fee = request.params[2].get_bool(); } @@ -825,7 +825,7 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef std::vector txOutProofData = ParseHex(request.params[1].get_str()); std::set claim_scripts; - if (request.params.size() > 2) { + if (!request.params[2].isNull()) { const std::string claim_script = request.params[2].get_str(); if (!IsHex(claim_script)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script is not hex."); @@ -1270,12 +1270,12 @@ RPCHelpMan blindrawtransaction() } bool ignore_blind_fail = true; - if (request.params.size() > 1) { + if (!request.params[1].isNull()) { ignore_blind_fail = request.params[1].get_bool(); } std::vector > auxiliary_generators; - if (request.params.size() > 2) { + if (!request.params[2].isNull()) { UniValue assetCommitments = request.params[2].get_array(); if (assetCommitments.size() != 0 && assetCommitments.size() < tx.vin.size()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Asset commitment array must have at least as many entries as transaction inputs."); @@ -1543,11 +1543,11 @@ RPCHelpMan issueasset() throw JSONRPCError(RPC_TYPE_ERROR, "Issuance must have one non-zero component"); } - bool blind_issuances = request.params.size() < 3 || request.params[2].get_bool(); + bool blind_issuances = request.params[2].isNull() || request.params[2].get_bool(); // Check for optional contract to hash into definition uint256 contract_hash; - if (request.params.size() >= 4) { + if (!request.params[3].isNull()) { contract_hash = ParseHashV(request.params[3], "contract_hash"); } @@ -1739,7 +1739,7 @@ RPCHelpMan listissuances() std::string assetstr; CAsset asset_filter; - if (request.params.size() > 0) { + if (!request.params[0].isNull()) { assetstr = request.params[0].get_str(); asset_filter = GetAssetFromString(assetstr); } diff --git a/test/functional/feature_fedpeg.py b/test/functional/feature_fedpeg.py index 646f19dc753..f30ab5f3704 100755 --- a/test/functional/feature_fedpeg.py +++ b/test/functional/feature_fedpeg.py @@ -601,6 +601,12 @@ def run_test(self): peg_out_txid = sidechain.sendtomainchain(some_btc_addr, 1) + self.log.info("sendtomainchain with null argument") + verbose_result = sidechain.sendtomainchain(some_btc_addr, 1, None, True) + assert isinstance(verbose_result, dict) + assert 'txid' in verbose_result + assert 'fee_reason' in verbose_result + peg_out_details = sidechain.decoderawtransaction(sidechain.getrawtransaction(peg_out_txid)) # peg-out, change, fee assert len(peg_out_details["vout"]) == 3 diff --git a/test/functional/feature_issuance.py b/test/functional/feature_issuance.py index 5fe0785cf57..42bbbc81ae4 100755 --- a/test/functional/feature_issuance.py +++ b/test/functional/feature_issuance.py @@ -115,12 +115,19 @@ def run_test(self): # Make sure test starts with no initial issuance. assert_equal(len(self.nodes[0].listissuances()), 0) + self.log.info("listissuances with null argument") + assert_equal(self.nodes[0].listissuances(None), []) + # Unblinded issuance of asset contract_hash = "deadbeef"*8 issued = self.nodes[0].issueasset(1, 1, False, contract_hash) balance = self.nodes[0].getwalletinfo()["balance"] assert_equal(balance[issued["asset"]], 1) assert_equal(balance[issued["token"]], 1) + + self.log.info("issueasset with null argument") + assert_equal(len(self.nodes[0].listissuances(None)), len(self.nodes[0].listissuances())) + # Quick unblinded reissuance check, making 2*COIN total self.nodes[0].reissueasset(issued["asset"], 1) diff --git a/test/functional/feature_pak.py b/test/functional/feature_pak.py index 1ed03386516..a3e609ecad8 100755 --- a/test/functional/feature_pak.py +++ b/test/functional/feature_pak.py @@ -76,6 +76,11 @@ def run_test(self): assert_equal(new_init["address_lookahead"][0], init_results[1]["address_lookahead"][2]) assert new_init["liquid_pak"] != init_results[1]["liquid_pak"] + self.log.info("initpegoutwallet with null argument") + null_pak_init = self.nodes[2].initpegoutwallet(xpub, 5, None) + assert_equal(self.nodes[2].getwalletpakinfo()["bip32_counter"], "5") + assert null_pak_init["liquid_pak"] + # Restart and connect peers to check wallet persistence self.stop_nodes() self.start_nodes() @@ -192,6 +197,12 @@ def run_test(self): wpkh_stmc = self.nodes[1].sendtomainchain("", 1) wpkh_txid = wpkh_stmc['txid'] + self.log.info("sendtomainchain with null argument") + verbose_stmc = self.nodes[1].sendtomainchain("", 1, None, True) + assert isinstance(verbose_stmc, dict) + assert 'txid' in verbose_stmc + assert 'fee_reason' in verbose_stmc + # Also check some basic return fields of sendtomainchain with pak assert_equal(wpkh_stmc["bitcoin_address"], wpkh_info["address_lookahead"][0]) validata = self.nodes[1].validateaddress(wpkh_stmc["bitcoin_address"]) diff --git a/test/functional/feature_pegin_subsidy.py b/test/functional/feature_pegin_subsidy.py index 49e8758e7ac..04120d424aa 100755 --- a/test/functional/feature_pegin_subsidy.py +++ b/test/functional/feature_pegin_subsidy.py @@ -340,6 +340,16 @@ def parent_pegin(parent, node, amount=1.0, feerate=DEFAULT_FEERATE): assert_equal(len(pegin_tx["decoded"]["vout"]), 2) self.generate(sidechain2, 1, sync_fun=sync_sidechain) + self.log.info("createrawpegin with null argument") + txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=1.0, feerate=2.0) + pegintx = sidechain2.createrawpegin(bitcoin_txhex, txoutproof, None, 2.0) + signed = sidechain2.signrawtransactionwithwallet(pegintx["hex"]) + assert_equal(signed["complete"], True) + pegin_txid = sidechain2.sendrawtransaction(signed["hex"]) + pegin_tx = sidechain2.gettransaction(pegin_txid, True, True) + assert_equal(len(pegin_tx["decoded"]["vout"]), 2) + self.generate(sidechain2, 1, sync_fun=sync_sidechain) + self.log.info("claimpegin before enforcement, with validatepegin, below threshold") txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=1.0, feerate=2.0) pegin_txid = sidechain.claimpegin(bitcoin_txhex, txoutproof, claim_script) diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py index 96b84444f21..042a644b763 100755 --- a/test/functional/rpc_deriveaddresses.py +++ b/test/functional/rpc_deriveaddresses.py @@ -17,6 +17,7 @@ def run_test(self): descriptor = "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)#t6wfjs64" address = "ert1qjqmxmkpmxt80xz4y3746zgt0q3u3ferrfpgxn5" assert_equal(self.nodes[0].deriveaddresses(descriptor), [address]) + assert_equal(self.nodes[0].deriveaddresses(descriptor, None), [address]) descriptor = descriptor[:-9] assert_raises_rpc_error(-5, "Missing checksum", self.nodes[0].deriveaddresses, descriptor) diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index db4f89573eb..5143e29187b 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -97,6 +97,9 @@ def test_categories(self): # invalid argument assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", node.help, 0) + # null argument + assert_equal(node.help(None), node.help()) + # help of unknown command assert_equal(node.help('foo'), 'help: unknown command: foo') diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index 9c74ea0d82b..428763e2cdd 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -140,6 +140,7 @@ def run_test(self): # Check that second arg is needed for start assert_raises_rpc_error(-1, "scanobjects argument is required for the start action", self.nodes[0].scantxoutset, "start") + assert_raises_rpc_error(-1, "scanobjects argument is required for the start action", self.nodes[0].scantxoutset, "start", None) # Check that invalid command give error assert_raises_rpc_error(-8, "Invalid action 'invalid_command'", self.nodes[0].scantxoutset, "invalid_command") diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py index 8d6d324a70e..15326fc9f2e 100755 --- a/test/functional/wallet_fundrawtransaction.py +++ b/test/functional/wallet_fundrawtransaction.py @@ -706,6 +706,9 @@ def test_locked_wallet(self): fundedTx = wallet.fundrawtransaction(rawtx) blindedTx = wallet.blindrawtransaction(fundedTx['hex']) assert fundedTx["changepos"] != -1 + self.log.info("blindrawtransaction with null argument") + blindedTx_null_commitments = wallet.blindrawtransaction(fundedTx['hex'], None, None, False) + assert blindedTx_null_commitments # Now we need to unlock. with WalletUnlock(wallet, "test"):