partial bitcoin#19077: Add sqlite as an alternative wallet database and use it for new descriptor wallets

This commit is contained in:
Kittywhiskers Van Gogh 2023-02-04 18:26:20 +00:00 committed by PastaPastaPasta
parent 0fa0991e6e
commit f42288c984
24 changed files with 873 additions and 45 deletions

View File

@ -1279,6 +1279,9 @@ if test x$enable_wallet != xno; then
if test x$suppress_external_warnings != xno ; then
BDB_CPPFLAGS=SUPPRESS_WARNINGS($BDB_CPPFLAGS)
fi
dnl Check for sqlite3
PKG_CHECK_MODULES([SQLITE], [sqlite3 >= 3.7.17], , [AC_MSG_ERROR([sqlite3 not found.])])
fi
dnl Check for libminiupnpc (optional)
@ -1714,6 +1717,7 @@ AC_SUBST(LIBTOOL_APP_LDFLAGS)
AC_SUBST(USE_UPNP)
AC_SUBST(USE_QRCODE)
AC_SUBST(BOOST_LIBS)
AC_SUBST(SQLITE_LIBS)
AC_SUBST(TESTDEFS)
AC_SUBST(MINIUPNPC_CPPFLAGS)
AC_SUBST(MINIUPNPC_LIBS)

View File

@ -101,7 +101,7 @@ script: |
echo "REAL=\`which -a ${i}-${prog}-8 | grep -v ${WRAP_DIR}/${i}-${prog} | head -1\`" >> ${WRAP_DIR}/${i}-${prog}
echo "export LD_PRELOAD='/usr/\$LIB/faketime/libfaketime.so.1'" >> ${WRAP_DIR}/${i}-${prog}
echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${i}-${prog}
echo "\$REAL \$@" >> $WRAP_DIR/${i}-${prog}
echo "\$REAL \"\$@\"" >> $WRAP_DIR/${i}-${prog}
chmod +x ${WRAP_DIR}/${i}-${prog}
touch -d "${REFERENCE_DATETIME}" ${WRAP_DIR}/${i}-${prog}
fi

View File

@ -107,7 +107,7 @@ script: |
echo "# $(${prog} --version | head -1)" >> ${WRAP_DIR}/${i}-${prog}
echo "export LD_PRELOAD='/usr/\$LIB/faketime/libfaketime.so.1'" >> ${WRAP_DIR}/${i}-${prog}
echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${i}-${prog}
echo "\$REAL \$@" >> $WRAP_DIR/${i}-${prog}
echo "\$REAL \"\$@\"" >> $WRAP_DIR/${i}-${prog}
chmod +x ${WRAP_DIR}/${i}-${prog}
touch -d "${REFERENCE_DATETIME}" ${WRAP_DIR}/${i}-${prog}
done

View File

@ -135,7 +135,10 @@ qrencode_packages_$(NO_QR) = $(qrencode_$(host_os)_packages)
qt_packages_$(NO_QT) = $(qt_packages) $(qt_$(host_os)_packages) $(qt_$(host_arch)_$(host_os)_packages) $(qrencode_packages_)
wallet_packages_$(NO_WALLET) = $(wallet_packages)
bdb_packages_$(NO_BDB) = $(bdb_packages)
sqlite_packages_$(NO_SQLITE) = $(sqlite_packages)
wallet_packages_$(NO_WALLET) = $(bdb_packages_) $(sqlite_packages_)
upnp_packages_$(NO_UPNP) = $(upnp_packages)
natpmp_packages_$(NO_NATPMP) = $(natpmp_packages)

View File

@ -89,6 +89,10 @@ The following can be set when running make: `make FOO=bar`
<dd>Don't download/build/cache packages needed for enabling zeromq</dd>
<dt>NO_WALLET</dt>
<dd>Don't download/build/cache libs needed to enable the wallet</dd>
<dt>NO_BDB</dt>
<dd>Don't download/build/cache BerkeleyDB</dd>
<dt>NO_SQLITE</dt>
<dd>Don't download/build/cache SQLite</dd>
<dt>NO_UPNP</dt>
<dd>Don't download/build/cache packages needed for enabling upnp</dd>
<dt>NO_NATPMP</dt>

View File

@ -11,7 +11,8 @@ qt_android_packages=qt
qt_darwin_packages=qt
qt_mingw32_packages=qt
wallet_packages=bdb
bdb_packages=bdb
sqlite_packages=sqlite
zmq_packages=zeromq

View File

