upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/wallet_sqlite.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-06 01:53:03 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-06 01:53:03 +0000
commitef9f06d43fc6577c15df2256abbb25b20d4227b8 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/wallet_sqlite.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parentceae3d076b5ad11ee84729ae43f14e9312896646 (diff)
Merge branch 'chore/main-catchup-2026-08-26' into 'main'HEADmain
Bring main onto the line this project actually ships (incl. the 1.2.23 compactblock port) Closes #6, #7, #8, #11, #35, #34, #29, #28, #27, and #16 See merge request !16
Diffstat (limited to 'src/wallet_sqlite.cpp')
-rw-r--r--src/wallet_sqlite.cpp374
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
7static 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
16CWalletDBSQLite::CWalletDBSQLite() : pdb(NULL)
17{
18}
19
20CWalletDBSQLite::~CWalletDBSQLite()
21{
22 Close();
23}
24
25bool 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
61bool 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
80void CWalletDBSQLite::Close()
81{
82 if (pdb)
83 {
84 sqlite3_close(pdb);
85 pdb = NULL;
86 }
87}
88
89bool 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
99bool 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
109bool 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
119bool 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
129bool 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
147bool 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
157bool 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
201bool 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
255bool 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
290bool 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
344bool 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}