diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -634,10 +634,15 @@ nMinutes = 1; } - env->dbenv->txn_checkpoint( - nMinutes ? gArgs.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 - : 0, - nMinutes, 0); + // env is nullptr for dummy databases (i.e. in tests). Don't actually flush + // if env is nullptr so we don't segfault + if (env) { + env->dbenv->txn_checkpoint( + nMinutes + ? gArgs.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 + : 0, + nMinutes, 0); + } } void BerkeleyDatabase::IncrementUpdateCounter() { diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1519,64 +1519,24 @@ // All good, time to import pwallet->MarkDirty(); - for (const auto &entry : import_data.import_scripts) { - if (!pwallet->HaveCScript(CScriptID(entry)) && - !pwallet->AddCScript(entry)) { - throw JSONRPCError(RPC_WALLET_ERROR, - "Error adding script to wallet"); - } - } - for (const auto &entry : privkey_map) { - const CKey &key = entry.second; - CPubKey pubkey = key.GetPubKey(); - const CKeyID &id = entry.first; - assert(key.VerifyPubKey(pubkey)); - pwallet->mapKeyMetadata[id].nCreateTime = timestamp; - // If the private key is not present in the wallet, insert it. - if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) { - throw JSONRPCError(RPC_WALLET_ERROR, - "Error adding key to wallet"); - } - pwallet->UpdateTimeFirstKey(timestamp); + if (!pwallet->ImportScripts(import_data.import_scripts)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error adding script to wallet"); } - for (const auto &entry : import_data.key_origins) { - pwallet->AddKeyOrigin(entry.second.first, entry.second.second); + if (!pwallet->ImportPrivKeys(privkey_map, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } - for (const CKeyID &id : ordered_pubkeys) { - auto entry = pubkey_map.find(id); - if (entry == pubkey_map.end()) { - continue; - } - const CPubKey &pubkey = entry->second; - CPubKey temp; - if (!pwallet->GetPubKey(id, temp) && - !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), - timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, - "Error adding address to wallet"); - } - pwallet->mapKeyMetadata[id].nCreateTime = timestamp; - - // Add to keypool only works with pubkeys - if (add_keypool) { - pwallet->AddKeypoolPubkey(pubkey, internal); - } + if (!pwallet->ImportPubKeys(ordered_pubkeys, pubkey_map, + import_data.key_origins, add_keypool, + internal, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error adding address to wallet"); } - - for (const CScript &script : script_pub_keys) { - if (!have_solving_data || !::IsMine(*pwallet, script)) { - // Always call AddWatchOnly for non-solvable watch-only, so that - // watch timestamp gets updated - if (!pwallet->AddWatchOnly(script, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, - "Error adding address to wallet"); - } - } - CTxDestination dest; - ExtractDestination(script, dest); - if (!internal && IsValidDestination(dest)) { - pwallet->SetAddressBook(dest, label, "receive"); - } + if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, + have_solving_data, internal, + timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error adding address to wallet"); } result.pushKV("success", UniValue(true)); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -863,6 +863,35 @@ */ bool AddWatchOnly(const CScript &dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest) + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + /** Add a KeyOriginInfo to the wallet */ + bool AddKeyOriginWithDB(WalletBatch &batch, const CPubKey &pubkey, + const KeyOriginInfo &info); + + //! Adds a key to the store, and saves it to disk. + bool AddKeyPubKeyWithDB(WalletBatch &batch, const CKey &key, + const CPubKey &pubkey) + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + //! Adds a watch-only address to the store, and saves it to disk. + bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest, + int64_t create_time) + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + void AddKeypoolPubkeyWithDB(const CPubKey &pubkey, const bool internal, + WalletBatch &batch); + + bool SetAddressBookWithDB(WalletBatch &batch, const CTxDestination &address, + const std::string &strName, + const std::string &strPurpose); + + //! Adds a script to the store and saves it to disk + bool AddCScriptWithDB(WalletBatch &batch, const CScript &script); + + //! Unsets a wallet flag and saves it to disk + void UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag); /** Interface for accessing chain state. */ interfaces::Chain *m_chain; @@ -928,9 +957,6 @@ // Map from Script ID to key metadata (for watch-only keys). std::map m_script_metadata GUARDED_BY(cs_wallet); - bool WriteKeyMetadata(const CKeyMetadata &meta, const CPubKey &pubkey, - bool overwrite); - typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID = 0; @@ -1058,9 +1084,6 @@ //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool AddKeyPubKeyWithDB(WalletBatch &batch, const CKey &key, - const CPubKey &pubkey) - EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, without saving it to disk (used by LoadWallet) bool LoadKey(const CKey &key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); @@ -1242,6 +1265,23 @@ bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const; + bool ImportScripts(const std::set scripts) + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportPrivKeys(const std::map &privkey_map, + const int64_t timestamp) + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportPubKeys( + const std::vector &ordered_pubkeys, + const std::map &pubkey_map, + const std::map> &key_origins, + const bool add_keypool, const bool internal, const int64_t timestamp) + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportScriptPubKeys(const std::string &label, + const std::set &script_pub_keys, + const bool have_solving_data, const bool internal, + const int64_t timestamp) + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE}; bool m_spend_zero_conf_change{DEFAULT_SPEND_ZEROCONF_CHANGE}; // will be defined via chainparams @@ -1265,9 +1305,6 @@ bool NewKeyPool(); size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); - void AddKeypoolPubkey(const CPubKey &pubkey, const bool internal); - void AddKeypoolPubkeyWithDB(const CPubKey &pubkey, const bool internal, - WalletBatch &batch); /** * Reserves a key from the keypool and sets nIndex to its index @@ -1552,11 +1589,6 @@ * Implement lookup of key origin information through wallet key metadata. */ bool GetKeyOrigin(const CKeyID &keyid, KeyOriginInfo &info) const override; - - /** - * Add a KeyOriginInfo to the wallet - */ - bool AddKeyOrigin(const CPubKey &pubkey, const KeyOriginInfo &info); }; /** diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -396,7 +396,7 @@ mapKeyMetadata[pubkey.GetID()]); } - UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); return true; } @@ -436,13 +436,6 @@ m_script_metadata[script_id] = meta; } -// Writes a keymetadata for a public key. overwrite specifies whether to -// overwrite an existing metadata for that key if there exists one. -bool CWallet::WriteKeyMetadata(const CKeyMetadata &meta, const CPubKey &pubkey, - const bool overwrite) { - return WalletBatch(*database).WriteKeyMetadata(meta, pubkey, overwrite); -} - void CWallet::UpgradeKeyMetadata() { // mapKeyMetadata AssertLockHeld(cs_wallet); @@ -451,7 +444,6 @@ } auto batch = std::make_unique(*database); - size_t cnt = 0; for (auto &meta_pair : mapKeyMetadata) { CKeyMetadata &meta = meta_pair.second; // If the hdKeypath is "s", that's the seed and it doesn't have a key @@ -478,11 +470,6 @@ CPubKey pubkey; if (GetPubKey(meta_pair.first, pubkey)) { batch->WriteKeyMetadata(meta, pubkey, true); - if (++cnt % 1000 == 0) { - // avoid creating overlarge in-memory batches in case the - // wallet contains large amounts of keys - batch.reset(new WalletBatch(*database)); - } } } } @@ -513,12 +500,17 @@ } bool CWallet::AddCScript(const CScript &redeemScript) { + WalletBatch batch(*database); + return AddCScriptWithDB(batch, redeemScript); +} + +bool CWallet::AddCScriptWithDB(WalletBatch &batch, + const CScript &redeemScript) { if (!CCryptoKeyStore::AddCScript(redeemScript)) { return false; } - if (WalletBatch(*database).WriteCScript(Hash160(redeemScript), - redeemScript)) { - UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { + UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); return true; } return false; @@ -544,7 +536,7 @@ return CCryptoKeyStore::AddCScript(redeemScript); } -bool CWallet::AddWatchOnly(const CScript &dest) { +bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest) { if (!CCryptoKeyStore::AddWatchOnly(dest)) { return false; } @@ -552,13 +544,24 @@ const CKeyMetadata &meta = m_script_metadata[CScriptID(dest)]; UpdateTimeFirstKey(meta.nCreateTime); NotifyWatchonlyChanged(true); - if (WalletBatch(*database).WriteWatchOnly(dest, meta)) { - UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + if (batch.WriteWatchOnly(dest, meta)) { + UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); return true; } return false; } +bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest, + int64_t create_time) { + m_script_metadata[CScriptID(dest)].nCreateTime = create_time; + return AddWatchOnlyWithDB(batch, dest); +} + +bool CWallet::AddWatchOnly(const CScript &dest) { + WalletBatch batch(*database); + return AddWatchOnlyWithDB(batch, dest); +} + bool CWallet::AddWatchOnly(const CScript &dest, int64_t nCreateTime) { m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime; return AddWatchOnly(dest); @@ -1703,9 +1706,14 @@ } void CWallet::UnsetWalletFlag(uint64_t flag) { + WalletBatch batch(*database); + UnsetWalletFlagWithDB(batch, flag); +} + +void CWallet::UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag) { LOCK(cs_wallet); m_wallet_flags &= ~flag; - if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) { + if (!batch.WriteWalletFlags(m_wallet_flags)) { throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } @@ -1772,6 +1780,89 @@ return true; } +bool CWallet::ImportScripts(const std::set scripts) { + WalletBatch batch(*database); + for (const auto &entry : scripts) { + if (!HaveCScript(CScriptID(entry)) && !AddCScriptWithDB(batch, entry)) { + return false; + } + } + return true; +} + +bool CWallet::ImportPrivKeys(const std::map &privkey_map, + const int64_t timestamp) { + WalletBatch batch(*database); + for (const auto &entry : privkey_map) { + const CKey &key = entry.second; + CPubKey pubkey = key.GetPubKey(); + const CKeyID &id = entry.first; + assert(key.VerifyPubKey(pubkey)); + mapKeyMetadata[id].nCreateTime = timestamp; + // If the private key is not present in the wallet, insert it. + if (!HaveKey(id) && !AddKeyPubKeyWithDB(batch, key, pubkey)) { + return false; + } + UpdateTimeFirstKey(timestamp); + } + return true; +} + +bool CWallet::ImportPubKeys( + const std::vector &ordered_pubkeys, + const std::map &pubkey_map, + const std::map> &key_origins, + const bool add_keypool, const bool internal, const int64_t timestamp) { + WalletBatch batch(*database); + for (const auto &entry : key_origins) { + AddKeyOriginWithDB(batch, entry.second.first, entry.second.second); + } + for (const CKeyID &id : ordered_pubkeys) { + auto entry = pubkey_map.find(id); + if (entry == pubkey_map.end()) { + continue; + } + const CPubKey &pubkey = entry->second; + CPubKey temp; + if (!GetPubKey(id, temp) && + !AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), + timestamp)) { + return false; + } + mapKeyMetadata[id].nCreateTime = timestamp; + + // Add to keypool only works with pubkeys + if (add_keypool) { + AddKeypoolPubkeyWithDB(pubkey, internal, batch); + NotifyCanGetAddressesChanged(); + } + } + return true; +} + +bool CWallet::ImportScriptPubKeys(const std::string &label, + const std::set &script_pub_keys, + const bool have_solving_data, + const bool internal, + const int64_t timestamp) { + WalletBatch batch(*database); + for (const CScript &script : script_pub_keys) { + if (!have_solving_data || !::IsMine(*this, script)) { + // Always call AddWatchOnly for non-solvable watch-only, so that + // watch timestamp gets updated + if (!AddWatchOnlyWithDB(batch, script, timestamp)) { + return false; + } + } + CTxDestination dest; + ExtractDestination(script, dest); + if (!internal && IsValidDestination(dest)) { + SetAddressBookWithDB(batch, dest, label, "receive"); + } + } + return true; +} + int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) { std::vector txouts; @@ -3541,9 +3632,10 @@ return DBErrors::LOAD_OK; } -bool CWallet::SetAddressBook(const CTxDestination &address, - const std::string &strName, - const std::string &strPurpose) { +bool CWallet::SetAddressBookWithDB(WalletBatch &batch, + const CTxDestination &address, + const std::string &strName, + const std::string &strPurpose) { bool fUpdated = false; { LOCK(cs_wallet); @@ -3560,11 +3652,17 @@ NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO, strPurpose, (fUpdated ? CT_UPDATED : CT_NEW)); - if (!strPurpose.empty() && - !WalletBatch(*database).WritePurpose(address, strPurpose)) { + if (!strPurpose.empty() && !batch.WritePurpose(address, strPurpose)) { return false; } - return WalletBatch(*database).WriteName(address, strName); + return batch.WriteName(address, strName); +} + +bool CWallet::SetAddressBook(const CTxDestination &address, + const std::string &strName, + const std::string &strPurpose) { + WalletBatch batch(*database); + return SetAddressBookWithDB(batch, address, strName, strPurpose); } bool CWallet::DelAddressBook(const CTxDestination &address) { @@ -3720,12 +3818,6 @@ return true; } -void CWallet::AddKeypoolPubkey(const CPubKey &pubkey, const bool internal) { - WalletBatch batch(*database); - AddKeypoolPubkeyWithDB(pubkey, internal, batch); - NotifyCanGetAddressesChanged(); -} - void CWallet::AddKeypoolPubkeyWithDB(const CPubKey &pubkey, const bool internal, WalletBatch &batch) { LOCK(cs_wallet); @@ -5007,12 +5099,13 @@ return true; } -bool CWallet::AddKeyOrigin(const CPubKey &pubkey, const KeyOriginInfo &info) { +bool CWallet::AddKeyOriginWithDB(WalletBatch &batch, const CPubKey &pubkey, + const KeyOriginInfo &info) { LOCK(cs_wallet); std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint); mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path; mapKeyMetadata[pubkey.GetID()].has_key_origin = true; mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path); - return WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); + return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -143,9 +143,12 @@ /** * Access to the wallet database. - * This represents a single transaction at the database. It will be committed - * when the object goes out of scope. Optionally (on by default) it will flush - * to disk as well. + * Opens the database and provides read and write access to it. Each read and + * write is its own transaction. Multiple operation transactions can be started + * using TxnBegin() and committed using TxnCommit() Otherwise the transaction + * will be committed when the object goes out of scope. Optionally (on by + * default) it will flush to disk on close. Every 1000 writes will automatically + * trigger a flush to disk. */ class WalletBatch { private: @@ -155,6 +158,9 @@ return false; } m_database.IncrementUpdateCounter(); + if (m_database.nUpdateCounter % 1000 == 0) { + m_batch.Flush(); + } return true; } @@ -163,6 +169,9 @@ return false; } m_database.IncrementUpdateCounter(); + if (m_database.nUpdateCounter % 1000 == 0) { + m_batch.Flush(); + } return true; }