]>
Commit | Line | Data |
---|---|---|
0a61b0df | 1 | // Copyright (c) 2009-2010 Satoshi Nakamoto |
2 | // Distributed under the MIT/X11 software license, see the accompanying | |
3 | // file license.txt or http://www.opensource.org/licenses/mit-license.php. | |
4 | ||
5 | #include "headers.h" | |
6 | ||
7 | void ThreadFlushWalletDB(void* parg); | |
8 | ||
9 | ||
10 | unsigned int nWalletDBUpdated; | |
e4ff4e68 | 11 | uint64 nAccountingEntryNumber = 0; |
0a61b0df | 12 | |
13 | ||
14 | ||
15 | // | |
16 | // CDB | |
17 | // | |
18 | ||
19 | static CCriticalSection cs_db; | |
20 | static bool fDbEnvInit = false; | |
21 | DbEnv dbenv(0); | |
22 | static map<string, int> mapFileUseCount; | |
23 | static map<string, Db*> mapDb; | |
24 | ||
25 | class CDBInit | |
26 | { | |
27 | public: | |
28 | CDBInit() | |
29 | { | |
30 | } | |
31 | ~CDBInit() | |
32 | { | |
33 | if (fDbEnvInit) | |
34 | { | |
35 | dbenv.close(0); | |
36 | fDbEnvInit = false; | |
37 | } | |
38 | } | |
39 | } | |
40 | instance_of_cdbinit; | |
41 | ||
42 | ||
43 | CDB::CDB(const char* pszFile, const char* pszMode) : pdb(NULL) | |
44 | { | |
45 | int ret; | |
46 | if (pszFile == NULL) | |
47 | return; | |
48 | ||
49 | fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w')); | |
50 | bool fCreate = strchr(pszMode, 'c'); | |
51 | unsigned int nFlags = DB_THREAD; | |
52 | if (fCreate) | |
53 | nFlags |= DB_CREATE; | |
54 | ||
55 | CRITICAL_BLOCK(cs_db) | |
56 | { | |
57 | if (!fDbEnvInit) | |
58 | { | |
59 | if (fShutdown) | |
60 | return; | |
61 | string strDataDir = GetDataDir(); | |
62 | string strLogDir = strDataDir + "/database"; | |
63 | filesystem::create_directory(strLogDir.c_str()); | |
64 | string strErrorFile = strDataDir + "/db.log"; | |
65 | printf("dbenv.open strLogDir=%s strErrorFile=%s\n", strLogDir.c_str(), strErrorFile.c_str()); | |
66 | ||
67 | dbenv.set_lg_dir(strLogDir.c_str()); | |
68 | dbenv.set_lg_max(10000000); | |
69 | dbenv.set_lk_max_locks(10000); | |
70 | dbenv.set_lk_max_objects(10000); | |
71 | dbenv.set_errfile(fopen(strErrorFile.c_str(), "a")); /// debug | |
72 | dbenv.set_flags(DB_AUTO_COMMIT, 1); | |
73 | ret = dbenv.open(strDataDir.c_str(), | |
74 | DB_CREATE | | |
75 | DB_INIT_LOCK | | |
76 | DB_INIT_LOG | | |
77 | DB_INIT_MPOOL | | |
78 | DB_INIT_TXN | | |
79 | DB_THREAD | | |
0a61b0df | 80 | DB_RECOVER, |
81 | S_IRUSR | S_IWUSR); | |
82 | if (ret > 0) | |
d743f035 | 83 | throw runtime_error(strprintf("CDB() : error %d opening database environment", ret)); |
0a61b0df | 84 | fDbEnvInit = true; |
85 | } | |
86 | ||
87 | strFile = pszFile; | |
88 | ++mapFileUseCount[strFile]; | |
89 | pdb = mapDb[strFile]; | |
90 | if (pdb == NULL) | |
91 | { | |
92 | pdb = new Db(&dbenv, 0); | |
93 | ||
94 | ret = pdb->open(NULL, // Txn pointer | |
95 | pszFile, // Filename | |
96 | "main", // Logical db name | |
97 | DB_BTREE, // Database type | |
98 | nFlags, // Flags | |
99 | 0); | |
100 | ||
101 | if (ret > 0) | |
102 | { | |
103 | delete pdb; | |
104 | pdb = NULL; | |
105 | CRITICAL_BLOCK(cs_db) | |
106 | --mapFileUseCount[strFile]; | |
107 | strFile = ""; | |
d743f035 | 108 | throw runtime_error(strprintf("CDB() : can't open database file %s, error %d", pszFile, ret)); |
0a61b0df | 109 | } |
110 | ||
111 | if (fCreate && !Exists(string("version"))) | |
112 | { | |
113 | bool fTmp = fReadOnly; | |
114 | fReadOnly = false; | |
115 | WriteVersion(VERSION); | |
116 | fReadOnly = fTmp; | |
117 | } | |
118 | ||
119 | mapDb[strFile] = pdb; | |
120 | } | |
121 | } | |
122 | } | |
123 | ||
124 | void CDB::Close() | |
125 | { | |
126 | if (!pdb) | |
127 | return; | |
128 | if (!vTxn.empty()) | |
129 | vTxn.front()->abort(); | |
130 | vTxn.clear(); | |
131 | pdb = NULL; | |
132 | ||
133 | // Flush database activity from memory pool to disk log | |
134 | unsigned int nMinutes = 0; | |
f03304a9 | 135 | if (fReadOnly) |
136 | nMinutes = 1; | |
0a61b0df | 137 | if (strFile == "addr.dat") |
138 | nMinutes = 2; | |
139 | if (strFile == "blkindex.dat" && IsInitialBlockDownload() && nBestHeight % 500 != 0) | |
140 | nMinutes = 1; | |
141 | dbenv.txn_checkpoint(0, nMinutes, 0); | |
142 | ||
143 | CRITICAL_BLOCK(cs_db) | |
144 | --mapFileUseCount[strFile]; | |
145 | } | |
146 | ||
147 | void CloseDb(const string& strFile) | |
148 | { | |
149 | CRITICAL_BLOCK(cs_db) | |
150 | { | |
151 | if (mapDb[strFile] != NULL) | |
152 | { | |
153 | // Close the database handle | |
154 | Db* pdb = mapDb[strFile]; | |
155 | pdb->close(0); | |
156 | delete pdb; | |
157 | mapDb[strFile] = NULL; | |
158 | } | |
159 | } | |
160 | } | |
161 | ||
162 | void DBFlush(bool fShutdown) | |
163 | { | |
164 | // Flush log data to the actual data file | |
165 | // on all files that are not in use | |
166 | printf("DBFlush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started"); | |
167 | if (!fDbEnvInit) | |
168 | return; | |
169 | CRITICAL_BLOCK(cs_db) | |
170 | { | |
171 | map<string, int>::iterator mi = mapFileUseCount.begin(); | |
172 | while (mi != mapFileUseCount.end()) | |
173 | { | |
174 | string strFile = (*mi).first; | |
175 | int nRefCount = (*mi).second; | |
176 | printf("%s refcount=%d\n", strFile.c_str(), nRefCount); | |
177 | if (nRefCount == 0) | |
178 | { | |
179 | // Move log data to the dat file | |
180 | CloseDb(strFile); | |
181 | dbenv.txn_checkpoint(0, 0, 0); | |
182 | printf("%s flush\n", strFile.c_str()); | |
183 | dbenv.lsn_reset(strFile.c_str(), 0); | |
184 | mapFileUseCount.erase(mi++); | |
185 | } | |
186 | else | |
187 | mi++; | |
188 | } | |
189 | if (fShutdown) | |
190 | { | |
191 | char** listp; | |
192 | if (mapFileUseCount.empty()) | |
193 | dbenv.log_archive(&listp, DB_ARCH_REMOVE); | |
194 | dbenv.close(0); | |
195 | fDbEnvInit = false; | |
196 | } | |
197 | } | |
198 | } | |
199 | ||
200 | ||
201 | ||
202 | ||
203 | ||
204 | ||
205 | // | |
206 | // CTxDB | |
207 | // | |
208 | ||
209 | bool CTxDB::ReadTxIndex(uint256 hash, CTxIndex& txindex) | |
210 | { | |
211 | assert(!fClient); | |
212 | txindex.SetNull(); | |
213 | return Read(make_pair(string("tx"), hash), txindex); | |
214 | } | |
215 | ||
216 | bool CTxDB::UpdateTxIndex(uint256 hash, const CTxIndex& txindex) | |
217 | { | |
218 | assert(!fClient); | |
219 | return Write(make_pair(string("tx"), hash), txindex); | |
220 | } | |
221 | ||
222 | bool CTxDB::AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight) | |
223 | { | |
224 | assert(!fClient); | |
225 | ||
226 | // Add to tx index | |
227 | uint256 hash = tx.GetHash(); | |
228 | CTxIndex txindex(pos, tx.vout.size()); | |
229 | return Write(make_pair(string("tx"), hash), txindex); | |
230 | } | |
231 | ||
232 | bool CTxDB::EraseTxIndex(const CTransaction& tx) | |
233 | { | |
234 | assert(!fClient); | |
235 | uint256 hash = tx.GetHash(); | |
236 | ||
237 | return Erase(make_pair(string("tx"), hash)); | |
238 | } | |
239 | ||
240 | bool CTxDB::ContainsTx(uint256 hash) | |
241 | { | |
242 | assert(!fClient); | |
243 | return Exists(make_pair(string("tx"), hash)); | |
244 | } | |
245 | ||
246 | bool CTxDB::ReadOwnerTxes(uint160 hash160, int nMinHeight, vector<CTransaction>& vtx) | |
247 | { | |
248 | assert(!fClient); | |
249 | vtx.clear(); | |
250 | ||
251 | // Get cursor | |
252 | Dbc* pcursor = GetCursor(); | |
253 | if (!pcursor) | |
254 | return false; | |
255 | ||
256 | unsigned int fFlags = DB_SET_RANGE; | |
257 | loop | |
258 | { | |
259 | // Read next record | |
260 | CDataStream ssKey; | |
261 | if (fFlags == DB_SET_RANGE) | |
262 | ssKey << string("owner") << hash160 << CDiskTxPos(0, 0, 0); | |
263 | CDataStream ssValue; | |
264 | int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); | |
265 | fFlags = DB_NEXT; | |
266 | if (ret == DB_NOTFOUND) | |
267 | break; | |
268 | else if (ret != 0) | |
269 | { | |
270 | pcursor->close(); | |
271 | return false; | |
272 | } | |
273 | ||
274 | // Unserialize | |
275 | string strType; | |
276 | uint160 hashItem; | |
277 | CDiskTxPos pos; | |
278 | ssKey >> strType >> hashItem >> pos; | |
279 | int nItemHeight; | |
280 | ssValue >> nItemHeight; | |
281 | ||
282 | // Read transaction | |
283 | if (strType != "owner" || hashItem != hash160) | |
284 | break; | |
285 | if (nItemHeight >= nMinHeight) | |
286 | { | |
287 | vtx.resize(vtx.size()+1); | |
288 | if (!vtx.back().ReadFromDisk(pos)) | |
289 | { | |
290 | pcursor->close(); | |
291 | return false; | |
292 | } | |
293 | } | |
294 | } | |
295 | ||
296 | pcursor->close(); | |
297 | return true; | |
298 | } | |
299 | ||
300 | bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex) | |
301 | { | |
302 | assert(!fClient); | |
303 | tx.SetNull(); | |
304 | if (!ReadTxIndex(hash, txindex)) | |
305 | return false; | |
306 | return (tx.ReadFromDisk(txindex.pos)); | |
307 | } | |
308 | ||
309 | bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx) | |
310 | { | |
311 | CTxIndex txindex; | |
312 | return ReadDiskTx(hash, tx, txindex); | |
313 | } | |
314 | ||
315 | bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex) | |
316 | { | |
317 | return ReadDiskTx(outpoint.hash, tx, txindex); | |
318 | } | |
319 | ||
320 | bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx) | |
321 | { | |
322 | CTxIndex txindex; | |
323 | return ReadDiskTx(outpoint.hash, tx, txindex); | |
324 | } | |
325 | ||
326 | bool CTxDB::WriteBlockIndex(const CDiskBlockIndex& blockindex) | |
327 | { | |
328 | return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex); | |
329 | } | |
330 | ||
331 | bool CTxDB::EraseBlockIndex(uint256 hash) | |
332 | { | |
333 | return Erase(make_pair(string("blockindex"), hash)); | |
334 | } | |
335 | ||
336 | bool CTxDB::ReadHashBestChain(uint256& hashBestChain) | |
337 | { | |
338 | return Read(string("hashBestChain"), hashBestChain); | |
339 | } | |
340 | ||
341 | bool CTxDB::WriteHashBestChain(uint256 hashBestChain) | |
342 | { | |
343 | return Write(string("hashBestChain"), hashBestChain); | |
344 | } | |
345 | ||
346 | bool CTxDB::ReadBestInvalidWork(CBigNum& bnBestInvalidWork) | |
347 | { | |
348 | return Read(string("bnBestInvalidWork"), bnBestInvalidWork); | |
349 | } | |
350 | ||
351 | bool CTxDB::WriteBestInvalidWork(CBigNum bnBestInvalidWork) | |
352 | { | |
353 | return Write(string("bnBestInvalidWork"), bnBestInvalidWork); | |
354 | } | |
355 | ||
356 | CBlockIndex* InsertBlockIndex(uint256 hash) | |
357 | { | |
358 | if (hash == 0) | |
359 | return NULL; | |
360 | ||
361 | // Return existing | |
362 | map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash); | |
363 | if (mi != mapBlockIndex.end()) | |
364 | return (*mi).second; | |
365 | ||
366 | // Create new | |
367 | CBlockIndex* pindexNew = new CBlockIndex(); | |
368 | if (!pindexNew) | |
369 | throw runtime_error("LoadBlockIndex() : new CBlockIndex failed"); | |
370 | mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; | |
371 | pindexNew->phashBlock = &((*mi).first); | |
372 | ||
373 | return pindexNew; | |
374 | } | |
375 | ||
376 | bool CTxDB::LoadBlockIndex() | |
377 | { | |
378 | // Get database cursor | |
379 | Dbc* pcursor = GetCursor(); | |
380 | if (!pcursor) | |
381 | return false; | |
382 | ||
383 | // Load mapBlockIndex | |
384 | unsigned int fFlags = DB_SET_RANGE; | |
385 | loop | |
386 | { | |
387 | // Read next record | |
388 | CDataStream ssKey; | |
389 | if (fFlags == DB_SET_RANGE) | |
390 | ssKey << make_pair(string("blockindex"), uint256(0)); | |
391 | CDataStream ssValue; | |
392 | int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); | |
393 | fFlags = DB_NEXT; | |
394 | if (ret == DB_NOTFOUND) | |
395 | break; | |
396 | else if (ret != 0) | |
397 | return false; | |
398 | ||
399 | // Unserialize | |
400 | string strType; | |
401 | ssKey >> strType; | |
402 | if (strType == "blockindex") | |
403 | { | |
404 | CDiskBlockIndex diskindex; | |
405 | ssValue >> diskindex; | |
406 | ||
407 | // Construct block index object | |
408 | CBlockIndex* pindexNew = InsertBlockIndex(diskindex.GetBlockHash()); | |
409 | pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev); | |
410 | pindexNew->pnext = InsertBlockIndex(diskindex.hashNext); | |
411 | pindexNew->nFile = diskindex.nFile; | |
412 | pindexNew->nBlockPos = diskindex.nBlockPos; | |
413 | pindexNew->nHeight = diskindex.nHeight; | |
414 | pindexNew->nVersion = diskindex.nVersion; | |
415 | pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot; | |
416 | pindexNew->nTime = diskindex.nTime; | |
417 | pindexNew->nBits = diskindex.nBits; | |
418 | pindexNew->nNonce = diskindex.nNonce; | |
419 | ||
420 | // Watch for genesis block | |
421 | if (pindexGenesisBlock == NULL && diskindex.GetBlockHash() == hashGenesisBlock) | |
422 | pindexGenesisBlock = pindexNew; | |
423 | ||
424 | if (!pindexNew->CheckIndex()) | |
425 | return error("LoadBlockIndex() : CheckIndex failed at %d", pindexNew->nHeight); | |
426 | } | |
427 | else | |
428 | { | |
429 | break; | |
430 | } | |
431 | } | |
432 | pcursor->close(); | |
433 | ||
434 | // Calculate bnChainWork | |
435 | vector<pair<int, CBlockIndex*> > vSortedByHeight; | |
436 | vSortedByHeight.reserve(mapBlockIndex.size()); | |
437 | foreach(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex) | |
438 | { | |
439 | CBlockIndex* pindex = item.second; | |
440 | vSortedByHeight.push_back(make_pair(pindex->nHeight, pindex)); | |
441 | } | |
442 | sort(vSortedByHeight.begin(), vSortedByHeight.end()); | |
443 | foreach(const PAIRTYPE(int, CBlockIndex*)& item, vSortedByHeight) | |
444 | { | |
445 | CBlockIndex* pindex = item.second; | |
446 | pindex->bnChainWork = (pindex->pprev ? pindex->pprev->bnChainWork : 0) + pindex->GetBlockWork(); | |
447 | } | |
448 | ||
449 | // Load hashBestChain pointer to end of best chain | |
450 | if (!ReadHashBestChain(hashBestChain)) | |
451 | { | |
452 | if (pindexGenesisBlock == NULL) | |
453 | return true; | |
454 | return error("CTxDB::LoadBlockIndex() : hashBestChain not loaded"); | |
455 | } | |
456 | if (!mapBlockIndex.count(hashBestChain)) | |
457 | return error("CTxDB::LoadBlockIndex() : hashBestChain not found in the block index"); | |
458 | pindexBest = mapBlockIndex[hashBestChain]; | |
459 | nBestHeight = pindexBest->nHeight; | |
460 | bnBestChainWork = pindexBest->bnChainWork; | |
461 | printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight); | |
462 | ||
463 | // Load bnBestInvalidWork, OK if it doesn't exist | |
464 | ReadBestInvalidWork(bnBestInvalidWork); | |
465 | ||
466 | // Verify blocks in the best chain | |
467 | CBlockIndex* pindexFork = NULL; | |
468 | for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev) | |
469 | { | |
a790fa46 | 470 | if (pindex->nHeight < nBestHeight-2500 && !mapArgs.count("-checkblocks")) |
0a61b0df | 471 | break; |
472 | CBlock block; | |
473 | if (!block.ReadFromDisk(pindex)) | |
474 | return error("LoadBlockIndex() : block.ReadFromDisk failed"); | |
475 | if (!block.CheckBlock()) | |
476 | { | |
477 | printf("LoadBlockIndex() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); | |
478 | pindexFork = pindex->pprev; | |
479 | } | |
480 | } | |
481 | if (pindexFork) | |
482 | { | |
483 | // Reorg back to the fork | |
484 | printf("LoadBlockIndex() : *** moving best chain pointer back to block %d\n", pindexFork->nHeight); | |
485 | CBlock block; | |
486 | if (!block.ReadFromDisk(pindexFork)) | |
487 | return error("LoadBlockIndex() : block.ReadFromDisk failed"); | |
488 | CTxDB txdb; | |
489 | block.SetBestChain(txdb, pindexFork); | |
490 | } | |
491 | ||
492 | return true; | |
493 | } | |
494 | ||
495 | ||
496 | ||
497 | ||
498 | ||
499 | // | |
500 | // CAddrDB | |
501 | // | |
502 | ||
503 | bool CAddrDB::WriteAddress(const CAddress& addr) | |
504 | { | |
505 | return Write(make_pair(string("addr"), addr.GetKey()), addr); | |
506 | } | |
507 | ||
c891967b | 508 | bool CAddrDB::EraseAddress(const CAddress& addr) |
509 | { | |
510 | return Erase(make_pair(string("addr"), addr.GetKey())); | |
511 | } | |
512 | ||
0a61b0df | 513 | bool CAddrDB::LoadAddresses() |
514 | { | |
515 | CRITICAL_BLOCK(cs_mapAddresses) | |
516 | { | |
517 | // Load user provided addresses | |
518 | CAutoFile filein = fopen((GetDataDir() + "/addr.txt").c_str(), "rt"); | |
519 | if (filein) | |
520 | { | |
521 | try | |
522 | { | |
523 | char psz[1000]; | |
524 | while (fgets(psz, sizeof(psz), filein)) | |
525 | { | |
526 | CAddress addr(psz, NODE_NETWORK); | |
527 | addr.nTime = 0; // so it won't relay unless successfully connected | |
528 | if (addr.IsValid()) | |
529 | AddAddress(addr); | |
530 | } | |
531 | } | |
532 | catch (...) { } | |
533 | } | |
534 | ||
535 | // Get cursor | |
536 | Dbc* pcursor = GetCursor(); | |
537 | if (!pcursor) | |
538 | return false; | |
539 | ||
540 | loop | |
541 | { | |
542 | // Read next record | |
543 | CDataStream ssKey; | |
544 | CDataStream ssValue; | |
545 | int ret = ReadAtCursor(pcursor, ssKey, ssValue); | |
546 | if (ret == DB_NOTFOUND) | |
547 | break; | |
548 | else if (ret != 0) | |
549 | return false; | |
550 | ||
551 | // Unserialize | |
552 | string strType; | |
553 | ssKey >> strType; | |
554 | if (strType == "addr") | |
555 | { | |
556 | CAddress addr; | |
557 | ssValue >> addr; | |
558 | mapAddresses.insert(make_pair(addr.GetKey(), addr)); | |
559 | } | |
560 | } | |
561 | pcursor->close(); | |
562 | ||
563 | printf("Loaded %d addresses\n", mapAddresses.size()); | |
0a61b0df | 564 | } |
565 | ||
566 | return true; | |
567 | } | |
568 | ||
569 | bool LoadAddresses() | |
570 | { | |
571 | return CAddrDB("cr+").LoadAddresses(); | |
572 | } | |
573 | ||
574 | ||
575 | ||
576 | ||
577 | // | |
578 | // CWalletDB | |
579 | // | |
580 | ||
10384941 | 581 | static set<int64> setKeyPool; |
582 | static CCriticalSection cs_setKeyPool; | |
583 | ||
e4ff4e68 | 584 | bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account) |
585 | { | |
586 | account.SetNull(); | |
587 | return Read(make_pair(string("acc"), strAccount), account); | |
588 | } | |
589 | ||
590 | bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account) | |
591 | { | |
592 | return Write(make_pair(string("acc"), strAccount), account); | |
593 | } | |
594 | ||
809ee795 | 595 | bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry) |
e4ff4e68 | 596 | { |
809ee795 | 597 | return Write(make_tuple(string("acentry"), acentry.strAccount, ++nAccountingEntryNumber), acentry); |
e4ff4e68 | 598 | } |
599 | ||
600 | int64 CWalletDB::GetAccountCreditDebit(const string& strAccount) | |
bfd471f5 | 601 | { |
602 | list<CAccountingEntry> entries; | |
603 | ListAccountCreditDebit(strAccount, entries); | |
604 | ||
605 | int64 nCreditDebit = 0; | |
606 | foreach (const CAccountingEntry& entry, entries) | |
607 | nCreditDebit += entry.nCreditDebit; | |
608 | ||
609 | return nCreditDebit; | |
610 | } | |
611 | ||
612 | void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries) | |
e4ff4e68 | 613 | { |
614 | int64 nCreditDebit = 0; | |
615 | ||
809ee795 | 616 | bool fAllAccounts = (strAccount == "*"); |
617 | ||
e4ff4e68 | 618 | Dbc* pcursor = GetCursor(); |
619 | if (!pcursor) | |
bfd471f5 | 620 | throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor"); |
e4ff4e68 | 621 | unsigned int fFlags = DB_SET_RANGE; |
622 | loop | |
623 | { | |
624 | // Read next record | |
625 | CDataStream ssKey; | |
626 | if (fFlags == DB_SET_RANGE) | |
809ee795 | 627 | ssKey << make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64(0)); |
e4ff4e68 | 628 | CDataStream ssValue; |
629 | int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); | |
630 | fFlags = DB_NEXT; | |
631 | if (ret == DB_NOTFOUND) | |
632 | break; | |
633 | else if (ret != 0) | |
634 | { | |
635 | pcursor->close(); | |
bfd471f5 | 636 | throw runtime_error("CWalletDB::ListAccountCreditDebit() : error scanning DB"); |
e4ff4e68 | 637 | } |
638 | ||
639 | // Unserialize | |
640 | string strType; | |
641 | ssKey >> strType; | |
642 | if (strType != "acentry") | |
643 | break; | |
809ee795 | 644 | CAccountingEntry acentry; |
645 | ssKey >> acentry.strAccount; | |
646 | if (!fAllAccounts && acentry.strAccount != strAccount) | |
e4ff4e68 | 647 | break; |
648 | ||
e4ff4e68 | 649 | ssValue >> acentry; |
bfd471f5 | 650 | entries.push_back(acentry); |
e4ff4e68 | 651 | } |
652 | ||
653 | pcursor->close(); | |
e4ff4e68 | 654 | } |
655 | ||
809ee795 | 656 | |
0a61b0df | 657 | bool CWalletDB::LoadWallet() |
658 | { | |
659 | vchDefaultKey.clear(); | |
660 | int nFileVersion = 0; | |
865c3a23 | 661 | vector<uint256> vWalletUpgrade; |
0a61b0df | 662 | |
663 | // Modify defaults | |
664 | #ifndef __WXMSW__ | |
665 | // Tray icon sometimes disappears on 9.10 karmic koala 64-bit, leaving no way to access the program | |
666 | fMinimizeToTray = false; | |
667 | fMinimizeOnClose = false; | |
668 | #endif | |
669 | ||
670 | //// todo: shouldn't we catch exceptions and try to recover and continue? | |
0a61b0df | 671 | CRITICAL_BLOCK(cs_mapWallet) |
f5f1878b | 672 | CRITICAL_BLOCK(cs_mapKeys) |
0a61b0df | 673 | { |
674 | // Get cursor | |
675 | Dbc* pcursor = GetCursor(); | |
676 | if (!pcursor) | |
677 | return false; | |
678 | ||
679 | loop | |
680 | { | |
681 | // Read next record | |
682 | CDataStream ssKey; | |
683 | CDataStream ssValue; | |
684 | int ret = ReadAtCursor(pcursor, ssKey, ssValue); | |
685 | if (ret == DB_NOTFOUND) | |
686 | break; | |
687 | else if (ret != 0) | |
688 | return false; | |
689 | ||
690 | // Unserialize | |
691 | // Taking advantage of the fact that pair serialization | |
692 | // is just the two items serialized one after the other | |
693 | string strType; | |
694 | ssKey >> strType; | |
695 | if (strType == "name") | |
696 | { | |
697 | string strAddress; | |
698 | ssKey >> strAddress; | |
699 | ssValue >> mapAddressBook[strAddress]; | |
700 | } | |
701 | else if (strType == "tx") | |
702 | { | |
703 | uint256 hash; | |
704 | ssKey >> hash; | |
705 | CWalletTx& wtx = mapWallet[hash]; | |
706 | ssValue >> wtx; | |
707 | ||
708 | if (wtx.GetHash() != hash) | |
709 | printf("Error in wallet.dat, hash mismatch\n"); | |
710 | ||
865c3a23 | 711 | // Undo serialize changes in 31600 |
712 | if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) | |
713 | { | |
714 | if (!ssValue.empty()) | |
715 | { | |
716 | char fTmp; | |
717 | char fUnused; | |
718 | ssValue >> fTmp >> fUnused >> wtx.strFromAccount; | |
719 | printf("LoadWallet() upgrading tx ver=%d %d '%s' %s\n", wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount.c_str(), hash.ToString().c_str()); | |
720 | wtx.fTimeReceivedIsTxTime = fTmp; | |
721 | } | |
722 | else | |
723 | { | |
724 | printf("LoadWallet() repairing tx ver=%d %s\n", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str()); | |
725 | wtx.fTimeReceivedIsTxTime = 0; | |
726 | } | |
727 | vWalletUpgrade.push_back(hash); | |
728 | } | |
729 | ||
0a61b0df | 730 | //// debug print |
731 | //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); | |
732 | //printf(" %12I64d %s %s %s\n", | |
733 | // wtx.vout[0].nValue, | |
734 | // DateTimeStrFormat("%x %H:%M:%S", wtx.GetBlockTime()).c_str(), | |
735 | // wtx.hashBlock.ToString().substr(0,20).c_str(), | |
736 | // wtx.mapValue["message"].c_str()); | |
737 | } | |
e4ff4e68 | 738 | else if (strType == "acentry") |
739 | { | |
740 | string strAccount; | |
741 | ssKey >> strAccount; | |
742 | uint64 nNumber; | |
743 | ssKey >> nNumber; | |
744 | if (nNumber > nAccountingEntryNumber) | |
745 | nAccountingEntryNumber = nNumber; | |
746 | } | |
0a61b0df | 747 | else if (strType == "key" || strType == "wkey") |
748 | { | |
749 | vector<unsigned char> vchPubKey; | |
750 | ssKey >> vchPubKey; | |
751 | CWalletKey wkey; | |
752 | if (strType == "key") | |
753 | ssValue >> wkey.vchPrivKey; | |
754 | else | |
755 | ssValue >> wkey; | |
756 | ||
757 | mapKeys[vchPubKey] = wkey.vchPrivKey; | |
758 | mapPubKeys[Hash160(vchPubKey)] = vchPubKey; | |
759 | } | |
760 | else if (strType == "defaultkey") | |
761 | { | |
762 | ssValue >> vchDefaultKey; | |
763 | } | |
10384941 | 764 | else if (strType == "pool") |
765 | { | |
766 | int64 nIndex; | |
767 | ssKey >> nIndex; | |
768 | setKeyPool.insert(nIndex); | |
769 | } | |
0a61b0df | 770 | else if (strType == "version") |
771 | { | |
772 | ssValue >> nFileVersion; | |
773 | if (nFileVersion == 10300) | |
774 | nFileVersion = 300; | |
775 | } | |
776 | else if (strType == "setting") | |
777 | { | |
778 | string strKey; | |
779 | ssKey >> strKey; | |
780 | ||
781 | // Menu state | |
782 | if (strKey == "fGenerateBitcoins") ssValue >> fGenerateBitcoins; | |
783 | ||
784 | // Options | |
785 | if (strKey == "nTransactionFee") ssValue >> nTransactionFee; | |
786 | if (strKey == "addrIncoming") ssValue >> addrIncoming; | |
787 | if (strKey == "fLimitProcessors") ssValue >> fLimitProcessors; | |
788 | if (strKey == "nLimitProcessors") ssValue >> nLimitProcessors; | |
789 | if (strKey == "fMinimizeToTray") ssValue >> fMinimizeToTray; | |
790 | if (strKey == "fMinimizeOnClose") ssValue >> fMinimizeOnClose; | |
791 | if (strKey == "fUseProxy") ssValue >> fUseProxy; | |
792 | if (strKey == "addrProxy") ssValue >> addrProxy; | |
8bb5edc1 | 793 | if (fHaveUPnP && strKey == "fUseUPnP") ssValue >> fUseUPnP; |
0a61b0df | 794 | } |
795 | } | |
796 | pcursor->close(); | |
797 | } | |
798 | ||
865c3a23 | 799 | foreach(uint256 hash, vWalletUpgrade) |
800 | WriteTx(hash, mapWallet[hash]); | |
801 | ||
0a61b0df | 802 | printf("nFileVersion = %d\n", nFileVersion); |
803 | printf("fGenerateBitcoins = %d\n", fGenerateBitcoins); | |
804 | printf("nTransactionFee = %"PRI64d"\n", nTransactionFee); | |
805 | printf("addrIncoming = %s\n", addrIncoming.ToString().c_str()); | |
806 | printf("fMinimizeToTray = %d\n", fMinimizeToTray); | |
807 | printf("fMinimizeOnClose = %d\n", fMinimizeOnClose); | |
808 | printf("fUseProxy = %d\n", fUseProxy); | |
809 | printf("addrProxy = %s\n", addrProxy.ToString().c_str()); | |
8bb5edc1 MC |
810 | if (fHaveUPnP) |
811 | printf("fUseUPnP = %d\n", fUseUPnP); | |
0a61b0df | 812 | |
813 | ||
0a61b0df | 814 | // Upgrade |
815 | if (nFileVersion < VERSION) | |
816 | { | |
817 | // Get rid of old debug.log file in current directory | |
818 | if (nFileVersion <= 105 && !pszSetDataDir[0]) | |
819 | unlink("debug.log"); | |
820 | ||
821 | WriteVersion(VERSION); | |
822 | } | |
823 | ||
865c3a23 | 824 | |
0a61b0df | 825 | return true; |
826 | } | |
827 | ||
828 | bool LoadWallet(bool& fFirstRunRet) | |
829 | { | |
830 | fFirstRunRet = false; | |
831 | if (!CWalletDB("cr+").LoadWallet()) | |
832 | return false; | |
833 | fFirstRunRet = vchDefaultKey.empty(); | |
834 | ||
835 | if (mapKeys.count(vchDefaultKey)) | |
836 | { | |
837 | // Set keyUser | |
838 | keyUser.SetPubKey(vchDefaultKey); | |
839 | keyUser.SetPrivKey(mapKeys[vchDefaultKey]); | |
840 | } | |
841 | else | |
842 | { | |
843 | // Create new keyUser and set as default key | |
844 | RandAddSeedPerfmon(); | |
845 | keyUser.MakeNewKey(); | |
846 | if (!AddKey(keyUser)) | |
847 | return false; | |
b1ca5eb5 | 848 | if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "")) |
0a61b0df | 849 | return false; |
850 | CWalletDB().WriteDefaultKey(keyUser.GetPubKey()); | |
851 | } | |
852 | ||
853 | CreateThread(ThreadFlushWalletDB, NULL); | |
854 | return true; | |
855 | } | |
856 | ||
857 | void ThreadFlushWalletDB(void* parg) | |
858 | { | |
859 | static bool fOneThread; | |
860 | if (fOneThread) | |
861 | return; | |
862 | fOneThread = true; | |
863 | if (mapArgs.count("-noflushwallet")) | |
864 | return; | |
865 | ||
866 | unsigned int nLastSeen = nWalletDBUpdated; | |
867 | unsigned int nLastFlushed = nWalletDBUpdated; | |
868 | int64 nLastWalletUpdate = GetTime(); | |
869 | while (!fShutdown) | |
870 | { | |
871 | Sleep(500); | |
872 | ||
873 | if (nLastSeen != nWalletDBUpdated) | |
874 | { | |
875 | nLastSeen = nWalletDBUpdated; | |
876 | nLastWalletUpdate = GetTime(); | |
877 | } | |
878 | ||
879 | if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2) | |
880 | { | |
881 | TRY_CRITICAL_BLOCK(cs_db) | |
882 | { | |
883 | // Don't do this if any databases are in use | |
884 | int nRefCount = 0; | |
885 | map<string, int>::iterator mi = mapFileUseCount.begin(); | |
886 | while (mi != mapFileUseCount.end()) | |
887 | { | |
888 | nRefCount += (*mi).second; | |
889 | mi++; | |
890 | } | |
891 | ||
892 | if (nRefCount == 0 && !fShutdown) | |
893 | { | |
894 | string strFile = "wallet.dat"; | |
895 | map<string, int>::iterator mi = mapFileUseCount.find(strFile); | |
896 | if (mi != mapFileUseCount.end()) | |
897 | { | |
898 | printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); | |
899 | printf("Flushing wallet.dat\n"); | |
900 | nLastFlushed = nWalletDBUpdated; | |
901 | int64 nStart = GetTimeMillis(); | |
902 | ||
903 | // Flush wallet.dat so it's self contained | |
904 | CloseDb(strFile); | |
905 | dbenv.txn_checkpoint(0, 0, 0); | |
906 | dbenv.lsn_reset(strFile.c_str(), 0); | |
907 | ||
908 | mapFileUseCount.erase(mi++); | |
909 | printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart); | |
910 | } | |
911 | } | |
912 | } | |
913 | } | |
914 | } | |
915 | } | |
d743f035 | 916 | |
917 | void BackupWallet(const string& strDest) | |
918 | { | |
919 | while (!fShutdown) | |
920 | { | |
921 | CRITICAL_BLOCK(cs_db) | |
922 | { | |
923 | const string strFile = "wallet.dat"; | |
924 | if (!mapFileUseCount.count(strFile) || mapFileUseCount[strFile] == 0) | |
925 | { | |
926 | // Flush log data to the dat file | |
927 | CloseDb(strFile); | |
928 | dbenv.txn_checkpoint(0, 0, 0); | |
929 | dbenv.lsn_reset(strFile.c_str(), 0); | |
930 | mapFileUseCount.erase(strFile); | |
931 | ||
932 | // Copy wallet.dat | |
f1e1fb4b | 933 | filesystem::path pathSrc(GetDataDir() + "/" + strFile); |
d743f035 | 934 | filesystem::path pathDest(strDest); |
935 | if (filesystem::is_directory(pathDest)) | |
936 | pathDest = pathDest / strFile; | |
f1e1fb4b | 937 | #if BOOST_VERSION >= 104000 |
938 | filesystem::copy_file(pathSrc, pathDest, filesystem::copy_option::overwrite_if_exists); | |
939 | #else | |
940 | filesystem::copy_file(pathSrc, pathDest); | |
941 | #endif | |
d743f035 | 942 | printf("copied wallet.dat to %s\n", pathDest.string().c_str()); |
943 | ||
944 | return; | |
945 | } | |
946 | } | |
947 | Sleep(100); | |
948 | } | |
949 | } | |
10384941 | 950 | |
5cbf7532 | 951 | |
10384941 | 952 | void CWalletDB::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) |
953 | { | |
954 | nIndex = -1; | |
955 | keypool.vchPubKey.clear(); | |
5cbf7532 | 956 | CRITICAL_BLOCK(cs_main) |
957 | CRITICAL_BLOCK(cs_mapWallet) | |
10384941 | 958 | CRITICAL_BLOCK(cs_setKeyPool) |
959 | { | |
960 | // Top up key pool | |
961 | int64 nTargetSize = max(GetArg("-keypool", 100), (int64)0); | |
962 | while (setKeyPool.size() < nTargetSize+1) | |
963 | { | |
964 | int64 nEnd = 1; | |
965 | if (!setKeyPool.empty()) | |
966 | nEnd = *(--setKeyPool.end()) + 1; | |
967 | if (!Write(make_pair(string("pool"), nEnd), CKeyPool(GenerateNewKey()))) | |
968 | throw runtime_error("ReserveKeyFromKeyPool() : writing generated key failed"); | |
969 | setKeyPool.insert(nEnd); | |
970 | printf("keypool added key %"PRI64d", size=%d\n", nEnd, setKeyPool.size()); | |
971 | } | |
972 | ||
973 | // Get the oldest key | |
974 | assert(!setKeyPool.empty()); | |
975 | nIndex = *(setKeyPool.begin()); | |
976 | setKeyPool.erase(setKeyPool.begin()); | |
977 | if (!Read(make_pair(string("pool"), nIndex), keypool)) | |
978 | throw runtime_error("ReserveKeyFromKeyPool() : read failed"); | |
979 | if (!mapKeys.count(keypool.vchPubKey)) | |
980 | throw runtime_error("ReserveKeyFromKeyPool() : unknown key in key pool"); | |
981 | assert(!keypool.vchPubKey.empty()); | |
982 | printf("keypool reserve %"PRI64d"\n", nIndex); | |
983 | } | |
984 | } | |
985 | ||
986 | void CWalletDB::KeepKey(int64 nIndex) | |
987 | { | |
988 | // Remove from key pool | |
5cbf7532 | 989 | CRITICAL_BLOCK(cs_main) |
990 | CRITICAL_BLOCK(cs_mapWallet) | |
991 | { | |
992 | Erase(make_pair(string("pool"), nIndex)); | |
993 | } | |
10384941 | 994 | printf("keypool keep %"PRI64d"\n", nIndex); |
995 | } | |
996 | ||
997 | void CWalletDB::ReturnKey(int64 nIndex) | |
998 | { | |
999 | // Return to key pool | |
1000 | CRITICAL_BLOCK(cs_setKeyPool) | |
1001 | setKeyPool.insert(nIndex); | |
1002 | printf("keypool return %"PRI64d"\n", nIndex); | |
1003 | } | |
1004 | ||
776d0f34 | 1005 | vector<unsigned char> GetKeyFromKeyPool() |
10384941 | 1006 | { |
e4ff4e68 | 1007 | CWalletDB walletdb; |
10384941 | 1008 | int64 nIndex = 0; |
1009 | CKeyPool keypool; | |
e4ff4e68 | 1010 | walletdb.ReserveKeyFromKeyPool(nIndex, keypool); |
1011 | walletdb.KeepKey(nIndex); | |
10384941 | 1012 | return keypool.vchPubKey; |
1013 | } | |
c285051c | 1014 | |
776d0f34 | 1015 | int64 GetOldestKeyPoolTime() |
c285051c | 1016 | { |
e4ff4e68 | 1017 | CWalletDB walletdb; |
c285051c | 1018 | int64 nIndex = 0; |
1019 | CKeyPool keypool; | |
e4ff4e68 | 1020 | walletdb.ReserveKeyFromKeyPool(nIndex, keypool); |
1021 | walletdb.ReturnKey(nIndex); | |
c285051c | 1022 | return keypool.nTime; |
1023 | } |