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
pull/13756/head
Karl-Johan Alm 6 years ago
parent f904723e0d
commit 0bdfbd34cf
No known key found for this signature in database
GPG Key ID: 57AF762DB3353322

@ -36,6 +36,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendtoaddress", 4, "subtractfeefromamount" }, { "sendtoaddress", 4, "subtractfeefromamount" },
{ "sendtoaddress", 5 , "replaceable" }, { "sendtoaddress", 5 , "replaceable" },
{ "sendtoaddress", 6 , "conf_target" }, { "sendtoaddress", 6 , "conf_target" },
{ "sendtoaddress", 8, "avoid_reuse" },
{ "settxfee", 0, "amount" }, { "settxfee", 0, "amount" },
{ "sethdseed", 0, "newkeypool" }, { "sethdseed", 0, "newkeypool" },
{ "getreceivedbyaddress", 1, "minconf" }, { "getreceivedbyaddress", 1, "minconf" },
@ -48,6 +49,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listreceivedbylabel", 2, "include_watchonly" }, { "listreceivedbylabel", 2, "include_watchonly" },
{ "getbalance", 1, "minconf" }, { "getbalance", 1, "minconf" },
{ "getbalance", 2, "include_watchonly" }, { "getbalance", 2, "include_watchonly" },
{ "getbalance", 3, "avoid_reuse" },
{ "getblockhash", 0, "height" }, { "getblockhash", 0, "height" },
{ "waitforblockheight", 0, "height" }, { "waitforblockheight", 0, "height" },
{ "waitforblockheight", 1, "timeout" }, { "waitforblockheight", 1, "timeout" },
@ -163,6 +165,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "rescanblockchain", 1, "stop_height"}, { "rescanblockchain", 1, "stop_height"},
{ "createwallet", 1, "disable_private_keys"}, { "createwallet", 1, "disable_private_keys"},
{ "createwallet", 2, "blank"}, { "createwallet", 2, "blank"},
{ "createwallet", 4, "avoid_reuse"},
{ "getnodeaddresses", 0, "count"}, { "getnodeaddresses", 0, "count"},
{ "stop", 0, "wait" }, { "stop", 0, "wait" },
}; };

