Merge #19619: Remove wallet.dat path handling from wallet.cpp, rpcwallet.cpp

7bf6dfbb48 wallet: Remove path checking code from bitcoin-wallet tool (Russell Yanofsky)
77d5bb72b8 wallet: Remove path checking code from createwallet RPC (Russell Yanofsky)
a987438e9d wallet: Remove path checking code from loadwallet RPC (Russell Yanofsky)
8b5e7297c0 refactor: Pass wallet database into CWallet::Create (Russell Yanofsky)
3c815cfe54 wallet: Remove Verify and IsLoaded methods (Russell Yanofsky)
0d94e60625 refactor: Use DatabaseStatus and DatabaseOptions types (Russell Yanofsky)
b5b414151a wallet: Add MakeDatabase function (Russell Yanofsky)
288b4ffb6b Remove WalletLocation class (Russell Yanofsky)

Pull request description:

  Get rid of file path handling in wallet application code and move it down to database layer.

  There is no change in behavior except for some changed error messages.

  Motivation for this change is to make code more understandable, but also to prepare for adding SQLite support in #19077 so SQLite implementation can be contained at the database layer and wallet loading code does not need to become more complicated.

ACKs for top commit:
  achow101:
    ACK 7bf6dfbb48
  meshcollider:
    Code re-review and functional test run ACK 7bf6dfbb48

Tree-SHA512: 23ad18324c9e8947f0cf88a3734c2e9fb25536b2cb4d552cf5d1a4ade320fbffb73bb2d1b3a99585c11630aa7092e0fcfc2dd4fe65b91e3a54161433a5cd13cb
This commit is contained in:
Samuel Dobson 2020-09-07 11:19:46 +12:00
commit 56d47e19ed
No known key found for this signature in database
GPG key ID: D300116E1C875A3D
30 changed files with 291 additions and 270 deletions

View file

@ -31,7 +31,7 @@ static void CoinSelection(benchmark::Bench& bench)
{
NodeContext node;
auto chain = interfaces::MakeChain(node);
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
wallet.SetupLegacyScriptPubKeyMan();
std::vector<std::unique_ptr<CWalletTx>> wtxs;
LOCK(wallet.cs_wallet);
@ -65,7 +65,7 @@ static void CoinSelection(benchmark::Bench& bench)
typedef std::set<CInputCoin> CoinSet;
static NodeContext testNode;
static auto testChain = interfaces::MakeChain(testNode);
static CWallet testWallet(testChain.get(), WalletLocation(), CreateDummyWalletDatabase());
static CWallet testWallet(testChain.get(), "", CreateDummyWalletDatabase());
std::vector<std::unique_ptr<CWalletTx>> wtxn;
// Copied from src/wallet/test/coinselector_tests.cpp

View file

@ -26,7 +26,7 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
NodeContext node;
std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node);
CWallet wallet{chain.get(), WalletLocation(), CreateMockWalletDatabase()};
CWallet wallet{chain.get(), "", CreateMockWalletDatabase()};
{
wallet.SetupLegacyScriptPubKeyMan();
bool first_run;

View file

@ -514,15 +514,22 @@ public:
void setMockTime(int64_t time) override { return SetMockTime(time); }
//! WalletClient methods
std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, WalletCreationStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) override
std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) override
{
std::shared_ptr<CWallet> wallet;
status = CreateWallet(*m_context.chain, passphrase, wallet_creation_flags, name, true /* load_on_start */, error, warnings, wallet);
return MakeWallet(std::move(wallet));
DatabaseOptions options;
DatabaseStatus status;
options.require_create = true;
options.create_flags = wallet_creation_flags;
options.create_passphrase = passphrase;
return MakeWallet(CreateWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings));
}
std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) override
{
return MakeWallet(LoadWallet(*m_context.chain, WalletLocation(name), true /* load_on_start */, error, warnings));
DatabaseOptions options;
DatabaseStatus status;
options.require_existing = true;
return MakeWallet(LoadWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings));
}
std::string getWalletDir() override
{

View file

@ -29,7 +29,6 @@ class CWallet;
enum class FeeReason;
enum class OutputType;
enum class TransactionError;
enum class WalletCreationStatus;
enum isminetype : unsigned int;
struct CRecipient;
struct PartiallySignedTransaction;
@ -311,7 +310,7 @@ class WalletClient : public ChainClient
{
public:
//! Create new wallet.
virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, WalletCreationStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
//! Load existing wallet.
virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;

View file

@ -61,7 +61,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
{
TestChain100Setup test;
node.setContext(&test.m_node);
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), WalletLocation(), CreateMockWalletDatabase());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
bool firstRun;
wallet->LoadWallet(firstRun);

View file

@ -139,7 +139,7 @@ void TestGUI(interfaces::Node& node)
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
}
node.setContext(&test.m_node);
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), WalletLocation(), CreateMockWalletDatabase());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
bool firstRun;
wallet->LoadWallet(firstRun);
{

View file

@ -249,10 +249,9 @@ void CreateWalletActivity::createWallet()
}
QTimer::singleShot(500, worker(), [this, name, flags] {
WalletCreationStatus status;
std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().createWallet(name, m_passphrase, flags, status, m_error_message, m_warning_message);
std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message);
if (status == WalletCreationStatus::SUCCESS) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
QTimer::singleShot(500, this, &CreateWalletActivity::finish);
});

