Skip to content

Commit c7869cb

Browse files
committed
Merge bitcoin/bitcoin#30844: RPC: improve SFFO arg parsing, error catching and coverage
cddcbaf RPC: improve SFFO arg parsing, error catching and coverage (furszy) 4f4cd35 rpc: decouple sendtoaddress 'subtractfeefromamount' boolean parsing (furszy) Pull request description: Following changes were made: 1) Catch and signal error for duplicate string destinations. 2) Catch and signal error for invalid value type. 3) Catch and signal error for string destination not found in tx outputs. 4) Improved `InterpretSubtractFeeFromOutputInstructions()` code organization. 5) Added test coverage for all possible error failures. Also, fixed two PEP 8 warnings at the 'wallet_sendmany.py' file: - PEP 8: E302 expected 2 blank lines, found 1 at the SendmanyTest class declaration. - PEP 8: E303 too many blank lines (2) at skip_test_if_missing_module() and set_test_params() ACKs for top commit: achow101: ACK cddcbaf murchandamus: crACK cddcbaf naiyoma: TACK [https://github.com/bitcoin/bitcoin/pull/30844/commits/cddcbaf81e8e3d107cd321ee2c9a7fd786a8917e](https://github.com/bitcoin/bitcoin/pull/30844/commits/cddcbaf81e8e3d107cd321ee2c9a7fd786a8917e) ismaelsadeeq: Code review and Tested ACK cddcbaf Tree-SHA512: c9c15582b81101a93987458d155394ff2c9ca42864624c034ee808a31c3a7d7f55105dea98e86fce17d3c7b2c1a6b5b77942da66b287f8b8881a60cde78c1a3c
2 parents 1e0c5bd + cddcbaf commit c7869cb

File tree

2 files changed

+38
-28
lines changed

2 files changed

+38
-28
lines changed

src/wallet/rpc/spend.cpp

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -68,29 +68,26 @@ std::set<int> InterpretSubtractFeeFromOutputInstructions(const UniValue& sffo_in
6868
{
6969
std::set<int> sffo_set;
7070
if (sffo_instructions.isNull()) return sffo_set;
71-
if (sffo_instructions.isBool()) {
72-
if (sffo_instructions.get_bool()) sffo_set.insert(0);
73-
return sffo_set;
74-
}
71+
7572
for (const auto& sffo : sffo_instructions.getValues()) {
73+
int pos{-1};
7674
if (sffo.isStr()) {
77-
for (size_t i = 0; i < destinations.size(); ++i) {
78-
if (sffo.get_str() == destinations.at(i)) {
79-
sffo_set.insert(i);
80-
break;
81-
}
82-
}
83-
}
84-
if (sffo.isNum()) {
85-
int pos = sffo.getInt<int>();
86-
if (sffo_set.contains(pos))
87-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos));
88-
if (pos < 0)
89-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos));
90-
if (pos >= int(destinations.size()))
91-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos));
92-
sffo_set.insert(pos);
75+
auto it = find(destinations.begin(), destinations.end(), sffo.get_str());
76+
if (it == destinations.end()) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', destination %s not found in tx outputs", sffo.get_str()));
77+
pos = it - destinations.begin();
78+
} else if (sffo.isNum()) {
79+
pos = sffo.getInt<int>();
80+
} else {
81+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', invalid value type: %s", uvTypeName(sffo.type())));
9382
}
83+
84+
if (sffo_set.contains(pos))
85+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', duplicated position: %d", pos));
86+
if (pos < 0)
87+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', negative position: %d", pos));
88+
if (pos >= int(destinations.size()))
89+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', position too large: %d", pos));
90+
sffo_set.insert(pos);
9491
}
9592
return sffo_set;
9693
}
@@ -310,10 +307,13 @@ RPCHelpMan sendtoaddress()
310307
UniValue address_amounts(UniValue::VOBJ);
311308
const std::string address = request.params[0].get_str();
312309
address_amounts.pushKV(address, request.params[1]);
313-
std::vector<CRecipient> recipients = CreateRecipients(
314-
ParseOutputs(address_amounts),
315-
InterpretSubtractFeeFromOutputInstructions(request.params[4], address_amounts.getKeys())
316-
);
310+
311+
std::set<int> sffo_set;
312+
if (!request.params[4].isNull() && request.params[4].get_bool()) {
313+
sffo_set.insert(0);
314+
}
315+
316+
std::vector<CRecipient> recipients{CreateRecipients(ParseOutputs(address_amounts), sffo_set)};
317317
const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()};
318318

319319
return SendMoney(*pwallet, coin_control, recipients, mapValue, verbose);

test/functional/wallet_sendmany.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
"""Test the sendmany RPC command."""
66

77
from test_framework.test_framework import BitcoinTestFramework
8+
from test_framework.util import assert_raises_rpc_error
9+
810

911
class SendmanyTest(BitcoinTestFramework):
1012
# Setup and helpers
1113
def add_options(self, parser):
1214
self.add_wallet_options(parser)
1315

14-
1516
def skip_test_if_missing_module(self):
1617
self.skip_if_no_wallet()
1718

18-
1919
def set_test_params(self):
2020
self.num_nodes = 1
2121
self.setup_clean_chain = True
@@ -26,9 +26,19 @@ def test_sffo_repeated_address(self):
2626
addr_3 = self.wallet.getnewaddress()
2727

2828
self.log.info("Test using duplicate address in SFFO argument")
29-
self.def_wallet.sendmany(dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[addr_1, addr_1, addr_1])
29+
assert_raises_rpc_error(-8, "Invalid parameter 'subtract fee from output', duplicated position: 0", self.def_wallet.sendmany, dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[addr_1, addr_1, addr_1])
3030
self.log.info("Test using address not present in tx.vout in SFFO argument")
31-
self.def_wallet.sendmany(dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[addr_3])
31+
assert_raises_rpc_error(-8, f"Invalid parameter 'subtract fee from output', destination {addr_3} not found in tx outputs", self.def_wallet.sendmany, dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[addr_3])
32+
self.log.info("Test using negative index in SFFO argument")
33+
assert_raises_rpc_error(-8, "Invalid parameter 'subtract fee from output', negative position: -5", self.def_wallet.sendmany, dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[-5])
34+
self.log.info("Test using an out of bounds index in SFFO argument")
35+
assert_raises_rpc_error(-8, "Invalid parameter 'subtract fee from output', position too large: 5", self.def_wallet.sendmany, dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[5])
36+
self.log.info("Test using an unexpected type in SFFO argument")
37+
assert_raises_rpc_error(-8, "Invalid parameter 'subtract fee from output', invalid value type: bool", self.def_wallet.sendmany, dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[False])
38+
self.log.info("Test duplicates in SFFO argument, mix string destinations with numeric indexes")
39+
assert_raises_rpc_error(-8, "Invalid parameter 'subtract fee from output', duplicated position: 0", self.def_wallet.sendmany, dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[0, addr_1])
40+
self.log.info("Test valid mixing of string destinations with numeric indexes in SFFO argument")
41+
self.def_wallet.sendmany(dummy='', amounts={addr_1: 1, addr_2: 1}, subtractfeefrom=[0, addr_2])
3242

3343
def run_test(self):
3444
self.nodes[0].createwallet("activewallet")

0 commit comments

Comments
 (0)