diff options
Diffstat (limited to 'src/wallet/rpcwallet.cpp')
-rw-r--r-- | src/wallet/rpcwallet.cpp | 834 |
1 files changed, 599 insertions, 235 deletions
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 473acd8367..8b95c56a5f 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2016 The Bitcoin Core developers +// Copyright (c) 2009-2017 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -16,14 +16,17 @@ #include <policy/policy.h> #include <policy/rbf.h> #include <rpc/mining.h> +#include <rpc/rawtransaction.h> #include <rpc/safemode.h> #include <rpc/server.h> +#include <rpc/util.h> #include <script/sign.h> #include <timedata.h> #include <util.h> #include <utilmoneystr.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> +#include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> @@ -85,25 +88,25 @@ void EnsureWalletIsUnlocked(CWallet * const pwallet) void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) { int confirms = wtx.GetDepthInMainChain(); - entry.push_back(Pair("confirmations", confirms)); + entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) - entry.push_back(Pair("generated", true)); + entry.pushKV("generated", true); if (confirms > 0) { - entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex())); - entry.push_back(Pair("blockindex", wtx.nIndex)); - entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime())); + entry.pushKV("blockhash", wtx.hashBlock.GetHex()); + entry.pushKV("blockindex", wtx.nIndex); + entry.pushKV("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()); } else { - entry.push_back(Pair("trusted", wtx.IsTrusted())); + entry.pushKV("trusted", wtx.IsTrusted()); } uint256 hash = wtx.GetHash(); - entry.push_back(Pair("txid", hash.GetHex())); + entry.pushKV("txid", hash.GetHex()); UniValue conflicts(UniValue::VARR); for (const uint256& conflict : wtx.GetConflicts()) conflicts.push_back(conflict.GetHex()); - entry.push_back(Pair("walletconflicts", conflicts)); - entry.push_back(Pair("time", wtx.GetTxTime())); - entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived)); + entry.pushKV("walletconflicts", conflicts); + entry.pushKV("time", wtx.GetTxTime()); + entry.pushKV("timereceived", (int64_t)wtx.nTimeReceived); // Add opt-in RBF status std::string rbfStatus = "no"; @@ -115,10 +118,10 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) else if (rbfState == RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125) rbfStatus = "yes"; } - entry.push_back(Pair("bip125-replaceable", rbfStatus)); + entry.pushKV("bip125-replaceable", rbfStatus); for (const std::pair<std::string, std::string>& item : wtx.mapValue) - entry.push_back(Pair(item.first, item.second)); + entry.pushKV(item.first, item.second); } std::string AccountFromValue(const UniValue& value) @@ -136,14 +139,15 @@ UniValue getnewaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) + if (request.fHelp || request.params.size() > 2) throw std::runtime_error( - "getnewaddress ( \"account\" )\n" + "getnewaddress ( \"account\" \"address_type\" )\n" "\nReturns a new Bitcoin address for receiving payments.\n" "If 'account' is specified (DEPRECATED), it is added to the address book \n" "so payments received with the address will be credited to 'account'.\n" "\nArguments:\n" "1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n" + "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n" "\nResult:\n" "\"address\" (string) The new bitcoin address\n" "\nExamples:\n" @@ -158,6 +162,14 @@ UniValue getnewaddress(const JSONRPCRequest& request) if (!request.params[0].isNull()) strAccount = AccountFromValue(request.params[0]); + OutputType output_type = g_address_type; + if (!request.params[1].isNull()) { + output_type = ParseOutputType(request.params[1].get_str(), g_address_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); + } + } + if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); } @@ -167,22 +179,23 @@ UniValue getnewaddress(const JSONRPCRequest& request) if (!pwallet->GetKeyFromPool(newKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - CKeyID keyID = newKey.GetID(); + pwallet->LearnRelatedScripts(newKey, output_type); + CTxDestination dest = GetDestinationForKey(newKey, output_type); - pwallet->SetAddressBook(keyID, strAccount, "receive"); + pwallet->SetAddressBook(dest, strAccount, "receive"); - return EncodeDestination(keyID); + return EncodeDestination(dest); } -CTxDestination GetAccountAddress(CWallet* const pwallet, std::string strAccount, bool bForceNew=false) +CTxDestination GetAccountDestination(CWallet* const pwallet, std::string strAccount, bool bForceNew=false) { - CPubKey pubKey; - if (!pwallet->GetAccountPubkey(pubKey, strAccount, bForceNew)) { + CTxDestination dest; + if (!pwallet->GetAccountDestination(dest, strAccount, bForceNew)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - return pubKey.GetID(); + return dest; } UniValue getaccountaddress(const JSONRPCRequest& request) @@ -214,7 +227,7 @@ UniValue getaccountaddress(const JSONRPCRequest& request) UniValue ret(UniValue::VSTR); - ret = EncodeDestination(GetAccountAddress(pwallet, strAccount)); + ret = EncodeDestination(GetAccountDestination(pwallet, strAccount)); return ret; } @@ -226,11 +239,13 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) + if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getrawchangeaddress\n" + "getrawchangeaddress ( \"address_type\" )\n" "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n" + "\nArguments:\n" + "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" "\nResult:\n" "\"address\" (string) The address\n" "\nExamples:\n" @@ -244,6 +259,14 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) pwallet->TopUpKeyPool(); } + OutputType output_type = g_change_type != OUTPUT_TYPE_NONE ? g_change_type : g_address_type; + if (!request.params[0].isNull()) { + output_type = ParseOutputType(request.params[0].get_str(), output_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); + } + } + CReserveKey reservekey(pwallet); CPubKey vchPubKey; if (!reservekey.GetReservedKey(vchPubKey, true)) @@ -251,9 +274,10 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) reservekey.KeepKey(); - CKeyID keyID = vchPubKey.GetID(); + pwallet->LearnRelatedScripts(vchPubKey, output_type); + CTxDestination dest = GetDestinationForKey(vchPubKey, output_type); - return EncodeDestination(keyID); + return EncodeDestination(dest); } @@ -292,8 +316,8 @@ UniValue setaccount(const JSONRPCRequest& request) // Detect when changing the account of an address that is the 'unused current key' of another account: if (pwallet->mapAddressBook.count(dest)) { std::string strOldAccount = pwallet->mapAddressBook[dest].name; - if (dest == GetAccountAddress(pwallet, strOldAccount)) { - GetAccountAddress(pwallet, strOldAccount, true); + if (dest == GetAccountDestination(pwallet, strOldAccount)) { + GetAccountDestination(pwallet, strOldAccount, true); } } pwallet->SetAddressBook(dest, strAccount, "receive"); @@ -413,7 +437,7 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA } CValidationState state; if (!pwallet->CommitTransaction(wtxNew, reservekey, g_connman.get(), state)) { - strError = strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()); + strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strError); } } @@ -771,6 +795,8 @@ UniValue getbalance(const JSONRPCRequest& request) throw std::runtime_error( "getbalance ( \"account\" minconf include_watchonly )\n" "\nIf account is not specified, returns the server's total available balance.\n" + "The available balance is what the wallet considers currently spendable, and is\n" + "thus affected by options which limit spendability such as -spendzeroconfchange.\n" "If account is specified (DEPRECATED), returns the balance in the account.\n" "Note that the account \"\" is not the same as leaving the parameter out.\n" "The server total may be different to the balance in the default \"\" account.\n" @@ -1036,7 +1062,7 @@ UniValue sendmany(const JSONRPCRequest& request) "\nSend two amounts to two different addresses, subtract fee from amount:\n" + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + "\nAs a json rpc call\n" - + HelpExampleRpc("sendmany", "\"\", \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"") + + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"") ); ObserveSafeMode(); @@ -1131,16 +1157,13 @@ UniValue sendmany(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state)) { - strFailReason = strprintf("Transaction commit failed:: %s", state.GetRejectReason()); + strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } return wtx.GetHash().GetHex(); } -// Defined in rpc/misc.cpp -extern CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params); - UniValue addmultisigaddress(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -1148,25 +1171,29 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) - { - std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" )\n" + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { + std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" \"address_type\" )\n" "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" "Each key is a Bitcoin address or hex-encoded public key.\n" + "This functionality is only intended for use with non-watchonly addresses.\n" + "See `importaddress` for watchonly p2sh address support.\n" "If 'account' is specified (DEPRECATED), assign address to that account.\n" "\nArguments:\n" - "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" - "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n" + "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" + "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n" " [\n" - " \"address\" (string) bitcoin address or hex-encoded public key\n" + " \"address\" (string) bitcoin address or hex-encoded public key\n" " ...,\n" " ]\n" - "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n" + "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n" + "4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n" "\nResult:\n" - "\"address\" (string) A bitcoin address associated with the keys.\n" - + "{\n" + " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n" + " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n" + "}\n" "\nExamples:\n" "\nAdd a multisig address from 2 addresses\n" + HelpExampleCli("addmultisigaddress", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + @@ -1182,13 +1209,37 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) if (!request.params[2].isNull()) strAccount = AccountFromValue(request.params[2]); + int required = request.params[0].get_int(); + + // Get the public keys + const UniValue& keys_or_addrs = request.params[1].get_array(); + std::vector<CPubKey> pubkeys; + for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) { + if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { + pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); + } else { + pubkeys.push_back(AddrToPubKey(pwallet, keys_or_addrs[i].get_str())); + } + } + + OutputType output_type = g_address_type; + if (!request.params[3].isNull()) { + output_type = ParseOutputType(request.params[3].get_str(), output_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str())); + } + } + // Construct using pay-to-script-hash: - CScript inner = _createmultisig_redeemScript(pwallet, request.params); - CScriptID innerID(inner); + CScript inner = CreateMultisigRedeemscript(required, pubkeys); pwallet->AddCScript(inner); + CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, output_type); + pwallet->SetAddressBook(dest, strAccount, "send"); - pwallet->SetAddressBook(innerID, strAccount, "send"); - return EncodeDestination(innerID); + UniValue result(UniValue::VOBJ); + result.pushKV("address", EncodeDestination(dest)); + result.pushKV("redeemScript", HexStr(inner.begin(), inner.end())); + return result; } class Witnessifier : public boost::static_visitor<bool> @@ -1204,12 +1255,7 @@ public: if (pwallet) { CScript basescript = GetScriptForDestination(keyID); CScript witscript = GetScriptForWitness(basescript); - SignatureData sigs; - // This check is to make sure that the script we created can actually be solved for and signed by us - // if we were to have the private keys. This is just to make sure that the script is valid and that, - // if found in a transaction, we would still accept and relay that transaction. - if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) || - !VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) { + if (!IsSolvable(*pwallet, witscript)) { return false; } return ExtractDestination(witscript, result); @@ -1228,12 +1274,7 @@ public: return true; } CScript witscript = GetScriptForWitness(subscript); - SignatureData sigs; - // This check is to make sure that the script we created can actually be solved for and signed by us - // if we were to have the private keys. This is just to make sure that the script is valid and that, - // if found in a transaction, we would still accept and relay that transaction. - if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) || - !VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) { + if (!IsSolvable(*pwallet, witscript)) { return false; } return ExtractDestination(witscript, result); @@ -1269,7 +1310,8 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { std::string msg = "addwitnessaddress \"address\" ( p2sh )\n" - "\nAdd a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n" + "\nDEPRECATED: set the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n" + "Add a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n" "It returns the witness script.\n" "\nArguments:\n" @@ -1283,6 +1325,12 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) throw std::runtime_error(msg); } + if (!IsDeprecatedRPCEnabled("addwitnessaddress")) { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "addwitnessaddress is deprecated and will be fully removed in v0.17. " + "To use addwitnessaddress in v0.16, restart bitcoind with -deprecatedrpc=addwitnessaddress.\n" + "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"); + } + { LOCK(cs_main); if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !gArgs.GetBoolArg("-walletprematurewitness", false)) { @@ -1317,7 +1365,7 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types"); } } else { - pwallet->AddCScript(witprogram); + pwallet->AddCScript(witprogram); // Implicit for single-key now, but necessary for multisig and for compatibility with older software pwallet->SetAddressBook(w.result, "", "receive"); } @@ -1417,13 +1465,13 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA { UniValue obj(UniValue::VOBJ); if(fIsWatchonly) - obj.push_back(Pair("involvesWatchonly", true)); - obj.push_back(Pair("address", EncodeDestination(dest))); - obj.push_back(Pair("account", strAccount)); - obj.push_back(Pair("amount", ValueFromAmount(nAmount))); - obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf))); + obj.pushKV("involvesWatchonly", true); + obj.pushKV("address", EncodeDestination(dest)); + obj.pushKV("account", strAccount); + obj.pushKV("amount", ValueFromAmount(nAmount)); + obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); if (!fByAccounts) - obj.push_back(Pair("label", strAccount)); + obj.pushKV("label", strAccount); UniValue transactions(UniValue::VARR); if (it != mapTally.end()) { @@ -1432,7 +1480,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA transactions.push_back(_item.GetHex()); } } - obj.push_back(Pair("txids", transactions)); + obj.pushKV("txids", transactions); ret.push_back(obj); } } @@ -1445,10 +1493,10 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA int nConf = entry.second.nConf; UniValue obj(UniValue::VOBJ); if (entry.second.fIsWatchonly) - obj.push_back(Pair("involvesWatchonly", true)); - obj.push_back(Pair("account", entry.first)); - obj.push_back(Pair("amount", ValueFromAmount(nAmount))); - obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf))); + obj.pushKV("involvesWatchonly", true); + obj.pushKV("account", entry.first); + obj.pushKV("amount", ValueFromAmount(nAmount)); + obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); ret.push_back(obj); } } @@ -1554,7 +1602,7 @@ UniValue listreceivedbyaccount(const JSONRPCRequest& request) static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) { if (IsValidDestination(dest)) { - entry.push_back(Pair("address", EncodeDestination(dest))); + entry.pushKV("address", EncodeDestination(dest)); } } @@ -1588,20 +1636,20 @@ void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::s { UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) { - entry.push_back(Pair("involvesWatchonly", true)); + entry.pushKV("involvesWatchonly", true); } - entry.push_back(Pair("account", strSentAccount)); + entry.pushKV("account", strSentAccount); MaybePushAddress(entry, s.destination); - entry.push_back(Pair("category", "send")); - entry.push_back(Pair("amount", ValueFromAmount(-s.amount))); + entry.pushKV("category", "send"); + entry.pushKV("amount", ValueFromAmount(-s.amount)); if (pwallet->mapAddressBook.count(s.destination)) { - entry.push_back(Pair("label", pwallet->mapAddressBook[s.destination].name)); + entry.pushKV("label", pwallet->mapAddressBook[s.destination].name); } - entry.push_back(Pair("vout", s.vout)); - entry.push_back(Pair("fee", ValueFromAmount(-nFee))); + entry.pushKV("vout", s.vout); + entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) WalletTxToJSON(wtx, entry); - entry.push_back(Pair("abandoned", wtx.isAbandoned())); + entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } } @@ -1619,28 +1667,28 @@ void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::s { UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) { - entry.push_back(Pair("involvesWatchonly", true)); + entry.pushKV("involvesWatchonly", true); } - entry.push_back(Pair("account", account)); + entry.pushKV("account", account); MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { if (wtx.GetDepthInMainChain() < 1) - entry.push_back(Pair("category", "orphan")); + entry.pushKV("category", "orphan"); else if (wtx.GetBlocksToMaturity() > 0) - entry.push_back(Pair("category", "immature")); + entry.pushKV("category", "immature"); else - entry.push_back(Pair("category", "generate")); + entry.pushKV("category", "generate"); } else { - entry.push_back(Pair("category", "receive")); + entry.pushKV("category", "receive"); } - entry.push_back(Pair("amount", ValueFromAmount(r.amount))); + entry.pushKV("amount", ValueFromAmount(r.amount)); if (pwallet->mapAddressBook.count(r.destination)) { - entry.push_back(Pair("label", account)); + entry.pushKV("label", account); } - entry.push_back(Pair("vout", r.vout)); + entry.pushKV("vout", r.vout); if (fLong) WalletTxToJSON(wtx, entry); ret.push_back(entry); @@ -1656,12 +1704,12 @@ void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccoun if (fAllAccounts || acentry.strAccount == strAccount) { UniValue entry(UniValue::VOBJ); - entry.push_back(Pair("account", acentry.strAccount)); - entry.push_back(Pair("category", "move")); - entry.push_back(Pair("time", acentry.nTime)); - entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit))); - entry.push_back(Pair("otheraccount", acentry.strOtherAccount)); - entry.push_back(Pair("comment", acentry.strComment)); + entry.pushKV("account", acentry.strAccount); + entry.pushKV("category", "move"); + entry.pushKV("time", acentry.nTime); + entry.pushKV("amount", ValueFromAmount(acentry.nCreditDebit)); + entry.pushKV("otheraccount", acentry.strOtherAccount); + entry.pushKV("comment", acentry.strComment); ret.push_back(entry); } } @@ -1739,8 +1787,6 @@ UniValue listtransactions(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); - std::string strAccount = "*"; if (!request.params[0].isNull()) strAccount = request.params[0].get_str(); @@ -1762,20 +1808,25 @@ UniValue listtransactions(const JSONRPCRequest& request) UniValue ret(UniValue::VARR); - const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; - - // iterate backwards until we have nCount items to return: - for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { - CWalletTx *const pwtx = (*it).second.first; - if (pwtx != nullptr) - ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter); - CAccountingEntry *const pacentry = (*it).second.second; - if (pacentry != nullptr) - AcentryToJSON(*pacentry, strAccount, ret); + LOCK2(cs_main, pwallet->cs_wallet); - if ((int)ret.size() >= (nCount+nFrom)) break; + const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; + + // iterate backwards until we have nCount items to return: + for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx != nullptr) + ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter); + CAccountingEntry *const pacentry = (*it).second.second; + if (pacentry != nullptr) + AcentryToJSON(*pacentry, strAccount, ret); + + if ((int)ret.size() >= (nCount+nFrom)) break; + } } + // ret is newest to oldest if (nFrom > (int)ret.size()) @@ -1885,7 +1936,7 @@ UniValue listaccounts(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); for (const std::pair<std::string, CAmount>& accountBalance : mapAccountBalances) { - ret.push_back(Pair(accountBalance.first, ValueFromAmount(accountBalance.second))); + ret.pushKV(accountBalance.first, ValueFromAmount(accountBalance.second)); } return ret; } @@ -2025,9 +2076,9 @@ UniValue listsinceblock(const JSONRPCRequest& request) uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256(); UniValue ret(UniValue::VOBJ); - ret.push_back(Pair("transactions", transactions)); - if (include_removed) ret.push_back(Pair("removed", removed)); - ret.push_back(Pair("lastblock", lastblock.GetHex())); + ret.pushKV("transactions", transactions); + if (include_removed) ret.pushKV("removed", removed); + ret.pushKV("lastblock", lastblock.GetHex()); return ret; } @@ -2112,18 +2163,18 @@ UniValue gettransaction(const JSONRPCRequest& request) CAmount nNet = nCredit - nDebit; CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); - entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); + entry.pushKV("amount", ValueFromAmount(nNet - nFee)); if (wtx.IsFromMe(filter)) - entry.push_back(Pair("fee", ValueFromAmount(nFee))); + entry.pushKV("fee", ValueFromAmount(nFee)); WalletTxToJSON(wtx, entry); UniValue details(UniValue::VARR); ListTransactions(pwallet, wtx, "*", 0, false, details, filter); - entry.push_back(Pair("details", details)); + entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); - entry.push_back(Pair("hex", strHex)); + entry.pushKV("hex", strHex); return entry; } @@ -2135,14 +2186,14 @@ UniValue abandontransaction(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) + if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "abandontransaction \"txid\"\n" "\nMark in-wallet transaction <txid> as abandoned\n" "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n" "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n" "It only works on transactions which are not included in a block and are not currently in the mempool.\n" - "It has no effect on transactions which are already conflicted or abandoned.\n" + "It has no effect on transactions which are already abandoned.\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id\n" "\nResult:\n" @@ -2150,6 +2201,7 @@ UniValue abandontransaction(const JSONRPCRequest& request) + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") ); + } ObserveSafeMode(); @@ -2267,7 +2319,8 @@ UniValue walletpassphrase(const JSONRPCRequest& request) "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" "\nArguments:\n" "1. \"passphrase\" (string, required) The wallet passphrase\n" - "2. timeout (numeric, required) The time to keep the decryption key in seconds.\n" + "2. timeout (numeric, required) The time to keep the decryption key in seconds. Limited to at most 1073741824 (2^30) seconds.\n" + " Any value greater than 1073741824 seconds will be set to 1073741824 seconds.\n" "\nNote:\n" "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" "time that overrides the old one.\n" @@ -2296,6 +2349,17 @@ UniValue walletpassphrase(const JSONRPCRequest& request) // Alternately, find a way to make request.params[0] mlock()'d to begin with. strWalletPass = request.params[0].get_str().c_str(); + // Get the timeout + int64_t nSleepTime = request.params[1].get_int64(); + // Timeout cannot be negative, otherwise it will relock immediately + if (nSleepTime < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative."); + } + // Clamp timeout to 2^30 seconds + if (nSleepTime > (int64_t)1 << 30) { + nSleepTime = (int64_t)1 << 30; + } + if (strWalletPass.length() > 0) { if (!pwallet->Unlock(strWalletPass)) { @@ -2309,7 +2373,6 @@ UniValue walletpassphrase(const JSONRPCRequest& request) pwallet->TopUpKeyPool(); - int64_t nSleepTime = request.params[1].get_int64(); pwallet->nRelockTime = GetTime() + nSleepTime; RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), boost::bind(LockWallet, pwallet), nSleepTime); @@ -2641,8 +2704,8 @@ UniValue listlockunspent(const JSONRPCRequest& request) for (COutPoint &outpt : vOutpts) { UniValue o(UniValue::VOBJ); - o.push_back(Pair("txid", outpt.hash.GetHex())); - o.push_back(Pair("vout", (int)outpt.n)); + o.pushKV("txid", outpt.hash.GetHex()); + o.pushKV("vout", (int)outpt.n); ret.push_back(o); } @@ -2702,7 +2765,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request) " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" - " \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n" + " \"hdmasterkeyid\": \"<hash160>\" (string, optional) the Hash160 of the HD master pubkey (only present when HD is enabled)\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") @@ -2720,24 +2783,24 @@ UniValue getwalletinfo(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); - obj.push_back(Pair("walletname", pwallet->GetName())); - obj.push_back(Pair("walletversion", pwallet->GetVersion())); - obj.push_back(Pair("balance", ValueFromAmount(pwallet->GetBalance()))); - obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance()))); - obj.push_back(Pair("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance()))); - obj.push_back(Pair("txcount", (int)pwallet->mapWallet.size())); - obj.push_back(Pair("keypoololdest", pwallet->GetOldestKeyPoolTime())); - obj.push_back(Pair("keypoolsize", (int64_t)kpExternalSize)); + obj.pushKV("walletname", pwallet->GetName()); + obj.pushKV("walletversion", pwallet->GetVersion()); + obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance())); + obj.pushKV("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())); + obj.pushKV("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())); + obj.pushKV("txcount", (int)pwallet->mapWallet.size()); + obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime()); + obj.pushKV("keypoolsize", (int64_t)kpExternalSize); CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID; if (!masterKeyID.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { - obj.push_back(Pair("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize))); + obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)); } if (pwallet->IsCrypted()) { - obj.push_back(Pair("unlocked_until", pwallet->nRelockTime)); + obj.pushKV("unlocked_until", pwallet->nRelockTime); } - obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); + obj.pushKV("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())); if (!masterKeyID.IsNull()) - obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex())); + obj.pushKV("hdmasterkeyid", masterKeyID.GetHex()); return obj; } @@ -2942,31 +3005,31 @@ UniValue listunspent(const JSONRPCRequest& request) continue; UniValue entry(UniValue::VOBJ); - entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); - entry.push_back(Pair("vout", out.i)); + entry.pushKV("txid", out.tx->GetHash().GetHex()); + entry.pushKV("vout", out.i); if (fValidAddress) { - entry.push_back(Pair("address", EncodeDestination(address))); + entry.pushKV("address", EncodeDestination(address)); if (pwallet->mapAddressBook.count(address)) { - entry.push_back(Pair("account", pwallet->mapAddressBook[address].name)); + entry.pushKV("account", pwallet->mapAddressBook[address].name); } if (scriptPubKey.IsPayToScriptHash()) { const CScriptID& hash = boost::get<CScriptID>(address); CScript redeemScript; if (pwallet->GetCScript(hash, redeemScript)) { - entry.push_back(Pair("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()))); + entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); } } } - entry.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); - entry.push_back(Pair("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue))); - entry.push_back(Pair("confirmations", out.nDepth)); - entry.push_back(Pair("spendable", out.fSpendable)); - entry.push_back(Pair("solvable", out.fSolvable)); - entry.push_back(Pair("safe", out.fSafe)); + entry.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); + entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue)); + entry.pushKV("confirmations", out.nDepth); + entry.pushKV("spendable", out.fSpendable); + entry.pushKV("solvable", out.fSolvable); + entry.pushKV("safe", out.fSafe); results.push_back(entry); } @@ -2980,9 +3043,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "fundrawtransaction \"hexstring\" ( options )\n" + "fundrawtransaction \"hexstring\" ( options iswitness )\n" "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" "This will not modify existing inputs, and will add at most one change output to the outputs.\n" "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" @@ -2999,6 +3062,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) " {\n" " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" " \"changePosition\" (numeric, optional, default random) The index of the change output\n" + " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" @@ -3017,6 +3081,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) " \"CONSERVATIVE\"\n" " }\n" " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" + "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n" + " If iswitness is not present, heuristic tests will be used in decoding\n" + "\nResult:\n" "{\n" " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" @@ -3053,7 +3120,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) coinControl.fAllowWatchOnly = request.params[1].get_bool(); } else { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ, UniValue::VBOOL}); UniValue options = request.params[1]; @@ -3061,9 +3128,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) { {"changeAddress", UniValueType(UniValue::VSTR)}, {"changePosition", UniValueType(UniValue::VNUM)}, + {"change_type", UniValueType(UniValue::VSTR)}, {"includeWatching", UniValueType(UniValue::VBOOL)}, {"lockUnspents", UniValueType(UniValue::VBOOL)}, - {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so. {"feeRate", UniValueType()}, // will be checked below {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, {"replaceable", UniValueType(UniValue::VBOOL)}, @@ -3085,6 +3152,16 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) if (options.exists("changePosition")) changePosition = options["changePosition"].get_int(); + if (options.exists("change_type")) { + if (options.exists("changeAddress")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options"); + } + coinControl.change_type = ParseOutputType(options["change_type"].get_str(), coinControl.change_type); + if (coinControl.change_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str())); + } + } + if (options.exists("includeWatching")) coinControl.fAllowWatchOnly = options["includeWatching"].get_bool(); @@ -3122,8 +3199,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) // parse hex string from parameter CMutableTransaction tx; - if (!DecodeHexTx(tx, request.params[0].get_str(), true)) + bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); + bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool(); + if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } if (tx.vout.size() == 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); @@ -3150,13 +3230,82 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) } UniValue result(UniValue::VOBJ); - result.push_back(Pair("hex", EncodeHexTx(tx))); - result.push_back(Pair("changepos", changePosition)); - result.push_back(Pair("fee", ValueFromAmount(nFeeOut))); + result.pushKV("hex", EncodeHexTx(tx)); + result.pushKV("changepos", changePosition); + result.pushKV("fee", ValueFromAmount(nFeeOut)); return result; } +UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + throw std::runtime_error( + "signrawtransactionwithwallet \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n" + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" + "The second optional argument (may be null) is an array of previous transaction outputs that\n" + "this transaction depends on but may not yet be in the block chain.\n" + + HelpRequiringPassphrase(pwallet) + "\n" + + "\nArguments:\n" + "1. \"hexstring\" (string, required) The transaction hex string\n" + "2. \"prevtxs\" (string, optional) An json array of previous dependent transaction outputs\n" + " [ (json array of json objects, or 'null' if none provided)\n" + " {\n" + " \"txid\":\"id\", (string, required) The transaction id\n" + " \"vout\":n, (numeric, required) The output number\n" + " \"scriptPubKey\": \"hex\", (string, required) script key\n" + " \"redeemScript\": \"hex\", (string, required for P2SH or P2WSH) redeem script\n" + " \"amount\": value (numeric, required) The amount spent\n" + " }\n" + " ,...\n" + " ]\n" + "3. \"sighashtype\" (string, optional, default=ALL) The signature hash type. Must be one of\n" + " \"ALL\"\n" + " \"NONE\"\n" + " \"SINGLE\"\n" + " \"ALL|ANYONECANPAY\"\n" + " \"NONE|ANYONECANPAY\"\n" + " \"SINGLE|ANYONECANPAY\"\n" + + "\nResult:\n" + "{\n" + " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" + " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" + " \"errors\" : [ (json array of objects) Script verification errors (if there are any)\n" + " {\n" + " \"txid\" : \"hash\", (string) The hash of the referenced, previous transaction\n" + " \"vout\" : n, (numeric) The index of the output to spent and used as input\n" + " \"scriptSig\" : \"hex\", (string) The hex-encoded signature script\n" + " \"sequence\" : n, (numeric) Script sequence number\n" + " \"error\" : \"text\" (string) Verification or signing error related to the input\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + + HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); + + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_str(), true)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + // Sign the transaction + LOCK2(cs_main, pwallet->cs_wallet); + return SignTransaction(mtx, request.params[1], pwallet, false, request.params[2]); +} + UniValue bumpfee(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -3173,8 +3322,8 @@ UniValue bumpfee(const JSONRPCRequest& request) "If the change output is not big enough to cover the increased fee, the command will currently fail\n" "instead of adding new inputs to compensate. (A future implementation could improve this.)\n" "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" - "By default, the new fee will be calculated automatically using estimatefee.\n" - "The user can specify a confirmation target for estimatefee.\n" + "By default, the new fee will be calculated automatically using estimatesmartfee.\n" + "The user can specify a confirmation target for estimatesmartfee.\n" "Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n" "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n" "returned by getnetworkinfo) to enter the node's mempool.\n" @@ -3294,14 +3443,14 @@ UniValue bumpfee(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, errors[0]); } UniValue result(UniValue::VOBJ); - result.push_back(Pair("txid", txid.GetHex())); - result.push_back(Pair("origfee", ValueFromAmount(old_fee))); - result.push_back(Pair("fee", ValueFromAmount(new_fee))); + result.pushKV("txid", txid.GetHex()); + result.pushKV("origfee", ValueFromAmount(old_fee)); + result.pushKV("fee", ValueFromAmount(new_fee)); UniValue result_errors(UniValue::VARR); for (const std::string& error : errors) { result_errors.push_back(error); } - result.push_back(Pair("errors", result_errors)); + result.pushKV("errors", result_errors); return result; } @@ -3376,30 +3525,41 @@ UniValue rescanblockchain(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + WalletRescanReserver reserver(pwallet); + if (!reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } - CBlockIndex *pindexStart = chainActive.Genesis(); + CBlockIndex *pindexStart = nullptr; CBlockIndex *pindexStop = nullptr; - if (!request.params[0].isNull()) { - pindexStart = chainActive[request.params[0].get_int()]; - if (!pindexStart) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); - } - } + CBlockIndex *pChainTip = nullptr; + { + LOCK(cs_main); + pindexStart = chainActive.Genesis(); + pChainTip = chainActive.Tip(); - if (!request.params[1].isNull()) { - pindexStop = chainActive[request.params[1].get_int()]; - if (!pindexStop) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); + if (!request.params[0].isNull()) { + pindexStart = chainActive[request.params[0].get_int()]; + if (!pindexStart) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); + } } - else if (pindexStop->nHeight < pindexStart->nHeight) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height"); + + if (!request.params[1].isNull()) { + pindexStop = chainActive[request.params[1].get_int()]; + if (!pindexStop) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); + } + else if (pindexStop->nHeight < pindexStart->nHeight) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height"); + } } } // We can't rescan beyond non-pruned blocks, stop and throw an error if (fPruneMode) { - CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip(); + LOCK(cs_main); + CBlockIndex *block = pindexStop ? pindexStop : pChainTip; while (block && block->nHeight >= pindexStart->nHeight) { if (!(block->nStatus & BLOCK_HAVE_DATA)) { throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); @@ -3408,24 +3568,226 @@ UniValue rescanblockchain(const JSONRPCRequest& request) } } - CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, true); + CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true); if (!stopBlock) { if (pwallet->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted."); } // if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex - stopBlock = pindexStop ? pindexStop : chainActive.Tip(); + stopBlock = pindexStop ? pindexStop : pChainTip; } else { throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); } - UniValue response(UniValue::VOBJ); response.pushKV("start_height", pindexStart->nHeight); response.pushKV("stop_height", stopBlock->nHeight); return response; } +class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue> +{ +public: + CWallet * const pwallet; + + void ProcessSubScript(const CScript& subscript, UniValue& obj, bool include_addresses = false) const + { + // Always present: script type and redeemscript + txnouttype which_type; + std::vector<std::vector<unsigned char>> solutions_data; + Solver(subscript, which_type, solutions_data); + obj.pushKV("script", GetTxnOutputType(which_type)); + obj.pushKV("hex", HexStr(subscript.begin(), subscript.end())); + + CTxDestination embedded; + UniValue a(UniValue::VARR); + if (ExtractDestination(subscript, embedded)) { + // Only when the script corresponds to an address. + UniValue subobj(UniValue::VOBJ); + UniValue detail = DescribeAddress(embedded); + subobj.pushKVs(detail); + UniValue wallet_detail = boost::apply_visitor(*this, embedded); + subobj.pushKVs(wallet_detail); + subobj.pushKV("address", EncodeDestination(embedded)); + subobj.pushKV("scriptPubKey", HexStr(subscript.begin(), subscript.end())); + // Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works. + if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]); + obj.pushKV("embedded", std::move(subobj)); + if (include_addresses) a.push_back(EncodeDestination(embedded)); + } else if (which_type == TX_MULTISIG) { + // Also report some information on multisig scripts (which do not have a corresponding address). + // TODO: abstract out the common functionality between this logic and ExtractDestinations. + obj.pushKV("sigsrequired", solutions_data[0][0]); + UniValue pubkeys(UniValue::VARR); + for (size_t i = 1; i < solutions_data.size() - 1; ++i) { + CPubKey key(solutions_data[i].begin(), solutions_data[i].end()); + if (include_addresses) a.push_back(EncodeDestination(key.GetID())); + pubkeys.push_back(HexStr(key.begin(), key.end())); + } + obj.pushKV("pubkeys", std::move(pubkeys)); + } + + // The "addresses" field is confusing because it refers to public keys using their P2PKH address. + // For that reason, only add the 'addresses' field when needed for backward compatibility. New applications + // can use the 'embedded'->'address' field for P2SH or P2WSH wrapped addresses, and 'pubkeys' for + // inspecting multisig participants. + if (include_addresses) obj.pushKV("addresses", std::move(a)); + } + + explicit DescribeWalletAddressVisitor(CWallet* _pwallet) : pwallet(_pwallet) {} + + UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); } + + UniValue operator()(const CKeyID& keyID) const + { + UniValue obj(UniValue::VOBJ); + CPubKey vchPubKey; + if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) { + obj.pushKV("pubkey", HexStr(vchPubKey)); + obj.pushKV("iscompressed", vchPubKey.IsCompressed()); + } + return obj; + } + + UniValue operator()(const CScriptID& scriptID) const + { + UniValue obj(UniValue::VOBJ); + CScript subscript; + if (pwallet && pwallet->GetCScript(scriptID, subscript)) { + ProcessSubScript(subscript, obj, IsDeprecatedRPCEnabled("validateaddress")); + } + return obj; + } + + UniValue operator()(const WitnessV0KeyHash& id) const + { + UniValue obj(UniValue::VOBJ); + CPubKey pubkey; + if (pwallet && pwallet->GetPubKey(CKeyID(id), pubkey)) { + obj.pushKV("pubkey", HexStr(pubkey)); + } + return obj; + } + + UniValue operator()(const WitnessV0ScriptHash& id) const + { + UniValue obj(UniValue::VOBJ); + CScript subscript; + CRIPEMD160 hasher; + uint160 hash; + hasher.Write(id.begin(), 32).Finalize(hash.begin()); + if (pwallet && pwallet->GetCScript(CScriptID(hash), subscript)) { + ProcessSubScript(subscript, obj); + } + return obj; + } + + UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); } +}; + +UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& dest) +{ + UniValue ret(UniValue::VOBJ); + UniValue detail = DescribeAddress(dest); + ret.pushKVs(detail); + ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(pwallet), dest)); + return ret; +} + +UniValue getaddressinfo(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error( + "getaddressinfo \"address\"\n" + "\nReturn information about the given bitcoin address. Some information requires the address\n" + "to be in the wallet.\n" + "\nArguments:\n" + "1. \"address\" (string, required) The bitcoin address to get the information of.\n" + "\nResult:\n" + "{\n" + " \"address\" : \"address\", (string) The bitcoin address validated\n" + " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n" + " \"ismine\" : true|false, (boolean) If the address is yours or not\n" + " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" + " \"isscript\" : true|false, (boolean) If the key is a script\n" + " \"iswitness\" : true|false, (boolean) If the address is a witness address\n" + " \"witness_version\" : version (numeric, optional) The version number of the witness program\n" + " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n" + " \"script\" : \"type\" (string, optional) The output script type. Only if \"isscript\" is true and the redeemscript is known. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash, witness_v0_scripthash, witness_unknown\n" + " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address\n" + " \"pubkeys\" (string, optional) Array of pubkeys associated with the known redeemscript (only if \"script\" is \"multisig\")\n" + " [\n" + " \"pubkey\"\n" + " ,...\n" + " ]\n" + " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n" + " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n" + " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdmasterkeyid\") and relation to the wallet (\"ismine\", \"iswatchonly\", \"account\").\n" + " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" + " \"account\" : \"account\" (string) The account associated with the address, \"\" is the default account\n" + " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" + " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" + " \"hdmasterkeyid\" : \"<hash160>\" (string, optional) The Hash160 of the HD master pubkey\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + ); + } + + LOCK(pwallet->cs_wallet); + + UniValue ret(UniValue::VOBJ); + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + + // Make sure the destination is valid + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + std::string currentAddress = EncodeDestination(dest); + ret.pushKV("address", currentAddress); + + CScript scriptPubKey = GetScriptForDestination(dest); + ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); + + isminetype mine = IsMine(*pwallet, dest); + ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); + ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); + UniValue detail = DescribeWalletAddress(pwallet, dest); + ret.pushKVs(detail); + if (pwallet->mapAddressBook.count(dest)) { + ret.pushKV("account", pwallet->mapAddressBook[dest].name); + } + const CKeyMetadata* meta = nullptr; + CKeyID key_id = GetKeyForDestination(*pwallet, dest); + if (!key_id.IsNull()) { + auto it = pwallet->mapKeyMetadata.find(key_id); + if (it != pwallet->mapKeyMetadata.end()) { + meta = &it->second; + } + } + if (!meta) { + auto it = pwallet->m_script_metadata.find(CScriptID(scriptPubKey)); + if (it != pwallet->m_script_metadata.end()) { + meta = &it->second; + } + } + if (meta) { + ret.pushKV("timestamp", meta->nCreateTime); + if (!meta->hdKeypath.empty()) { + ret.pushKV("hdkeypath", meta->hdKeypath); + ret.pushKV("hdmasterkeyid", meta->hdMasterKeyID.GetHex()); + } + } + return ret; +} + extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue importprivkey(const JSONRPCRequest& request); @@ -3439,61 +3801,63 @@ extern UniValue importmulti(const JSONRPCRequest& request); extern UniValue rescanblockchain(const JSONRPCRequest& request); static const CRPCCommand commands[] = -{ // category name actor (function) argNames - // --------------------- ------------------------ ----------------------- ---------- - { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options"} }, - { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, - { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, - { "wallet", "abortrescan", &abortrescan, {} }, - { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","account"} }, - { "wallet", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, - { "wallet", "backupwallet", &backupwallet, {"destination"} }, - { "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, - { "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, - { "wallet", "dumpwallet", &dumpwallet, {"filename"} }, - { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} }, - { "wallet", "getaccountaddress", &getaccountaddress, {"account"} }, - { "wallet", "getaccount", &getaccount, {"address"} }, - { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} }, - { "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} }, - { "wallet", "getnewaddress", &getnewaddress, {"account"} }, - { "wallet", "getrawchangeaddress", &getrawchangeaddress, {} }, - { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} }, - { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, - { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, - { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, - { "wallet", "getwalletinfo", &getwalletinfo, {} }, - { "wallet", "importmulti", &importmulti, {"requests","options"} }, - { "wallet", "importprivkey", &importprivkey, {"privkey","label","rescan"} }, - { "wallet", "importwallet", &importwallet, {"filename"} }, - { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} }, - { "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} }, - { "wallet", "importpubkey", &importpubkey, {"pubkey","label","rescan"} }, - { "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} }, - { "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} }, - { "wallet", "listaddressgroupings", &listaddressgroupings, {} }, - { "wallet", "listlockunspent", &listlockunspent, {} }, - { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, {"minconf","include_empty","include_watchonly"} }, - { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly"} }, - { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, - { "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} }, - { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, - { "wallet", "listwallets", &listwallets, {} }, - { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, - { "wallet", "move", &movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, - { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, - { "wallet", "sendmany", &sendmany, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, - { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, - { "wallet", "setaccount", &setaccount, {"address","account"} }, - { "wallet", "settxfee", &settxfee, {"amount"} }, - { "wallet", "signmessage", &signmessage, {"address","message"} }, - { "wallet", "walletlock", &walletlock, {} }, - { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, - { "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} }, - { "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} }, - { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} }, - - { "generating", "generate", &generate, {"nblocks","maxtries"} }, +{ // category name actor (function) argNames + // --------------------- ------------------------ ----------------------- ---------- + { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, + { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, + { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, + { "wallet", "abortrescan", &abortrescan, {} }, + { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","account","address_type"} }, + { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, + { "wallet", "backupwallet", &backupwallet, {"destination"} }, + { "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, + { "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, + { "wallet", "dumpwallet", &dumpwallet, {"filename"} }, + { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} }, + { "wallet", "getaccountaddress", &getaccountaddress, {"account"} }, + { "wallet", "getaccount", &getaccount, {"address"} }, + { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} }, + { "wallet", "getaddressinfo", &getaddressinfo, {"address"} }, + { "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} }, + { "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} }, + { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, + { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} }, + { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, + { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, + { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, + { "wallet", "getwalletinfo", &getwalletinfo, {} }, + { "wallet", "importmulti", &importmulti, {"requests","options"} }, + { "wallet", "importprivkey", &importprivkey, {"privkey","label","rescan"} }, + { "wallet", "importwallet", &importwallet, {"filename"} }, + { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} }, + { "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} }, + { "wallet", "importpubkey", &importpubkey, {"pubkey","label","rescan"} }, + { "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} }, + { "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} }, + { "wallet", "listaddressgroupings", &listaddressgroupings, {} }, + { "wallet", "listlockunspent", &listlockunspent, {} }, + { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, + { "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} }, + { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, + { "wallet", "listwallets", &listwallets, {} }, + { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, + { "wallet", "move", &movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, + { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, + { "wallet", "sendmany", &sendmany, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, + { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, + { "wallet", "setaccount", &setaccount, {"address","account"} }, + { "wallet", "settxfee", &settxfee, {"amount"} }, + { "wallet", "signmessage", &signmessage, {"address","message"} }, + { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} }, + { "wallet", "walletlock", &walletlock, {} }, + { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, + { "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} }, + { "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} }, + { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} }, + + { "generating", "generate", &generate, {"nblocks","maxtries"} }, }; void RegisterWalletRPCCommands(CRPCTable &t) |