v29.4-roots.1 · Commit 4: [PATCH 04/13] wallet: harden database and signing behavior
src/wallet/sqlite.cpp
Before: src/wallet/sqlite.cpp · After: src/wallet/sqlite.cpp
File metadata
index 896a2fc0f33fdf8e93ec97b6b2f723322b28f973..34429fb34b471e3ce9035223d5741141912c36ce 100644
Download original patch · Release notes
Diff rows 1–156 of 156. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
src/wallet/sqlite.cpp
CHANGED
|
@@ -40,7 +40,7 @@ static void ErrorLogCallback(void* arg, int code, const char* msg)
|
|
| 40 |
// invoked."
|
| 41 |
// Assert that this is the case:
|
| 42 |
assert(arg == nullptr);
|
| 43 |
-
LogPrintf("SQLite Error. Code: %d. Message: %s\n", code, msg);
|
| 44 |
}
|
| 45 |
|
| 46 |
static int TraceSqlCallback(unsigned code, void* context, void* param1, void* param2)
|
|
@@ -69,7 +69,7 @@ static bool BindBlobToStatement(sqlite3_stmt* stmt,
|
|
| 69 |
// instead of the empty blob value X'', which would mess up SQL comparisons.
|
| 70 |
int res = sqlite3_bind_blob(stmt, index, blob.data() ? static_cast<const void*>(blob.data()) : "", blob.size(), SQLITE_STATIC);
|
| 71 |
if (res != SQLITE_OK) {
|
| 72 |
-
LogPrintf("Unable to bind %s to statement: %s\n", description, sqlite3_errstr(res));
|
| 73 |
sqlite3_clear_bindings(stmt);
|
| 74 |
sqlite3_reset(stmt);
|
| 75 |
return false;
|
|
@@ -182,7 +182,7 @@ void SQLiteDatabase::Cleanup() noexcept
|
|
| 182 |
if (--g_sqlite_count == 0) {
|
| 183 |
int ret = sqlite3_shutdown();
|
| 184 |
if (ret != SQLITE_OK) {
|
| 185 |
-
LogPrintf("SQLiteDatabase: Failed to shutdown SQLite: %s\n", sqlite3_errstr(ret));
|
| 186 |
}
|
| 187 |
}
|
| 188 |
}
|
|
@@ -254,7 +254,11 @@ void SQLiteDatabase::Open()
|
|
| 254 |
if (m_db == nullptr) {
|
| 255 |
if (!m_mock) {
|
| 256 |
TryCreateDirectories(m_dir_path);
|
|
|
|
|
|
|
|
|
|
| 257 |
}
|
|
|
|
| 258 |
int ret = sqlite3_open_v2(m_file_path.c_str(), &m_db, flags, nullptr);
|
| 259 |
if (ret != SQLITE_OK) {
|
| 260 |
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database: %s\n", sqlite3_errstr(ret)));
|
|
@@ -267,7 +271,7 @@ void SQLiteDatabase::Open()
|
|
| 267 |
if (LogAcceptCategory(BCLog::WALLETDB, BCLog::Level::Trace)) {
|
| 268 |
ret = sqlite3_trace_v2(m_db, SQLITE_TRACE_STMT, TraceSqlCallback, this);
|
| 269 |
if (ret != SQLITE_OK) {
|
| 270 |
-
LogPrintf("Failed to enable SQL tracing for %s\n", Filename());
|
| 271 |
}
|
| 272 |
}
|
| 273 |
}
|
|
@@ -294,7 +298,7 @@ void SQLiteDatabase::Open()
|
|
| 294 |
|
| 295 |
if (m_use_unsafe_sync) {
|
| 296 |
// Use normal synchronous mode for the journal
|
| 297 |
-
LogPrintf("WARNING SQLite is configured to not wait for data to be flushed to disk. Data loss and corruption may occur.\n");
|
| 298 |
SetPragma(m_db, "synchronous", "OFF", "Failed to set synchronous mode to OFF");
|
| 299 |
}
|
| 300 |
|
|
@@ -353,14 +357,14 @@ bool SQLiteDatabase::Backup(const std::string& dest) const
|
|
| 353 |
}
|
| 354 |
sqlite3_backup* backup = sqlite3_backup_init(db_copy, "main", m_db, "main");
|
| 355 |
if (!backup) {
|
| 356 |
-
LogPrintf("%s: Unable to begin backup: %s\n", __func__, sqlite3_errmsg(m_db));
|
| 357 |
sqlite3_close(db_copy);
|
| 358 |
return false;
|
| 359 |
}
|
| 360 |
// Specifying -1 will copy all of the pages
|
| 361 |
res = sqlite3_backup_step(backup, -1);
|
| 362 |
if (res != SQLITE_DONE) {
|
| 363 |
-
LogPrintf("%s: Unable to backup: %s\n", __func__, sqlite3_errstr(res));
|
| 364 |
sqlite3_backup_finish(backup);
|
| 365 |
sqlite3_close(db_copy);
|
| 366 |
return false;
|
|
@@ -412,13 +416,13 @@ void SQLiteBatch::Close()
|
|
| 412 |
// If we began a transaction, and it wasn't committed, abort the transaction in progress
|
| 413 |
if (m_txn) {
|
| 414 |
if (TxnAbort()) {
|
| 415 |
-
LogPrintf("SQLiteBatch: Batch closed unexpectedly without the transaction being explicitly committed or aborted\n");
|
| 416 |
} else {
|
| 417 |
// If transaction cannot be aborted, it means there is a bug or there has been data corruption. Try to recover in this case
|
| 418 |
// by closing and reopening the database. Closing the database should also ensure that any changes made since the transaction
|
| 419 |
// was opened will be rolled back and future transactions can succeed without committing old data.
|
| 420 |
force_conn_refresh = true;
|
| 421 |
-
LogPrintf("SQLiteBatch: Batch closed and failed to abort transaction, resetting db connection..\n");
|
| 422 |
}
|
| 423 |
}
|
| 424 |
|
|
@@ -434,7 +438,7 @@ void SQLiteBatch::Close()
|
|
| 434 |
for (const auto& [stmt_prepared, stmt_description] : statements) {
|
| 435 |
int res = sqlite3_finalize(*stmt_prepared);
|
| 436 |
if (res != SQLITE_OK) {
|
| 437 |
-
LogPrintf("SQLiteBatch: Batch closed but could not finalize %s statement: %s\n",
|
| 438 |
stmt_description, sqlite3_errstr(res));
|
| 439 |
}
|
| 440 |
*stmt_prepared = nullptr;
|
|
@@ -465,7 +469,7 @@ bool SQLiteBatch::ReadKey(DataStream&& key, DataStream& value)
|
|
| 465 |
if (res != SQLITE_ROW) {
|
| 466 |
if (res != SQLITE_DONE) {
|
| 467 |
// SQLITE_DONE means "not found", don't log an error in that case.
|
| 468 |
-
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
|
| 469 |
}
|
| 470 |
sqlite3_clear_bindings(m_read_stmt);
|
| 471 |
sqlite3_reset(m_read_stmt);
|
|
@@ -505,7 +509,7 @@ bool SQLiteBatch::WriteKey(DataStream&& key, DataStream&& value, bool overwrite)
|
|
| 505 |
sqlite3_clear_bindings(stmt);
|
| 506 |
sqlite3_reset(stmt);
|
| 507 |
if (res != SQLITE_DONE) {
|
| 508 |
-
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
|
| 509 |
}
|
| 510 |
|
| 511 |
if (!m_txn) m_database.m_write_semaphore.post();
|
|
@@ -529,7 +533,7 @@ bool SQLiteBatch::ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob)
|
|
| 529 |
sqlite3_clear_bindings(stmt);
|
| 530 |
sqlite3_reset(stmt);
|
| 531 |
if (res != SQLITE_DONE) {
|
| 532 |
-
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
|
| 533 |
}
|
| 534 |
|
| 535 |
if (!m_txn) m_database.m_write_semaphore.post();
|
|
@@ -567,7 +571,7 @@ DatabaseCursor::Status SQLiteCursor::Next(DataStream& key, DataStream& value)
|
|
| 567 |
return Status::DONE;
|
| 568 |
}
|
| 569 |
if (res != SQLITE_ROW) {
|
| 570 |
-
LogPrintf("%s: Unable to execute cursor step: %s\n", __func__, sqlite3_errstr(res));
|
| 571 |
return Status::FAIL;
|
| 572 |
}
|
| 573 |
|
|
@@ -586,8 +590,8 @@ SQLiteCursor::~SQLiteCursor()
|
|
| 586 |
sqlite3_reset(m_cursor_stmt);
|
| 587 |
int res = sqlite3_finalize(m_cursor_stmt);
|
| 588 |
if (res != SQLITE_OK) {
|
| 589 |
-
LogPrintf("%s: cursor closed but could not finalize cursor statement: %s\n",
|
| 590 |
-
__func__, sqlite3_errstr(res));
|
| 591 |
}
|
| 592 |
}
|
| 593 |
|
|
@@ -655,7 +659,7 @@ bool SQLiteBatch::TxnBegin()
|
|
| 655 |
Assert(!m_database.HasActiveTxn());
|
| 656 |
int res = Assert(m_exec_handler)->Exec(m_database, "BEGIN TRANSACTION");
|
| 657 |
if (res != SQLITE_OK) {
|
| 658 |
-
LogPrintf("SQLiteBatch: Failed to begin the transaction\n");
|
| 659 |
m_database.m_write_semaphore.post();
|
| 660 |
} else {
|
| 661 |
m_txn = true;
|
|
@@ -669,7 +673,7 @@ bool SQLiteBatch::TxnCommit()
|
|
| 669 |
Assert(m_database.HasActiveTxn());
|
| 670 |
int res = Assert(m_exec_handler)->Exec(m_database, "COMMIT TRANSACTION");
|
| 671 |
if (res != SQLITE_OK) {
|
| 672 |
-
LogPrintf("SQLiteBatch: Failed to commit the transaction\n");
|
| 673 |
} else {
|
| 674 |
m_txn = false;
|
| 675 |
m_database.m_write_semaphore.post();
|
|
@@ -683,7 +687,7 @@ bool SQLiteBatch::TxnAbort()
|
|
| 683 |
Assert(m_database.HasActiveTxn());
|
| 684 |
int res = Assert(m_exec_handler)->Exec(m_database, "ROLLBACK TRANSACTION");
|
| 685 |
if (res != SQLITE_OK) {
|
| 686 |
-
LogPrintf("SQLiteBatch: Failed to abort the transaction\n");
|
| 687 |
} else {
|
| 688 |
m_txn = false;
|
| 689 |
m_database.m_write_semaphore.post();
|
|
|
|
| 40 |
// invoked."
|
| 41 |
// Assert that this is the case:
|
| 42 |
assert(arg == nullptr);
|
| 43 |
+
LogWarning("SQLite Error. Code: %d. Message: %s", code, msg);
|
| 44 |
}
|
| 45 |
|
| 46 |
static int TraceSqlCallback(unsigned code, void* context, void* param1, void* param2)
|
|
|
|
| 69 |
// instead of the empty blob value X'', which would mess up SQL comparisons.
|
| 70 |
int res = sqlite3_bind_blob(stmt, index, blob.data() ? static_cast<const void*>(blob.data()) : "", blob.size(), SQLITE_STATIC);
|
| 71 |
if (res != SQLITE_OK) {
|
| 72 |
+
LogWarning("Unable to bind %s to statement: %s", description, sqlite3_errstr(res));
|
| 73 |
sqlite3_clear_bindings(stmt);
|
| 74 |
sqlite3_reset(stmt);
|
| 75 |
return false;
|
|
|
|
| 182 |
if (--g_sqlite_count == 0) {
|
| 183 |
int ret = sqlite3_shutdown();
|
| 184 |
if (ret != SQLITE_OK) {
|
| 185 |
+
LogWarning("SQLiteDatabase: Failed to shutdown SQLite: %s", sqlite3_errstr(ret));
|
| 186 |
}
|
| 187 |
}
|
| 188 |
}
|
|
|
|
| 254 |
if (m_db == nullptr) {
|
| 255 |
if (!m_mock) {
|
| 256 |
TryCreateDirectories(m_dir_path);
|
| 257 |
+
if (!IsDirWritable(m_dir_path)) {
|
| 258 |
+
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database in directory '%s': directory is not writable", fs::PathToString(m_dir_path)));
|
| 259 |
+
}
|
| 260 |
}
|
| 261 |
+
|
| 262 |
int ret = sqlite3_open_v2(m_file_path.c_str(), &m_db, flags, nullptr);
|
| 263 |
if (ret != SQLITE_OK) {
|
| 264 |
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database: %s\n", sqlite3_errstr(ret)));
|
|
|
|
| 271 |
if (LogAcceptCategory(BCLog::WALLETDB, BCLog::Level::Trace)) {
|
| 272 |
ret = sqlite3_trace_v2(m_db, SQLITE_TRACE_STMT, TraceSqlCallback, this);
|
| 273 |
if (ret != SQLITE_OK) {
|
| 274 |
+
LogWarning("Failed to enable SQL tracing for %s", Filename());
|
| 275 |
}
|
| 276 |
}
|
| 277 |
}
|
|
|
|
| 298 |
|
| 299 |
if (m_use_unsafe_sync) {
|
| 300 |
// Use normal synchronous mode for the journal
|
| 301 |
+
LogWarning("SQLite is configured to not wait for data to be flushed to disk. Data loss and corruption may occur.");
|
| 302 |
SetPragma(m_db, "synchronous", "OFF", "Failed to set synchronous mode to OFF");
|
| 303 |
}
|
| 304 |
|
|
|
|
| 357 |
}
|
| 358 |
sqlite3_backup* backup = sqlite3_backup_init(db_copy, "main", m_db, "main");
|
| 359 |
if (!backup) {
|
| 360 |
+
LogWarning("Unable to begin sqlite backup: %s", sqlite3_errmsg(m_db));
|
| 361 |
sqlite3_close(db_copy);
|
| 362 |
return false;
|
| 363 |
}
|
| 364 |
// Specifying -1 will copy all of the pages
|
| 365 |
res = sqlite3_backup_step(backup, -1);
|
| 366 |
if (res != SQLITE_DONE) {
|
| 367 |
+
LogWarning("Unable to continue sqlite backup: %s", sqlite3_errstr(res));
|
| 368 |
sqlite3_backup_finish(backup);
|
| 369 |
sqlite3_close(db_copy);
|
| 370 |
return false;
|
|
|
|
| 416 |
// If we began a transaction, and it wasn't committed, abort the transaction in progress
|
| 417 |
if (m_txn) {
|
| 418 |
if (TxnAbort()) {
|
| 419 |
+
LogWarning("SQLiteBatch: Batch closed unexpectedly without the transaction being explicitly committed or aborted");
|
| 420 |
} else {
|
| 421 |
// If transaction cannot be aborted, it means there is a bug or there has been data corruption. Try to recover in this case
|
| 422 |
// by closing and reopening the database. Closing the database should also ensure that any changes made since the transaction
|
| 423 |
// was opened will be rolled back and future transactions can succeed without committing old data.
|
| 424 |
force_conn_refresh = true;
|
| 425 |
+
LogWarning("SQLiteBatch: Batch closed and failed to abort transaction, resetting db connection..");
|
| 426 |
}
|
| 427 |
}
|
| 428 |
|
|
|
|
| 438 |
for (const auto& [stmt_prepared, stmt_description] : statements) {
|
| 439 |
int res = sqlite3_finalize(*stmt_prepared);
|
| 440 |
if (res != SQLITE_OK) {
|
| 441 |
+
LogWarning("SQLiteBatch: Batch closed but could not finalize %s statement: %s",
|
| 442 |
stmt_description, sqlite3_errstr(res));
|
| 443 |
}
|
| 444 |
*stmt_prepared = nullptr;
|
|
|
|
| 469 |
if (res != SQLITE_ROW) {
|
| 470 |
if (res != SQLITE_DONE) {
|
| 471 |
// SQLITE_DONE means "not found", don't log an error in that case.
|
| 472 |
+
LogWarning("Unable to execute read statement: %s", sqlite3_errstr(res));
|
| 473 |
}
|
| 474 |
sqlite3_clear_bindings(m_read_stmt);
|
| 475 |
sqlite3_reset(m_read_stmt);
|
|
|
|
| 509 |
sqlite3_clear_bindings(stmt);
|
| 510 |
sqlite3_reset(stmt);
|
| 511 |
if (res != SQLITE_DONE) {
|
| 512 |
+
LogWarning("Unable to execute write statement: %s", sqlite3_errstr(res));
|
| 513 |
}
|
| 514 |
|
| 515 |
if (!m_txn) m_database.m_write_semaphore.post();
|
|
|
|
| 533 |
sqlite3_clear_bindings(stmt);
|
| 534 |
sqlite3_reset(stmt);
|
| 535 |
if (res != SQLITE_DONE) {
|
| 536 |
+
LogWarning("Unable to execute exec statement: %s", sqlite3_errstr(res));
|
| 537 |
}
|
| 538 |
|
| 539 |
if (!m_txn) m_database.m_write_semaphore.post();
|
|
|
|
| 571 |
return Status::DONE;
|
| 572 |
}
|
| 573 |
if (res != SQLITE_ROW) {
|
| 574 |
+
LogWarning("Unable to execute cursor step: %s", sqlite3_errstr(res));
|
| 575 |
return Status::FAIL;
|
| 576 |
}
|
| 577 |
|
|
|
|
| 590 |
sqlite3_reset(m_cursor_stmt);
|
| 591 |
int res = sqlite3_finalize(m_cursor_stmt);
|
| 592 |
if (res != SQLITE_OK) {
|
| 593 |
+
LogWarning("Cursor closed but could not finalize cursor statement: %s",
|
| 594 |
+
sqlite3_errstr(res));
|
| 595 |
}
|
| 596 |
}
|
| 597 |
|
|
|
|
| 659 |
Assert(!m_database.HasActiveTxn());
|
| 660 |
int res = Assert(m_exec_handler)->Exec(m_database, "BEGIN TRANSACTION");
|
| 661 |
if (res != SQLITE_OK) {
|
| 662 |
+
LogWarning("SQLiteBatch: Failed to begin the transaction");
|
| 663 |
m_database.m_write_semaphore.post();
|
| 664 |
} else {
|
| 665 |
m_txn = true;
|
|
|
|
| 673 |
Assert(m_database.HasActiveTxn());
|
| 674 |
int res = Assert(m_exec_handler)->Exec(m_database, "COMMIT TRANSACTION");
|
| 675 |
if (res != SQLITE_OK) {
|
| 676 |
+
LogWarning("SQLiteBatch: Failed to commit the transaction");
|
| 677 |
} else {
|
| 678 |
m_txn = false;
|
| 679 |
m_database.m_write_semaphore.post();
|
|
|
|
| 687 |
Assert(m_database.HasActiveTxn());
|
| 688 |
int res = Assert(m_exec_handler)->Exec(m_database, "ROLLBACK TRANSACTION");
|
| 689 |
if (res != SQLITE_OK) {
|
| 690 |
+
LogWarning("SQLiteBatch: Failed to abort the transaction");
|
| 691 |
} else {
|
| 692 |
m_txn = false;
|
| 693 |
m_database.m_write_semaphore.post();
|