@ -0,0 +1,26 @@
package=sqlite
$(package)_version=3320100
$(package)_download_path=https://sqlite.org/2020/
$(package)_file_name=sqlite-autoconf-$($(package)_version).tar.gz
$(package)_sha256_hash=486748abfb16abd8af664e3a5f03b228e5f124682b0c942e157644bf6fff7d10
define $(package)_set_vars
$(package)_config_opts=--disable-shared --disable-readline --disable-dynamic-extensions --enable-option-checking
$(package)_config_opts_linux=--with-pic
endef
define $(package)_config_cmds
$($(package)_autoconf)
endef
define $(package)_build_cmds
$(MAKE) libsqlite3.la
endef
define $(package)_stage_cmds
$(MAKE) DESTDIR=$($(package)_staging_dir) install-libLTLIBRARIES install-includeHEADERS install-pkgconfigDATA
endef
define $(package)_postprocess_cmds
rm lib/*.la
endef

View File

@ -20,7 +20,7 @@ Then install [Homebrew](https://brew.sh).
## Base build dependencies
```shell
brew install automake libtool pkg-config libnatpmp
brew install automake libtool pkg-config libnatpmp sqlite
```
See [dependencies.md](dependencies.md) for a complete overview.
@ -48,7 +48,7 @@ compiled in `disable-wallet` mode with:
./configure --disable-wallet
```
In this case there is no dependency on Berkeley DB 4.8.
In this case there is no dependency on Berkeley DB 4.8 and SQLite.
Mining is also possible in disable-wallet mode using the `getblocktemplate` RPC call.

View File

@ -13,12 +13,12 @@ Run the following commands to install required packages:
##### Debian/Ubuntu:
```bash
$ sudo apt-get install curl build-essential libtool autotools-dev automake pkg-config python3 bsdmainutils bison
$ sudo apt-get install curl build-essential libtool autotools-dev automake pkg-config python3 bsdmainutils bison libsqlite3-dev
```
##### Fedora:
```bash
$ sudo dnf install gcc-c++ libtool make autoconf automake python3 libstdc++-static patch
$ sudo dnf install gcc-c++ libtool make autoconf automake python3 libstdc++-static patch sqlite-devel
```
##### Arch Linux:
@ -99,7 +99,7 @@ disable-wallet mode with:
./configure --prefix=<prefix> --disable-wallet
In this case there is no dependency on Berkeley DB 4.8.
In this case there is no dependency on Berkeley DB 4.8 and SQLite.
Mining is also possible in disable-wallet mode using the `getblocktemplate` RPC call.

View File

@ -24,6 +24,7 @@ These are the dependencies currently used by Dash Core. You can find instruction
| Python (tests) | | [3.5](https://www.python.org/downloads) | | | |
| qrencode | [3.4.4](https://fukuchi.org/works/qrencode) | | No | | |
| Qt | [5.12.11](https://download.qt.io/official_releases/qt/) | [5.5.1](https://github.com/bitcoin/bitcoin/issues/13478) | No | | |
| SQLite | [3.32.1](https://sqlite.org/download.html) | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | | | |
| XCB | | | | | [Yes](https://github.com/dashpay/dash/blob/develop/depends/packages/qt.mk) (Linux only) |
| xkbcommon | | | | | [Yes](https://github.com/dashpay/dash/blob/develop/depends/packages/qt.mk) (Linux only) |
| ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | |
@ -37,6 +38,8 @@ Some dependencies are not needed in all configurations. The following are some f
#### Options passed to `./configure`
* MiniUPnPc is not needed with `--without-miniupnpc`.
* Berkeley DB is not needed with `--disable-wallet`.
* SQLite is not needed with `--disable-wallet`.
* libnatpmp is not needed with `--without-natpmp`.
* Qt is not needed with `--without-gui`.
* If the qrencode dependency is absent, QR support won't be added. To force an error when that happens, pass `--with-qrencode`.

View File

@ -75,8 +75,9 @@ Subdirectory | File(s) | Description
-------------|-------------------|------------
`database/` | BDB logging files | Part of BDB environment; created at start and deleted on shutdown; a user *must keep it as safe* as personal wallet `wallet.dat`
`./` | `db.log` | BDB error file
`./` | `wallet.dat` | Personal wallet (BDB) with keys and transactions
`./` | `wallet.dat` | Personal wallet with keys and transactions. May be either a Berkeley DB or SQLite database file.
`./` | `.walletlock` | Wallet lock file
`./` | `wallet.dat-journal` | SQLite Rollback Journal file for `wallet.dat`. Usually created at start and deleted on shutdown. A user *must keep it as safe* as the `wallet.dat` file.
1. Each user-defined wallet named "wallet_name" resides in `wallets/wallet_name/` subdirectory.

View File

@ -337,6 +337,7 @@ BITCOIN_CORE_H = \
wallet/rpcwallet.h \
wallet/salvage.h \
wallet/scriptpubkeyman.h \
wallet/sqlite.h \
wallet/wallet.h \
wallet/walletdb.h \
wallet/wallettool.h \
@ -502,6 +503,7 @@ libbitcoin_wallet_a_SOURCES = \
wallet/rpcwallet.cpp \
wallet/salvage.cpp \
wallet/scriptpubkeyman.cpp \
wallet/sqlite.cpp \
wallet/wallet.cpp \
wallet/walletdb.cpp \
wallet/walletutil.cpp \
@ -771,7 +773,7 @@ dashd_LDADD = \
$(LIBMEMENV) \
$(LIBSECP256K1)
dashd_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(GMP_LIBS)
dashd_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(GMP_LIBS)
# dash-cli binary #
dash_cli_SOURCES = bitcoin-cli.cpp
@ -842,7 +844,7 @@ dash_wallet_LDADD = \
$(LIBSECP256K1) \
$(LIBUNIVALUE)
dash_wallet_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(ZMQ_LIBS) $(GMP_LIBS)
dash_wallet_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) $(ZMQ_LIBS) $(GMP_LIBS)
#
# dashconsensus library #

View File

@ -79,7 +79,7 @@ bench_bench_dash_SOURCES += bench/coin_selection.cpp
bench_bench_dash_SOURCES += bench/wallet_balance.cpp
endif
bench_bench_dash_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(GMP_LIBS)
bench_bench_dash_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) $(GMP_LIBS)
bench_bench_dash_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS)
CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_BENCH_FILES)

View File

@ -389,7 +389,7 @@ if ENABLE_ZMQ
qt_dash_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
endif
qt_dash_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBDASHBLS) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) \
$(BACKTRACE_LIB) $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(LIBSECP256K1) \
$(BACKTRACE_LIB) $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) $(LIBSECP256K1) \
$(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(GMP_LIBS)
qt_dash_qt_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS)
qt_dash_qt_LIBTOOLFLAGS = $(AM_LIBTOOLFLAGS) --tag CXX

View File

@ -59,7 +59,7 @@ qt_test_test_dash_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
endif
qt_test_test_dash_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBDASHBLS) $(LIBUNIVALUE) $(LIBLEVELDB) \
$(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BACKTRACE_LIB) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \
$(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(LIBSECP256K1) \
$(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) $(LIBSECP256K1) \
$(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(GMP_LIBS)
qt_test_test_dash_qt_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS)
qt_test_test_dash_qt_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS)

View File

@ -49,6 +49,7 @@ FUZZ_SUITE_LD_COMMON = \
$(LIBBITCOIN_CLI) \
$(LIBDASHBLS) \
$(BDB_LIBS) \
$(SQLITE_LIBS) \
$(LIBUNIVALUE) \
$(LIBLEVELDB) \
$(LIBLEVELDB_SSE42) \
@ -189,7 +190,7 @@ test_test_dash_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMM
$(LIBDASHBLS) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BACKTRACE_LIB) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS)
test_test_dash_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_test_dash_LDADD += $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(GMP_LIBS)
test_test_dash_LDADD += $(BDB_LIBS) $(MINIUPNPC_LIBS) $(SQLITE_LIBS) $(NATPMP_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(GMP_LIBS)
test_test_dash_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) -static
if ENABLE_ZMQ

View File

@ -813,7 +813,7 @@ 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);
return IsBDBFile(env_directory / data_filename);
}
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
@ -839,3 +839,28 @@ std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, con
status = DatabaseStatus::SUCCESS;
return db;
}
bool IsBDBFile(const fs::path& path)
{
if (!fs::exists(path)) return false;
// A Berkeley DB Btree file has at least 4K.
// This check also prevents opening lock files.
boost::system::error_code ec;
auto size = fs::file_size(path, ec);
if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
if (size < 4096) return false;
fsbridge::ifstream file(path, std::ios::binary);
if (!file.is_open()) return false;
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
uint32_t data = 0;
file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
// Berkeley DB Btree magic bytes, from:
// https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
// - big endian systems - 00 05 31 62
// - little endian systems - 62 31 05 00
return data == 0x00053162 || data == 0x62310500;
}

View File

@ -87,7 +87,7 @@ public:
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
/** Check format of database file */
bool IsBerkeleyBtree(const fs::path& path);
bool IsBDBFile(const fs::path& path);
class BerkeleyBatch;

