diff options
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r-- | src/wallet/wallet.cpp | 217 |
1 files changed, 107 insertions, 110 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 34a5b7b8d2..73e11a5b52 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -33,6 +33,8 @@ #include <wallet/coincontrol.h> #include <wallet/fees.h> +#include <univalue.h> + #include <algorithm> #include <assert.h> @@ -54,6 +56,42 @@ static RecursiveMutex cs_wallets; static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets); static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets); +bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name) +{ + util::SettingsValue setting_value = chain.getRwSetting("wallet"); + if (!setting_value.isArray()) setting_value.setArray(); + for (const util::SettingsValue& value : setting_value.getValues()) { + if (value.isStr() && value.get_str() == wallet_name) return true; + } + setting_value.push_back(wallet_name); + return chain.updateRwSetting("wallet", setting_value); +} + +bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name) +{ + util::SettingsValue setting_value = chain.getRwSetting("wallet"); + if (!setting_value.isArray()) return true; + util::SettingsValue new_value(util::SettingsValue::VARR); + for (const util::SettingsValue& value : setting_value.getValues()) { + if (!value.isStr() || value.get_str() != wallet_name) new_value.push_back(value); + } + if (new_value.size() == setting_value.size()) return true; + return chain.updateRwSetting("wallet", new_value); +} + +static void UpdateWalletSetting(interfaces::Chain& chain, + const std::string& wallet_name, + Optional<bool> load_on_startup, + std::vector<bilingual_str>& warnings) +{ + if (load_on_startup == nullopt) return; + if (load_on_startup.get() && !AddWalletSetting(chain, wallet_name)) { + warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup.")); + } else if (!load_on_startup.get() && !RemoveWalletSetting(chain, wallet_name)) { + warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may still be loaded next node startup.")); + } +} + bool AddWallet(const std::shared_ptr<CWallet>& wallet) { LOCK(cs_wallets); @@ -65,18 +103,32 @@ bool AddWallet(const std::shared_ptr<CWallet>& wallet) return true; } -bool RemoveWallet(const std::shared_ptr<CWallet>& wallet) +bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start, std::vector<bilingual_str>& warnings) { assert(wallet); + + interfaces::Chain& chain = wallet->chain(); + std::string name = wallet->GetName(); + // Unregister with the validation interface which also drops shared ponters. wallet->m_chain_notifications_handler.reset(); LOCK(cs_wallets); std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); if (i == vpwallets.end()) return false; vpwallets.erase(i); + + // Write the wallet setting + UpdateWalletSetting(chain, name, load_on_start, warnings); + return true; } +bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start) +{ + std::vector<bilingual_str> warnings; + return RemoveWallet(wallet, load_on_start, warnings); +} + std::vector<std::shared_ptr<CWallet>> GetWallets() { LOCK(cs_wallets); @@ -148,43 +200,54 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet) } namespace { -std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings) +std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) { try { - if (!CWallet::Verify(chain, location, error, warnings)) { + std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error); + if (!database) { error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error; return nullptr; } - std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, error, warnings); + std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), options.create_flags, error, warnings); if (!wallet) { error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error; + status = DatabaseStatus::FAILED_LOAD; return nullptr; } AddWallet(wallet); wallet->postInitProcess(); + + // Write the wallet setting + UpdateWalletSetting(chain, name, load_on_start, warnings); + return wallet; } catch (const std::runtime_error& e) { error = Untranslated(e.what()); + status = DatabaseStatus::FAILED_LOAD; return nullptr; } } } // namespace -std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings) +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) { - auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(location.GetName())); + auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(name)); if (!result.second) { error = Untranslated("Wallet already being loading."); + status = DatabaseStatus::FAILED_LOAD; return nullptr; } - auto wallet = LoadWalletInternal(chain, location, error, warnings); + auto wallet = LoadWalletInternal(chain, name, load_on_start, options, status, error, warnings); WITH_LOCK(g_loading_wallet_mutex, g_loading_wallet_set.erase(result.first)); return wallet; } -WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings, std::shared_ptr<CWallet>& result) +std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) { + uint64_t wallet_creation_flags = options.create_flags; + const SecureString& passphrase = options.create_passphrase; + // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET); @@ -193,43 +256,42 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET; } - // Check the wallet file location - WalletLocation location(name); - if (location.Exists()) { - error = strprintf(Untranslated("Wallet %s already exists."), location.GetName()); - return WalletCreationStatus::CREATION_FAILED; - } - // Wallet::Verify will check if we're trying to create a wallet with a duplicate name. - if (!CWallet::Verify(chain, location, error, warnings)) { + std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error); + if (!database) { error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error; - return WalletCreationStatus::CREATION_FAILED; + status = DatabaseStatus::FAILED_VERIFY; + return nullptr; } // Do not allow a passphrase when private keys are disabled if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { error = Untranslated("Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled."); - return WalletCreationStatus::CREATION_FAILED; + status = DatabaseStatus::FAILED_CREATE; + return nullptr; } // Make the wallet - std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, error, warnings, wallet_creation_flags); + std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), wallet_creation_flags, error, warnings); if (!wallet) { error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error; - return WalletCreationStatus::CREATION_FAILED; + status = DatabaseStatus::FAILED_CREATE; + return nullptr; } // Encrypt the wallet if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (!wallet->EncryptWallet(passphrase)) { error = Untranslated("Error: Wallet created but failed to encrypt."); - return WalletCreationStatus::ENCRYPTION_FAILED; + status = DatabaseStatus::FAILED_ENCRYPT; + return nullptr; } if (!create_blank) { // Unlock the wallet if (!wallet->Unlock(passphrase)) { error = Untranslated("Error: Wallet was encrypted but could not be unlocked"); - return WalletCreationStatus::ENCRYPTION_FAILED; + status = DatabaseStatus::FAILED_ENCRYPT; + return nullptr; } // Set a seed for the wallet @@ -241,7 +303,8 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) { if (!spk_man->SetupGeneration()) { error = Untranslated("Unable to generate initial keys"); - return WalletCreationStatus::CREATION_FAILED; + status = DatabaseStatus::FAILED_CREATE; + return nullptr; } } } @@ -253,8 +316,12 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& } AddWallet(wallet); wallet->postInitProcess(); - result = wallet; - return WalletCreationStatus::SUCCESS; + + // Write the wallet settings + UpdateWalletSetting(chain, name, load_on_start, warnings); + + status = DatabaseStatus::SUCCESS; + return wallet; } const uint256 CWalletTx::ABANDON_HASH(UINT256_ONE()); @@ -2286,6 +2353,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const { + AssertLockHeld(cs_wallet); const CTransaction* ptx = &tx; int n = output; while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { @@ -3175,25 +3243,6 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 return DBErrors::LOAD_OK; } -DBErrors CWallet::ZapWalletTx(std::list<CWalletTx>& vWtx) -{ - DBErrors nZapWalletTxRet = WalletBatch(*database,"cr+").ZapWalletTx(vWtx); - if (nZapWalletTxRet == DBErrors::NEED_REWRITE) - { - if (database->Rewrite("\x04pool")) - { - for (const auto& spk_man_pair : m_spk_managers) { - spk_man_pair.second->RewriteDB(); - } - } - } - - if (nZapWalletTxRet != DBErrors::LOAD_OK) - return nZapWalletTxRet; - - return DBErrors::LOAD_OK; -} - bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose) { bool fUpdated = false; @@ -3223,6 +3272,7 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s bool CWallet::DelAddressBook(const CTxDestination& address) { bool is_mine; + WalletBatch batch(*database); { LOCK(cs_wallet); // If we want to delete receiving addresses, we need to take care that DestData "used" (and possibly newer DestData) gets preserved (and the "deleted" address transformed into a change entry instead of actually being deleted) @@ -3236,7 +3286,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address) std::string strAddress = EncodeDestination(address); for (const std::pair<const std::string, std::string> &item : m_address_book[address].destdata) { - WalletBatch(*database).EraseDestData(strAddress, item.first); + batch.EraseDestData(strAddress, item.first); } m_address_book.erase(address); is_mine = IsMine(address) != ISMINE_NO; @@ -3244,8 +3294,8 @@ bool CWallet::DelAddressBook(const CTxDestination& address) NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED); - WalletBatch(*database).ErasePurpose(EncodeDestination(address)); - return WalletBatch(*database).EraseName(EncodeDestination(address)); + batch.ErasePurpose(EncodeDestination(address)); + return batch.EraseName(EncodeDestination(address)); } size_t CWallet::KeypoolCountExternalKeys() const @@ -3729,7 +3779,7 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const return values; } -bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error_string, std::vector<bilingual_str>& warnings) +std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string) { // Do some checking on wallet path. It should be either a: // @@ -3737,54 +3787,25 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b // 2. Path to an existing directory. // 3. Path to a symlink to a directory. // 4. For backwards compatibility, the name of a data file in -walletdir. - LOCK(cs_wallets); - const fs::path& wallet_path = location.GetPath(); + const fs::path& wallet_path = fs::absolute(name, GetWalletDir()); fs::file_type path_type = fs::symlink_status(wallet_path).type(); if (!(path_type == fs::file_not_found || path_type == fs::directory_file || (path_type == fs::symlink_file && fs::is_directory(wallet_path)) || - (path_type == fs::regular_file && fs::path(location.GetName()).filename() == location.GetName()))) { + (path_type == fs::regular_file && fs::path(name).filename() == name))) { error_string = Untranslated(strprintf( "Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and " "database/log.?????????? files can be stored, a location where such a directory could be created, " "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)", - location.GetName(), GetWalletDir())); - return false; - } - - // Make sure that the wallet path doesn't clash with an existing wallet path - if (IsWalletLoaded(wallet_path)) { - error_string = Untranslated(strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName())); - return false; - } - - // Keep same database environment instance across Verify/Recover calls below. - std::unique_ptr<WalletDatabase> database = CreateWalletDatabase(wallet_path); - - try { - return database->Verify(error_string); - } catch (const fs::filesystem_error& e) { - error_string = Untranslated(strprintf("Error loading wallet %s. %s", location.GetName(), fsbridge::get_filesystem_error_message(e))); - return false; + name, GetWalletDir())); + status = DatabaseStatus::FAILED_BAD_PATH; + return nullptr; } + return MakeDatabase(wallet_path, options, status, error_string); } -std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings, uint64_t wallet_creation_flags) +std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) { - const std::string walletFile = WalletDataFilePath(location.GetPath()).string(); - - // needed to restore wallet transaction meta data after -zapwallettxes - std::list<CWalletTx> vWtx; - - if (gArgs.GetBoolArg("-zapwallettxes", false)) { - chain.initMessage(_("Zapping all transactions from wallet...").translated); - - std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(&chain, location, CreateWalletDatabase(location.GetPath())); - DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); - if (nZapWalletRet != DBErrors::LOAD_OK) { - error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile); - return nullptr; - } - } + const std::string& walletFile = database->Filename(); chain.initMessage(_("Loading wallet...").translated); @@ -3792,7 +3813,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, bool fFirstRun = true; // 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(&chain, location, CreateWalletDatabase(location.GetPath())), ReleaseWallet); + std::shared_ptr<CWallet> walletInstance(new CWallet(&chain, name, std::move(database)), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { if (nLoadWalletRet == DBErrors::CORRUPT) { @@ -4062,30 +4083,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } walletInstance->chainStateFlushed(chain.getTipLocator()); walletInstance->database->IncrementUpdateCounter(); - - // Restore wallet transaction metadata after -zapwallettxes=1 - if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2") - { - WalletBatch batch(*walletInstance->database); - - for (const CWalletTx& wtxOld : vWtx) - { - uint256 hash = wtxOld.GetHash(); - std::map<uint256, CWalletTx>::iterator mi = walletInstance->mapWallet.find(hash); - if (mi != walletInstance->mapWallet.end()) - { - const CWalletTx* copyFrom = &wtxOld; - CWalletTx* copyTo = &mi->second; - copyTo->mapValue = copyFrom->mapValue; - copyTo->vOrderForm = copyFrom->vOrderForm; - copyTo->nTimeReceived = copyFrom->nTimeReceived; - copyTo->nTimeSmart = copyFrom->nTimeSmart; - copyTo->fFromMe = copyFrom->fFromMe; - copyTo->nOrderPos = copyFrom->nOrderPos; - batch.WriteTx(*copyTo); - } - } - } } { |