@ -321,7 +321,7 @@ static UniValue setlabel(const JSONRPCRequest& request)
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) 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)
{ {
CAmount curBalance = pwallet->GetBalance().m_mine_trusted; CAmount curBalance = pwallet->GetBalance(0, coin_control.m_avoid_address_reuse).m_mine_trusted;
// Check amount // Check amount
if (nValue <= 0) if (nValue <= 0)
@ -368,7 +368,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) if (request.fHelp || request.params.size() < 2 || request.params.size() > 9)
throw std::runtime_error( throw std::runtime_error(
RPCHelpMan{"sendtoaddress", RPCHelpMan{"sendtoaddress",
"\nSend an amount to a given address." + "\nSend an amount to a given address." +
@ -389,6 +389,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
" \"UNSET\"\n" " \"UNSET\"\n"
" \"ECONOMICAL\"\n" " \"ECONOMICAL\"\n"
" \"CONSERVATIVE\""}, " \"CONSERVATIVE\""},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) ? "true" : "unavailable", "Avoid spending from dirty addresses; addresses are considered\n"
" dirty if they have previously been used in a transaction."},
}, },
RPCResult{ RPCResult{
"\"txid\" (string) The transaction id.\n" "\"txid\" (string) The transaction id.\n"
@ -445,6 +447,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
} }
} }
coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[8]);
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
@ -734,7 +737,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || (request.params.size() > 3 )) if (request.fHelp || request.params.size() > 4)
throw std::runtime_error( throw std::runtime_error(
RPCHelpMan{"getbalance", RPCHelpMan{"getbalance",
"\nReturns the total available balance.\n" "\nReturns the total available balance.\n"
@ -744,6 +747,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
{"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to \"*\"."}, {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to \"*\"."},
{"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."}, {"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."},
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see 'importaddress')"}, {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see 'importaddress')"},
{"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."},
}, },
RPCResult{ RPCResult{
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this wallet.\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this wallet.\n"
@ -780,7 +784,9 @@ static UniValue getbalance(const JSONRPCRequest& request)
include_watchonly = true; include_watchonly = true;
} }
const auto bal = pwallet->GetBalance(min_depth); bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]);
const auto bal = pwallet->GetBalance(min_depth, avoid_reuse);
return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0));
} }
@ -2474,6 +2480,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
" \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
" \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n" " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
" \"avoid_reuse\": true|false (boolean) whether this wallet tracks clean/dirty coins in terms of reuse\n"
" \"scanning\": (json object) current scanning details, or false if no scan is in progress\n" " \"scanning\": (json object) current scanning details, or false if no scan is in progress\n"
" {\n" " {\n"
" \"duration\" : xxxx (numeric) elapsed seconds since scan start\n" " \"duration\" : xxxx (numeric) elapsed seconds since scan start\n"
@ -2522,6 +2529,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
obj.pushKV("hdseedid", seed_id.GetHex()); obj.pushKV("hdseedid", seed_id.GetHex());
} }
obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
if (pwallet->IsScanning()) { if (pwallet->IsScanning()) {
UniValue scanning(UniValue::VOBJ); UniValue scanning(UniValue::VOBJ);
scanning.pushKV("duration", pwallet->ScanningDuration() / 1000); scanning.pushKV("duration", pwallet->ScanningDuration() / 1000);
@ -2730,6 +2738,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
{"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."}, {"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
{"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."}, {"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."},
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."}, {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
}, },
RPCResult{ RPCResult{
"{\n" "{\n"
@ -2771,6 +2780,10 @@ static UniValue createwallet(const JSONRPCRequest& request)
flags |= WALLET_FLAG_BLANK_WALLET; flags |= WALLET_FLAG_BLANK_WALLET;
} }
if (!request.params[4].isNull() && request.params[4].get_bool()) {
flags |= WALLET_FLAG_AVOID_REUSE;
}
WalletLocation location(request.params[0].get_str()); WalletLocation location(request.params[0].get_str());
if (location.Exists()) { if (location.Exists()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + location.GetName() + " already exists."); throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + location.GetName() + " already exists.");
@ -2872,6 +2885,8 @@ static UniValue listunspent(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
bool avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
if (request.fHelp || request.params.size() > 5) if (request.fHelp || request.params.size() > 5)
throw std::runtime_error( throw std::runtime_error(
RPCHelpMan{"listunspent", RPCHelpMan{"listunspent",
@ -2911,6 +2926,9 @@ static UniValue listunspent(const JSONRPCRequest& request)
" \"witnessScript\" : \"script\" (string) witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH\n" " \"witnessScript\" : \"script\" (string) witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH\n"
" \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n" " \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
" \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n" " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
+ (avoid_reuse ?
" \"reused\" : xxx, (bool) Whether this output is reused/dirty (sent to an address that was previously spent from)\n" :
"") +
" \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n" " \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n"
" \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n" " \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n"
" from outside keys and unconfirmed replacement transactions are considered unsafe\n" " from outside keys and unconfirmed replacement transactions are considered unsafe\n"
@ -2990,9 +3008,11 @@ static UniValue listunspent(const JSONRPCRequest& request)
UniValue results(UniValue::VARR); UniValue results(UniValue::VARR);
std::vector<COutput> vecOutputs; std::vector<COutput> vecOutputs;
{ {
CCoinControl cctl;
cctl.m_avoid_address_reuse = false;
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth);
} }
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
@ -3001,6 +3021,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
CTxDestination address; CTxDestination address;
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address); bool fValidAddress = ExtractDestination(scriptPubKey, address);
bool reused = avoid_reuse && pwallet->IsUsedDestination(address);
if (destinations.size() && (!fValidAddress || !destinations.count(address))) if (destinations.size() && (!fValidAddress || !destinations.count(address)))
continue; continue;
@ -3057,6 +3078,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
auto descriptor = InferDescriptor(scriptPubKey, *pwallet); auto descriptor = InferDescriptor(scriptPubKey, *pwallet);
entry.pushKV("desc", descriptor->ToString()); entry.pushKV("desc", descriptor->ToString());
} }
if (avoid_reuse) entry.pushKV("reused", reused);
entry.pushKV("safe", out.fSafe); entry.pushKV("safe", out.fSafe);
results.push_back(entry); results.push_back(entry);
} }
@ -4261,13 +4283,13 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} }, { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} }, { "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, { "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase"} }, { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, { "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} }, { "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} }, { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
{ "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} }, { "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} },
{ "wallet", "getaddressinfo", &getaddressinfo, {"address"} }, { "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
{ "wallet", "getbalance", &getbalance, {"dummy","minconf","include_watchonly"} }, { "wallet", "getbalance", &getbalance, {"dummy","minconf","include_watchonly","avoid_reuse"} },
{ "wallet", "getnewaddress", &getnewaddress, {"label","address_type"} }, { "wallet", "getnewaddress", &getnewaddress, {"label","address_type"} },
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
@ -4298,7 +4320,7 @@ static const CRPCCommand commands[] =
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} }, { "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} }, { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
{ "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, { "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse"} },
{ "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} }, { "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} },
{ "wallet", "setlabel", &setlabel, {"address","label"} }, { "wallet", "setlabel", &setlabel, {"address","label"} },
{ "wallet", "settxfee", &settxfee, {"amount"} }, { "wallet", "settxfee", &settxfee, {"amount"} },

Loading…
Cancel
Save