bitcoin/src/wallet/sqlite.cpp

717 lines
26 KiB
C++
Raw Normal View History

// Copyright (c) 2020-2022 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 <bitcoin-build-config.h> // IWYU pragma: keep
scripted-diff: Fix bitcoin_config_h includes -BEGIN VERIFY SCRIPT- regex_string='^(?!//).*(AC_APPLE_UNIVERSAL_BUILD|BOOST_PROCESS_USE_STD_FS|CHAR_EQUALS_INT8|CLIENT_VERSION_BUILD|CLIENT_VERSION_IS_RELEASE|CLIENT_VERSION_MAJOR|CLIENT_VERSION_MINOR|COPYRIGHT_HOLDERS|COPYRIGHT_HOLDERS_FINAL|COPYRIGHT_HOLDERS_SUBSTITUTION|COPYRIGHT_YEAR|ENABLE_ARM_SHANI|ENABLE_AVX2|ENABLE_EXTERNAL_SIGNER|ENABLE_SSE41|ENABLE_TRACING|ENABLE_WALLET|ENABLE_X86_SHANI|ENABLE_ZMQ|HAVE_BOOST|HAVE_BUILTIN_CLZL|HAVE_BUILTIN_CLZLL|HAVE_BYTESWAP_H|HAVE_CLMUL|HAVE_CONSENSUS_LIB|HAVE_CXX20|HAVE_DECL_BE16TOH|HAVE_DECL_BE32TOH|HAVE_DECL_BE64TOH|HAVE_DECL_BSWAP_16|HAVE_DECL_BSWAP_32|HAVE_DECL_BSWAP_64|HAVE_DECL_FORK|HAVE_DECL_FREEIFADDRS|HAVE_DECL_GETIFADDRS|HAVE_DECL_HTOBE16|HAVE_DECL_HTOBE32|HAVE_DECL_HTOBE64|HAVE_DECL_HTOLE16|HAVE_DECL_HTOLE32|HAVE_DECL_HTOLE64|HAVE_DECL_LE16TOH|HAVE_DECL_LE32TOH|HAVE_DECL_LE64TOH|HAVE_DECL_PIPE2|HAVE_DECL_SETSID|HAVE_DECL_STRERROR_R|HAVE_DEFAULT_VISIBILITY_ATTRIBUTE|HAVE_DLFCN_H|HAVE_DLLEXPORT_ATTRIBUTE|HAVE_ENDIAN_H|HAVE_EVHTTP_CONNECTION_GET_PEER_CONST_CHAR|HAVE_FDATASYNC|HAVE_GETENTROPY_RAND|HAVE_GETRANDOM|HAVE_GMTIME_R|HAVE_INTTYPES_H|HAVE_LIBADVAPI32|HAVE_LIBCOMCTL32|HAVE_LIBCOMDLG32|HAVE_LIBGDI32|HAVE_LIBIPHLPAPI|HAVE_LIBKERNEL32|HAVE_LIBOLE32|HAVE_LIBOLEAUT32|HAVE_LIBSHELL32|HAVE_LIBSHLWAPI|HAVE_LIBUSER32|HAVE_LIBUUID|HAVE_LIBWINMM|HAVE_LIBWS2_32|HAVE_MALLOC_INFO|HAVE_MALLOPT_ARENA_MAX|HAVE_MINIUPNPC_MINIUPNPC_H|HAVE_MINIUPNPC_UPNPCOMMANDS_H|HAVE_MINIUPNPC_UPNPERRORS_H|HAVE_NATPMP_H|HAVE_O_CLOEXEC|HAVE_POSIX_FALLOCATE|HAVE_PTHREAD|HAVE_PTHREAD_PRIO_INHERIT|HAVE_STDINT_H|HAVE_STDIO_H|HAVE_STDLIB_H|HAVE_STRERROR_R|HAVE_STRINGS_H|HAVE_STRING_H|HAVE_STRONG_GETAUXVAL|HAVE_SYSCTL|HAVE_SYSCTL_ARND|HAVE_SYSTEM|HAVE_SYS_ENDIAN_H|HAVE_SYS_PRCTL_H|HAVE_SYS_RESOURCES_H|HAVE_SYS_SELECT_H|HAVE_SYS_STAT_H|HAVE_SYS_SYSCTL_H|HAVE_SYS_TYPES_H|HAVE_SYS_VMMETER_H|HAVE_THREAD_LOCAL|HAVE_TIMINGSAFE_BCMP|HAVE_UNISTD_H|HAVE_VM_VM_PARAM_H|LT_OBJDIR|PACKAGE_BUGREPORT|PACKAGE_NAME|PACKAGE_STRING|PACKAGE_TARNAME|PACKAGE_URL|PACKAGE_VERSION|PTHREAD_CREATE_JOINABLE|QT_QPA_PLATFORM_ANDROID|QT_QPA_PLATFORM_COCOA|QT_QPA_PLATFORM_MINIMAL|QT_QPA_PLATFORM_WINDOWS|QT_QPA_PLATFORM_XCB|QT_STATICPLUGIN|STDC_HEADERS|STRERROR_R_CHAR_P|USE_ASM|USE_BDB|USE_DBUS|USE_NATPMP|USE_QRCODE|USE_SQLITE|USE_UPNP|_FILE_OFFSET_BITS|_LARGE_FILES)' exclusion_files=":(exclude)src/minisketch :(exclude)src/crc32c :(exclude)src/secp256k1 :(exclude)src/crypto/sha256_arm_shani.cpp :(exclude)src/crypto/sha256_avx2.cpp :(exclude)src/crypto/sha256_sse41.cpp :(exclude)src/crypto/sha256_x86_shani.cpp" git grep --perl-regexp --files-with-matches "$regex_string" -- '*.cpp' $exclusion_files | xargs git grep -L "bitcoin-config.h" | while read -r file; do line_number=$(awk -v my_file="$file" '/\/\/ file COPYING or https?:\/\/www.opensource.org\/licenses\/mit-license.php\./ {line = NR} /^\/\// && NR == line + 1 {while(getline && /^\/\//) line = NR} END {print line+1}' "$file"); sed -i "${line_number}i\\\\n\#if defined(HAVE_CONFIG_H)\\n#include <config/bitcoin-config.h>\\n\#endif" "$file"; done; git grep --perl-regexp --files-with-matches "$regex_string" -- '*.h' $exclusion_files | xargs git grep -L "bitcoin-config.h" | while read -r file; do sed -i "/#define.*_H/a \\\\n\#if defined(HAVE_CONFIG_H)\\n#include <config/bitcoin-config.h>\\n\#endif" "$file"; done; for file in $(git grep --files-with-matches 'bitcoin-config.h' -- '*.cpp' '*.h' $exclusion_files); do if ! grep -q --perl-regexp "$regex_string" $file; then sed -i '/HAVE_CONFIG_H/{N;N;N;d;}' $file; fi; done; -END VERIFY SCRIPT- The first command creates a regular expression for matching all bitcoin-config.h symbols in the following form: ^(?!//).*(AC_APPLE_UNIVERSAL_BUILD|BOOST_PROCESS_USE_STD_FS|...|_LARGE_FILES). It was generated with: ./autogen.sh && printf '^(?!//).*(%s)' $(awk '/^#undef/ {print $2}' src/config/bitcoin-config.h.in | paste -sd "|" -) The second command holds a list of files and directories that should not be processed. These include subtree directories as well as some crypto files that already get their symbols through the makefile. The third command checks for missing bitcoin-config headers in .cpp files and adds the header if it is missing. The fourth command checks for missing bitcoin-config headers in .h files and adds the header if it is missing. The fifth command checks for unneeded bitcoin-config headers in sources files and removes the header if it is unneeded.
2024-02-13 08:03:02 +01:00
#include <wallet/sqlite.h>
#include <chainparams.h>
#include <crypto/common.h>
#include <logging.h>
#include <sync.h>
#include <util/fs_helpers.h>
#include <util/check.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <wallet/db.h>
2020-05-26 20:53:05 -04:00
#include <sqlite3.h>
#include <stdint.h>
#include <optional>
#include <utility>
#include <vector>
namespace wallet {
2020-09-09 20:22:32 -04:00
static constexpr int32_t WALLET_SCHEMA_VERSION = 0;
static Span<const std::byte> SpanFromBlob(sqlite3_stmt* stmt, int col)
{
return {reinterpret_cast<const std::byte*>(sqlite3_column_blob(stmt, col)),
static_cast<size_t>(sqlite3_column_bytes(stmt, col))};
}
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);
}
static int TraceSqlCallback(unsigned code, void* context, void* param1, void* param2)
{
auto* db = static_cast<SQLiteDatabase*>(context);
if (code == SQLITE_TRACE_STMT) {
auto* stmt = static_cast<sqlite3_stmt*>(param1);
// To be conservative and avoid leaking potentially secret information
// in the log file, only expand statements that query the database, not
// statements that update the database.
char* expanded{sqlite3_stmt_readonly(stmt) ? sqlite3_expanded_sql(stmt) : nullptr};
LogTrace(BCLog::WALLETDB, "[%s] SQLite Statement: %s\n", db->Filename(), expanded ? expanded : sqlite3_sql(stmt));
if (expanded) sqlite3_free(expanded);
}
return SQLITE_OK;
}
static bool BindBlobToStatement(sqlite3_stmt* stmt,
int index,
Span<const std::byte> blob,
const std::string& description)
{
// Pass a pointer to the empty string "" below instead of passing the
// blob.data() pointer if the blob.data() pointer is null. Passing a null
// data pointer to bind_blob would cause sqlite to bind the SQL NULL value
// instead of the empty blob value X'', which would mess up SQL comparisons.
int res = sqlite3_bind_blob(stmt, index, blob.data() ? static_cast<const void*>(blob.data()) : "", blob.size(), SQLITE_STATIC);
if (res != SQLITE_OK) {
LogPrintf("Unable to bind %s to statement: %s\n", description, sqlite3_errstr(res));
sqlite3_clear_bindings(stmt);
sqlite3_reset(stmt);
return false;
}
return true;
}
static std::optional<int> ReadPragmaInteger(sqlite3* db, const std::string& key, const std::string& description, bilingual_str& error)
{
std::string stmt_text = strprintf("PRAGMA %s", key);
sqlite3_stmt* pragma_read_stmt{nullptr};
int ret = sqlite3_prepare_v2(db, stmt_text.c_str(), -1, &pragma_read_stmt, nullptr);
if (ret != SQLITE_OK) {
sqlite3_finalize(pragma_read_stmt);
error = Untranslated(strprintf("SQLiteDatabase: Failed to prepare the statement to fetch %s: %s", description, sqlite3_errstr(ret)));
return std::nullopt;
}
ret = sqlite3_step(pragma_read_stmt);
if (ret != SQLITE_ROW) {
sqlite3_finalize(pragma_read_stmt);
error = Untranslated(strprintf("SQLiteDatabase: Failed to fetch %s: %s", description, sqlite3_errstr(ret)));
return std::nullopt;
}
int result = sqlite3_column_int(pragma_read_stmt, 0);
sqlite3_finalize(pragma_read_stmt);
return result;
}
static void SetPragma(sqlite3* db, const std::string& key, const std::string& value, const std::string& err_msg)
{
std::string stmt_text = strprintf("PRAGMA %s = %s", key, value);
int ret = sqlite3_exec(db, stmt_text.c_str(), nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: %s: %s\n", err_msg, sqlite3_errstr(ret)));
}
}
Mutex SQLiteDatabase::g_sqlite_mutex;
int SQLiteDatabase::g_sqlite_count = 0;
SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock)
: WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path)), m_write_semaphore(1), m_use_unsafe_sync(options.use_unsafe_sync)
{
{
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;
}
}
2020-05-26 20:53:41 -04:00
void SQLiteBatch::SetupSQLStatements()
{
const std::vector<std::pair<sqlite3_stmt**, const char*>> statements{
{&m_read_stmt, "SELECT value FROM main WHERE key = ?"},
{&m_insert_stmt, "INSERT INTO main VALUES(?, ?)"},
{&m_overwrite_stmt, "INSERT or REPLACE into main values(?, ?)"},
{&m_delete_stmt, "DELETE FROM main WHERE key = ?"},
{&m_delete_prefix_stmt, "DELETE FROM main WHERE instr(key, ?) = 1"},
};
for (const auto& [stmt_prepared, stmt_text] : statements) {
if (*stmt_prepared == nullptr) {
int res = sqlite3_prepare_v2(m_database.m_db, stmt_text, -1, stmt_prepared, nullptr);
if (res != SQLITE_OK) {
throw std::runtime_error(strprintf(
"SQLiteDatabase: Failed to setup SQL statements: %s\n", sqlite3_errstr(res)));
}
2020-05-26 20:53:41 -04:00
}
}
}
SQLiteDatabase::~SQLiteDatabase()
{
Cleanup();
}
void SQLiteDatabase::Cleanup() noexcept
{
AssertLockNotHeld(g_sqlite_mutex);
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));
}
}
}
2020-05-26 20:54:00 -04:00
bool SQLiteDatabase::Verify(bilingual_str& error)
{
assert(m_db);
// Check the application ID matches our network magic
auto read_result = ReadPragmaInteger(m_db, "application_id", "the application id", error);
if (!read_result.has_value()) return false;
uint32_t app_id = static_cast<uint32_t>(read_result.value());
uint32_t net_magic = ReadBE32(Params().MessageStart().data());
if (app_id != net_magic) {
error = strprintf(_("SQLiteDatabase: Unexpected application id. Expected %u, got %u"), net_magic, app_id);
return false;
}
2020-09-09 20:22:32 -04:00
// Check our schema version
read_result = ReadPragmaInteger(m_db, "user_version", "sqlite wallet schema version", error);
if (!read_result.has_value()) return false;
int32_t user_ver = read_result.value();
2020-09-09 20:22:32 -04:00
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;
}
2020-05-26 20:54:00 -04:00
sqlite3_stmt* stmt{nullptr};
int ret = sqlite3_prepare_v2(m_db, "PRAGMA integrity_check", -1, &stmt, nullptr);
2020-05-26 20:54:00 -04:00
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()
{
2020-05-26 20:53:30 -04:00
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) {
2020-10-26 15:10:16 -04:00
if (!m_mock) {
TryCreateDirectories(fs::PathFromString(m_dir_path));
2020-10-26 15:10:16 -04:00
}
2020-05-26 20:53:30 -04:00
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)));
}
ret = sqlite3_extended_result_codes(m_db, 1);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to enable extended result codes: %s\n", sqlite3_errstr(ret)));
}
// Trace SQL statements if tracing is enabled with -debug=walletdb -loglevel=walletdb:trace
if (LogAcceptCategory(BCLog::WALLETDB, BCLog::Level::Trace)) {
ret = sqlite3_trace_v2(m_db, SQLITE_TRACE_STMT, TraceSqlCallback, this);
if (ret != SQLITE_OK) {
LogPrintf("Failed to enable SQL tracing for %s\n", Filename());
}
}
2020-05-26 20:53:30 -04:00
}
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
SetPragma(m_db, "locking_mode", "exclusive", "Unable to change database locking mode to exclusive");
2020-05-26 20:53:30 -04:00
// Now begin a transaction to acquire the exclusive lock. This lock won't be released until we close because of the exclusive locking mode.
int ret = sqlite3_exec(m_db, "BEGIN EXCLUSIVE TRANSACTION", nullptr, nullptr, nullptr);
2020-05-26 20:53:30 -04:00
if (ret != SQLITE_OK) {
throw std::runtime_error("SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another instance of " CLIENT_NAME "?\n");
2020-05-26 20:53:30 -04:00
}
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
SetPragma(m_db, "fullfsync", "true", "Failed to enable fullfsync");
2020-05-26 20:53:30 -04:00
if (m_use_unsafe_sync) {
// Use normal synchronous mode for the journal
LogPrintf("WARNING SQLite is configured to not wait for data to be flushed to disk. Data loss and corruption may occur.\n");
SetPragma(m_db, "synchronous", "OFF", "Failed to set synchronous mode to OFF");
}
2020-05-26 20:53:30 -04:00
// 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().data());
SetPragma(m_db, "application_id", strprintf("%d", static_cast<int32_t>(app_id)),
"Failed to set the application id");
2020-09-09 20:22:32 -04:00
// Set the user version
SetPragma(m_db, "user_version", strprintf("%d", WALLET_SCHEMA_VERSION),
"Failed to set the wallet schema version");
2020-05-26 20:53:30 -04:00
}
}
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
{
2020-05-26 20:53:48 -04:00
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()
{
2020-05-26 20:53:32 -04:00
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;
}
bool SQLiteDatabase::HasActiveTxn()
{
// 'sqlite3_get_autocommit' returns true by default, and false if a transaction has begun and not been committed or rolled back.
return m_db && sqlite3_get_autocommit(m_db) == 0;
}
int SQliteExecHandler::Exec(SQLiteDatabase& database, const std::string& statement)
{
return sqlite3_exec(database.m_db, statement.data(), nullptr, nullptr, nullptr);
}
std::unique_ptr<DatabaseBatch> SQLiteDatabase::MakeBatch(bool flush_on_close)
{
2020-06-16 15:38:12 -04:00
// 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)
{
2020-05-26 20:53:30 -04:00
// Make sure we have a db handle
assert(m_database.m_db);
2020-05-26 20:53:41 -04:00
SetupSQLStatements();
}
void SQLiteBatch::Close()
{
bool force_conn_refresh = false;
// If we began a transaction, and it wasn't committed, abort the transaction in progress
if (m_txn) {
2020-06-16 14:57:30 -04:00
if (TxnAbort()) {
LogPrintf("SQLiteBatch: Batch closed unexpectedly without the transaction being explicitly committed or aborted\n");
} else {
// If transaction cannot be aborted, it means there is a bug or there has been data corruption. Try to recover in this case
// by closing and reopening the database. Closing the database should also ensure that any changes made since the transaction
// was opened will be rolled back and future transactions can succeed without committing old data.
force_conn_refresh = true;
LogPrintf("SQLiteBatch: Batch closed and failed to abort transaction, resetting db connection..\n");
2020-06-16 14:57:30 -04:00
}
}
2020-05-26 20:53:41 -04:00
// Free all of the prepared statements
const std::vector<std::pair<sqlite3_stmt**, const char*>> statements{
{&m_read_stmt, "read"},
{&m_insert_stmt, "insert"},
{&m_overwrite_stmt, "overwrite"},
{&m_delete_stmt, "delete"},
{&m_delete_prefix_stmt, "delete prefix"},
};
for (const auto& [stmt_prepared, stmt_description] : statements) {
int res = sqlite3_finalize(*stmt_prepared);
if (res != SQLITE_OK) {
LogPrintf("SQLiteBatch: Batch closed but could not finalize %s statement: %s\n",
stmt_description, sqlite3_errstr(res));
}
*stmt_prepared = nullptr;
2020-05-26 20:53:41 -04:00
}
if (force_conn_refresh) {
m_database.Close();
try {
m_database.Open();
// If TxnAbort failed and we refreshed the connection, the semaphore was not released, so release it here to avoid deadlocks on future writes.
m_database.m_write_semaphore.post();
} catch (const std::runtime_error&) {
// If open fails, cleanup this object and rethrow the exception
m_database.Close();
throw;
}
}
}
2023-01-03 13:21:44 +01:00
bool SQLiteBatch::ReadKey(DataStream&& key, DataStream& value)
{
if (!m_database.m_db) return false;
assert(m_read_stmt);
// Bind: leftmost parameter in statement is index 1
if (!BindBlobToStatement(m_read_stmt, 1, key, "key")) return false;
int 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
value.clear();
value.write(SpanFromBlob(m_read_stmt, 0));
sqlite3_clear_bindings(m_read_stmt);
sqlite3_reset(m_read_stmt);
return true;
}
2023-01-03 13:21:44 +01:00
bool SQLiteBatch::WriteKey(DataStream&& key, DataStream&& 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
if (!BindBlobToStatement(stmt, 1, key, "key")) return false;
if (!BindBlobToStatement(stmt, 2, value, "value")) return false;
// Acquire semaphore if not previously acquired when creating a transaction.
if (!m_txn) m_database.m_write_semaphore.wait();
// Execute
int 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));
}
if (!m_txn) m_database.m_write_semaphore.post();
return res == SQLITE_DONE;
}
bool SQLiteBatch::ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob)
{
if (!m_database.m_db) return false;
assert(stmt);
// Bind: leftmost parameter in statement is index 1
if (!BindBlobToStatement(stmt, 1, blob, "key")) return false;
// Acquire semaphore if not previously acquired when creating a transaction.
if (!m_txn) m_database.m_write_semaphore.wait();
// Execute
int 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));
}
if (!m_txn) m_database.m_write_semaphore.post();
return res == SQLITE_DONE;
}
bool SQLiteBatch::EraseKey(DataStream&& key)
{
return ExecStatement(m_delete_stmt, key);
}
bool SQLiteBatch::ErasePrefix(Span<const std::byte> prefix)
{
return ExecStatement(m_delete_prefix_stmt, prefix);
}
2023-01-03 13:21:44 +01:00
bool SQLiteBatch::HasKey(DataStream&& key)
{
if (!m_database.m_db) return false;
assert(m_read_stmt);
// Bind: leftmost parameter in statement is index 1
if (!BindBlobToStatement(m_read_stmt, 1, key, "key")) return false;
int res = sqlite3_step(m_read_stmt);
sqlite3_clear_bindings(m_read_stmt);
sqlite3_reset(m_read_stmt);
return res == SQLITE_ROW;
}
2023-01-03 13:21:44 +01:00
DatabaseCursor::Status SQLiteCursor::Next(DataStream& key, DataStream& value)
{
int res = sqlite3_step(m_cursor_stmt);
if (res == SQLITE_DONE) {
return Status::DONE;
}
if (res != SQLITE_ROW) {
LogPrintf("%s: Unable to execute cursor step: %s\n", __func__, sqlite3_errstr(res));
return Status::FAIL;
}
key.clear();
value.clear();
// Leftmost column in result is index 0
key.write(SpanFromBlob(m_cursor_stmt, 0));
value.write(SpanFromBlob(m_cursor_stmt, 1));
return Status::MORE;
}
SQLiteCursor::~SQLiteCursor()
{
sqlite3_clear_bindings(m_cursor_stmt);
sqlite3_reset(m_cursor_stmt);
int res = sqlite3_finalize(m_cursor_stmt);
if (res != SQLITE_OK) {
LogPrintf("%s: cursor closed but could not finalize cursor statement: %s\n",
__func__, sqlite3_errstr(res));
}
}
std::unique_ptr<DatabaseCursor> SQLiteBatch::GetNewCursor()
{
if (!m_database.m_db) return nullptr;
auto cursor = std::make_unique<SQLiteCursor>();
const char* stmt_text = "SELECT key, value FROM main";
int res = sqlite3_prepare_v2(m_database.m_db, stmt_text, -1, &cursor->m_cursor_stmt, nullptr);
if (res != SQLITE_OK) {
throw std::runtime_error(strprintf(
"%s: Failed to setup cursor SQL statement: %s\n", __func__, sqlite3_errstr(res)));
}
return cursor;
}
std::unique_ptr<DatabaseCursor> SQLiteBatch::GetNewPrefixCursor(Span<const std::byte> prefix)
{
if (!m_database.m_db) return nullptr;
// To get just the records we want, the SQL statement does a comparison of the binary data
// where the data must be greater than or equal to the prefix, and less than
// the prefix incremented by one (when interpreted as an integer)
std::vector<std::byte> start_range(prefix.begin(), prefix.end());
std::vector<std::byte> end_range(prefix.begin(), prefix.end());
auto it = end_range.rbegin();
for (; it != end_range.rend(); ++it) {
if (*it == std::byte(std::numeric_limits<unsigned char>::max())) {
*it = std::byte(0);
continue;
}
*it = std::byte(std::to_integer<unsigned char>(*it) + 1);
break;
}
if (it == end_range.rend()) {
// If the prefix is all 0xff bytes, clear end_range as we won't need it
end_range.clear();
}
auto cursor = std::make_unique<SQLiteCursor>(start_range, end_range);
if (!cursor) return nullptr;
const char* stmt_text = end_range.empty() ? "SELECT key, value FROM main WHERE key >= ?" :
"SELECT key, value FROM main WHERE key >= ? AND key < ?";
int res = sqlite3_prepare_v2(m_database.m_db, stmt_text, -1, &cursor->m_cursor_stmt, nullptr);
if (res != SQLITE_OK) {
throw std::runtime_error(strprintf(
"SQLiteDatabase: Failed to setup cursor SQL statement: %s\n", sqlite3_errstr(res)));
}
if (!BindBlobToStatement(cursor->m_cursor_stmt, 1, cursor->m_prefix_range_start, "prefix_start")) return nullptr;
if (!end_range.empty()) {
if (!BindBlobToStatement(cursor->m_cursor_stmt, 2, cursor->m_prefix_range_end, "prefix_end")) return nullptr;
}
return cursor;
}
bool SQLiteBatch::TxnBegin()
{
if (!m_database.m_db || m_txn) return false;
m_database.m_write_semaphore.wait();
Assert(!m_database.HasActiveTxn());
int res = Assert(m_exec_handler)->Exec(m_database, "BEGIN TRANSACTION");
if (res != SQLITE_OK) {
LogPrintf("SQLiteBatch: Failed to begin the transaction\n");
m_database.m_write_semaphore.post();
} else {
m_txn = true;
}
return res == SQLITE_OK;
}
bool SQLiteBatch::TxnCommit()
{
if (!m_database.m_db || !m_txn) return false;
Assert(m_database.HasActiveTxn());
int res = Assert(m_exec_handler)->Exec(m_database, "COMMIT TRANSACTION");
if (res != SQLITE_OK) {
LogPrintf("SQLiteBatch: Failed to commit the transaction\n");
} else {
m_txn = false;
m_database.m_write_semaphore.post();
}
return res == SQLITE_OK;
}
bool SQLiteBatch::TxnAbort()
{
if (!m_database.m_db || !m_txn) return false;
Assert(m_database.HasActiveTxn());
int res = Assert(m_exec_handler)->Exec(m_database, "ROLLBACK TRANSACTION");
if (res != SQLITE_OK) {
LogPrintf("SQLiteBatch: Failed to abort the transaction\n");
} else {
m_txn = false;
m_database.m_write_semaphore.post();
}
return res == SQLITE_OK;
}
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
{
2020-05-26 20:54:00 -04:00
try {
fs::path data_file = SQLiteDataFile(path);
auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file, options);
2020-05-26 20:54:00 -04:00
if (options.verify && !db->Verify(error)) {
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
}
status = DatabaseStatus::SUCCESS;
2020-05-26 20:54:00 -04:00
return db;
} catch (const std::runtime_error& e) {
status = DatabaseStatus::FAILED_LOAD;
2020-11-05 11:28:37 +02:00
error = Untranslated(e.what());
2020-05-26 20:54:00 -04:00
return nullptr;
}
}
2020-05-26 20:53:05 -04:00
std::string SQLiteDatabaseVersion()
{
return std::string(sqlite3_libversion());
}
} // namespace wallet