Skip to content

Commit ecddd12

Browse files
committed
Merge bitcoin/bitcoin#18418: wallet: Increase OUTPUT_GROUP_MAX_ENTRIES to 100
e6fe1c3 rpc: Improve avoidpartialspends and avoid_reuse documentation (Fabian Jahr) 8f07307 wallet: Increase OUTPUT_GROUP_MAX_ENTRIES to 100 (Fabian Jahr) Pull request description: Follow-up to #17824. This increases OUTPUT_GROUP_MAX_ENTRIES to 100 which means that OutputGroups will now be up to 100 outputs large, up from previously 10. The main motivation for this change is that during the PR review club on #17824 [several participants signaled](https://bitcoincore.reviews/17824.html#l-339) that 100 might be a better value here. I think fees should be manageable for users but more importantly, users should know what they can expect when using the wallet with this configuration, so I also tried to clarify the documentation on `-avoidpartialspends` and `avoid_reuse` a bit. If there are other additional ways how or docs where users can be made aware of the potential consequences of using these parameters, please let me know. Another small upside is that [there seem to be a high number of batching transactions with 100 and 200 inputs](https://miro.medium.com/max/3628/1*sZ5eaBSbsJsHx-J9iztq2g.png)([source](https://medium.com/@hasufly/an-analysis-of-batching-in-bitcoin-9bdf81a394e0)) giving these transactions a bit of a larger anonymity set, although that is probably a very weak argument. ACKs for top commit: jnewbery: ACK e6fe1c3 Xekyo: retACK e6fe1c3 rajarshimaitra: tACK `e6fe1c3` achow101: ACK e6fe1c3 glozow: code review ACK bitcoin/bitcoin@e6fe1c3 Tree-SHA512: 79685c58bafa64ed8303b0ecd616fce50fc9a2b758aa79833e4ad9f15760e09ab60c007bc16ab4cbc4222e644cfd154f1fa494b0f3a5d86faede7af33a6f2826
2 parents 3ec033e + e6fe1c3 commit ecddd12

File tree

4 files changed

+27
-27
lines changed

4 files changed

+27
-27
lines changed

src/wallet/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const WalletInitInterface& g_wallet_init_interface = WalletInit();
4343
void WalletInit::AddWalletOptions(ArgsManager& argsman) const
4444
{
4545
argsman.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
46-
argsman.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u (always enabled for wallets with \"avoid_reuse\" enabled))", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
46+
argsman.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting many (possibly all) or none, instead of selecting on a per-output basis. Privacy is improved as addresses are mostly swept with fewer transactions and outputs are aggregated in clean change addresses. It may result in higher fees due to less optimal coin selection caused by this added limitation and possibly a larger-than-necessary number of inputs being used. Always enabled for wallets with \"avoid_reuse\" enabled, otherwise default: %u.", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
4747
argsman.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
4848
argsman.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
4949
argsman.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kvB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). "

src/wallet/rpcwallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ static RPCHelpMan sendtoaddress()
447447
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
448448
" \"" + FeeModes("\"\n\"") + "\""},
449449
{"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
450-
"dirty if they have previously been used in a transaction."},
450+
"dirty if they have previously been used in a transaction. If true, this also activates avoidpartialspends, grouping outputs by their addresses."},
451451
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
452452
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."},
453453
},

src/wallet/wallet.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
5353
},
5454
};
5555

56-
static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
56+
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
5757

5858
RecursiveMutex cs_wallets;
5959
static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
@@ -2483,7 +2483,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
24832483
// form groups from remaining coins; note that preset coins will not
24842484
// automatically have their associated (same address) coins included
24852485
if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
2486-
// Cases where we have 11+ outputs all pointing to the same destination may result in
2486+
// Cases where we have 101+ outputs all pointing to the same destination may result in
24872487
// privacy leaks as they will potentially be deterministically sorted. We solve that by
24882488
// explicitly shuffling the outputs before processing
24892489
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());

test/functional/wallet_avoidreuse.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,25 @@ def count_unspent(node):
4242
r["reused"]["supported"] = supports_reused
4343
return r
4444

45-
def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None, reused_count=None, reused_sum=None):
45+
def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None, reused_count=None, reused_sum=None, margin=0.001):
4646
'''Make assertions about a node's unspent output statistics'''
4747
stats = count_unspent(node)
4848
if total_count is not None:
4949
assert_equal(stats["total"]["count"], total_count)
5050
if total_sum is not None:
51-
assert_approx(stats["total"]["sum"], total_sum, 0.001)
51+
assert_approx(stats["total"]["sum"], total_sum, margin)
5252
if reused_supported is not None:
5353
assert_equal(stats["reused"]["supported"], reused_supported)
5454
if reused_count is not None:
5555
assert_equal(stats["reused"]["count"], reused_count)
5656
if reused_sum is not None:
57-
assert_approx(stats["reused"]["sum"], reused_sum, 0.001)
57+
assert_approx(stats["reused"]["sum"], reused_sum, margin)
5858