View File

@ -194,11 +194,13 @@ public:
enum class DatabaseFormat {
BERKELEY,
SQLITE,
};
struct DatabaseOptions {
bool require_existing = false;
bool require_create = false;
std::optional<DatabaseFormat> require_format;
uint64_t create_flags = 0;
SecureString create_passphrase;
bool verify = true;

628
src/wallet/sqlite.cpp Normal file
View File

@ -0,0 +1,628 @@
// Copyright (c) 2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/sqlite.h>
#include <chainparams.h>
#include <crypto/common.h>
#include <logging.h>
#include <sync.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <util/translation.h>
#include <wallet/db.h>
#include <sqlite3.h>
#include <stdint.h>
static const char* const DATABASE_FILENAME = "wallet.dat";
static constexpr int32_t WALLET_SCHEMA_VERSION = 0;
static Mutex g_sqlite_mutex;
static int g_sqlite_count GUARDED_BY(g_sqlite_mutex) = 0;
static void ErrorLogCallback(void* arg, int code, const char* msg)
{
// From sqlite3_config() documentation for the SQLITE_CONFIG_LOG option:
// "The void pointer that is the second argument to SQLITE_CONFIG_LOG is passed through as
// the first parameter to the application-defined logger function whenever that function is
// invoked."
// Assert that this is the case:
assert(arg == nullptr);
LogPrintf("SQLite Error. Code: %d. Message: %s\n", code, msg);
}
SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock)
: WalletDatabase(), m_mock(mock), m_dir_path(dir_path.string()), m_file_path(file_path.string())
{
{
LOCK(g_sqlite_mutex);
LogPrintf("Using SQLite Version %s\n", SQLiteDatabaseVersion());
LogPrintf("Using wallet %s\n", m_dir_path);
if (++g_sqlite_count == 1) {
// Setup logging
int ret = sqlite3_config(SQLITE_CONFIG_LOG, ErrorLogCallback, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to setup error log: %s\n", sqlite3_errstr(ret)));
}
// Force serialized threading mode
ret = sqlite3_config(SQLITE_CONFIG_SERIALIZED);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to configure serialized threading mode: %s\n", sqlite3_errstr(ret)));
}
}
int ret = sqlite3_initialize(); // This is a no-op if sqlite3 is already initialized
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to initialize SQLite: %s\n", sqlite3_errstr(ret)));
}
}
try {
Open();
} catch (const std::runtime_error&) {
// If open fails, cleanup this object and rethrow the exception
Cleanup();
throw;
}
}
void SQLiteBatch::SetupSQLStatements()
{
int res;
if (!m_read_stmt) {
if ((res = sqlite3_prepare_v2(m_database.m_db, "SELECT value FROM main WHERE key = ?", -1, &m_read_stmt, nullptr)) != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to setup SQL statements: %s\n", sqlite3_errstr(res)));
}
}
if (!m_insert_stmt) {
if ((res = sqlite3_prepare_v2(m_database.m_db, "INSERT INTO main VALUES(?, ?)", -1, &m_insert_stmt, nullptr)) != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to setup SQL statements: %s\n", sqlite3_errstr(res)));
}
}
if (!m_overwrite_stmt) {
if ((res = sqlite3_prepare_v2(m_database.m_db, "INSERT or REPLACE into main values(?, ?)", -1, &m_overwrite_stmt, nullptr)) != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to setup SQL statements: %s\n", sqlite3_errstr(res)));
}
}
if (!m_delete_stmt) {
if ((res = sqlite3_prepare_v2(m_database.m_db, "DELETE FROM main WHERE key = ?", -1, &m_delete_stmt, nullptr)) != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to setup SQL statements: %s\n", sqlite3_errstr(res)));
}
}
if (!m_cursor_stmt) {
if ((res = sqlite3_prepare_v2(m_database.m_db, "SELECT key, value FROM main", -1, &m_cursor_stmt, nullptr)) != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to setup SQL statements : %s\n", sqlite3_errstr(res)));
}
}
}
SQLiteDatabase::~SQLiteDatabase()
{
Cleanup();
}
void SQLiteDatabase::Cleanup() noexcept
{
Close();
LOCK(g_sqlite_mutex);
if (--g_sqlite_count == 0) {
int ret = sqlite3_shutdown();
if (ret != SQLITE_OK) {
LogPrintf("SQLiteDatabase: Failed to shutdown SQLite: %s\n", sqlite3_errstr(ret));
}
}
}
bool SQLiteDatabase::Verify(bilingual_str& error)
{
assert(m_db);
// Check the application ID matches our network magic
sqlite3_stmt* app_id_stmt{nullptr};
int ret = sqlite3_prepare_v2(m_db, "PRAGMA application_id", -1, &app_id_stmt, nullptr);
if (ret != SQLITE_OK) {
sqlite3_finalize(app_id_stmt);
error = strprintf(_("SQLiteDatabase: Failed to prepare the statement to fetch the application id: %s"), sqlite3_errstr(ret));
return false;
}
ret = sqlite3_step(app_id_stmt);
if (ret != SQLITE_ROW) {
sqlite3_finalize(app_id_stmt);
error = strprintf(_("SQLiteDatabase: Failed to fetch the application id: %s"), sqlite3_errstr(ret));
return false;
}
uint32_t app_id = static_cast<uint32_t>(sqlite3_column_int(app_id_stmt, 0));
sqlite3_finalize(app_id_stmt);
uint32_t net_magic = ReadBE32(Params().MessageStart());
if (app_id != net_magic) {
error = strprintf(_("SQLiteDatabase: Unexpected application id. Expected %u, got %u"), net_magic, app_id);
return false;
}
// Check our schema version
sqlite3_stmt* user_ver_stmt{nullptr};
ret = sqlite3_prepare_v2(m_db, "PRAGMA user_version", -1, &user_ver_stmt, nullptr);
if (ret != SQLITE_OK) {
sqlite3_finalize(user_ver_stmt);
error = strprintf(_("SQLiteDatabase: Failed to prepare the statement to fetch sqlite wallet schema version: %s"), sqlite3_errstr(ret));
return false;
}
ret = sqlite3_step(user_ver_stmt);
if (ret != SQLITE_ROW) {
sqlite3_finalize(user_ver_stmt);
error = strprintf(_("SQLiteDatabase: Failed to fetch sqlite wallet schema version: %s"), sqlite3_errstr(ret));
return false;
}
int32_t user_ver = sqlite3_column_int(user_ver_stmt, 0);
sqlite3_finalize(user_ver_stmt);
if (user_ver != WALLET_SCHEMA_VERSION) {
error = strprintf(_("SQLiteDatabase: Unknown sqlite wallet schema version %d. Only version %d is supported"), user_ver, WALLET_SCHEMA_VERSION);
return false;
}
sqlite3_stmt* stmt{nullptr};
ret = sqlite3_prepare_v2(m_db, "PRAGMA integrity_check", -1, &stmt, nullptr);
if (ret != SQLITE_OK) {
sqlite3_finalize(stmt);
error = strprintf(_("SQLiteDatabase: Failed to prepare statement to verify database: %s"), sqlite3_errstr(ret));
return false;
}
while (true) {
ret = sqlite3_step(stmt);
if (ret == SQLITE_DONE) {
break;
}
if (ret != SQLITE_ROW) {
error = strprintf(_("SQLiteDatabase: Failed to execute statement to verify database: %s"), sqlite3_errstr(ret));
break;
}
const char* msg = (const char*)sqlite3_column_text(stmt, 0);
if (!msg) {
error = strprintf(_("SQLiteDatabase: Failed to read database verification error: %s"), sqlite3_errstr(ret));
break;
}
std::string str_msg(msg);
if (str_msg == "ok") {
continue;
}
if (error.empty()) {
error = _("Failed to verify database") + Untranslated("\n");
}
error += Untranslated(strprintf("%s\n", str_msg));
}
sqlite3_finalize(stmt);
return error.empty();
}
void SQLiteDatabase::Open()
{
int flags = SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
if (m_mock) {
flags |= SQLITE_OPEN_MEMORY; // In memory database for mock db
}
if (m_db == nullptr) {
TryCreateDirectories(m_dir_path);
int ret = sqlite3_open_v2(m_file_path.c_str(), &m_db, flags, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database: %s\n", sqlite3_errstr(ret)));
}
}
if (sqlite3_db_readonly(m_db, "main") != 0) {
throw std::runtime_error("SQLiteDatabase: Database opened in readonly mode but read-write permissions are needed");
}
// Acquire an exclusive lock on the database
// First change the locking mode to exclusive
int ret = sqlite3_exec(m_db, "PRAGMA locking_mode = exclusive", nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Unable to change database locking mode to exclusive: %s\n", sqlite3_errstr(ret)));
}
// Now begin a transaction to acquire the exclusive lock. This lock won't be released until we close because of the exclusive locking mode.
ret = sqlite3_exec(m_db, "BEGIN EXCLUSIVE TRANSACTION", nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error("SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?\n");
}
ret = sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Unable to end exclusive lock transaction: %s\n", sqlite3_errstr(ret)));
}
// Enable fullfsync for the platforms that use it
ret = sqlite3_exec(m_db, "PRAGMA fullfsync = true", nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to enable fullfsync: %s\n", sqlite3_errstr(ret)));
}
// Make the table for our key-value pairs
// First check that the main table exists
sqlite3_stmt* check_main_stmt{nullptr};
ret = sqlite3_prepare_v2(m_db, "SELECT name FROM sqlite_master WHERE type='table' AND name='main'", -1, &check_main_stmt, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to prepare statement to check table existence: %s\n", sqlite3_errstr(ret)));
}
ret = sqlite3_step(check_main_stmt);
if (sqlite3_finalize(check_main_stmt) != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to finalize statement checking table existence: %s\n", sqlite3_errstr(ret)));
}
bool table_exists;
if (ret == SQLITE_DONE) {
table_exists = false;
} else if (ret == SQLITE_ROW) {
table_exists = true;
} else {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to execute statement to check table existence: %s\n", sqlite3_errstr(ret)));
}
// Do the db setup things because the table doesn't exist only when we are creating a new wallet
if (!table_exists) {
ret = sqlite3_exec(m_db, "CREATE TABLE main(key BLOB PRIMARY KEY NOT NULL, value BLOB NOT NULL)", nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to create new database: %s\n", sqlite3_errstr(ret)));
}
// Set the application id
uint32_t app_id = ReadBE32(Params().MessageStart());
std::string set_app_id = strprintf("PRAGMA application_id = %d", static_cast<int32_t>(app_id));
ret = sqlite3_exec(m_db, set_app_id.c_str(), nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to set the application id: %s\n", sqlite3_errstr(ret)));
}
// Set the user version
std::string set_user_ver = strprintf("PRAGMA user_version = %d", WALLET_SCHEMA_VERSION);
ret = sqlite3_exec(m_db, set_user_ver.c_str(), nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to set the wallet schema version: %s\n", sqlite3_errstr(ret)));
}
}
}
bool SQLiteDatabase::Rewrite(const char* skip)
{
// Rewrite the database using the VACUUM command: https://sqlite.org/lang_vacuum.html
int ret = sqlite3_exec(m_db, "VACUUM", nullptr, nullptr, nullptr);
return ret == SQLITE_OK;
}
bool SQLiteDatabase::Backup(const std::string& dest) const
{
sqlite3* db_copy;
int res = sqlite3_open(dest.c_str(), &db_copy);
if (res != SQLITE_OK) {
sqlite3_close(db_copy);
return false;
}
sqlite3_backup* backup = sqlite3_backup_init(db_copy, "main", m_db, "main");
if (!backup) {
LogPrintf("%s: Unable to begin backup: %s\n", __func__, sqlite3_errmsg(m_db));
sqlite3_close(db_copy);
return false;
}
// Specifying -1 will copy all of the pages
res = sqlite3_backup_step(backup, -1);
if (res != SQLITE_DONE) {
LogPrintf("%s: Unable to backup: %s\n", __func__, sqlite3_errstr(res));
sqlite3_backup_finish(backup);
sqlite3_close(db_copy);
return false;
}
res = sqlite3_backup_finish(backup);
sqlite3_close(db_copy);
return res == SQLITE_OK;
}
void SQLiteDatabase::Close()
{
int res = sqlite3_close(m_db);
if (res != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to close database: %s\n", sqlite3_errstr(res)));
}
m_db = nullptr;
}
std::unique_ptr<DatabaseBatch> SQLiteDatabase::MakeBatch(bool flush_on_close)
{
// We ignore flush_on_close because we don't do manual flushing for SQLite
return std::make_unique<SQLiteBatch>(*this);
}
SQLiteBatch::SQLiteBatch(SQLiteDatabase& database)
: m_database(database)
{
// Make sure we have a db handle
assert(m_database.m_db);
SetupSQLStatements();
}
void SQLiteBatch::Close()
{
// If m_db is in a transaction (i.e. not in autocommit mode), then abort the transaction in progress
if (m_database.m_db && sqlite3_get_autocommit(m_database.m_db) == 0) {
if (TxnAbort()) {
LogPrintf("SQLiteBatch: Batch closed unexpectedly without the transaction being explicitly committed or aborted\n");
} else {
LogPrintf("SQLiteBatch: Batch closed and failed to abort transaction\n");
}
}
// Free all of the prepared statements
int ret = sqlite3_finalize(m_read_stmt);
if (ret != SQLITE_OK) {
LogPrintf("SQLiteBatch: Batch closed but could not finalize read statement: %s\n", sqlite3_errstr(ret));
}
ret = sqlite3_finalize(m_insert_stmt);
if (ret != SQLITE_OK) {
LogPrintf("SQLiteBatch: Batch closed but could not finalize insert statement: %s\n", sqlite3_errstr(ret));
}
ret = sqlite3_finalize(m_overwrite_stmt);
if (ret != SQLITE_OK) {
LogPrintf("SQLiteBatch: Batch closed but could not finalize overwrite statement: %s\n", sqlite3_errstr(ret));
}
ret = sqlite3_finalize(m_delete_stmt);
if (ret != SQLITE_OK) {
LogPrintf("SQLiteBatch: Batch closed but could not finalize delete statement: %s\n", sqlite3_errstr(ret));
}
ret = sqlite3_finalize(m_cursor_stmt);
if (ret != SQLITE_OK) {
LogPrintf("SQLiteBatch: Batch closed but could not finalize cursor statement: %s\n", sqlite3_errstr(ret));
}
m_read_stmt = nullptr;
m_insert_stmt = nullptr;
m_overwrite_stmt = nullptr;
m_delete_stmt = nullptr;
m_cursor_stmt = nullptr;
}
bool SQLiteBatch::ReadKey(CDataStream&& key, CDataStream& value)
{
if (!m_database.m_db) return false;
assert(m_read_stmt);
// Bind: leftmost parameter in statement is index 1
int res = sqlite3_bind_blob(m_read_stmt, 1, key.data(), key.size(), SQLITE_STATIC);
if (res != SQLITE_OK) {
LogPrintf("%s: Unable to bind statement: %s\n", __func__, sqlite3_errstr(res));
sqlite3_clear_bindings(m_read_stmt);
sqlite3_reset(m_read_stmt);
return false;
}
res = sqlite3_step(m_read_stmt);
if (res != SQLITE_ROW) {
if (res != SQLITE_DONE) {
// SQLITE_DONE means "not found", don't log an error in that case.
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
}
sqlite3_clear_bindings(m_read_stmt);
sqlite3_reset(m_read_stmt);
return false;
}
// Leftmost column in result is index 0
const char* data = reinterpret_cast<const char*>(sqlite3_column_blob(m_read_stmt, 0));
int data_size = sqlite3_column_bytes(m_read_stmt, 0);
value.write(data, data_size);
sqlite3_clear_bindings(m_read_stmt);
sqlite3_reset(m_read_stmt);
return true;
}
bool SQLiteBatch::WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite)
{
if (!m_database.m_db) return false;
assert(m_insert_stmt && m_overwrite_stmt);
sqlite3_stmt* stmt;
if (overwrite) {
stmt = m_overwrite_stmt;
} else {
stmt = m_insert_stmt;
}
// Bind: leftmost parameter in statement is index 1
// Insert index 1 is key, 2 is value
int res = sqlite3_bind_blob(stmt, 1, key.data(), key.size(), SQLITE_STATIC);
if (res != SQLITE_OK) {
LogPrintf("%s: Unable to bind key to statement: %s\n", __func__, sqlite3_errstr(res));
sqlite3_clear_bindings(stmt);
sqlite3_reset(stmt);
return false;
}
res = sqlite3_bind_blob(stmt, 2, value.data(), value.size(), SQLITE_STATIC);
if (res != SQLITE_OK) {
LogPrintf("%s: Unable to bind value to statement: %s\n", __func__, sqlite3_errstr(res));
sqlite3_clear_bindings(stmt);
sqlite3_reset(stmt);
return false;
}
// Execute
res = sqlite3_step(stmt);
sqlite3_clear_bindings(stmt);
sqlite3_reset(stmt);
if (res != SQLITE_DONE) {
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
}
return res == SQLITE_DONE;
}
bool SQLiteBatch::EraseKey(CDataStream&& key)
{
if (!m_database.m_db) return false;
assert(m_delete_stmt);
// Bind: leftmost parameter in statement is index 1
int res = sqlite3_bind_blob(m_delete_stmt, 1, key.data(), key.size(), SQLITE_STATIC);
if (res != SQLITE_OK) {
LogPrintf("%s: Unable to bind statement: %s\n", __func__, sqlite3_errstr(res));
sqlite3_clear_bindings(m_delete_stmt);
sqlite3_reset(m_delete_stmt);
return false;
}
// Execute
res = sqlite3_step(m_delete_stmt);
sqlite3_clear_bindings(m_delete_stmt);
sqlite3_reset(m_delete_stmt);
if (res != SQLITE_DONE) {
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
}
return res == SQLITE_DONE;
}
bool SQLiteBatch::HasKey(CDataStream&& key)
{
if (!m_database.m_db) return false;
assert(m_read_stmt);
// Bind: leftmost parameter in statement is index 1
bool ret = false;
int res = sqlite3_bind_blob(m_read_stmt, 1, key.data(), key.size(), SQLITE_STATIC);
if (res == SQLITE_OK) {
res = sqlite3_step(m_read_stmt);
if (res == SQLITE_ROW) {
ret = true;
}
}
sqlite3_clear_bindings(m_read_stmt);
sqlite3_reset(m_read_stmt);
return ret;
}
bool SQLiteBatch::StartCursor()
{
assert(!m_cursor_init);
if (!m_database.m_db) return false;
m_cursor_init = true;
return true;
}
bool SQLiteBatch::ReadAtCursor(CDataStream& key, CDataStream& value, bool& complete)
{
complete = false;
if (!m_cursor_init) return false;
int res = sqlite3_step(m_cursor_stmt);
if (res == SQLITE_DONE) {
complete = true;
return true;
}
if (res != SQLITE_ROW) {
LogPrintf("SQLiteBatch::ReadAtCursor: Unable to execute cursor step: %s\n", sqlite3_errstr(res));
return false;
}
// Leftmost column in result is index 0
const char* key_data = reinterpret_cast<const char*>(sqlite3_column_blob(m_cursor_stmt, 0));
int key_data_size = sqlite3_column_bytes(m_cursor_stmt, 0);
key.write(key_data, key_data_size);
const char* value_data = reinterpret_cast<const char*>(sqlite3_column_blob(m_cursor_stmt, 1));
int value_data_size = sqlite3_column_bytes(m_cursor_stmt, 1);
value.write(value_data, value_data_size);
return true;
}
void SQLiteBatch::CloseCursor()
{
sqlite3_reset(m_cursor_stmt);
m_cursor_init = false;
}
bool SQLiteBatch::TxnBegin()
{
if (!m_database.m_db || sqlite3_get_autocommit(m_database.m_db) == 0) return false;
int res = sqlite3_exec(m_database.m_db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr);
if (res != SQLITE_OK) {
LogPrintf("SQLiteBatch: Failed to begin the transaction\n");
}
return res == SQLITE_OK;
}
bool SQLiteBatch::TxnCommit()
{
if (!m_database.m_db || sqlite3_get_autocommit(m_database.m_db) != 0) return false;
int res = sqlite3_exec(m_database.m_db, "COMMIT TRANSACTION", nullptr, nullptr, nullptr);
if (res != SQLITE_OK) {
LogPrintf("SQLiteBatch: Failed to commit the transaction\n");
}
return res == SQLITE_OK;
}
bool SQLiteBatch::TxnAbort()
{
if (!m_database.m_db || sqlite3_get_autocommit(m_database.m_db) != 0) return false;
int res = sqlite3_exec(m_database.m_db, "ROLLBACK TRANSACTION", nullptr, nullptr, nullptr);
if (res != SQLITE_OK) {
LogPrintf("SQLiteBatch: Failed to abort the transaction\n");
}
return res == SQLITE_OK;
}
bool ExistsSQLiteDatabase(const fs::path& path)
{
const fs::path file = path / DATABASE_FILENAME;
return fs::symlink_status(file).type() == fs::regular_file && IsSQLiteFile(file);
}
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
{
const fs::path file = path / DATABASE_FILENAME;
try {
auto db = std::make_unique<SQLiteDatabase>(path, file);
if (options.verify && !db->Verify(error)) {
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
}
return db;
} catch (const std::runtime_error& e) {
status = DatabaseStatus::FAILED_LOAD;
error.original = e.what();
return nullptr;
}
}
std::string SQLiteDatabaseVersion()
{
return std::string(sqlite3_libversion());
}
bool IsSQLiteFile(const fs::path& path)
{
if (!fs::exists(path)) return false;
// A SQLite Database file is at least 512 bytes.
boost::system::error_code ec;
auto size = fs::file_size(path, ec);
if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
if (size < 512) return false;
fsbridge::ifstream file(path, std::ios::binary);
if (!file.is_open()) return false;
// Magic is at beginning and is 16 bytes long
char magic[16];
file.read(magic, 16);
// Application id is at offset 68 and 4 bytes long
file.seekg(68, std::ios::beg);
char app_id[4];
file.read(app_id, 4);
file.close();
// Check the magic, see https://sqlite.org/fileformat2.html
std::string magic_str(magic);
if (magic_str != std::string("SQLite format 3")) {
return false;
}
// Check the application id matches our network magic
return memcmp(Params().MessageStart(), app_id, 4) == 0;
}