View file

@ -52,18 +52,6 @@ bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
return memcmp(value, &rhs.value, sizeof(value)) == 0;
}
bool IsBDBWalletLoaded(const fs::path& wallet_path)
{
fs::path env_directory;
std::string database_filename;
SplitWalletPath(wallet_path, env_directory, database_filename);
LOCK(cs_db);
auto env = g_dbenvs.find(env_directory.string());
if (env == g_dbenvs.end()) return false;
auto database = env->second.lock();
return database && database->IsDatabaseLoaded(database_filename);
}
/**
* @param[in] wallet_path Path to wallet directory. Or (for backwards compatibility only) a path to a berkeley btree data file inside a wallet directory.
* @param[out] database_filename Filename of berkeley btree data file inside the wallet directory.
@ -371,7 +359,6 @@ void BerkeleyDatabase::Open(const char* pszMode)
if (ret != 0) {
throw std::runtime_error(strprintf("BerkeleyDatabase: Error %d, can't open database %s", ret, strFile));
}
m_file_path = (env->Directory() / strFile).string();
// Call CheckUniqueFileid on the containing BDB environment to
// avoid BDB data consistency bugs that happen when different data
@ -824,3 +811,35 @@ std::unique_ptr<DatabaseBatch> BerkeleyDatabase::MakeBatch(const char* mode, boo
{
return MakeUnique<BerkeleyBatch>(*this, mode, flush_on_close);
}
bool ExistsBerkeleyDatabase(const fs::path& path)
{
fs::path env_directory;
std::string data_filename;
SplitWalletPath(path, env_directory, data_filename);
return IsBerkeleyBtree(env_directory / data_filename);
}
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
{
std::unique_ptr<BerkeleyDatabase> db;
{
LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor
std::string data_filename;
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(path, data_filename);
if (env->m_databases.count(data_filename)) {
error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", (env->Directory() / data_filename).string()));
status = DatabaseStatus::FAILED_ALREADY_LOADED;
return nullptr;
}
db = MakeUnique<BerkeleyDatabase>(std::move(env), std::move(data_filename));
}
if (options.verify && !db->Verify(error)) {
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
}
status = DatabaseStatus::SUCCESS;
return db;
}

View file

@ -63,7 +63,6 @@ public:
bool IsMock() const { return fMockDb; }
bool IsInitialized() const { return fDbEnvInit; }
bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
fs::path Directory() const { return strPath; }
bool Open(bilingual_str& error);
@ -87,8 +86,8 @@ public:
/** Get BerkeleyEnvironment and database filename given a wallet path. */
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
/** Return whether a BDB wallet database is currently loaded. */
bool IsBDBWalletLoaded(const fs::path& wallet_path);
/** Check format of database file */
bool IsBerkeleyBtree(const fs::path& path);
class BerkeleyBatch;
@ -143,7 +142,10 @@ public:
void ReloadDbEnv() override;
/** Verifies the environment and database file */
bool Verify(bilingual_str& error) override;
bool Verify(bilingual_str& error);
/** Return path to main database filename */
std::string Filename() override { return (env->Directory() / strFile).string(); }
/**
* Pointer to shared database environment.
@ -224,4 +226,10 @@ public:
std::string BerkeleyDatabaseVersion();
//! Check if Berkeley database exists at specified path.
bool ExistsBerkeleyDatabase(const fs::path& path);
//! Return object giving access to Berkeley database at specified path.
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
#endif // BITCOIN_WALLET_BDB_H

View file

@ -23,11 +23,3 @@ void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::
database_filename = "wallet.dat";
}
}
fs::path WalletDataFilePath(const fs::path& wallet_path)
{
fs::path env_directory;
std::string database_filename;
SplitWalletPath(wallet_path, env_directory, database_filename);
return env_directory / database_filename;
}

View file

@ -9,6 +9,7 @@
#include <clientversion.h>
#include <fs.h>
#include <streams.h>
#include <support/allocators/secure.h>
#include <util/memory.h>
#include <atomic>
@ -17,8 +18,6 @@
struct bilingual_str;
/** Given a wallet directory path or legacy file path, return path to main data file in the wallet database. */
fs::path WalletDataFilePath(const fs::path& wallet_path);
void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename);
/** RAII class that provides access to a WalletDatabase */
@ -141,16 +140,14 @@ public:
virtual void ReloadDbEnv() = 0;
/** Return path to main database file for logs and error messages. */
virtual std::string Filename() = 0;
std::atomic<unsigned int> nUpdateCounter;
unsigned int nLastSeen;
unsigned int nLastFlushed;
int64_t nLastWalletUpdate;
/** Verifies the environment and database file */
virtual bool Verify(bilingual_str& error) = 0;
std::string m_file_path;
/** Make a DatabaseBatch connected to this database */
virtual std::unique_ptr<DatabaseBatch> MakeBatch(const char* mode = "r+", bool flush_on_close = true) = 0;
};
@ -191,8 +188,34 @@ public:
bool PeriodicFlush() override { return true; }
void IncrementUpdateCounter() override { ++nUpdateCounter; }
void ReloadDbEnv() override {}
bool Verify(bilingual_str& errorStr) override { return true; }
std::string Filename() override { return "dummy"; }
std::unique_ptr<DatabaseBatch> MakeBatch(const char* mode = "r+", bool flush_on_close = true) override { return MakeUnique<DummyBatch>(); }
};
enum class DatabaseFormat {
BERKELEY,
};
struct DatabaseOptions {
bool require_existing = false;
bool require_create = false;
uint64_t create_flags = 0;
SecureString create_passphrase;
bool verify = true;
};
enum class DatabaseStatus {
SUCCESS,
FAILED_BAD_PATH,
FAILED_BAD_FORMAT,
FAILED_ALREADY_LOADED,
FAILED_ALREADY_EXISTS,
FAILED_NOT_FOUND,
FAILED_CREATE,
FAILED_VERIFY,
FAILED_ENCRYPT,
};
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
#endif // BITCOIN_WALLET_DB_H

