Skip to content

Commit 0bdfbd3

Browse files
committed
wallet/rpc: add 'avoid_reuse' option to RPC commands
createwallet, getbalance, getwalletinfo, listunspent, sendtoaddress rpc/wallet: listunspent include reused flag and show reused utxos by default
1 parent f904723 commit 0bdfbd3

File tree

2 files changed

+33
-8
lines changed

2 files changed

+33
-8
lines changed

src/rpc/client.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
3636
{ "sendtoaddress", 4, "subtractfeefromamount" },
3737
{ "sendtoaddress", 5 , "replaceable" },
3838
{ "sendtoaddress", 6 , "conf_target" },
39+
{ "sendtoaddress", 8, "avoid_reuse" },
3940
{ "settxfee", 0, "amount" },
4041
{ "sethdseed", 0, "newkeypool" },
4142
{ "getreceivedbyaddress", 1, "minconf" },
@@ -48,6 +49,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
4849
{ "listreceivedbylabel", 2, "include_watchonly" },
4950
{ "getbalance", 1, "minconf" },
5051
{ "getbalance", 2, "include_watchonly" },
52+
{ "getbalance", 3, "avoid_reuse" },
5153
{ "getblockhash", 0, "height" },
5254
{ "waitforblockheight", 0, "height" },
5355
{ "waitforblockheight", 1, "timeout" },
@@ -163,6 +165,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
163165
{ "rescanblockchain", 1, "stop_height"},
164166
{ "createwallet", 1, "disable_private_keys"},
165167
{ "createwallet", 2, "blank"},
168+
{ "createwallet", 4, "avoid_reuse"},
166169
{ "getnodeaddresses", 0, "count"},
167170
{ "stop", 0, "wait" },
168171
};

src/wallet/rpcwallet.cpp

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ static UniValue setlabel(const JSONRPCRequest& request)
321321

322322
static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue)
323323
{
324-
CAmount curBalance = pwallet->GetBalance().m_mine_trusted;
324+
CAmount curBalance = pwallet->GetBalance(0, coin_control.m_avoid_address_reuse).m_mine_trusted;
325325

326326
// Check amount
327327
if (nValue <= 0)
@@ -368,7 +368,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
368368
return NullUniValue;
369369
}
370370

371-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
371+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 9)
372372
throw std::runtime_error(
373373
RPCHelpMan{"sendtoaddress",
374374
"\nSend an amount to a given address." +
@@ -389,6 +389,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
389389
" \"UNSET\"\n"
390390
" \"ECONOMICAL\"\n"
391391
" \"CONSERVATIVE\""},
392+
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) ? "true" : "unavailable", "Avoid spending from dirty addresses; addresses are considered\n"
393+
" dirty if they have previously been used in a transaction."},
392394
},
393395
RPCResult{
394396
"\"txid\" (string) The transaction id.\n"
@@ -445,6 +447,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
445447
}
446448
}
447449

450+
coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[8]);
448451

449452
EnsureWalletIsUnlocked(pwallet);
450453

@@ -734,7 +737,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
734737
return NullUniValue;
735738
}
736739

737-
if (request.fHelp || (request.params.size() > 3 ))
740+
if (request.fHelp || request.params.size() > 4)
738741
throw std::runtime_error(
739742
RPCHelpMan{"getbalance",
740743
"\nReturns the total available balance.\n"
@@ -744,6 +747,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
744747
{"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to \"*\"."},
745748
{"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."},
746749
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see 'importaddress')"},
750+
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) ? "true" : "unavailable", "Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."},
747751
},
748752
RPCResult{
749753
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this wallet.\n"
@@ -780,7 +784,9 @@ static UniValue getbalance(const JSONRPCRequest& request)
780784
include_watchonly = true;
781785
}
782786

783-
const auto bal = pwallet->GetBalance(min_depth);
787+
bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]);
788+
789+
const auto bal = pwallet->GetBalance(min_depth, avoid_reuse);
784790