59-
def assert_balances(node, mine):
59+
def assert_balances(node, mine, margin=0.001):
6060
'''Make assertions about a node's getbalances output'''
6161
got = node.getbalances()["mine"]
6262
for k,v in mine.items():
63-
assert_approx(got[k], v, 0.001)
63+
assert_approx(got[k], v, margin)
6464

6565
class AvoidReuseTest(BitcoinTestFramework):
6666

@@ -299,7 +299,7 @@ def test_getbalances_used(self):
299299
ret_addr = self.nodes[0].getnewaddress()
300300

301301
# send multiple transactions, reusing one address
302-
for _ in range(11):
302+
for _ in range(101):
303303
self.nodes[0].sendtoaddress(new_addr, 1)
304304

305305
self.nodes[0].generate(1)
@@ -311,14 +311,14 @@ def test_getbalances_used(self):
311311

312312
# getbalances and listunspent should show the remaining outputs
313313
# in the reused address as used/reused
314-
assert_unspent(self.nodes[1], total_count=2, total_sum=6, reused_count=1, reused_sum=1)
315-
assert_balances(self.nodes[1], mine={"used": 1, "trusted": 5})
314+
assert_unspent(self.nodes[1], total_count=2, total_sum=96, reused_count=1, reused_sum=1, margin=0.01)
315+
assert_balances(self.nodes[1], mine={"used": 1, "trusted": 95}, margin=0.01)
316316

317317
def test_full_destination_group_is_preferred(self):
318318
'''
319-
Test the case where [1] only has 11 outputs of 1 BTC in the same reused
319+
Test the case where [1] only has 101 outputs of 1 BTC in the same reused
320320
address and tries to send a small payment of 0.5 BTC. The wallet
321-
should use 10 outputs from the reused address as inputs and not a
321+
should use 100 outputs from the reused address as inputs and not a
322322
single 1 BTC input, in order to join several outputs from the reused
323323
address.
324324
'''
@@ -330,8 +330,8 @@ def test_full_destination_group_is_preferred(self):
330330
new_addr = self.nodes[1].getnewaddress()
331331
ret_addr = self.nodes[0].getnewaddress()
332332

333-
# Send 11 outputs of 1 BTC to the same, reused address in the wallet
334-
for _ in range(11):
333+
# Send 101 outputs of 1 BTC to the same, reused address in the wallet
334+
for _ in range(101):
335335
self.nodes[0].sendtoaddress(new_addr, 1)
336336

337337
self.nodes[0].generate(1)
@@ -342,14 +342,14 @@ def test_full_destination_group_is_preferred(self):
342342
txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=0.5)
343343
inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"]
344344

345-
# The transaction should use 10 inputs exactly
346-
assert_equal(len(inputs), 10)
345+
# The transaction should use 100 inputs exactly
346+
assert_equal(len(inputs), 100)
347347

348348
def test_all_destination_groups_are_used(self):
349349
'''
350-
Test the case where [1] only has 22 outputs of 1 BTC in the same reused
351-
address and tries to send a payment of 20.5 BTC. The wallet
352-
should use all 22 outputs from the reused address as inputs.
350+
Test the case where [1] only has 202 outputs of 1 BTC in the same reused
351+
address and tries to send a payment of 200.5 BTC. The wallet
352+
should use all 202 outputs from the reused address as inputs.
353353
'''
354354
self.log.info("Test that all destination groups are used")
355355

@@ -359,20 +359,20 @@ def test_all_destination_groups_are_used(self):
359359
new_addr = self.nodes[1].getnewaddress()
360360
ret_addr = self.nodes[0].getnewaddress()
361361

362-
# Send 22 outputs of 1 BTC to the same, reused address in the wallet
363-
for _ in range(22):
362+
# Send 202 outputs of 1 BTC to the same, reused address in the wallet
363+
for _ in range(202):
364364
self.nodes[0].sendtoaddress(new_addr, 1)
365365

366366
self.nodes[0].generate(1)
367367
self.sync_all()
368368

369369
# Sending a transaction that needs to use the full groups
370-
# of 10 inputs but also the incomplete group of 2 inputs.
371-
txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=20.5)
370+
# of 100 inputs but also the incomplete group of 2 inputs.
371+
txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=200.5)
372372
inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"]
373373

374-
# The transaction should use 22 inputs exactly
375-
assert_equal(len(inputs), 22)
374+
# The transaction should use 202 inputs exactly
375+
assert_equal(len(inputs), 202)
376376

377377

378378
if __name__ == '__main__':

0 commit comments

Comments
 (0)