View file

@ -5,6 +5,7 @@
#include <wallet/load.h>
#include <fs.h>
#include <interfaces/chain.h>
#include <scheduler.h>
#include <util/string.h>
@ -44,18 +45,18 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal
std::set<fs::path> wallet_paths;
for (const auto& wallet_file : wallet_files) {
WalletLocation location(wallet_file);
const fs::path path = fs::absolute(wallet_file, GetWalletDir());
if (!wallet_paths.insert(location.GetPath()).second) {
if (!wallet_paths.insert(path).second) {
chain.initError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
return false;
}
DatabaseOptions options;
DatabaseStatus status;
options.verify = true;
bilingual_str error_string;
std::vector<bilingual_str> warnings;
bool verify_success = CWallet::Verify(chain, location, error_string, warnings);
if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
if (!verify_success) {
if (!MakeWalletDatabase(wallet_file, options, status, error_string)) {
chain.initError(error_string);
return false;
}
@ -67,10 +68,14 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal
bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files)
{
try {
for (const std::string& walletFile : wallet_files) {
for (const std::string& name : wallet_files) {
DatabaseOptions options;
DatabaseStatus status;
options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
bilingual_str error;
std::vector<bilingual_str> warnings;
std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile), error, warnings);
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(chain, name, std::move(database), options.create_flags, error, warnings) : nullptr;
if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
if (!pwallet) {
chain.initError(error);

View file

@ -2502,23 +2502,21 @@ static UniValue loadwallet(const JSONRPCRequest& request)
}.Check(request);
WalletContext& context = EnsureWalletContext(request.context);
WalletLocation location(request.params[0].get_str());
if (!location.Exists()) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found.");
} else if (fs::is_directory(location.GetPath())) {
// The given filename is a directory. Check that there's a wallet.dat file.
fs::path wallet_dat_file = location.GetPath() / "wallet.dat";
if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + location.GetName() + " does not contain a wallet.dat file.");
}
}
const std::string name(request.params[0].get_str());
DatabaseOptions options;
DatabaseStatus status;
options.require_existing = true;
bilingual_str error;
std::vector<bilingual_str> warnings;
Optional<bool> load_on_start = request.params[1].isNull() ? nullopt : Optional<bool>(request.params[1].get_bool());
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, location, load_on_start, error, warnings);
if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error.original);
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, name, load_on_start, options, status, error, warnings);
if (!wallet) {
// Map bad format to not found, since bad format is returned when the
// wallet directory exists, but doesn't contain a data file.
RPCErrorCode code = status == DatabaseStatus::FAILED_NOT_FOUND || status == DatabaseStatus::FAILED_BAD_FORMAT ? RPC_WALLET_NOT_FOUND : RPC_WALLET_ERROR;
throw JSONRPCError(code, error.original);
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
@ -2647,18 +2645,17 @@ static UniValue createwallet(const JSONRPCRequest& request)
warnings.emplace_back(Untranslated("Wallet is an experimental descriptor wallet"));
}
DatabaseOptions options;
DatabaseStatus status;
options.require_create = true;
options.create_flags = flags;
options.create_passphrase = passphrase;
bilingual_str error;
std::shared_ptr<CWallet> wallet;
Optional<bool> load_on_start = request.params[6].isNull() ? nullopt : Optional<bool>(request.params[6].get_bool());
WalletCreationStatus status = CreateWallet(*context.chain, passphrase, flags, request.params[0].get_str(), load_on_start, error, warnings, wallet);
switch (status) {
case WalletCreationStatus::CREATION_FAILED:
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
case WalletCreationStatus::ENCRYPTION_FAILED:
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error.original);
case WalletCreationStatus::SUCCESS:
break;
// no default case, so the compiler can warn about missing cases
std::shared_ptr<CWallet> wallet = CreateWallet(*context.chain, request.params[0].get_str(), load_on_start, options, status, error, warnings);
if (!wallet) {
RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
throw JSONRPCError(code, error.original);
}
UniValue obj(UniValue::VOBJ);

