From a72fc5fa0c9811ff5a57af90bf4440023c94ac5a Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Mon, 17 Aug 2026 10:50:00 -0300 Subject: wallet: add typed SQLite wallet records --- src/selftest.cpp | 51 ++++++++++++++++++++++++++++++++++ src/wallet_sqlite.cpp | 43 ++++++++++++++++++++++++++-- src/wallet_sqlite.h | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 167 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/selftest.cpp b/src/selftest.cpp index 14bf45a..4b1e733 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp @@ -1115,6 +1115,57 @@ static int RunWalletSQLiteSelfTest() nFail += Check(!ssReadDefault.fail() && vchReadPubKey == vchPubKey, "SQLite wallet preserves public key blobs") ? 0 : 1; } + + { + CWalletDBSQLite db; + nFail += Check(db.Open(strPath, strError), + "SQLite wallet reopens for typed records") ? 0 : 1; + + nFail += Check(db.WriteTypedRecord(make_pair(string("setting"), + string("sqlite-typed")), + (int64)777, strError), + "SQLite wallet writes a typed setting record") ? 0 : 1; + + int64 nTypedRead = 0; + nFail += Check(db.ReadTypedRecord(make_pair(string("setting"), + string("sqlite-typed")), + nTypedRead, strError) && + nTypedRead == 777, + "SQLite wallet reads a typed setting record") ? 0 : 1; + + nFail += Check(!db.WriteTypedRecord(make_pair(string("setting"), + string("sqlite-typed")), + (int64)888, strError, false), + "SQLite wallet refuses typed overwrite when asked") ? 0 : 1; + + nTypedRead = 0; + nFail += Check(db.ReadTypedRecord(make_pair(string("setting"), + string("sqlite-typed")), + nTypedRead, strError) && + nTypedRead == 777, + "SQLite wallet preserves the old typed value after overwrite refusal") ? 0 : 1; + + nFail += Check(db.EraseTypedRecord(make_pair(string("setting"), + string("sqlite-typed")), + strError), + "SQLite wallet erases a typed record") ? 0 : 1; + + nTypedRead = 0; + nFail += Check(!db.ReadTypedRecord(make_pair(string("setting"), + string("sqlite-typed")), + nTypedRead, strError), + "SQLite wallet typed erase removes the record") ? 0 : 1; + + nFail += Check(db.WriteTypedRecord(string("defaultkey"), vchPubKey, + strError), + "SQLite wallet writes a typed defaultkey record") ? 0 : 1; + + vector vchTypedPubKey; + nFail += Check(db.ReadTypedRecord(string("defaultkey"), vchTypedPubKey, + strError) && + vchTypedPubKey == vchPubKey, + "SQLite wallet reads a typed defaultkey record") ? 0 : 1; + } } catch (const std::exception& e) { diff --git a/src/wallet_sqlite.cpp b/src/wallet_sqlite.cpp index ca81573..f4b4b37 100644 --- a/src/wallet_sqlite.cpp +++ b/src/wallet_sqlite.cpp @@ -156,7 +156,8 @@ bool CWalletDBSQLite::InitSchema(string& strError) bool CWalletDBSQLite::WriteRecord(const vector& vchKey, const vector& vchValue, - string& strError) + string& strError, + bool fOverwrite) { if (!pdb) { @@ -170,8 +171,9 @@ bool CWalletDBSQLite::WriteRecord(const vector& vchKey, } sqlite3_stmt* stmt = NULL; - const char* pszSql = - "INSERT OR REPLACE INTO wallet_records(key, value) VALUES(?, ?);"; + const char* pszSql = fOverwrite + ? "INSERT OR REPLACE INTO wallet_records(key, value) VALUES(?, ?);" + : "INSERT INTO wallet_records(key, value) VALUES(?, ?);"; int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); if (ret != SQLITE_OK) { @@ -250,6 +252,41 @@ bool CWalletDBSQLite::ReadRecord(const vector& vchKey, return false; } +bool CWalletDBSQLite::EraseRecord(const vector& vchKey, + string& strError) +{ + if (!pdb) + { + strError = "SQLite wallet is not open"; + return false; + } + if (vchKey.empty()) + { + strError = "SQLite wallet record key is empty"; + return false; + } + + sqlite3_stmt* stmt = NULL; + const char* pszSql = "DELETE FROM wallet_records WHERE key = ?;"; + int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); + if (ret != SQLITE_OK) + { + SetSQLiteError(pdb, "cannot prepare SQLite wallet erase", strError); + return false; + } + + ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(), + SQLITE_TRANSIENT); + if (ret == SQLITE_OK) + ret = sqlite3_step(stmt); + + bool fOk = (ret == SQLITE_DONE); + if (!fOk) + SetSQLiteError(pdb, "cannot erase SQLite wallet record", strError); + sqlite3_finalize(stmt); + return fOk; +} + bool CWalletDBSQLite::ScanRecords(CWalletRecordVisitor& visitor, string& strError) { diff --git a/src/wallet_sqlite.h b/src/wallet_sqlite.h index 5e6467b..d27e13a 100644 --- a/src/wallet_sqlite.h +++ b/src/wallet_sqlite.h @@ -25,13 +25,88 @@ public: bool WriteRecord(const vector& vchKey, const vector& vchValue, - string& strError); + string& strError, + bool fOverwrite=true); bool ReadRecord(const vector& vchKey, vector& vchValueRet, string& strError); + bool EraseRecord(const vector& vchKey, + string& strError); bool ScanRecords(CWalletRecordVisitor& visitor, string& strError); bool CountRecords(int& nRecordsRet, string& strError); + template + bool WriteTypedRecord(const K& key, const T& value, string& strError, + bool fOverwrite=true) + { + CDataStream ssKey(SER_DISK); + ssKey.reserve(1000); + ssKey << key; + + CDataStream ssValue(SER_DISK); + ssValue.reserve(10000); + ssValue << value; + + vector vchKey(ssKey.begin(), ssKey.end()); + vector vchValue(ssValue.begin(), ssValue.end()); + bool fOk = WriteRecord(vchKey, vchValue, strError, fOverwrite); + + if (!vchKey.empty()) + memset(&vchKey[0], 0, vchKey.size()); + if (!vchValue.empty()) + memset(&vchValue[0], 0, vchValue.size()); + if (!ssKey.empty()) + memset(&ssKey[0], 0, ssKey.size()); + if (!ssValue.empty()) + memset(&ssValue[0], 0, ssValue.size()); + return fOk; + } + + template + bool ReadTypedRecord(const K& key, T& valueRet, string& strError) + { + CDataStream ssKey(SER_DISK); + ssKey.reserve(1000); + ssKey << key; + vector vchKey(ssKey.begin(), ssKey.end()); + + vector vchValue; + bool fOk = ReadRecord(vchKey, vchValue, strError); + if (!vchKey.empty()) + memset(&vchKey[0], 0, vchKey.size()); + if (!ssKey.empty()) + memset(&ssKey[0], 0, ssKey.size()); + if (!fOk) + return false; + + CDataStream ssValue(vchValue, SER_DISK); + ssValue >> valueRet; + bool fReadOk = !ssValue.fail(); + if (!vchValue.empty()) + memset(&vchValue[0], 0, vchValue.size()); + if (!ssValue.empty()) + memset(&ssValue[0], 0, ssValue.size()); + if (!fReadOk) + strError = "SQLite wallet record could not be deserialized"; + return fReadOk; + } + + template + bool EraseTypedRecord(const K& key, string& strError) + { + CDataStream ssKey(SER_DISK); + ssKey.reserve(1000); + ssKey << key; + vector vchKey(ssKey.begin(), ssKey.end()); + + bool fOk = EraseRecord(vchKey, strError); + if (!vchKey.empty()) + memset(&vchKey[0], 0, vchKey.size()); + if (!ssKey.empty()) + memset(&ssKey[0], 0, ssKey.size()); + return fOk; + } + private: sqlite3* pdb; -- cgit v1.2.3