121
src/wallet/sqlite.h Normal file
View File

@ -0,0 +1,121 @@
// Copyright (c) 2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_SQLITE_H
#define BITCOIN_WALLET_SQLITE_H
#include <wallet/db.h>
#include <sqlite3.h>
struct bilingual_str;
class SQLiteDatabase;
/** RAII class that provides access to a WalletDatabase */
class SQLiteBatch : public DatabaseBatch
{
private:
SQLiteDatabase& m_database;
bool m_cursor_init = false;
sqlite3_stmt* m_read_stmt{nullptr};
sqlite3_stmt* m_insert_stmt{nullptr};
sqlite3_stmt* m_overwrite_stmt{nullptr};
sqlite3_stmt* m_delete_stmt{nullptr};
sqlite3_stmt* m_cursor_stmt{nullptr};
void SetupSQLStatements();
bool ReadKey(CDataStream&& key, CDataStream& value) override;
bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite = true) override;
bool EraseKey(CDataStream&& key) override;
bool HasKey(CDataStream&& key) override;
public:
explicit SQLiteBatch(SQLiteDatabase& database);
~SQLiteBatch() override { Close(); }
/* No-op. See commeng on SQLiteDatabase::Flush */
void Flush() override {}
void Close() override;
bool StartCursor() override;
bool ReadAtCursor(CDataStream& key, CDataStream& value, bool& complete) override;
void CloseCursor() override;
bool TxnBegin() override;
bool TxnCommit() override;
bool TxnAbort() override;
};
/** An instance of this class represents one SQLite3 database.
**/
class SQLiteDatabase : public WalletDatabase
{
private:
const bool m_mock{false};
const std::string m_dir_path;
const std::string m_file_path;
void Cleanup() noexcept;
public:
SQLiteDatabase() = delete;
/** Create DB handle to real database */
SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock = false);
~SQLiteDatabase();
bool Verify(bilingual_str& error);
/** Open the database if it is not already opened */
void Open() override;
/** Close the database */
void Close() override;
/* These functions are unused */
void AddRef() override { assert(false); }
void RemoveRef() override { assert(false); }
/** Rewrite the entire database on disk */
bool Rewrite(const char* skip = nullptr) override;
/** Back up the entire database to a file.
*/
bool Backup(const std::string& dest) const override;
/** No-ops
*
* SQLite always flushes everything to the database file after each transaction
* (each Read/Write/Erase that we do is its own transaction unless we called
* TxnBegin) so there is no need to have Flush or Periodic Flush.
*
* There is no DB env to reload, so ReloadDbEnv has nothing to do
*/
void Flush() override {}
bool PeriodicFlush() override { return false; }
void ReloadDbEnv() override {}
void IncrementUpdateCounter() override { ++nUpdateCounter; }
std::string Filename() override { return m_file_path; }
/** Make a SQLiteBatch connected to this database */
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override;
sqlite3* m_db{nullptr};
};
bool ExistsSQLiteDatabase(const fs::path& path);
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
std::string SQLiteDatabaseVersion();
bool IsSQLiteFile(const fs::path& path);
#endif // BITCOIN_WALLET_SQLITE_H