View file

@ -23,6 +23,13 @@ static bool KeyFilter(const std::string& type)
bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
DatabaseOptions options;
DatabaseStatus status;
options.require_existing = true;
options.verify = false;
std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
if (!database) return false;
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
@ -123,7 +130,7 @@ bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::v
}
DbTxn* ptxn = env->TxnBegin();
CWallet dummyWallet(nullptr, WalletLocation(), CreateDummyWalletDatabase());
CWallet dummyWallet(nullptr, "", CreateDummyWalletDatabase());
for (KeyValPair& row : salvagedData)
{
/* Filter for only private key type KV pairs to be added to the salvaged wallet */

View file

@ -29,7 +29,7 @@ typedef std::set<CInputCoin> CoinSet;
static std::vector<COutput> vCoins;
static NodeContext testNode;
static auto testChain = interfaces::MakeChain(testNode);
static CWallet testWallet(testChain.get(), WalletLocation(), CreateDummyWalletDatabase());
static CWallet testWallet(testChain.get(), "", CreateDummyWalletDatabase());
static CAmount balance = 0;
CoinEligibilityFilter filter_standard(1, 6, 0);
@ -283,7 +283,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
// Make sure that can use BnB when there are preset inputs
empty_wallet();
{
std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), CreateMockWalletDatabase());
std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_chain.get(), "", CreateMockWalletDatabase());
bool firstRun;
wallet->LoadWallet(firstRun);
wallet->SetupLegacyScriptPubKeyMan();

