diff options
| author | Bitflash <admin@bitflash.network> | 2026-08-17 11:08:42 -0300 |
|---|---|---|
| committer | Bitflash <admin@bitflash.network> | 2026-08-17 11:08:42 -0300 |
| commit | 8e91ff3a57a085663312a6e1821ec4cb1c152144 (patch) | |
| tree | d511b288e1d0f555723114a9502c897b07cb048a /src | |
| parent | f7033201689c6eb70f16b242b9f90be548f67aa5 (diff) | |
| parent | a72fc5fa0c9811ff5a57af90bf4440023c94ac5a (diff) | |
Merge pull request 'wallet: add typed SQLite wallet records' (#56) from wallet-storage-backend-stage into main
Diffstat (limited to 'src')
| -rw-r--r-- | src/selftest.cpp | 51 | ||||
| -rw-r--r-- | src/wallet_sqlite.cpp | 43 | ||||
| -rw-r--r-- | src/wallet_sqlite.h | 77 |
3 files changed, 167 insertions, 4 deletions
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() | |||
| 1115 | nFail += Check(!ssReadDefault.fail() && vchReadPubKey == vchPubKey, | 1115 | nFail += Check(!ssReadDefault.fail() && vchReadPubKey == vchPubKey, |
| 1116 | "SQLite wallet preserves public key blobs") ? 0 : 1; | 1116 | "SQLite wallet preserves public key blobs") ? 0 : 1; |
| 1117 | } | 1117 | } |
| 1118 | |||
| 1119 | { | ||
| 1120 | CWalletDBSQLite db; | ||
| 1121 | nFail += Check(db.Open(strPath, strError), | ||
| 1122 | "SQLite wallet reopens for typed records") ? 0 : 1; | ||
| 1123 | |||
| 1124 | nFail += Check(db.WriteTypedRecord(make_pair(string("setting"), | ||
| 1125 | string("sqlite-typed")), | ||
| 1126 | (int64)777, strError), | ||
| 1127 | "SQLite wallet writes a typed setting record") ? 0 : 1; | ||
| 1128 | |||
| 1129 | int64 nTypedRead = 0; | ||
| 1130 | nFail += Check(db.ReadTypedRecord(make_pair(string("setting"), | ||
| 1131 | string("sqlite-typed")), | ||
| 1132 | nTypedRead, strError) && | ||
| 1133 | nTypedRead == 777, | ||
| 1134 | "SQLite wallet reads a typed setting record") ? 0 : 1; | ||
| 1135 | |||
| 1136 | nFail += Check(!db.WriteTypedRecord(make_pair(string("setting"), | ||
| 1137 | string("sqlite-typed")), | ||
| 1138 | (int64)888, strError, false), | ||
| 1139 | "SQLite wallet refuses typed overwrite when asked") ? 0 : 1; | ||
| 1140 | |||
| 1141 | nTypedRead = 0; | ||
| 1142 | nFail += Check(db.ReadTypedRecord(make_pair(string("setting"), | ||
| 1143 | string("sqlite-typed")), | ||
| 1144 | nTypedRead, strError) && | ||
| 1145 | nTypedRead == 777, | ||
| 1146 | "SQLite wallet preserves the old typed value after overwrite refusal") ? 0 : 1; | ||
| 1147 | |||
| 1148 | nFail += Check(db.EraseTypedRecord(make_pair(string("setting"), | ||
| 1149 | string("sqlite-typed")), | ||
| 1150 | strError), | ||
| 1151 | "SQLite wallet erases a typed record") ? 0 : 1; | ||
| 1152 | |||
| 1153 | nTypedRead = 0; | ||
| 1154 | nFail += Check(!db.ReadTypedRecord(make_pair(string("setting"), | ||
| 1155 | string("sqlite-typed")), | ||
| 1156 | nTypedRead, strError), | ||
| 1157 | "SQLite wallet typed erase removes the record") ? 0 : 1; | ||
| 1158 | |||
| 1159 | nFail += Check(db.WriteTypedRecord(string("defaultkey"), vchPubKey, | ||
| 1160 | strError), | ||
| 1161 | "SQLite wallet writes a typed defaultkey record") ? 0 : 1; | ||
| 1162 | |||
| 1163 | vector<unsigned char> vchTypedPubKey; | ||
| 1164 | nFail += Check(db.ReadTypedRecord(string("defaultkey"), vchTypedPubKey, | ||
| 1165 | strError) && | ||
| 1166 | vchTypedPubKey == vchPubKey, | ||
| 1167 | "SQLite wallet reads a typed defaultkey record") ? 0 : 1; | ||
| 1168 | } | ||
| 1118 | } | 1169 | } |
| 1119 | catch (const std::exception& e) | 1170 | catch (const std::exception& e) |
| 1120 | { | 1171 | { |
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) | |||
| 156 | 156 | ||
| 157 | bool CWalletDBSQLite::WriteRecord(const vector<unsigned char>& vchKey, | 157 | bool CWalletDBSQLite::WriteRecord(const vector<unsigned char>& vchKey, |
| 158 | const vector<unsigned char>& vchValue, | 158 | const vector<unsigned char>& vchValue, |
| 159 | string& strError) | 159 | string& strError, |
| 160 | bool fOverwrite) | ||
| 160 | { | 161 | { |
| 161 | if (!pdb) | 162 | if (!pdb) |
| 162 | { | 163 | { |
| @@ -170,8 +171,9 @@ bool CWalletDBSQLite::WriteRecord(const vector<unsigned char>& vchKey, | |||
| 170 | } | 171 | } |
| 171 | 172 | ||
| 172 | sqlite3_stmt* stmt = NULL; | 173 | sqlite3_stmt* stmt = NULL; |
| 173 | const char* pszSql = | 174 | const char* pszSql = fOverwrite |
| 174 | "INSERT OR REPLACE INTO wallet_records(key, value) VALUES(?, ?);"; | 175 | ? "INSERT OR REPLACE INTO wallet_records(key, value) VALUES(?, ?);" |
| 176 | : "INSERT INTO wallet_records(key, value) VALUES(?, ?);"; | ||
| 175 | int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); | 177 | int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); |
| 176 | if (ret != SQLITE_OK) | 178 | if (ret != SQLITE_OK) |
| 177 | { | 179 | { |
| @@ -250,6 +252,41 @@ bool CWalletDBSQLite::ReadRecord(const vector<unsigned char>& vchKey, | |||
| 250 | return false; | 252 | return false; |
| 251 | } | 253 | } |
| 252 | 254 | ||
| 255 | bool CWalletDBSQLite::EraseRecord(const vector<unsigned char>& vchKey, | ||
| 256 | string& strError) | ||
| 257 | { | ||
| 258 | if (!pdb) | ||
| 259 | { | ||
| 260 | strError = "SQLite wallet is not open"; | ||
| 261 | return false; | ||
| 262 | } | ||
| 263 | if (vchKey.empty()) | ||
| 264 | { | ||
| 265 | strError = "SQLite wallet record key is empty"; | ||
| 266 | return false; | ||
| 267 | } | ||
| 268 | |||
| 269 | sqlite3_stmt* stmt = NULL; | ||
| 270 | const char* pszSql = "DELETE FROM wallet_records WHERE key = ?;"; | ||
| 271 | int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); | ||
| 272 | if (ret != SQLITE_OK) | ||
| 273 | { | ||
| 274 | SetSQLiteError(pdb, "cannot prepare SQLite wallet erase", strError); | ||
| 275 | return false; | ||
| 276 | } | ||
| 277 | |||
| 278 | ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(), | ||
| 279 | SQLITE_TRANSIENT); | ||
| 280 | if (ret == SQLITE_OK) | ||
| 281 | ret = sqlite3_step(stmt); | ||
| 282 | |||
| 283 | bool fOk = (ret == SQLITE_DONE); | ||
| 284 | if (!fOk) | ||
| 285 | SetSQLiteError(pdb, "cannot erase SQLite wallet record", strError); | ||
| 286 | sqlite3_finalize(stmt); | ||
| 287 | return fOk; | ||
| 288 | } | ||
| 289 | |||
| 253 | bool CWalletDBSQLite::ScanRecords(CWalletRecordVisitor& visitor, | 290 | bool CWalletDBSQLite::ScanRecords(CWalletRecordVisitor& visitor, |
| 254 | string& strError) | 291 | string& strError) |
| 255 | { | 292 | { |
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: | |||
| 25 | 25 | ||
| 26 | bool WriteRecord(const vector<unsigned char>& vchKey, | 26 | bool WriteRecord(const vector<unsigned char>& vchKey, |
| 27 | const vector<unsigned char>& vchValue, | 27 | const vector<unsigned char>& vchValue, |
| 28 | string& strError); | 28 | string& strError, |
| 29 | bool fOverwrite=true); | ||
| 29 | bool ReadRecord(const vector<unsigned char>& vchKey, | 30 | bool ReadRecord(const vector<unsigned char>& vchKey, |
| 30 | vector<unsigned char>& vchValueRet, | 31 | vector<unsigned char>& vchValueRet, |
| 31 | string& strError); | 32 | string& strError); |
| 33 | bool EraseRecord(const vector<unsigned char>& vchKey, | ||
| 34 | string& strError); | ||
| 32 | bool ScanRecords(CWalletRecordVisitor& visitor, string& strError); | 35 | bool ScanRecords(CWalletRecordVisitor& visitor, string& strError); |
| 33 | bool CountRecords(int& nRecordsRet, string& strError); | 36 | bool CountRecords(int& nRecordsRet, string& strError); |
| 34 | 37 | ||
| 38 | template<typename K, typename T> | ||
| 39 | bool WriteTypedRecord(const K& key, const T& value, string& strError, | ||
| 40 | bool fOverwrite=true) | ||
| 41 | { | ||
| 42 | CDataStream ssKey(SER_DISK); | ||
| 43 | ssKey.reserve(1000); | ||
| 44 | ssKey << key; | ||
| 45 | |||
| 46 | CDataStream ssValue(SER_DISK); | ||
| 47 | ssValue.reserve(10000); | ||
| 48 | ssValue << value; | ||
| 49 | |||
| 50 | vector<unsigned char> vchKey(ssKey.begin(), ssKey.end()); | ||
| 51 | vector<unsigned char> vchValue(ssValue.begin(), ssValue.end()); | ||
| 52 | bool fOk = WriteRecord(vchKey, vchValue, strError, fOverwrite); | ||
| 53 | |||
| 54 | if (!vchKey.empty()) | ||
| 55 | memset(&vchKey[0], 0, vchKey.size()); | ||
| 56 | if (!vchValue.empty()) | ||
| 57 | memset(&vchValue[0], 0, vchValue.size()); | ||
| 58 | if (!ssKey.empty()) | ||
| 59 | memset(&ssKey[0], 0, ssKey.size()); | ||
| 60 | if (!ssValue.empty()) | ||
| 61 | memset(&ssValue[0], 0, ssValue.size()); | ||
| 62 | return fOk; | ||
| 63 | } | ||
| 64 | |||
| 65 | template<typename K, typename T> | ||
| 66 | bool ReadTypedRecord(const K& key, T& valueRet, string& strError) | ||
| 67 | { | ||
| 68 | CDataStream ssKey(SER_DISK); | ||
| 69 | ssKey.reserve(1000); | ||
| 70 | ssKey << key; | ||
| 71 | vector<unsigned char> vchKey(ssKey.begin(), ssKey.end()); | ||
| 72 | |||
| 73 | vector<unsigned char> vchValue; | ||
| 74 | bool fOk = ReadRecord(vchKey, vchValue, strError); | ||
| 75 | if (!vchKey.empty()) | ||
| 76 | memset(&vchKey[0], 0, vchKey.size()); | ||
| 77 | if (!ssKey.empty()) | ||
| 78 | memset(&ssKey[0], 0, ssKey.size()); | ||
| 79 | if (!fOk) | ||
| 80 | return false; | ||
| 81 | |||
| 82 | CDataStream ssValue(vchValue, SER_DISK); | ||
| 83 | ssValue >> valueRet; | ||
| 84 | bool fReadOk = !ssValue.fail(); | ||
| 85 | if (!vchValue.empty()) | ||
| 86 | memset(&vchValue[0], 0, vchValue.size()); | ||
| 87 | if (!ssValue.empty()) | ||
| 88 | memset(&ssValue[0], 0, ssValue.size()); | ||
| 89 | if (!fReadOk) | ||
| 90 | strError = "SQLite wallet record could not be deserialized"; | ||
| 91 | return fReadOk; | ||
| 92 | } | ||
| 93 | |||
| 94 | template<typename K> | ||
| 95 | bool EraseTypedRecord(const K& key, string& strError) | ||
| 96 | { | ||
| 97 | CDataStream ssKey(SER_DISK); | ||
| 98 | ssKey.reserve(1000); | ||
| 99 | ssKey << key; | ||
| 100 | vector<unsigned char> vchKey(ssKey.begin(), ssKey.end()); | ||
| 101 | |||
| 102 | bool fOk = EraseRecord(vchKey, strError); | ||
| 103 | if (!vchKey.empty()) | ||
| 104 | memset(&vchKey[0], 0, vchKey.size()); | ||
| 105 | if (!ssKey.empty()) | ||
| 106 | memset(&ssKey[0], 0, ssKey.size()); | ||
| 107 | return fOk; | ||
| 108 | } | ||
| 109 | |||
| 35 | private: | 110 | private: |
| 36 | sqlite3* pdb; | 111 | sqlite3* pdb; |
| 37 | 112 | ||