View File

@ -4633,6 +4633,11 @@ bool CWallet::AutoBackupWallet(const fs::path& wallet_path, bilingual_str& error
strWalletName = "wallet.dat";
}
if (!ExistsBerkeleyDatabase(wallet_path)) {
WalletLogPrintf("Automatic wallet backups are currently only supported with Berkeley DB!\n");
return false;
}
if (nWalletBackups <= 0) {
WalletLogPrintf("Automatic wallet backups are disabled!\n");
return false;

View File

@ -19,6 +19,7 @@
#include <util/time.h>
#include <util/translation.h>
#include <wallet/bdb.h>
#include <wallet/sqlite.h>
#include <wallet/wallet.h>
#include <validation.h>
@ -828,6 +829,14 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas
if (ExistsBerkeleyDatabase(path)) {
format = DatabaseFormat::BERKELEY;
}
if (ExistsSQLiteDatabase(path)) {
if (format) {
error = Untranslated(strprintf("Failed to load database path '%s'. Data is in ambiguous format.", path.string()));
status = DatabaseStatus::FAILED_BAD_FORMAT;
return nullptr;
}
format = DatabaseFormat::SQLITE;
}
} 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;
@ -846,6 +855,20 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas
return nullptr;
}
// A db already exists so format is set, but options also specifies the format, so make sure they agree
if (format && options.require_format && format != options.require_format) {
error = Untranslated(strprintf("Failed to load database path '%s'. Data is not in required format.", path.string()));
status = DatabaseStatus::FAILED_BAD_FORMAT;
return nullptr;
}
// Format is not set when a db doesn't already exist, so use the format specified by the options if it is set.
if (!format && options.require_format) format = options.require_format;
if (format && format == DatabaseFormat::SQLITE) {
return MakeSQLiteDatabase(path, options, status, error);
}
return MakeBerkeleyDatabase(path, options, status, error);
}