View file

@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PK compressed
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForRawPubKey(pubkeys[0]);
@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PK uncompressed
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey);
@ -69,7 +69,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PKH compressed
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForDestination(PKHash(pubkeys[0]));
@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PKH uncompressed
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForDestination(PKHash(uncompressedPubkey));
@ -103,7 +103,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2SH
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2SH inside P2SH (invalid)
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2SH inside P2WSH (invalid)
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@ -163,7 +163,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH inside P2WSH (invalid)
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@ -179,7 +179,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2WSH inside P2WSH (invalid)
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@ -197,7 +197,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH compressed
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@ -212,7 +212,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH uncompressed
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
@ -231,7 +231,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// scriptPubKey multisig
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@ -262,7 +262,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2SH multisig
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
@ -283,7 +283,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig with compressed keys
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@ -309,7 +309,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig with uncompressed key
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
@ -335,7 +335,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig wrapped in P2SH
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@ -362,7 +362,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// OP_RETURN
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@ -376,7 +376,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// witness unspendable
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@ -390,7 +390,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// witness unknown
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@ -404,7 +404,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Nonstandard
{
CWallet keystore(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));

View file

@ -19,7 +19,7 @@ BOOST_AUTO_TEST_CASE(CanProvide)
// Set up wallet and keyman variables.
NodeContext node;
std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node);
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan();
// Make a 1 of 2 multisig script

View file

