From cda99dbfeafadad344c8dfb2ef5fc2c3d8c309a1 Mon Sep 17 00:00:00 2001 From: merge-script Date: Mon, 6 Jul 2026 13:22:18 +0100 Subject: [PATCH 1/4] Merge ElementsProject/elements#1559: rpc: use null for optional parameters 14a52bf188f7c400923ffe56e5f939a7568610fd rpc: use null for optional parameters (Ruslan Kasheparov) Pull request description: Fixes: #1558 ACKs for top commit: tomt1664: Tested ACK 14a52bf188f7c400923ffe56e5f939a7568610fd Tree-SHA512: 161da840bb23c8573dca19b3216e1da03924d65a2ac17075e37c2fb8697994fe9a14b82ee8453f6122692409cb417c5dfe1f726fa0710458c20d207c153a124c --- src/rpc/blockchain.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_fundrawtransaction.py | 4 ++++ test/functional/rpc_help.py | 3 +++ test/functional/rpc_scantxoutset.py | 1 + 11 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 1001a9b5b9f..b7f8eb0eb7f 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2776,7 +2776,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/server.cpp b/src/rpc/server.cpp index c70236cc1c3..7a96ad0f248 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -145,7 +145,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 2eb0810dd83..1d995042770 100644 --- a/src/wallet/rpc/elements.cpp +++ b/src/wallet/rpc/elements.cpp @@ -330,7 +330,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); @@ -347,7 +347,7 @@ RPCHelpMan initpegoutwallet() // Parse offline counter int counter = 0; - if (request.params.size() > 1) { + if (!request.params[1].isNull()) { counter = request.params[1].get_int(); if (counter < 0 || counter > 1000000000) { throw JSONRPCError(RPC_INVALID_PARAMETER, "bip32_counter must be between 0 and 1,000,000,000, inclusive."); @@ -502,7 +502,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(); } @@ -523,7 +523,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 */); @@ -615,7 +615,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(); } @@ -827,7 +827,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."); @@ -1273,12 +1273,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."); @@ -1542,11 +1542,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"); } @@ -1733,7 +1733,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 ee57b8b5d10..52b27b25342 100755 --- a/test/functional/feature_fedpeg.py +++ b/test/functional/feature_fedpeg.py @@ -600,6 +600,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 ba34ed7291f..35d056fab71 100755 --- a/test/functional/feature_issuance.py +++ b/test/functional/feature_issuance.py @@ -112,12 +112,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 8297931c4b7..7ed01d8d5b7 100755 --- a/test/functional/feature_pak.py +++ b/test/functional/feature_pak.py @@ -71,6 +71,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() @@ -187,6 +192,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 020622bde7b..49f2c316ee2 100755 --- a/test/functional/feature_pegin_subsidy.py +++ b/test/functional/feature_pegin_subsidy.py @@ -339,6 +339,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 413c46aafaf..c759cc44979 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_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 865d54cff61..932a2eb7506 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -649,6 +649,10 @@ def test_locked_wallet(self): rawtx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawtx) blindedTx = self.nodes[1].blindrawtransaction(fundedTx['hex']) + assert fundedTx["changepos"] != -1 + self.log.info("blindrawtransaction with null argument") + blindedTx_null_commitments = self.nodes[1].blindrawtransaction(fundedTx['hex'], None, None, False) + assert blindedTx_null_commitments # Now we need to unlock. self.nodes[1].walletpassphrase("test", 600) diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index ccb380e25ba..1dbf35885c4 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -94,6 +94,9 @@ def test_categories(self): # invalid argument assert_raises_rpc_error(-1, 'JSON value is not a string as expected', 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 ffc814c02be..0502fc9c4e3 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -126,6 +126,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) if __name__ == "__main__": From 69c23bfe82de80bd65aef7e6c3218d27c02972e1 Mon Sep 17 00:00:00 2001 From: merge-script Date: Mon, 6 Jul 2026 13:36:52 +0100 Subject: [PATCH 2/4] Merge ElementsProject/elements#1560: rpc: fix fields description in a man in 'decodepsbt' ef1d2673d700061eb7349431c54e59d91da64efc rpc: fix fields type in man in 'decodepsbt' (Ruslan Kasheparov) 91ae0e92e67cc32c5d9141948f070ec516854b82 rpc: fix fields describtion in man in 'decodepsbt' (Ruslan Kasheparov) Pull request description: Top commit has no ACKs. Tree-SHA512: 736f7ebfd3c54267aee8e7b25970a25356f9a4c38ac23caecd40a7f4b2a96c0142adc0e73bcbd6881243981297af2d5fcc68bae9023cca6179bee823d1dd9ecb --- src/rpc/rawtransaction.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 65e47e29b54..08d649d2ba3 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1237,11 +1237,9 @@ static RPCHelpMan decodepsbt() {RPCResult::Type::NUM, "fallback_locktime", /*optional=*/true, "The locktime to fallback to if no inputs specify a required locktime."}, {RPCResult::Type::NUM, "input_count", /*optional=*/true, "The number of inputs in this psbt"}, {RPCResult::Type::NUM, "output_count", /*optional=*/true, "The number of outputs in this psbt."}, - {RPCResult::Type::NUM, "inputs_modifiable", /*optional=*/true, "Whether inputs can be modified"}, - {RPCResult::Type::NUM, "outputs_modifiable", /*optional=*/true, "Whether outputs can be modified"}, - {RPCResult::Type::ARR, "sighash_single_indexes", /*optional=*/true, "The indexes which have SIGHASH_SINGLE signatures", - {{RPCResult::Type::NUM, "", "Index of an input with a SIGHASH_SINGLE signature"}}, - }, + {RPCResult::Type::BOOL, "inputs_modifiable", /*optional=*/true, "Whether inputs can be modified"}, + {RPCResult::Type::BOOL, "outputs_modifiable", /*optional=*/true, "Whether outputs can be modified"}, + {RPCResult::Type::BOOL, "has_sighash_single", /*optional=*/true, "Whether this PSBT has SIGHASH_SINGLE inputs"}, {RPCResult::Type::NUM, "psbt_version", "The PSBT version number. Not to be confused with the unsigned transaction version"}, {RPCResult::Type::OBJ_DYN, "scalar_offsets", /*optional=*/true, "The PSET scalar elements", { From d59af1ef121e3fa9627d081defb522e9efab7792 Mon Sep 17 00:00:00 2001 From: merge-script Date: Mon, 6 Jul 2026 15:04:30 +0100 Subject: [PATCH 3/4] Merge ElementsProject/elements#1570: rpc: fix fields describtion in man a58014af6e8cba2545f38731dddb91dc0680260e rpc: fix fields describtion in man (Ruslan Kasheparov) Pull request description: ACKs for top commit: tomt1664: ACK a58014af6e8cba2545f38731dddb91dc0680260e tested locally Tree-SHA512: b7ea26dcd2119d87f37cd7fdd459347ee5432bd3a1a19c2d639e65bf6e516d0f12cad9e06a34a99184654bc7bfe138c8e568eb74c7bdff4c54c94c5b9a16910e --- src/rpc/rawtransaction.cpp | 2 +- src/wallet/rpc/backup.cpp | 4 ++-- src/wallet/rpc/elements.cpp | 10 ++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 08d649d2ba3..0eb62390619 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -2779,7 +2779,7 @@ static RPCHelpMan rawblindrawtransaction() "Returns the hex-encoded raw transaction.\n" "The input raw transaction cannot have already-blinded outputs.\n" "The output keys used can be specified by using a confidential address in createrawtransaction.\n" - "If an additional blinded output is required to make a balanced blinding, a 0-value unspendable output will be added. Since there is no access to the wallet the blinding pubkey from the last output with blinding key will be repeated.\n" + "If blinded inputs exist but no output has a blinding pubkey, the caller must add another blindable output; this RPC cannot derive a wallet blinding key and will fail instead of adding a dummy output.\n" "You can not blind issuances with this call.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A hex-encoded raw transaction."}, diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index e83e3380535..15368c15052 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -1961,7 +1961,7 @@ RPCHelpMan restorewallet() RPCHelpMan getwalletpakinfo() { return RPCHelpMan{"getwalletpakinfo", - "\nReturns relevant pegout authorization key (PAK) information about this wallet. Throws an error if initpegoutwallet` has not been invoked on this wallet.\n", + "\nReturns relevant pegout authorization key (PAK) information about this wallet. Throws an error if `initpegoutwallet` has not been invoked on this wallet.\n", {}, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2153,7 +2153,7 @@ RPCHelpMan importissuanceblindingkey() }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ - HelpExampleCli("importblindingkey", "\"my blinded CT address\" ") + HelpExampleCli("importissuanceblindingkey", "\"\" 0 \"\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { diff --git a/src/wallet/rpc/elements.cpp b/src/wallet/rpc/elements.cpp index 1d995042770..a352b750892 100644 --- a/src/wallet/rpc/elements.cpp +++ b/src/wallet/rpc/elements.cpp @@ -587,7 +587,7 @@ RPCHelpMan sendtomainchain_pak() { {RPCResult::Type::STR, "bitcoin_address", "destination address on Bitcoin mainchain"}, {RPCResult::Type::STR_HEX, "txid", "transaction ID of the resulting Liquid transaction"}, - {RPCResult::Type::STR, "fee reason", "If verbose is set to true, the Liquid transaction fee reason"}, + {RPCResult::Type::STR, "fee_reason", /*optional=*/true, "If verbose is set to true, the Liquid transaction fee reason"}, {RPCResult::Type::STR, "bitcoin_descriptor", "xpubkey of the child destination address"}, {RPCResult::Type::STR, "bip32_counter", "derivation counter for the `bitcoin_descriptor`"}, }, @@ -1956,7 +1956,7 @@ RPCHelpMan getpegoutkeys() "\n(DEPRECATED) Please see `initpegoutwallet` and `sendtomainchain` for best-supported and easiest workflow. This call is for the Liquid network participants' `offline` wallet ONLY. Returns `sumkeys` corresponding to the sum of the Offline PAK and the imported Bitcoin key. The wallet must have the Offline private PAK to succeed. The output will be used in `generatepegoutproof` and `sendtomainchain`. Care is required to keep the bitcoin private key, as well as the `sumkey` safe, as a leak of both results in the leak of your `offlinekey`. Therefore it is recommended to create Bitcoin keys and do Bitcoin transaction signing directly on an offline wallet co-located with your offline Liquid wallet.\n", { {"btcprivkey", RPCArg::Type::STR, RPCArg::Optional::NO, "Base58 Bitcoin private key that will be combined with the offline privkey"}, - {"offlinepubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "Hex pubkey of key to combine with btcprivkey. Primarily intended for integration testing."}, + {"offlinepubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "33-byte compressed public key encoded as 66 hex characters, to combine with btcprivkey. Primarily intended for integration testing."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -1967,10 +1967,8 @@ RPCHelpMan getpegoutkeys() }, }, RPCExamples{ - HelpExampleCli("getpegoutkeys", "") - + HelpExampleCli("getpegoutkeys", "\"5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF\" \"0389275d512326f7016e014d8625f709c01f23bd0dc16522bf9845a9ee1ef6cbf9\"") - + HelpExampleRpc("getpegoutkeys", "") - + HelpExampleRpc("getpegoutkeys", "\"5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF\", \"0389275d512326f7016e014d8625f709c01f23bd0dc16522bf9845a9ee1ef6cbf9\"") + HelpExampleCli("getpegoutkeys", "\"5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF\" \"0389275d512326f7016e014d8625f709c01f23bd0dc16522bf9845a9ee1ef6cbf9\"") + + HelpExampleRpc("getpegoutkeys", "\"5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF\", \"0389275d512326f7016e014d8625f709c01f23bd0dc16522bf9845a9ee1ef6cbf9\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { From 94074cb1d304d0d8e26bc76e2dd499357475afe7 Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Tue, 7 Jul 2026 16:26:00 +0100 Subject: [PATCH 4/4] fix RPC documentation inconsistencies --- src/rpc/blockchain.cpp | 8 +++--- src/rpc/mining.cpp | 14 +++++----- src/rpc/misc.cpp | 2 +- src/rpc/rawtransaction.cpp | 12 ++++---- src/wallet/rpc/coins.cpp | 14 +++++----- src/wallet/rpc/elements.cpp | 2 +- src/wallet/rpc/spend.cpp | 4 +-- test/functional/feature_blocksign.py | 42 ++++++++++++++++++++++++++++ test/functional/rpc_scantxoutset.py | 2 -- 9 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index b7f8eb0eb7f..04c5b5fb6c3 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1132,7 +1132,7 @@ static RPCHelpMan getblock() {RPCResult::Type::ELISION, "", ""} }}, }}, - {RPCResult::Type::OBJ, "proposed", "Proposed parameters. Uninforced. Must be published in full", + {RPCResult::Type::OBJ, "proposed", "Proposed parameters. Unenforced. Must be published in full", { {RPCResult::Type::ELISION, "", "same entries as \"current\""} }}, @@ -2732,7 +2732,7 @@ static RPCHelpMan scantxoutset() {RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"}, {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched scriptPubKey"}, {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"}, - {RPCResult::Type::STR_HEX, "asset", "The asset ID"}, + {RPCResult::Type::STR_HEX, "asset", /*optional=*/true, "The asset ID"}, {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"}, }}, }}, @@ -3101,10 +3101,10 @@ static RPCHelpMan getsidechaininfo() RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "fedpegscript", "The fedpegscript from genesis block"}, - {RPCResult::Type::ARR, "current_fedpegscripts", "The currently-enforced fedpegscripts in hex. Peg-ins for any entries on this list are honored by consensus and policy. Newest first. Two total entries are possible", - {{RPCResult::Type::STR_HEX, "", "active fedpegscript"}}}, {RPCResult::Type::ARR, "current_fedpeg_programs", "The currently-enforced fedpegscript scriptPubKeys in hex. Prior to a transition this may be P2SH scriptpubkey, otherwise it will be a native segwit script. Results are paired in-order with current_fedpegscripts", {{RPCResult::Type::STR_HEX, "", "active fedpegscript scriptPubKeys"}}}, + {RPCResult::Type::ARR, "current_fedpegscripts", "The currently-enforced fedpegscripts in hex. Peg-ins for any entries on this list are honored by consensus and policy. Newest first. Two total entries are possible", + {{RPCResult::Type::STR_HEX, "", "active fedpegscript"}}}, {RPCResult::Type::STR_HEX, "pegged_asset", "Pegged asset type"}, {RPCResult::Type::STR, "min_peg_diff", "The minimum difficulty parent chain header target. Peg-in headers that have less work will be rejected as an anti-Dos measure"}, {RPCResult::Type::STR_HEX, "parent_blockhash", "The parent genesis blockhash as source of pegged-in funds"}, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 53d75548ec8..7b1562ff53e 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -572,6 +572,10 @@ static RPCHelpMan getblocktemplate() RPCResult{"If the proposal was not accepted with mode=='proposal'", RPCResult::Type::STR, "", "According to BIP22"}, RPCResult{"Otherwise", RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::ARR, "capabilities", "", + { + {RPCResult::Type::STR, "value", "A supported feature, for example 'proposal'"}, + }}, {RPCResult::Type::NUM, "version", "The preferred block version"}, {RPCResult::Type::ARR, "rules", "specific block rules that are to be enforced", { @@ -581,10 +585,6 @@ static RPCHelpMan getblocktemplate() { {RPCResult::Type::NUM, "rulename", "identifies the bit number as indicating acceptance and readiness for the named softfork rule"}, }}, - {RPCResult::Type::ARR, "capabilities", "", - { - {RPCResult::Type::STR, "value", "A supported feature, for example 'proposal'"}, - }}, {RPCResult::Type::NUM, "vbrequired", "bit mask of versionbits the server requires set in submissions"}, {RPCResult::Type::STR, "previousblockhash", "The hash of current highest block"}, {RPCResult::Type::ARR, "transactions", "contents of non-coinbase transactions that should be included in the next block", @@ -1393,7 +1393,7 @@ static RPCHelpMan getnewblockhex() const NodeContext& node = EnsureAnyNodeContext(request.context); std::unique_ptr pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), *node.mempool, Params()).CreateNewBlock(feeDestinationScript, std::chrono::seconds(required_wait), &proposed, data_commitments.empty() ? nullptr : &data_commitments)); if (!pblocktemplate.get()) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet keypool empty"); + throw JSONRPCError(RPC_INTERNAL_ERROR, "Block template empty"); } { @@ -1433,7 +1433,7 @@ static RPCHelpMan combineblocksigs() }, }, }, - {"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The hex-encoded witnessScript for the signblockscript"}, + {"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded witnessScript for the signblockscript. Required for dynafed blocks; omitted for non-dynafed blocks."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -1514,7 +1514,7 @@ static RPCHelpMan getcompactsketch() {"block_hex", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex serialized block proposal from `getnewblockhex`."}, }, RPCResult{ - RPCResult::Type::STR, "sketch", "serialized block sketch", + RPCResult::Type::STR_HEX, "sketch", "serialized block sketch", }, RPCExamples{ HelpExampleCli("getcompactsketch", ""), diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 6cdb80a3364..05a758f948b 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -310,7 +310,7 @@ static RPCHelpMan deriveaddresses() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } - 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/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 0eb62390619..7defd97c88c 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -171,7 +171,7 @@ static RPCHelpMan getrawtransaction() }, { RPCResult{"if verbose is not set or set to false", - RPCResult::Type::STR, "data", "The serialized, hex-encoded data for 'txid'" + RPCResult::Type::STR_HEX, "data", "The serialized, hex-encoded data for 'txid'" }, RPCResult{"if verbose is set to true", RPCResult::Type::OBJ, "", "", @@ -812,7 +812,7 @@ static RPCHelpMan combinerawtransaction() }, }, RPCResult{ - RPCResult::Type::STR, "", "The hex-encoded raw transaction with signature(s)" + RPCResult::Type::STR_HEX, "", "The hex-encoded raw transaction with signature(s)" }, RPCExamples{ HelpExampleCli("combinerawtransaction", R"('["myhex1", "myhex2", "myhex3"]')") @@ -893,7 +893,7 @@ static RPCHelpMan signrawtransactionwithkey() {"hexstring", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction hex string"}, {"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The base58-encoded private keys for signing", { - {"privatekey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "private key in base58-encoding"}, + {"privatekey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "private key in base58-encoding"}, }, }, {"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The previous dependent transaction outputs", @@ -2808,7 +2808,7 @@ static RPCHelpMan rawblindrawtransaction() {"ignoreblindfail", RPCArg::Type::BOOL, RPCArg::Default{true}, "Return a transaction even when a blinding attempt fails due to number of blinded inputs/outputs."}, }, RPCResult{ - RPCResult::Type::STR, "transaction", "hex string of the transaction" + RPCResult::Type::STR_HEX, "transaction", "hex string of the transaction" }, RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue @@ -2842,7 +2842,7 @@ static RPCHelpMan rawblindrawtransaction() } if (inputAmounts.size() != tx.vin.size()) { throw JSONRPCError(RPC_INVALID_PARAMETER, - "Invalid parameter: one (potentially empty) input blind for each input must be provided"); + "Invalid parameter: one (potentially empty) input amount for each input must be provided"); } if (inputAssets.size() != tx.vin.size()) { throw JSONRPCError(RPC_INVALID_PARAMETER, @@ -2893,7 +2893,7 @@ static RPCHelpMan rawblindrawtransaction() if (!IsHex(assetblind) || assetblind.length() != 32*2) throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset blinds must be an array of 32-byte hex-encoded strings"); if (!IsHex(asset) || asset.length() != 32*2) - throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset blinds must be an array of 32-byte hex-encoded strings"); + throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset IDs must be an array of 32-byte hex-encoded strings"); input_blinds.push_back(uint256S(blind)); input_asset_blinds.push_back(uint256S(assetblind)); diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index ad1fe5a4b2b..0a604879413 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -119,7 +119,7 @@ RPCHelpMan getreceivedbyaddress() "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 6") + "\nThe amount with at least 6 confirmations including immature coinbase outputs\n" - + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 6 true") + + + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 6 \"\" true") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 6") }, @@ -177,9 +177,9 @@ RPCHelpMan getreceivedbylabel() "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") + "\nThe amount with at least 6 confirmations including immature coinbase outputs\n" - + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6 true") + + + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6 \"\" true") + "\nAs a JSON-RPC call\n" - + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6, true") + + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6, \"\", true") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -614,14 +614,14 @@ RPCHelpMan listunspent() {RPCResult::Type::STR, "scriptPubKey", "the script key"}, {RPCResult::Type::STR_AMOUNT, "amount", "the transaction output amount in " + CURRENCY_UNIT}, {RPCResult::Type::STR_HEX, "amountcommitment", /*optional=*/true, "the transaction output commitment in hex"}, - {RPCResult::Type::STR_HEX, "asset", "the transaction output asset in hex"}, + {RPCResult::Type::STR_HEX, "asset", /*optional=*/true, "the transaction output asset in hex"}, {RPCResult::Type::STR_HEX, "assetcommitment", /*optional=*/true, "the transaction output asset commitment in hex"}, - {RPCResult::Type::STR_HEX, "amountblinder", "the transaction output amount blinding factor in hex"}, - {RPCResult::Type::STR_HEX, "assetblinder", "the transaction output asset blinding factor in hex"}, + {RPCResult::Type::STR_HEX, "amountblinder", /*optional=*/true, "the transaction output amount blinding factor in hex"}, + {RPCResult::Type::STR_HEX, "assetblinder", /*optional=*/true, "the transaction output asset blinding factor in hex"}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations"}, {RPCResult::Type::NUM, "ancestorcount", /*optional=*/true, "The number of in-mempool ancestor transactions, including this one (if transaction is in the mempool)"}, {RPCResult::Type::NUM, "ancestorsize", /*optional=*/true, "The virtual transaction size of in-mempool ancestors, including this one (if transaction is in the mempool)"}, - {RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true, "The total fees of in-mempool ancestors (including this one) with fee deltas used for mining priority in " + CURRENCY_ATOM + " (if transaction is in the mempool)"}, + {RPCResult::Type::NUM, "ancestorfees", /*optional=*/true, "The total fees of in-mempool ancestors (including this one) with fee deltas used for mining priority in " + CURRENCY_ATOM + " (if transaction is in the mempool)"}, {RPCResult::Type::STR_HEX, "redeemScript", /*optional=*/true, "The redeemScript if scriptPubKey is P2SH"}, {RPCResult::Type::STR, "witnessScript", /*optional=*/true, "witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH"}, {RPCResult::Type::BOOL, "spendable", "Whether we have the private keys to spend this output"}, diff --git a/src/wallet/rpc/elements.cpp b/src/wallet/rpc/elements.cpp index a352b750892..dc182787978 100644 --- a/src/wallet/rpc/elements.cpp +++ b/src/wallet/rpc/elements.cpp @@ -1003,7 +1003,7 @@ RPCHelpMan createrawpegin() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::STR, "hex", "raw transaction data"}, + {RPCResult::Type::STR_HEX, "hex", "raw transaction data"}, {RPCResult::Type::BOOL, "mature", "Whether the peg-in is mature (only included when validating peg-ins)"}, }, }, diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index bc2d16d0165..9086d3adb80 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -862,7 +862,7 @@ RPCHelpMan signrawtransactionwithwallet() {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"}, {"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "The amount spent (required if non-confidential segwit output)"}, - {"amountcommitment", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The amount commitment spent (required if confidential segwit output)"}, + {"amountcommitment", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The amount commitment spent (required if confidential segwit output)"}, }, }, }, @@ -1554,7 +1554,7 @@ RPCHelpMan walletcreatefundedpsbt() FundTxDoc()), "options"}, {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"}, - {"psbt_version", RPCArg::Type::NUM, RPCArg::Default{2}, "The PSBT version number to use."}, + {"psbt_version", RPCArg::Type::NUM, RPCArg::Default{2}, "The PSBT version number to use. Must be 2."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", diff --git a/test/functional/feature_blocksign.py b/test/functional/feature_blocksign.py index 3bc4b1c13e8..b3fcd876c57 100755 --- a/test/functional/feature_blocksign.py +++ b/test/functional/feature_blocksign.py @@ -177,6 +177,39 @@ def mine_blocks(self, num_blocks, transactions): for i in range(num_blocks): self.mine_block(transactions) + def test_combineblocksigs_witnessscript_arg(self): + # Regression test for combineblocksigs witnessScript optionality. + + node = self.nodes[0] + block = node.getnewblockhex() + + is_dyna = node.getdeploymentinfo()['deployments']['dynafed']['bip9']['status'] == "active" + + if is_dyna: + # no witnessScript must give RPC_INVALID_PARAMETER, not a help error from arg-count validation. + assert_raises_rpc_error( + -8, + "Signing dynamic blocks requires the witnessScript argument", + node.combineblocksigs, + block, + [], + ) + # Supplying witnessScript must work normally. + result = node.combineblocksigs(block, [], self.witnessScript) + assert "hex" in result + assert "complete" in result + else: + # Non-dynafed: 2-argument call must not be rejected by arg-count validation. + try: + result = node.combineblocksigs(block, []) + assert "hex" in result + assert "complete" in result + except Exception as e: + # If exception it must not be the help string + assert "combineblocksigs" not in str(e), \ + "Got help text — 2-arg call was rejected by arg-count validation" + raise + def run_test(self): # Have every node except last import its block signing private key. for i in range(self.num_keys): @@ -208,6 +241,10 @@ def run_test(self): assert_equal(info['deployments']['dynafed']['bip9']['status'], "defined") + # Test combineblocksigs witnessScript optionality pre-dynafed + self.log.info("Testing combineblocksigs witnessScript argument (pre-dynafed)") + self.test_combineblocksigs_witnessscript_arg() + # Next let's activate dynafed blocks_til_dynafed = 431 - self.nodes[0].getblockcount() self.log.info("Activating dynafed") @@ -218,6 +255,11 @@ def run_test(self): self.log.info("Mine some dynamic federation blocks without txns") self.mine_blocks(10, False) + + # Test combineblocksigs witnessScript optionality post-dynafed + self.log.info("Testing combineblocksigs witnessScript argument (post-dynafed)") + self.test_combineblocksigs_witnessscript_arg() + self.log.info("Mine some dynamic federation blocks with txns") self.mine_blocks(10, True) diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index 0502fc9c4e3..d33bea453a2 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -126,8 +126,6 @@ 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) - if __name__ == "__main__": ScantxoutsetTest().main()