785791
return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0));
786792
}
@@ -2474,6 +2480,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
24742480
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
24752481
" \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
24762482
" \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
2483+
" \"avoid_reuse\": true|false (boolean) whether this wallet tracks clean/dirty coins in terms of reuse\n"
24772484
" \"scanning\": (json object) current scanning details, or false if no scan is in progress\n"
24782485
" {\n"
24792486
" \"duration\" : xxxx (numeric) elapsed seconds since scan start\n"
@@ -2522,6 +2529,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
25222529
obj.pushKV("hdseedid", seed_id.GetHex());
25232530
}
25242531
obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
2532+
obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
25252533
if (pwallet->IsScanning()) {
25262534
UniValue scanning(UniValue::VOBJ);
25272535
scanning.pushKV("duration", pwallet->ScanningDuration() / 1000);
@@ -2730,6 +2738,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
27302738
{"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
27312739
{"blank", RPCArg::Type::BOOL, /* default */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
27322740
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
2741+
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
27332742
},
27342743
RPCResult{
27352744
"{\n"
@@ -2771,6 +2780,10 @@ static UniValue createwallet(const JSONRPCRequest& request)
27712780
flags |= WALLET_FLAG_BLANK_WALLET;
27722781
}
27732782

2783+
if (!request.params[4].isNull() && request.params[4].get_bool()) {
2784+
flags |= WALLET_FLAG_AVOID_REUSE;
2785+
}
2786+
27742787
WalletLocation location(request.params[0].get_str());
27752788
if (location.Exists()) {
27762789
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + location.GetName() + " already exists.");
@@ -2872,6 +2885,8 @@ static UniValue listunspent(const JSONRPCRequest& request)
28722885
return NullUniValue;
28732886
}
28742887

2888+
bool avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
2889+
28752890
if (request.fHelp || request.params.size() > 5)
28762891
throw std::runtime_error(
28772892
RPCHelpMan{"listunspent",
@@ -2911,6 +2926,9 @@ static UniValue listunspent(const JSONRPCRequest& request)
29112926
" \"witnessScript\" : \"script\" (string) witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH\n"
29122927
" \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
29132928
" \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
2929+
+ (avoid_reuse ?
2930+
" \"reused\" : xxx, (bool) Whether this output is reused/dirty (sent to an address that was previously spent from)\n" :
2931+
"") +
29142932
" \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n"
29152933
" \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n"
29162934
" from outside keys and unconfirmed replacement transactions are considered unsafe\n"
@@ -2990,9 +3008,11 @@ static UniValue listunspent(const JSONRPCRequest& request)
29903008
UniValue results(UniValue::VARR);
29913009
std::vector<COutput> vecOutputs;
29923010
{
3011+
CCoinControl cctl;
3012+
cctl.m_avoid_address_reuse = false;
29933013
auto locked_chain = pwallet->chain().lock();
29943014
LOCK(pwallet->cs_wallet);
2995-
pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth);
3015+
pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth);
29963016
}
29973017

29983018
LOCK(pwallet->cs_wallet);
@@ -3001,6 +3021,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
30013021
CTxDestination address;
30023022
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
30033023
bool fValidAddress = ExtractDestination(scriptPubKey, address);
3024+
bool reused = avoid_reuse && pwallet->IsUsedDestination(address);
30043025

30053026
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
30063027
continue;
@@ -3057,6 +3078,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
30573078
auto descriptor = InferDescriptor(scriptPubKey, *pwallet);
30583079
entry.pushKV("desc", descriptor->ToString());
30593080
}
3081+
if (avoid_reuse) entry.pushKV("reused", reused);
30603082
entry.pushKV("safe", out.fSafe);
30613083
results.push_back(entry);
30623084
}
@@ -4261,13 +4283,13 @@ static const CRPCCommand commands[] =
42614283
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
42624284
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
42634285
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
4264-
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase"} },
4286+
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse"} },
42654287
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
42664288
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
42674289
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
42684290
{ "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} },
42694291
{ "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
4270-
{ "wallet", "getbalance", &getbalance, {"dummy","minconf","include_watchonly"} },
4292+
{ "wallet", "getbalance", &getbalance, {"dummy","minconf","include_watchonly","avoid_reuse"} },
42714293
{ "wallet", "getnewaddress", &getnewaddress, {"label","address_type"} },
42724294
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
42734295
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
@@ -4298,7 +4320,7 @@ static const CRPCCommand commands[] =
42984320
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
42994321
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
43004322
{ "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
4301-
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} },
4323+
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse"} },
43024324
{ "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} },
43034325
{ "wallet", "setlabel", &setlabel, {"address","label"} },
43044326
{ "wallet", "settxfee", &settxfee, {"amount"} },

0 commit comments

Comments
 (0)