@ -6,7 +6,7 @@
WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
: TestingSetup(chainName),
m_wallet(m_chain.get(), WalletLocation(), CreateMockWalletDatabase())
m_wallet(m_chain.get(), "", CreateMockWalletDatabase())
{
bool fFirstRun;
m_wallet.LoadWallet(fFirstRun);

View file

@ -37,9 +37,12 @@ BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain& chain)
{
DatabaseOptions options;
DatabaseStatus status;
bilingual_str error;
std::vector<bilingual_str> warnings;
auto wallet = CWallet::CreateWalletFromFile(chain, WalletLocation(""), error, warnings);
auto database = MakeWalletDatabase("", options, status, error);
auto wallet = CWallet::Create(chain, "", std::move(database), options.create_flags, error, warnings);
wallet->postInitProcess();
return wallet;
}
@ -85,7 +88,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions fails to read an unknown start block.
{
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@ -104,7 +107,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@ -130,7 +133,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions only picks transactions in the new block
// file.
{
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@ -155,7 +158,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions scans no blocks.
{
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@ -194,7 +197,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
// before the missing block, and success for a key whose creation time is
// after.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
AddWallet(wallet);
@ -259,7 +262,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Import key into wallet and call dumpwallet to create backup file.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
{
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
@ -281,7 +284,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
// were scanned, and no prior blocks were scanned.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
LOCK(wallet->cs_wallet);
wallet->SetupLegacyScriptPubKeyMan();
@ -317,7 +320,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
NodeContext node;
auto chain = interfaces::MakeChain(node);
CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
CWalletTx wtx(&wallet, m_coinbase_txns.back());
@ -492,7 +495,7 @@ public:
ListCoinsTestingSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), CreateMockWalletDatabase());
wallet = MakeUnique<CWallet>(m_chain.get(), "", CreateMockWalletDatabase());
{
LOCK2(wallet->cs_wallet, ::cs_main);
wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@ -610,7 +613,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
{
NodeContext node;
auto chain = interfaces::MakeChain(node);
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), CreateDummyWalletDatabase());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
wallet->SetMinVersion(FEATURE_LATEST);
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);

View file

@ -200,15 +200,16 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
}
namespace {
std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const WalletLocation& location, Optional<bool> load_on_start, 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;
return nullptr;
@ -217,7 +218,7 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const Wall
wallet->postInitProcess();
// Write the wallet setting
UpdateWalletSetting(chain, location.GetName(), load_on_start, warnings);
UpdateWalletSetting(chain, name, load_on_start, warnings);
return wallet;
} catch (const std::runtime_error& e) {
@ -227,20 +228,23 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const Wall
}
} // namespace
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, Optional<bool> load_on_start, 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.");
return nullptr;
}
auto wallet = LoadWalletInternal(chain, location, load_on_start, 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, Optional<bool> load_on_start, 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);
@ -249,43 +253,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
@ -297,7 +300,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;
}
}
}
@ -309,12 +313,12 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString&
}
AddWallet(wallet);
wallet->postInitProcess();
result = wallet;
// Write the wallet settings
UpdateWalletSetting(chain, name, load_on_start, warnings);
return WalletCreationStatus::SUCCESS;
status = DatabaseStatus::SUCCESS;
return wallet;
}
const uint256 CWalletTx::ABANDON_HASH(UINT256_ONE());
@ -3770,7 +3774,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:
//
@ -3778,40 +3782,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();
const std::string& walletFile = database->Filename();
chain.initMessage(_("Loading wallet...").translated);
@ -3819,7 +3808,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) {

View file

@ -54,16 +54,10 @@ bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on
bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start);
std::vector<std::shared_ptr<CWallet>> GetWallets();
std::shared_ptr<CWallet> GetWallet(const std::string& name);
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, Optional<bool> load_on_start, 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);
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);
std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet);
enum class WalletCreationStatus {
SUCCESS,
CREATION_FAILED,
ENCRYPTION_FAILED
};
WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, Optional<bool> load_on_start, bilingual_str& error, std::vector<bilingual_str>& warnings, std::shared_ptr<CWallet>& result);
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
//! -paytxfee default
constexpr CAmount DEFAULT_PAY_TX_FEE = 0;
@ -700,8 +694,8 @@ private:
/** Interface for accessing chain state. */
interfaces::Chain* m_chain;
/** Wallet location which includes wallet name (see WalletLocation). */
WalletLocation m_location;
/** Wallet name: relative directory name or "" for default wallet. */
std::string m_name;
/** Internal database handle. */
std::unique_ptr<WalletDatabase> database;
@ -755,20 +749,18 @@ public:
bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet,
const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
const WalletLocation& GetLocation() const { return m_location; }
/** Get a name for this wallet for logging/debugging purposes.
*/
const std::string& GetName() const { return m_location.GetName(); }
const std::string& GetName() const { return m_name; }
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID = 0;
/** Construct wallet with specified name and database implementation. */
CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database)
CWallet(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database)
: m_chain(chain),
m_location(location),
m_name(name),
database(std::move(database))
{
}
@ -1153,11 +1145,8 @@ public:
/** Mark a transaction as replaced by another transaction (e.g., BIP 125). */
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
//! Verify wallet naming and perform salvage on the wallet if required
static bool Verify(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error_string, std::vector<bilingual_str>& warnings);
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
static std::shared_ptr<CWallet> CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings, uint64_t wallet_creation_flags = 0);
static std::shared_ptr<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);
/**
* Wallet post-init setup

View file

@ -13,6 +13,8 @@
#include <util/bip32.h>
#include <util/system.h>
#include <util/time.h>
#include <util/translation.h>
#include <wallet/bdb.h>
#include <wallet/wallet.h>
#include <atomic>
@ -993,16 +995,41 @@ bool WalletBatch::TxnAbort()
return m_batch->TxnAbort();
}
bool IsWalletLoaded(const fs::path& wallet_path)
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
{
return IsBDBWalletLoaded(wallet_path);
}
bool exists;
try {
exists = fs::symlink_status(path).type() != fs::file_not_found;
} catch (const fs::filesystem_error& e) {
error = Untranslated(strprintf("Failed to access database path '%s': %s", path.string(), fsbridge::get_filesystem_error_message(e)));
status = DatabaseStatus::FAILED_BAD_PATH;
return nullptr;
}
/** Return object for accessing database at specified path. */
std::unique_ptr<WalletDatabase> CreateWalletDatabase(const fs::path& path)
{
std::string filename;
return MakeUnique<BerkeleyDatabase>(GetWalletEnv(path, filename), std::move(filename));
Optional<DatabaseFormat> format;
if (exists) {
if (ExistsBerkeleyDatabase(path)) {
format = DatabaseFormat::BERKELEY;
}
} else if (options.require_existing) {
error = Untranslated(strprintf("Failed to load database path '%s'. Path does not exist.", path.string()));
status = DatabaseStatus::FAILED_NOT_FOUND;
return nullptr;
}
if (!format && options.require_existing) {
error = Untranslated(strprintf("Failed to load database path '%s'. Data is not in recognized format.", path.string()));
status = DatabaseStatus::FAILED_BAD_FORMAT;
return nullptr;
}
if (format && options.require_create) {
error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", path.string()));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
}
return MakeBerkeleyDatabase(path, options, status, error);
}
/** Return object for accessing dummy database with no read/write capabilities. */

