diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
| commit | ceae3d076b5ad11ee84729ae43f14e9312896646 (patch) | |
| tree | 7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/wallet_sqlite.cpp | |
| parent | be63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff) | |
| parent | 66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff) | |
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we
build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped
line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git
merge-base returns no result at all -- so anyone cloning this repository
landed on a default branch that was neither current nor related to the
running node.
The 28 commits on main were audited before this merge: every one is already
present in the tree by content, including the two that are hardest to see
because they were rewritten afterwards -- the payout reorg-safety work
(PayoutBlockStillOnChain, since evolved to re-check on every retry rather
than once at maturity) and the nostr relay certificate verification
(SSL_set1_host, now with a Windows root-store path). Nothing on main is lost
by this merge.
Recorded as a merge rather than a force-push: main only ever moves forward,
its old history stays reachable as the first parent, and this is revertible.
The tree is taken wholesale from the merged branch, so main's content is now
exactly what was built and tested.
What this brings onto main:
- compactblock: BIP152-style compact block relay ported from upstream 1.2.23,
primitives and unit test only, with the RAND_bytes failure path closed and
the reconstruction allocation bounded.
- upstream/main 17741d9: isolated testnet parameters and genesis, optional
2 MB large pages for RandomX. Mainnet genesis verified byte-identical
through upstream's BuildGenesisBlock() refactor.
- deploy: every image built on bookworm so binaries run on Debian 12, and the
container build now honours the RandomX and libsecp256k1 pins it had been
silently ignoring since before 1.2.15.
Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus
constants unchanged against e4f898c apart from the pre-existing
MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/wallet_sqlite.cpp')
| -rw-r--r-- | src/wallet_sqlite.cpp | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/src/wallet_sqlite.cpp b/src/wallet_sqlite.cpp new file mode 100644 index 0000000..f4b4b37 --- /dev/null +++ b/src/wallet_sqlite.cpp | |||
| @@ -0,0 +1,374 @@ | |||
| 1 | // Copyright (c) 2026 Bitflash developers | ||
| 2 | // Distributed under the MIT/X11 software license. | ||
| 3 | |||
| 4 | #include "headers.h" | ||
| 5 | #include "wallet_sqlite.h" | ||
| 6 | |||
| 7 | static void SetSQLiteError(sqlite3* pdb, const string& strContext, | ||
| 8 | string& strError) | ||
| 9 | { | ||
| 10 | if (pdb) | ||
| 11 | strError = strContext + ": " + sqlite3_errmsg(pdb); | ||
| 12 | else | ||
| 13 | strError = strContext; | ||
| 14 | } | ||
| 15 | |||
| 16 | CWalletDBSQLite::CWalletDBSQLite() : pdb(NULL) | ||
| 17 | { | ||
| 18 | } | ||
| 19 | |||
| 20 | CWalletDBSQLite::~CWalletDBSQLite() | ||
| 21 | { | ||
| 22 | Close(); | ||
| 23 | } | ||
| 24 | |||
| 25 | bool CWalletDBSQLite::Open(const string& strPath, string& strError) | ||
| 26 | { | ||
| 27 | Close(); | ||
| 28 | |||
| 29 | int ret = sqlite3_open_v2(strPath.c_str(), &pdb, | ||
| 30 | SQLITE_OPEN_READWRITE | | ||
| 31 | SQLITE_OPEN_CREATE | | ||
| 32 | SQLITE_OPEN_FULLMUTEX, | ||
| 33 | NULL); | ||
| 34 | if (ret != SQLITE_OK) | ||
| 35 | { | ||
| 36 | SetSQLiteError(pdb, "cannot open SQLite wallet", strError); | ||
| 37 | Close(); | ||
| 38 | return false; | ||
| 39 | } | ||
| 40 | |||
| 41 | sqlite3_busy_timeout(pdb, 5000); | ||
| 42 | |||
| 43 | if (!Exec("PRAGMA journal_mode=WAL;", strError)) | ||
| 44 | { | ||
| 45 | Close(); | ||
| 46 | return false; | ||
| 47 | } | ||
| 48 | if (!Exec("PRAGMA synchronous=FULL;", strError)) | ||
| 49 | { | ||
| 50 | Close(); | ||
| 51 | return false; | ||
| 52 | } | ||
| 53 | if (!InitSchema(strError)) | ||
| 54 | { | ||
| 55 | Close(); | ||
| 56 | return false; | ||
| 57 | } | ||
| 58 | return true; | ||
| 59 | } | ||
| 60 | |||
| 61 | bool CWalletDBSQLite::OpenReadOnly(const string& strPath, string& strError) | ||
| 62 | { | ||
| 63 | Close(); | ||
| 64 | |||
| 65 | int ret = sqlite3_open_v2(strPath.c_str(), &pdb, | ||
| 66 | SQLITE_OPEN_READONLY | | ||
| 67 | SQLITE_OPEN_FULLMUTEX, | ||
| 68 | NULL); | ||
| 69 | if (ret != SQLITE_OK) | ||
| 70 | { | ||
| 71 | SetSQLiteError(pdb, "cannot open SQLite wallet read-only", strError); | ||
| 72 | Close(); | ||
| 73 | return false; | ||
| 74 | } | ||
| 75 | |||
| 76 | sqlite3_busy_timeout(pdb, 5000); | ||
| 77 | return true; | ||
| 78 | } | ||
| 79 | |||
| 80 | void CWalletDBSQLite::Close() | ||
| 81 | { | ||
| 82 | if (pdb) | ||
| 83 | { | ||
| 84 | sqlite3_close(pdb); | ||
| 85 | pdb = NULL; | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | bool CWalletDBSQLite::BeginTransaction(string& strError) | ||
| 90 | { | ||
| 91 | if (!pdb) | ||
| 92 | { | ||
| 93 | strError = "SQLite wallet is not open"; | ||
| 94 | return false; | ||
| 95 | } | ||
| 96 | return Exec("BEGIN IMMEDIATE;", strError); | ||
| 97 | } | ||
| 98 | |||
| 99 | bool CWalletDBSQLite::CommitTransaction(string& strError) | ||
| 100 | { | ||
| 101 | if (!pdb) | ||
| 102 | { | ||
| 103 | strError = "SQLite wallet is not open"; | ||
| 104 | return false; | ||
| 105 | } | ||
| 106 | return Exec("COMMIT;", strError); | ||
| 107 | } | ||
| 108 | |||
| 109 | bool CWalletDBSQLite::RollbackTransaction(string& strError) | ||
| 110 | { | ||
| 111 | if (!pdb) | ||
| 112 | { | ||
| 113 | strError = "SQLite wallet is not open"; | ||
| 114 | return false; | ||
| 115 | } | ||
| 116 | return Exec("ROLLBACK;", strError); | ||
| 117 | } | ||
| 118 | |||
| 119 | bool CWalletDBSQLite::Checkpoint(string& strError) | ||
| 120 | { | ||
| 121 | if (!pdb) | ||
| 122 | { | ||
| 123 | strError = "SQLite wallet is not open"; | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | return Exec("PRAGMA wal_checkpoint(TRUNCATE);", strError); | ||
| 127 | } | ||
| 128 | |||
| 129 | bool CWalletDBSQLite::Exec(const char* pszSql, string& strError) | ||
| 130 | { | ||
| 131 | char* pszErr = NULL; | ||
| 132 | int ret = sqlite3_exec(pdb, pszSql, NULL, NULL, &pszErr); | ||
| 133 | if (ret != SQLITE_OK) | ||
| 134 | { | ||
| 135 | if (pszErr) | ||
| 136 | { | ||
| 137 | strError = pszErr; | ||
| 138 | sqlite3_free(pszErr); | ||
| 139 | } | ||
| 140 | else | ||
| 141 | SetSQLiteError(pdb, "SQLite exec failed", strError); | ||
| 142 | return false; | ||
| 143 | } | ||
| 144 | return true; | ||
| 145 | } | ||
| 146 | |||
| 147 | bool CWalletDBSQLite::InitSchema(string& strError) | ||
| 148 | { | ||
| 149 | return Exec("CREATE TABLE IF NOT EXISTS wallet_records (" | ||
| 150 | "key BLOB PRIMARY KEY NOT NULL," | ||
| 151 | "value BLOB NOT NULL" | ||
| 152 | ");", | ||
| 153 | strError) && | ||
| 154 | Exec("PRAGMA user_version=1;", strError); | ||
| 155 | } | ||
| 156 | |||
| 157 | bool CWalletDBSQLite::WriteRecord(const vector<unsigned char>& vchKey, | ||
| 158 | const vector<unsigned char>& vchValue, | ||
| 159 | string& strError, | ||
| 160 | bool fOverwrite) | ||
| 161 | { | ||
| 162 | if (!pdb) | ||
| 163 | { | ||
| 164 | strError = "SQLite wallet is not open"; | ||
| 165 | return false; | ||
| 166 | } | ||
| 167 | if (vchKey.empty()) | ||
| 168 | { | ||
| 169 | strError = "SQLite wallet record key is empty"; | ||
| 170 | return false; | ||
| 171 | } | ||
| 172 | |||
| 173 | sqlite3_stmt* stmt = NULL; | ||
| 174 | const char* pszSql = fOverwrite | ||
| 175 | ? "INSERT OR REPLACE INTO wallet_records(key, value) VALUES(?, ?);" | ||
| 176 | : "INSERT INTO wallet_records(key, value) VALUES(?, ?);"; | ||
| 177 | int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); | ||
| 178 | if (ret != SQLITE_OK) | ||
| 179 | { | ||
| 180 | SetSQLiteError(pdb, "cannot prepare SQLite wallet write", strError); | ||
| 181 | return false; | ||
| 182 | } | ||
| 183 | |||
| 184 | ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(), | ||
| 185 | SQLITE_TRANSIENT); | ||
| 186 | if (ret == SQLITE_OK) | ||
| 187 | ret = sqlite3_bind_blob(stmt, 2, | ||
| 188 | vchValue.empty() ? NULL : &vchValue[0], | ||
| 189 | (int)vchValue.size(), | ||
| 190 | SQLITE_TRANSIENT); | ||
| 191 | if (ret == SQLITE_OK) | ||
| 192 | ret = sqlite3_step(stmt); | ||
| 193 | |||
| 194 | bool fOk = (ret == SQLITE_DONE); | ||
| 195 | if (!fOk) | ||
| 196 | SetSQLiteError(pdb, "cannot write SQLite wallet record", strError); | ||
| 197 | sqlite3_finalize(stmt); | ||
| 198 | return fOk; | ||
| 199 | } | ||
| 200 | |||
| 201 | bool CWalletDBSQLite::ReadRecord(const vector<unsigned char>& vchKey, | ||
| 202 | vector<unsigned char>& vchValueRet, | ||
| 203 | string& strError) | ||
| 204 | { | ||
| 205 | vchValueRet.clear(); | ||
| 206 | if (!pdb) | ||
| 207 | { | ||
| 208 | strError = "SQLite wallet is not open"; | ||
| 209 | return false; | ||
| 210 | } | ||
| 211 | if (vchKey.empty()) | ||
| 212 | { | ||
| 213 | strError = "SQLite wallet record key is empty"; | ||
| 214 | return false; | ||
| 215 | } | ||
| 216 | |||
| 217 | sqlite3_stmt* stmt = NULL; | ||
| 218 | const char* pszSql = "SELECT value FROM wallet_records WHERE key = ?;"; | ||
| 219 | int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); | ||
| 220 | if (ret != SQLITE_OK) | ||
| 221 | { | ||
| 222 | SetSQLiteError(pdb, "cannot prepare SQLite wallet read", strError); | ||
| 223 | return false; | ||
| 224 | } | ||
| 225 | |||
| 226 | ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(), | ||
| 227 | SQLITE_TRANSIENT); | ||
| 228 | if (ret != SQLITE_OK) | ||
| 229 | { | ||
| 230 | SetSQLiteError(pdb, "cannot bind SQLite wallet key", strError); | ||
| 231 | sqlite3_finalize(stmt); | ||
| 232 | return false; | ||
| 233 | } | ||
| 234 | |||
| 235 | ret = sqlite3_step(stmt); | ||
| 236 | if (ret == SQLITE_ROW) | ||
| 237 | { | ||
| 238 | const unsigned char* pch = | ||
| 239 | (const unsigned char*)sqlite3_column_blob(stmt, 0); | ||
| 240 | int nBytes = sqlite3_column_bytes(stmt, 0); | ||
| 241 | if (nBytes > 0 && pch) | ||
| 242 | vchValueRet.assign(pch, pch + nBytes); | ||
| 243 | sqlite3_finalize(stmt); | ||
| 244 | return true; | ||
| 245 | } | ||
| 246 | |||
| 247 | if (ret == SQLITE_DONE) | ||
| 248 | strError = "SQLite wallet record not found"; | ||
| 249 | else | ||
| 250 | SetSQLiteError(pdb, "cannot read SQLite wallet record", strError); | ||
| 251 | sqlite3_finalize(stmt); | ||
| 252 | return false; | ||
| 253 | } | ||
| 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 | |||
| 290 | bool CWalletDBSQLite::ScanRecords(CWalletRecordVisitor& visitor, | ||
| 291 | string& strError) | ||
| 292 | { | ||
| 293 | if (!pdb) | ||
| 294 | { | ||
| 295 | strError = "SQLite wallet is not open"; | ||
| 296 | return false; | ||
| 297 | } | ||
| 298 | |||
| 299 | sqlite3_stmt* stmt = NULL; | ||
| 300 | const char* pszSql = "SELECT key, value FROM wallet_records ORDER BY rowid;"; | ||
| 301 | int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); | ||
| 302 | if (ret != SQLITE_OK) | ||
| 303 | { | ||
| 304 | SetSQLiteError(pdb, "cannot prepare SQLite wallet scan", strError); | ||
| 305 | return false; | ||
| 306 | } | ||
| 307 | |||
| 308 | while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) | ||
| 309 | { | ||
| 310 | const unsigned char* pchKey = | ||
| 311 | (const unsigned char*)sqlite3_column_blob(stmt, 0); | ||
| 312 | int nKeyBytes = sqlite3_column_bytes(stmt, 0); | ||
| 313 | const unsigned char* pchValue = | ||
| 314 | (const unsigned char*)sqlite3_column_blob(stmt, 1); | ||
| 315 | int nValueBytes = sqlite3_column_bytes(stmt, 1); | ||
| 316 | |||
| 317 | vector<unsigned char> vchKey; | ||
| 318 | vector<unsigned char> vchValue; | ||
| 319 | if (nKeyBytes > 0 && pchKey) | ||
| 320 | vchKey.assign(pchKey, pchKey + nKeyBytes); | ||
| 321 | if (nValueBytes > 0 && pchValue) | ||
| 322 | vchValue.assign(pchValue, pchValue + nValueBytes); | ||
| 323 | |||
| 324 | CDataStream ssKey(vchKey, SER_DISK); | ||
| 325 | CDataStream ssValue(vchValue, SER_DISK); | ||
| 326 | if (!visitor.VisitWalletRecord(ssKey, ssValue, strError)) | ||
| 327 | { | ||
| 328 | sqlite3_finalize(stmt); | ||
| 329 | return false; | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | if (ret != SQLITE_DONE) | ||
| 334 | { | ||
| 335 | SetSQLiteError(pdb, "cannot scan SQLite wallet records", strError); | ||
| 336 | sqlite3_finalize(stmt); | ||
| 337 | return false; | ||
| 338 | } | ||
| 339 | |||
| 340 | sqlite3_finalize(stmt); | ||
| 341 | return true; | ||
| 342 | } | ||
| 343 | |||
| 344 | bool CWalletDBSQLite::CountRecords(int& nRecordsRet, string& strError) | ||
| 345 | { | ||
| 346 | nRecordsRet = 0; | ||
| 347 | if (!pdb) | ||
| 348 | { | ||
| 349 | strError = "SQLite wallet is not open"; | ||
| 350 | return false; | ||
| 351 | } | ||
| 352 | |||
| 353 | sqlite3_stmt* stmt = NULL; | ||
| 354 | int ret = sqlite3_prepare_v2(pdb, | ||
| 355 | "SELECT COUNT(*) FROM wallet_records;", | ||
| 356 | -1, &stmt, NULL); | ||
| 357 | if (ret != SQLITE_OK) | ||
| 358 | { | ||
| 359 | SetSQLiteError(pdb, "cannot prepare SQLite wallet count", strError); | ||
| 360 | return false; | ||
| 361 | } | ||
| 362 | |||
| 363 | ret = sqlite3_step(stmt); | ||
| 364 | if (ret != SQLITE_ROW) | ||
| 365 | { | ||
| 366 | SetSQLiteError(pdb, "cannot count SQLite wallet records", strError); | ||
| 367 | sqlite3_finalize(stmt); | ||
| 368 | return false; | ||
| 369 | } | ||
| 370 | |||
| 371 | nRecordsRet = sqlite3_column_int(stmt, 0); | ||
| 372 | sqlite3_finalize(stmt); | ||
| 373 | return true; | ||
| 374 | } | ||