diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/crypter.h | 6 | ||||
-rw-r--r-- | src/wallet/db.cpp | 5 | ||||
-rw-r--r-- | src/wallet/init.cpp | 2 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 2 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 206 | ||||
-rw-r--r-- | src/wallet/test/coinselector_tests.cpp | 16 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 6 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 123 | ||||
-rw-r--r-- | src/wallet/wallet.h | 20 |
9 files changed, 211 insertions, 175 deletions
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 4c0c8ff5ec..52842cd978 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -116,7 +116,7 @@ class CCryptoKeyStore : public CBasicKeyStore { private: - CKeyingMaterial vMasterKey; + CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); //! if fUseCrypto is true, mapKeys must be empty //! if fUseCrypto is false, vMasterKey must be empty @@ -126,13 +126,15 @@ private: bool fDecryptionThoroughlyChecked; protected: + using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; + bool SetCrypted(); //! will encrypt previously unencrypted keys bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); bool Unlock(const CKeyingMaterial& vMasterKeyIn); - CryptedKeyMap mapCryptedKeys; + CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); public: CCryptoKeyStore() : fUseCrypto(false), fDecryptionThoroughlyChecked(false) diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 410dd5009f..01b8eacccb 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -694,8 +694,10 @@ void BerkeleyEnvironment::Flush(bool fShutdown) if (mapFileUseCount.empty()) { dbenv->log_archive(&listp, DB_ARCH_REMOVE); Close(); - if (!fMockDb) + if (!fMockDb) { fs::remove_all(fs::path(strPath) / "database"); + } + g_dbenvs.erase(strPath); } } } @@ -794,5 +796,6 @@ void BerkeleyDatabase::Flush(bool shutdown) { if (!IsDummy()) { env->Flush(shutdown); + if (shutdown) env = nullptr; } } diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index daeff1d0e8..74312b7124 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -200,7 +200,7 @@ bool WalletInit::Verify() const // Keep track of each wallet absolute path to detect duplicates. std::set<fs::path> wallet_paths; - for (const auto wallet_file : wallet_files) { + for (const auto& wallet_file : wallet_files) { fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir()); if (!wallet_paths.insert(wallet_path).second) { diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index d09af1dbd1..882ddbbe4e 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -441,7 +441,7 @@ UniValue importpubkey(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( "importpubkey \"pubkey\" ( \"label\" rescan )\n" "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 456f08bc14..b1d2532d86 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 The Bitcoin Core developers +// Copyright (c) 2009-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -20,6 +20,7 @@ #include <rpc/server.h> #include <rpc/util.h> #include <script/sign.h> +#include <shutdown.h> #include <timedata.h> #include <util.h> #include <utilmoneystr.h> @@ -30,8 +31,6 @@ #include <wallet/walletdb.h> #include <wallet/walletutil.h> -#include <init.h> // For StartShutdown - #include <stdint.h> #include <univalue.h> @@ -40,12 +39,21 @@ static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; -std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request) +bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name) { if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { // wallet endpoint was used - std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); - std::shared_ptr<CWallet> pwallet = GetWallet(requestedWallet); + wallet_name = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); + return true; + } + return false; +} + +std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request) +{ + std::string wallet_name; + if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { + std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name); if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); return pwallet; } @@ -66,11 +74,6 @@ bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) if (pwallet) return true; if (avoidException) return false; if (!HasWallets()) { - // Note: It isn't currently possible to trigger this error because - // wallet RPC methods aren't registered unless a wallet is loaded. But - // this error is being kept as a precaution, because it's possible in - // the future that wallet RPC methods might get or remain registered - // when no wallets are loaded. throw JSONRPCError( RPC_METHOD_NOT_FOUND, "Method not found (wallet method is disabled because no wallet is loaded)"); } @@ -120,7 +123,7 @@ static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) } entry.pushKV("bip125-replaceable", rbfStatus); - for (const std::pair<std::string, std::string>& item : wtx.mapValue) + for (const std::pair<const std::string, std::string>& item : wtx.mapValue) entry.pushKV(item.first, item.second); } @@ -198,7 +201,7 @@ CTxDestination GetLabelDestination(CWallet* const pwallet, const std::string& la return dest; } -static UniValue getlabeladdress(const JSONRPCRequest& request) +static UniValue getaccountaddress(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet* const pwallet = wallet.get(); @@ -207,53 +210,36 @@ static UniValue getlabeladdress(const JSONRPCRequest& request) return NullUniValue; } - if (!IsDeprecatedRPCEnabled("accounts") && request.strMethod == "getaccountaddress") { + if (!IsDeprecatedRPCEnabled("accounts")) { if (request.fHelp) { throw std::runtime_error("getaccountaddress (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)"); } throw JSONRPCError(RPC_METHOD_DEPRECATED, "getaccountaddress is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts."); } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "getlabeladdress \"label\" ( force ) \n" - "\nReturns the default receiving address for this label. This will reset to a fresh address once there's a transaction that spends to it.\n" + "getaccountaddress \"account\"\n" + "\n\nDEPRECATED. Returns the current Bitcoin address for receiving payments to this account.\n" "\nArguments:\n" - "1. \"label\" (string, required) The label for the address. It can also be set to the empty string \"\" to represent the default label.\n" - "2. \"force\" (bool, optional) Whether the label should be created if it does not yet exist. If False, the RPC will return an error if called with a label that doesn't exist.\n" - " Defaults to false (unless the getaccountaddress method alias is being called, in which case defaults to true for backwards compatibility).\n" + "1. \"account\" (string, required) The account for the address. 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 and a new address created if there is no account by the given name.\n" "\nResult:\n" - "\"address\" (string) The current receiving address for the label.\n" + "\"address\" (string) The account bitcoin address\n" "\nExamples:\n" - + HelpExampleCli("getlabeladdress", "") - + HelpExampleCli("getlabeladdress", "\"\"") - + HelpExampleCli("getlabeladdress", "\"mylabel\"") - + HelpExampleRpc("getlabeladdress", "\"mylabel\"") + + HelpExampleCli("getaccountaddress", "") + + HelpExampleCli("getaccountaddress", "\"\"") + + HelpExampleCli("getaccountaddress", "\"myaccount\"") + + HelpExampleRpc("getaccountaddress", "\"myaccount\"") ); LOCK2(cs_main, pwallet->cs_wallet); - // Parse the label first so we don't generate a key if there's an error - std::string label = LabelFromValue(request.params[0]); - bool force = request.strMethod == "getaccountaddress"; - if (!request.params[1].isNull()) { - force = request.params[1].get_bool(); - } - - bool label_found = false; - for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { - if (item.second.name == label) { - label_found = true; - break; - } - } - if (!force && !label_found) { - throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label)); - } + // Parse the account first so we don't generate a key if there's an error + std::string account = LabelFromValue(request.params[0]); UniValue ret(UniValue::VSTR); - ret = EncodeDestination(GetLabelDestination(pwallet, label)); + ret = EncodeDestination(GetLabelDestination(pwallet, account)); return ret; } @@ -343,23 +329,33 @@ static UniValue setlabel(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } + std::string old_label = pwallet->mapAddressBook[dest].name; std::string label = LabelFromValue(request.params[1]); if (IsMine(*pwallet, dest)) { - // Detect when changing the label of an address that is the receiving address of another label: - // If so, delete the account record for it. Labels, unlike addresses, can be deleted, - // and if we wouldn't do this, the record would stick around forever. - if (pwallet->mapAddressBook.count(dest)) { - std::string old_label = pwallet->mapAddressBook[dest].name; - if (old_label != label && dest == GetLabelDestination(pwallet, old_label)) { - pwallet->DeleteLabel(old_label); - } - } pwallet->SetAddressBook(dest, label, "receive"); + if (request.strMethod == "setaccount" && old_label != label && dest == GetLabelDestination(pwallet, old_label)) { + // for setaccount, call GetLabelDestination so a new receive address is created for the old account + GetLabelDestination(pwallet, old_label, true); + } } else { pwallet->SetAddressBook(dest, label, "send"); } + // Detect when there are no addresses using this label. + // If so, delete the account record for it. Labels, unlike addresses, can be deleted, + // and if we wouldn't do this, the record would stick around forever. + bool found_address = false; + for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { + if (item.second.name == label) { + found_address = true; + break; + } + } + if (!found_address) { + pwallet->DeleteLabel(old_label); + } + return NullUniValue; } @@ -447,7 +443,7 @@ static UniValue getaddressesbyaccount(const JSONRPCRequest& request) // Find all addresses that have the given account UniValue ret(UniValue::VARR); - for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { + for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { const CTxDestination& dest = item.first; const std::string& strName = item.second.name; if (strName == strAccount) { @@ -760,7 +756,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) // Tally CAmount nAmount = 0; - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; @@ -828,7 +824,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) // Tally CAmount nAmount = 0; - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; @@ -1024,6 +1020,14 @@ static UniValue sendfrom(const JSONRPCRequest& request) return NullUniValue; } + if (!IsDeprecatedRPCEnabled("accounts")) { + if (request.fHelp) { + throw std::runtime_error("sendfrom (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)"); + } + throw JSONRPCError(RPC_METHOD_DEPRECATED, "sendfrom is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts."); + } + + if (request.fHelp || request.params.size() < 3 || request.params.size() > 6) throw std::runtime_error( "sendfrom \"fromaccount\" \"toaddress\" amount ( minconf \"comment\" \"comment_to\" )\n" @@ -1262,9 +1266,11 @@ static UniValue sendmany(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); // Check funds - CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount); - if (totalAmount > nBalance) + if (IsDeprecatedRPCEnabled("accounts") && totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount)) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); + } else if (!IsDeprecatedRPCEnabled("accounts") && totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, nullptr)) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Wallet has insufficient funds"); + } // Shuffle recipient list std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); @@ -1457,13 +1463,6 @@ static UniValue addwitnessaddress(const JSONRPCRequest& request) "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)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Segregated witness not enabled on network"); - } - } - CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); @@ -1541,7 +1540,7 @@ static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bo // Tally std::map<CTxDestination, tallyitem> mapTally; - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) @@ -2120,13 +2119,13 @@ static UniValue listaccounts(const JSONRPCRequest& request) includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY; std::map<std::string, CAmount> mapAccountBalances; - for (const std::pair<CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { + for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { if (IsMine(*pwallet, entry.first) & includeWatchonly) { // This address belongs to me mapAccountBalances[entry.second.name] = 0; } } - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; CAmount nFee; std::string strSentAccount; @@ -2155,7 +2154,7 @@ static UniValue listaccounts(const JSONRPCRequest& request) mapAccountBalances[entry.strAccount] += entry.nCreditDebit; UniValue ret(UniValue::VOBJ); - for (const std::pair<std::string, CAmount>& accountBalance : mapAccountBalances) { + for (const std::pair<const std::string, CAmount>& accountBalance : mapAccountBalances) { ret.pushKV(accountBalance.first, ValueFromAmount(accountBalance.second)); } return ret; @@ -2264,7 +2263,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) UniValue transactions(UniValue::VARR); - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; if (depth == -1 || tx.GetDepthInMainChain() < depth) { @@ -3067,7 +3066,7 @@ static UniValue listwallets(const JSONRPCRequest& request) return obj; } -UniValue loadwallet(const JSONRPCRequest& request) +static UniValue loadwallet(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -3092,6 +3091,12 @@ UniValue loadwallet(const JSONRPCRequest& request) fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir()); if (fs::symlink_status(wallet_path).type() == fs::file_not_found) { throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + wallet_file + " not found."); + } else if (fs::is_directory(wallet_path)) { + // The given filename is a directory. Check that there's a wallet.dat file. + fs::path wallet_dat_file = wallet_path / "wallet.dat"; + if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) { + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + wallet_file + " does not contain a wallet.dat file."); + } } std::string warning; @@ -3114,7 +3119,7 @@ UniValue loadwallet(const JSONRPCRequest& request) return obj; } -UniValue createwallet(const JSONRPCRequest& request) +static UniValue createwallet(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( @@ -3161,6 +3166,55 @@ UniValue createwallet(const JSONRPCRequest& request) return obj; } +static UniValue unloadwallet(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() > 1) { + throw std::runtime_error( + "unloadwallet ( \"wallet_name\" )\n" + "Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n" + "Specifying the wallet name on a wallet endpoint is invalid." + "\nArguments:\n" + "1. \"wallet_name\" (string, optional) The name of the wallet to unload.\n" + "\nExamples:\n" + + HelpExampleCli("unloadwallet", "wallet_name") + + HelpExampleRpc("unloadwallet", "wallet_name") + ); + } + + std::string wallet_name; + if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { + if (!request.params[0].isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot unload the requested wallet"); + } + } else { + wallet_name = request.params[0].get_str(); + } + + std::shared_ptr<CWallet> wallet = GetWallet(wallet_name); + if (!wallet) { + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); + } + + // Release the "main" shared pointer and prevent further notifications. + // Note that any attempt to load the same wallet would fail until the wallet + // is destroyed (see CheckUniqueFileid). + if (!RemoveWallet(wallet)) { + throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded"); + } + UnregisterValidationInterface(wallet.get()); + + // The wallet can be in use so it's not possible to explicitly unload here. + // Just notify the unload intent so that all shared pointers are released. + // The wallet will be destroyed once the last shared pointer is released. + wallet->NotifyUnload(); + + // There's no point in waiting for the wallet to unload. + // At this point this method should never fail. The unloading could only + // fail due to an unexpected error which would cause a process termination. + + return NullUniValue; +} + static UniValue resendwallettransactions(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -4201,7 +4255,7 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) // Find all addresses that have the given label UniValue ret(UniValue::VOBJ); - for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { + for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { if (item.second.name == label) { ret.pushKV(EncodeDestination(item.first), AddressBookDataToJSON(item.second, false)); } @@ -4254,7 +4308,7 @@ static UniValue listlabels(const JSONRPCRequest& request) // Add to a set to sort by label name, then insert into Univalue array std::set<std::string> label_set; - for (const std::pair<CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { + for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { if (purpose.empty() || entry.second.purpose == purpose) { label_set.insert(entry.second.name); } @@ -4390,12 +4444,12 @@ static const CRPCCommand commands[] = { "wallet", "listwallets", &listwallets, {} }, { "wallet", "loadwallet", &loadwallet, {"filename"} }, { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, - { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, { "wallet", "sendmany", &sendmany, {"fromaccount|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", "settxfee", &settxfee, {"amount"} }, { "wallet", "signmessage", &signmessage, {"address","message"} }, { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} }, + { "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} }, { "wallet", "walletlock", &walletlock, {} }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} }, @@ -4404,17 +4458,17 @@ static const CRPCCommand commands[] = { "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} }, /** Account functions (deprecated) */ - { "wallet", "getaccountaddress", &getlabeladdress, {"account"} }, + { "wallet", "getaccountaddress", &getaccountaddress, {"account"} }, { "wallet", "getaccount", &getaccount, {"address"} }, { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} }, { "wallet", "getreceivedbyaccount", &getreceivedbylabel, {"account","minconf"} }, { "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} }, { "wallet", "listreceivedbyaccount", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, { "wallet", "setaccount", &setlabel, {"address","account"} }, + { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, { "wallet", "move", &movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, /** Label functions (to replace non-balance account functions) */ - { "wallet", "getlabeladdress", &getlabeladdress, {"label","force"} }, { "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} }, { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, { "wallet", "listlabels", &listlabels, {"purpose"} }, diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index e90370cf06..d90be33000 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -2,14 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "wallet/wallet.h" -#include "wallet/coinselection.h" -#include "wallet/coincontrol.h" -#include "amount.h" -#include "primitives/transaction.h" -#include "random.h" -#include "test/test_bitcoin.h" -#include "wallet/test/wallet_test_fixture.h" +#include <wallet/wallet.h> +#include <wallet/coinselection.h> +#include <wallet/coincontrol.h> +#include <amount.h> +#include <primitives/transaction.h> +#include <random.h> +#include <test/test_bitcoin.h> +#include <wallet/test/wallet_test_fixture.h> #include <boost/test/unit_test.hpp> #include <random> diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 03754154fc..a946b565f1 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -130,6 +130,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) LOCK(cs_main); + std::string backup_file = (SetDataDir("importwallet_rescan") / "wallet.backup").string(); + // Import key into wallet and call dumpwallet to create backup file. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy()); @@ -139,7 +141,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) JSONRPCRequest request; request.params.setArray(); - request.params.push_back((pathTemp / "wallet.backup").string()); + request.params.push_back(backup_file); AddWallet(wallet); ::dumpwallet(request); RemoveWallet(wallet); @@ -152,7 +154,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) JSONRPCRequest request; request.params.setArray(); - request.params.push_back((pathTemp / "wallet.backup").string()); + request.params.push_back(backup_file); AddWallet(wallet); ::importwallet(request); RemoveWallet(wallet); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3987e8e70a..cdba6c6441 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -11,7 +11,6 @@ #include <consensus/consensus.h> #include <consensus/validation.h> #include <fs.h> -#include <init.h> #include <key.h> #include <key_io.h> #include <keystore.h> @@ -23,6 +22,7 @@ #include <primitives/block.h> #include <primitives/transaction.h> #include <script/script.h> +#include <shutdown.h> #include <timedata.h> #include <txmempool.h> #include <utilmoneystr.h> @@ -79,6 +79,15 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name) return nullptr; } +// Custom deleter for shared_ptr<CWallet>. +static void ReleaseWallet(CWallet* wallet) +{ + LogPrintf("Releasing wallet %s\n", wallet->GetName()); + wallet->BlockUntilSyncedToCurrentChain(); + wallet->Flush(); + delete wallet; +} + const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); @@ -544,7 +553,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran for (TxSpends::iterator it = range.first; it != range.second; ++it) { const CWalletTx* wtx = &mapWallet.at(it->second); if (wtx->nOrderPos < nMinOrderPos) { - nMinOrderPos = wtx->nOrderPos;; + nMinOrderPos = wtx->nOrderPos; copyFrom = wtx; } } @@ -600,6 +609,8 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid) { mapTxSpends.insert(std::make_pair(outpoint, wtxid)); + setLockedCoins.erase(outpoint); + std::pair<TxSpends::iterator, TxSpends::iterator> range; range = mapTxSpends.equal_range(outpoint); SyncMetaData(range); @@ -923,11 +934,10 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) CWalletTx& wtx = (*ret.first).second; wtx.BindWallet(this); bool fInsertedNew = ret.second; - if (fInsertedNew) - { + if (fInsertedNew) { wtx.nTimeReceived = GetAdjustedTime(); wtx.nOrderPos = IncOrderPosNext(&batch); - wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); + wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); wtx.nTimeSmart = ComputeTimeSmart(wtx); AddToSpends(hash); } @@ -998,9 +1008,12 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) bool CWallet::LoadToWallet(const CWalletTx& wtxIn) { uint256 hash = wtxIn.GetHash(); - CWalletTx& wtx = mapWallet.emplace(hash, wtxIn).first->second; + const auto& ins = mapWallet.emplace(hash, wtxIn); + CWalletTx& wtx = ins.first->second; wtx.BindWallet(this); - wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); + if (/* insertion took place */ ins.second) { + wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); + } AddToSpends(hash); for (const CTxIn& txin : wtx.tx->vin) { auto it = mapWallet.find(txin.prevout.hash); @@ -1294,7 +1307,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() { LOCK(cs_main); const CBlockIndex* initialChainTip = chainActive.Tip(); - if (m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { + if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { return; } } @@ -1519,45 +1532,6 @@ int64_t CWalletTx::GetTxTime() const return n ? n : nTimeReceived; } -int CWalletTx::GetRequestCount() const -{ - // Returns -1 if it wasn't being tracked - int nRequests = -1; - { - LOCK(pwallet->cs_wallet); - if (IsCoinBase()) - { - // Generated block - if (!hashUnset()) - { - std::map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); - if (mi != pwallet->mapRequestCount.end()) - nRequests = (*mi).second; - } - } - else - { - // Did anyone request this transaction? - std::map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(GetHash()); - if (mi != pwallet->mapRequestCount.end()) - { - nRequests = (*mi).second; - - // How about the block it's in? - if (nRequests == 0 && !hashUnset()) - { - std::map<uint256, int>::const_iterator _mi = pwallet->mapRequestCount.find(hashBlock); - if (_mi != pwallet->mapRequestCount.end()) - nRequests = (*_mi).second; - else - nRequests = 1; // If it's in someone else's block it must have got out - } - } - } - } - return nRequests; -} - // Helper for producing a max-sized low-S signature (eg 72 bytes) bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout) const { @@ -2469,8 +2443,11 @@ bool CWallet::OutputEligibleForSpending(const COutput& output, const CoinEligibi if (output.nDepth < (output.tx->IsFromMe(ISMINE_ALL) ? eligibility_filter.conf_mine : eligibility_filter.conf_theirs)) return false; - if (!mempool.TransactionWithinChainLimit(output.tx->GetHash(), eligibility_filter.max_ancestors)) + size_t ancestors, descendants; + mempool.GetTransactionAncestry(output.tx->GetHash(), ancestors, descendants); + if (ancestors > eligibility_filter.max_ancestors || descendants > eligibility_filter.max_descendants) { return false; + } return true; } @@ -2582,16 +2559,17 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm ++it; } - size_t nMaxChainLength = std::min(gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT), gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); + size_t max_ancestors = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)); + size_t max_descendants = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); bool res = nTargetValue <= nValueFromPresetInputs || SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, nMaxChainLength/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, nMaxChainLength/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, nMaxChainLength), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset @@ -2609,7 +2587,7 @@ bool CWallet::SignTransaction(CMutableTransaction &tx) // sign the new tx int nIn = 0; - for (const auto& input : tx.vin) { + for (auto& input : tx.vin) { std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash); if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) { return false; @@ -2620,7 +2598,7 @@ bool CWallet::SignTransaction(CMutableTransaction &tx) if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) { return false; } - UpdateTransaction(tx, nIn, sigdata); + UpdateInput(input, sigdata); nIn++; } return true; @@ -3050,7 +3028,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac strFailReason = _("Signing transaction failed"); return false; } else { - UpdateTransaction(txNew, nIn, sigdata); + UpdateInput(txNew.vin.at(nIn), sigdata); } nIn++; @@ -3061,7 +3039,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac tx = MakeTransactionRef(std::move(txNew)); // Limit size - if (GetTransactionWeight(*tx) >= MAX_STANDARD_TX_WEIGHT) + if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) { strFailReason = _("Transaction too large"); return false; @@ -3128,9 +3106,6 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve } } - // Track how many getdata requests our transaction gets - mapRequestCount[wtxNew.GetHash()] = 0; - // Get the inserted-CWalletTx from mapWallet so that the // fInMempool flag is cached properly CWalletTx& wtx = mapWallet.at(wtxNew.GetHash()); @@ -3193,8 +3168,11 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) } } - // This wallet is in its first run if all of these are empty - fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty(); + { + LOCK(cs_KeyStore); + // This wallet is in its first run if all of these are empty + fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty(); + } if (nLoadWalletRet != DBErrors::LOAD_OK) return nLoadWalletRet; @@ -3206,8 +3184,11 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 { AssertLockHeld(cs_wallet); // mapWallet DBErrors nZapSelectTxRet = WalletBatch(*database,"cr+").ZapSelectTx(vHashIn, vHashOut); - for (uint256 hash : vHashOut) - mapWallet.erase(hash); + for (uint256 hash : vHashOut) { + const auto& it = mapWallet.find(hash); + wtxOrdered.erase(it->second.m_it_wtxOrdered); + mapWallet.erase(it); + } if (nZapSelectTxRet == DBErrors::NEED_REWRITE) { @@ -3280,7 +3261,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address) // Delete destdata tuples associated with address std::string strAddress = EncodeDestination(address); - for (const std::pair<std::string, std::string> &item : mapAddressBook[address].destdata) + for (const std::pair<const std::string, std::string> &item : mapAddressBook[address].destdata) { WalletBatch(*database).EraseDestData(strAddress, item.first); } @@ -3681,7 +3662,7 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co { LOCK(cs_wallet); std::set<CTxDestination> result; - for (const std::pair<CTxDestination, CAddressBookData>& item : mapAddressBook) + for (const std::pair<const CTxDestination, CAddressBookData>& item : mapAddressBook) { const CTxDestination& address = item.first; const std::string& strName = item.second.name; @@ -4057,7 +4038,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, int64_t nStart = GetTimeMillis(); bool fFirstRun = true; - std::shared_ptr<CWallet> walletInstance = std::make_shared<CWallet>(name, WalletDatabase::Create(path)); + // TODO: Can't use std::make_shared because we need a custom deleter but + // should be possible to use std::allocate_shared. + std::shared_ptr<CWallet> walletInstance(new CWallet(name, WalletDatabase::Create(path)), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -4086,8 +4069,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } } - uiInterface.LoadWallet(walletInstance); - int prev_version = walletInstance->nWalletVersion; if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) { @@ -4337,6 +4318,8 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } } + uiInterface.LoadWallet(walletInstance); + // Register with the validation interface. It's ok to do this after rescan since we're still holding cs_main. RegisterValidationInterface(walletInstance.get()); @@ -4516,9 +4499,7 @@ CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, Out return CScriptID(script); case OutputType::P2SH_SEGWIT: case OutputType::BECH32: { - WitnessV0ScriptHash hash; - CSHA256().Write(script.data(), script.size()).Finalize(hash.begin()); - CTxDestination witdest = hash; + CTxDestination witdest = WitnessV0ScriptHash(script); CScript witprog = GetScriptForDestination(witdest); // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) if (!IsSolvable(*this, witprog)) return CScriptID(script); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f1761b0fcf..ef03a1eaed 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -339,6 +339,7 @@ public: char fFromMe; std::string strFromAccount; int64_t nOrderPos; //!< position in ordered transaction list + std::multimap<int64_t, std::pair<CWalletTx*, CAccountingEntry*>>::const_iterator m_it_wtxOrdered; // memory only mutable bool fDebitCached; @@ -485,7 +486,6 @@ public: bool IsTrusted() const; int64_t GetTxTime() const; - int GetRequestCount() const; // RelayWalletTransaction may only be called if fBroadcastTransactions! bool RelayWalletTransaction(CConnman* connman); @@ -662,8 +662,10 @@ struct CoinEligibilityFilter const int conf_mine; const int conf_theirs; const uint64_t max_ancestors; + const uint64_t max_descendants; - CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors) {} + CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {} + CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {} }; class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime @@ -817,7 +819,6 @@ public: int64_t nOrderPosNext = 0; uint64_t nAccountingEntryNumber = 0; - std::map<uint256, int> mapRequestCount; std::map<CTxDestination, CAddressBookData> mapAddressBook; @@ -1061,16 +1062,6 @@ public: const std::string& GetLabelName(const CScript& scriptPubKey) const; - void Inventory(const uint256 &hash) override - { - { - LOCK(cs_wallet); - std::map<uint256, int>::iterator mi = mapRequestCount.find(hash); - if (mi != mapRequestCount.end()) - (*mi).second++; - } - } - void GetScriptForMining(std::shared_ptr<CReserveScript> &script); unsigned int GetKeyPoolSize() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) @@ -1097,6 +1088,9 @@ public: //! Flush wallet (bitdb flush) void Flush(bool shutdown=false); + /** Wallet is about to be unloaded */ + boost::signals2::signal<void ()> NotifyUnload; + /** * Address book entry changed. * @note called with lock cs_wallet held. |