View file

@ -285,12 +285,6 @@ using KeyFilterFn = std::function<bool(const std::string&)>;
//! Unserialize a given Key-Value pair and load it into the wallet
bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr, const KeyFilterFn& filter_fn = nullptr);
/** Return whether a wallet database is currently loaded. */
bool IsWalletLoaded(const fs::path& wallet_path);
/** Return object for accessing database at specified path. */
std::unique_ptr<WalletDatabase> CreateWalletDatabase(const fs::path& path);
/** Return object for accessing dummy database with no read/write capabilities. */
std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase();

View file

@ -21,21 +21,9 @@ static void WalletToolReleaseWallet(CWallet* wallet)
delete wallet;
}
static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::path& path)
static void WalletCreate(CWallet* wallet_instance)
{
if (fs::exists(path)) {
tfm::format(std::cerr, "Error: File exists already\n");
return nullptr;
}
// dummy chain interface
std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), CreateWalletDatabase(path)), WalletToolReleaseWallet);
LOCK(wallet_instance->cs_wallet);
bool first_run = true;
DBErrors load_wallet_ret = wallet_instance->LoadWallet(first_run);
if (load_wallet_ret != DBErrors::LOAD_OK) {
tfm::format(std::cerr, "Error creating %s", name);
return nullptr;
}
wallet_instance->SetMinVersion(FEATURE_HD_SPLIT);
@ -46,18 +34,26 @@ static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::
tfm::format(std::cout, "Topping up keypool...\n");
wallet_instance->TopUpKeyPool();
return wallet_instance;
}
static std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::path& path)
static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, bool create)
{
if (!fs::exists(path)) {
tfm::format(std::cerr, "Error: Wallet files does not exist\n");
DatabaseOptions options;
DatabaseStatus status;
if (create) {
options.require_create = true;
} else {
options.require_existing = true;
}
bilingual_str error;
std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
if (!database) {
tfm::format(std::cerr, "%s\n", error.original);
return nullptr;
}
// dummy chain interface
std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), CreateWalletDatabase(path)), WalletToolReleaseWallet);
std::shared_ptr<CWallet> wallet_instance{new CWallet(nullptr /* chain */, name, std::move(database)), WalletToolReleaseWallet};
DBErrors load_wallet_ret;
try {
bool first_run;
@ -89,6 +85,8 @@ static std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::pa
}
}
if (create) WalletCreate(wallet_instance.get());
return wallet_instance;
}
@ -109,19 +107,14 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
fs::path path = fs::absolute(name, GetWalletDir());
if (command == "create") {
std::shared_ptr<CWallet> wallet_instance = CreateWallet(name, path);
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ true);
if (wallet_instance) {
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();
}
} else if (command == "info" || command == "salvage") {
if (!fs::exists(path)) {
tfm::format(std::cerr, "Error: no wallet file at %s\n", name);
return false;
}
if (command == "info") {
std::shared_ptr<CWallet> wallet_instance = LoadWallet(name, path);
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ false);
if (!wallet_instance) return false;
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();

View file

@ -9,8 +9,6 @@
namespace WalletTool {
std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::path& path);
std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::path& path);
void WalletShowInfo(CWallet* wallet_instance);
bool ExecuteWalletToolFunc(const std::string& command, const std::string& file);

View file

@ -29,7 +29,7 @@ fs::path GetWalletDir()
return path;
}
static bool IsBerkeleyBtree(const fs::path& path)
bool IsBerkeleyBtree(const fs::path& path)
{
if (!fs::exists(path)) return false;
@ -91,19 +91,3 @@ std::vector<fs::path> ListWalletDir()
return paths;
}
WalletLocation::WalletLocation(const std::string& name)
: m_name(name)
, m_path(fs::absolute(name, GetWalletDir()))
{
}
bool WalletLocation::Exists() const
{
fs::path path = m_path;
// For the default wallet, check specifically for the wallet.dat file
if (m_name.empty()) {
path = fs::absolute("wallet.dat", m_path);
}
return fs::symlink_status(path).type() != fs::file_not_found;
}