View File

@ -7,6 +7,9 @@
#include <logging.h>
#include <util/system.h>
bool ExistsBerkeleyDatabase(const fs::path& path);
bool ExistsSQLiteDatabase(const fs::path& path);
fs::path GetWalletDir()
{
fs::path path;
@ -29,31 +32,6 @@ fs::path GetWalletDir()
return path;
}
bool IsBerkeleyBtree(const fs::path& path)
{
if (!fs::exists(path)) return false;
// A Berkeley DB Btree file has at least 4K.
// This check also prevents opening lock files.
boost::system::error_code ec;
auto size = fs::file_size(path, ec);
if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
if (size < 4096) return false;
fsbridge::ifstream file(path, std::ios::binary);
if (!file.is_open()) return false;
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
uint32_t data = 0;
file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
// Berkeley DB Btree magic bytes, from:
// https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
// - big endian systems - 00 05 31 62
// - little endian systems - 62 31 05 00
return data == 0x00053162 || data == 0x62310500;
}
std::vector<fs::path> ListWalletDir()
{
const fs::path wallet_dir = GetWalletDir();
@ -71,10 +49,11 @@ std::vector<fs::path> ListWalletDir()
// This can be replaced by boost::filesystem::lexically_relative once boost is bumped to 1.60.
const fs::path path = it->path().string().substr(offset);
if (it->status().type() == fs::directory_file && IsBerkeleyBtree(it->path() / "wallet.dat")) {
if (it->status().type() == fs::directory_file &&
(ExistsBerkeleyDatabase(it->path()) || ExistsSQLiteDatabase(it->path()))) {
// Found a directory which contains wallet.dat btree file, add it as a wallet.
paths.emplace_back(path);
} else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && IsBerkeleyBtree(it->path())) {
} else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && ExistsBerkeleyDatabase(it->path())) {
if (it->path().filename() == "wallet.dat") {
// Found top-level wallet.dat btree file, add top level directory ""
// as a wallet.