Skip to content

Commit cddcbaf

Browse files
committed
RPC: improve SFFO arg parsing, error catching and coverage
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()
1 parent 4f4cd35 commit cddcbaf

File tree

2 files changed

+30
-20
lines changed

2 files changed

+30
-20
lines changed

src/wallet/rpc/spend.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,24 @@ std::set<int> InterpretSubtractFeeFromOutputInstructions(const UniValue& sffo_in
7070
if (sffo_instructions.isNull()) return sffo_set;
7171

7272
for (const auto& sffo : sffo_instructions.getValues()) {
73+
int pos{-1};
7374
if (sffo.isStr()) {
74-
for (size_t i = 0; i < destinations.size(); ++i) {
75-
if (sffo.get_str() == destinations.at(i)) {
76-
sffo_set.insert(i);
77-
break;
78-
}
79-
}
80-
}
81-
if (sffo.isNum()) {
82-
int pos = sffo.getInt<int>();
83-
if (sffo_set.contains(pos))
84-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos));
85-
if (pos < 0)
86-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos));
87-
if (pos >= int(destinations.size()))
88-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos));
89-
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())));
9082
}
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);
9191
}
9292
return sffo_set;
9393
}

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)