View file

@ -67,26 +67,6 @@ fs::path GetWalletDir();
//! Get wallets in wallet directory.
std::vector<fs::path> ListWalletDir();
//! The WalletLocation class provides wallet information.
class WalletLocation final
{
std::string m_name;
fs::path m_path;
public:
explicit WalletLocation() {}
explicit WalletLocation(const std::string& name);
//! Get wallet name.
const std::string& GetName() const { return m_name; }
//! Get wallet absolute path.
const fs::path& GetPath() const { return m_path; }
//! Return whether the wallet exists.
bool Exists() const;
};
/** Descriptor with some wallet metadata */
class WalletDescriptor
{

View file

@ -129,7 +129,8 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
try:
node1.loadwallet('wmulti')
except JSONRPCException as e:
if e.error['code'] == -18 and 'Wallet wmulti not found' in e.error['message']:
path = os.path.join(self.options.tmpdir, "node1", "regtest", "wallets", "wmulti")
if e.error['code'] == -18 and "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path) in e.error['message']:
node1.createwallet(wallet_name='wmulti', disable_private_keys=True)
else:
raise

View file

@ -70,12 +70,14 @@ class ToolWalletTest(BitcoinTestFramework):
self.assert_raises_tool_error('Invalid command: help', 'help')
self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create')
self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')
locked_dir = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets")
self.assert_raises_tool_error(
'Error loading wallet.dat. Is wallet being used by another process?',
'Error initializing wallet database environment "{}"!'.format(locked_dir),
'-wallet=wallet.dat',
'info',
)
self.assert_raises_tool_error('Error: no wallet file at nonexistent.dat', '-wallet=nonexistent.dat', 'info')
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "nonexistent.dat")
self.assert_raises_tool_error("Failed to load database path '{}'. Path does not exist.".format(path), '-wallet=nonexistent.dat', 'info')
def test_tool_wallet_info(self):
# Stop the node to close the wallet to call the info command.

View file

@ -244,13 +244,16 @@ class MultiWalletTest(BitcoinTestFramework):
assert_equal(set(self.nodes[0].listwallets()), set(wallet_names))
# Fail to load if wallet doesn't exist
assert_raises_rpc_error(-18, 'Wallet wallets not found.', self.nodes[0].loadwallet, 'wallets')
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallets")
assert_raises_rpc_error(-18, "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path), self.nodes[0].loadwallet, 'wallets')
# Fail to load duplicate wallets
assert_raises_rpc_error(-4, 'Wallet file verification failed. Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0])
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "w1", "wallet.dat")
assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0])
# Fail to load duplicate wallets by different ways (directory and filepath)
assert_raises_rpc_error(-4, "Wallet file verification failed. Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat')
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallet.dat")
assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat')
# Fail to load if one wallet is a copy of another
assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
@ -264,12 +267,14 @@ class MultiWalletTest(BitcoinTestFramework):
# Fail to load if a directory is specified that doesn't contain a wallet
os.mkdir(wallet_dir('empty_wallet_dir'))
assert_raises_rpc_error(-18, "Directory empty_wallet_dir does not contain a wallet.dat file", self.nodes[0].loadwallet, 'empty_wallet_dir')
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "empty_wallet_dir")
assert_raises_rpc_error(-18, "Wallet file verification failed. Failed to load database path '{}'. Data is not in recognized format.".format(path), self.nodes[0].loadwallet, 'empty_wallet_dir')
self.log.info("Test dynamic wallet creation.")
# Fail to create a wallet if it already exists.
assert_raises_rpc_error(-4, "Wallet w2 already exists.", self.nodes[0].createwallet, 'w2')
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "w2")
assert_raises_rpc_error(-4, "Failed to create database path '{}'. Database already exists.".format(path), self.nodes[0].createwallet, 'w2')
# Successfully create a wallet with a new name
loadwallet_name = self.nodes[0].createwallet('w9')