upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/selftest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/selftest.cpp')
-rw-r--r--src/selftest.cpp1241
1 files changed, 1241 insertions, 0 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp
new file mode 100644
index 0000000..ff0289b
--- /dev/null
+++ b/src/selftest.cpp
@@ -0,0 +1,1241 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3//
4// Integration self-tests that need the real wallet/database code linked into
5// the node. These are intentionally run behind an explicit command-line flag
6// and against a temporary data directory.
7
8#include "headers_core.h"
9#include "bip32.h"
10#include "selftest.h"
11#include "walletcmd.h"
12
13// Test results go to the terminal, not to debug.log.
14//
15// printf in this tree is OutputDebugStringF, which writes into the data
16// directory's debug.log and binds that path, once, on its first call. For a
17// self-test that produced two bad outcomes at the same time. On Windows the
18// binary is linked -mwindows and has no console, so `make tests` printed the
19// bip32 results, echoed the self-test command, and then showed nothing at all --
20// a failure was still caught, because a non-zero return stops make, but it
21// arrived with no way to tell which check failed. And because the first line
22// printed before strSetDataDir was pointed at the temporary directory, every
23// line landed in the developer's real debug.log: the same file users are asked
24// to paste into issues.
25//
26// Undefining the macro here gives this file the real printf, so results reach
27// stdout. Nothing else in the tree is affected.
28#undef printf
29
30#include <mutex>
31#include <stdexcept>
32#include <thread>
33
34#ifdef _WIN32
35#include <direct.h>
36#include <fcntl.h>
37#include <io.h>
38#else
39#include <dirent.h>
40#include <sys/stat.h>
41#include <sys/types.h>
42#include <unistd.h>
43#endif
44
45static std::string HexStrLocal(const std::vector<unsigned char>& v)
46{
47 static const char* hex = "0123456789abcdef";
48 std::string out;
49 out.reserve(v.size() * 2);
50 for (unsigned char c : v)
51 {
52 out.push_back(hex[c >> 4]);
53 out.push_back(hex[c & 0x0f]);
54 }
55 return out;
56}
57
58static bool MakeTempDir(std::string& out)
59{
60#ifdef _WIN32
61 char base[MAX_PATH + 1] = {0};
62 if (!GetTempPathA(MAX_PATH, base))
63 return false;
64 for (int i = 0; i < 100; i++)
65 {
66 std::string path = strprintf("%sbitflash-selftest-%lu-%lld-%d",
67 base, (unsigned long)GetCurrentProcessId(), (long long)GetTime(), i);
68 if (CreateDirectoryA(path.c_str(), NULL))
69 {
70 out = path;
71 return true;
72 }
73 }
74 return false;
75#else
76 char tmpl[256];
77 snprintf(tmpl, sizeof(tmpl), "/tmp/bitflash-selftest-%ld-XXXXXX", (long)getpid());
78 char* p = mkdtemp(tmpl);
79 if (!p)
80 return false;
81 out = p;
82 return true;
83#endif
84}
85
86static bool GetCurrentDir(std::string& out)
87{
88#ifdef _WIN32
89 char buf[MAX_PATH + 1] = {0};
90 DWORD n = GetCurrentDirectoryA(MAX_PATH, buf);
91 if (n == 0 || n > MAX_PATH)
92 return false;
93 out = buf;
94 return true;
95#else
96 char buf[4096];
97 if (!getcwd(buf, sizeof(buf)))
98 return false;
99 out = buf;
100 return true;
101#endif
102}
103
104static bool SetCurrentDir(const std::string& path)
105{
106#ifdef _WIN32
107 return SetCurrentDirectoryA(path.c_str()) != 0;
108#else
109 return chdir(path.c_str()) == 0;
110#endif
111}
112
113static void RemoveTree(const std::string& path)
114{
115#ifdef _WIN32
116 DWORD attr = GetFileAttributesA(path.c_str());
117 if (attr == INVALID_FILE_ATTRIBUTES)
118 return;
119 if (!(attr & FILE_ATTRIBUTE_DIRECTORY))
120 {
121 DeleteFileA(path.c_str());
122 return;
123 }
124
125 WIN32_FIND_DATAA findData;
126 std::string pattern = path + "\\*";
127 HANDLE hFind = FindFirstFileA(pattern.c_str(), &findData);
128 if (hFind != INVALID_HANDLE_VALUE)
129 {
130 do
131 {
132 std::string name = findData.cFileName;
133 if (name == "." || name == "..")
134 continue;
135 RemoveTree(path + "\\" + name);
136 }
137 while (FindNextFileA(hFind, &findData));
138 FindClose(hFind);
139 }
140 RemoveDirectoryA(path.c_str());
141#else
142 struct stat st;
143 if (lstat(path.c_str(), &st) != 0)
144 return;
145 if (!S_ISDIR(st.st_mode))
146 {
147 unlink(path.c_str());
148 return;
149 }
150
151 DIR* dir = opendir(path.c_str());
152 if (dir)
153 {
154 struct dirent* ent;
155 while ((ent = readdir(dir)) != NULL)
156 {
157 std::string name = ent->d_name;
158 if (name == "." || name == "..")
159 continue;
160 RemoveTree(path + "/" + name);
161 }
162 closedir(dir);
163 }
164 rmdir(path.c_str());
165#endif
166}
167
168static bool WalletHasPrivateKey(const std::vector<unsigned char>& vchPubKey)
169{
170 CPrivKey priv;
171 return CWalletDB("r").ReadKey(vchPubKey, priv) && !priv.empty();
172}
173
174static bool Check(bool condition, const char* message)
175{
176 if (condition)
177 {
178 printf(" ok %s\n", message);
179 return true;
180 }
181 printf(" FAIL %s\n", message);
182 return false;
183}
184
185static int RunWalletKeyPoolSelfTest()
186{
187 fflush(stdout);
188 printf("wallet-keypool self-test\n");
189
190 std::string tmp;
191 std::string cwd;
192 if (!MakeTempDir(tmp))
193 {
194 printf(" FAIL could not create a temporary data directory\n");
195 return 1;
196 }
197 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
198 {
199 printf(" FAIL could not move into the temporary data directory\n");
200 RemoveTree(tmp);
201 return 1;
202 }
203
204 int nFail = 0;
205 // Before anything can open the database or resolve the log path: what the
206 // node itself prints during the test belongs in the temporary directory,
207 // and dies with it. The test's own results are on stdout, above.
208 strSetDataDir = tmp;
209 printf(" temp datadir: %s\n", tmp.c_str());
210
211 try
212 {
213 if (!LoadWallet())
214 throw std::runtime_error("LoadWallet failed");
215
216 TopUpKeyPool();
217 int nInitialPoolSize = 0;
218 CRITICAL_BLOCK(cs_keyPool)
219 nInitialPoolSize = (int)mapKeyPool.size();
220 nFail += Check(nInitialPoolSize == KEYPOOL_SIZE, "TopUpKeyPool fills the pool") ? 0 : 1;
221
222 bool fPooledKeysStored = true;
223 CRITICAL_BLOCK(cs_keyPool)
224 {
225 for (map<int64, vector<unsigned char> >::const_iterator mi = mapKeyPool.begin();
226 mi != mapKeyPool.end(); ++mi)
227 if (!WalletHasPrivateKey(mi->second))
228 fPooledKeysStored = false;
229 }
230 nFail += Check(fPooledKeysStored, "every pooled public key has a stored private key") ? 0 : 1;
231
232 std::vector<std::vector<unsigned char> > drawn;
233 std::mutex drawnMutex;
234 const int nThreads = 8;
235 const int nDrawsPerThread = 32;
236 std::vector<std::thread> threads;
237
238 for (int t = 0; t < nThreads; t++)
239 {
240 threads.emplace_back([&drawn, &drawnMutex, nDrawsPerThread]() {
241 for (int i = 0; i < nDrawsPerThread; i++)
242 {
243 std::vector<unsigned char> key = GetKeyFromPool();
244 std::lock_guard<std::mutex> lock(drawnMutex);
245 drawn.push_back(key);
246 }
247 });
248 }
249 for (std::thread& t : threads)
250 t.join();
251
252 nFail += Check((int)drawn.size() == nThreads * nDrawsPerThread,
253 "all concurrent draws returned a key") ? 0 : 1;
254
255 std::set<std::string> seen;
256 bool fUnique = true;
257 bool fAllStored = true;
258 for (const std::vector<unsigned char>& key : drawn)
259 {
260 if (!seen.insert(HexStrLocal(key)).second)
261 fUnique = false;
262 if (!WalletHasPrivateKey(key))
263 fAllStored = false;
264 }
265
266 nFail += Check(fUnique, "concurrent draws never return the same key") ? 0 : 1;
267 nFail += Check(fAllStored, "each drawn key was already stored in wallet.dat") ? 0 : 1;
268 int nPoolSize = 0;
269 CRITICAL_BLOCK(cs_keyPool)
270 nPoolSize = (int)mapKeyPool.size();
271 nFail += Check(nPoolSize == KEYPOOL_SIZE, "drawing leaves the key pool topped up") ? 0 : 1;
272 }
273 catch (const std::exception& e)
274 {
275 printf(" FAIL exception: %s\n", e.what());
276 nFail++;
277 }
278 catch (...)
279 {
280 printf(" FAIL unknown exception\n");
281 nFail++;
282 }
283
284 DBFlush(true);
285 SetCurrentDir(cwd);
286 RemoveTree(tmp);
287 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
288 nFail, nFail == 1 ? "" : "s");
289 fflush(stdout);
290 return nFail == 0 ? 0 : 1;
291}
292
293// AttachTerminal() moved to util.h: the wallet phrase commands need it too,
294// and two copies of a console-attach that must get its one condition right is
295// one copy too many.
296
297static int RunWalletHDSelfTest()
298{
299 fflush(stdout);
300 printf("wallet-hd self-test\n");
301
302 std::string tmp;
303 std::string cwd;
304 if (!MakeTempDir(tmp))
305 {
306 printf(" FAIL could not create a temporary data directory\n");
307 return 1;
308 }
309 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
310 {
311 printf(" FAIL could not move into the temporary data directory\n");
312 RemoveTree(tmp);
313 return 1;
314 }
315
316 int nFail = 0;
317 strSetDataDir = tmp;
318 printf(" temp datadir: %s\n", tmp.c_str());
319
320 // Test-vector phrases, so nothing here depends on randomness.
321 const std::string strPhraseA =
322 "abandon abandon abandon abandon abandon abandon "
323 "abandon abandon abandon abandon abandon about";
324 const std::string strPhraseB =
325 "legal winner thank year wave sausage worth useful "
326 "legal winner thank yellow";
327
328 try
329 {
330 if (!LoadWallet())
331 throw std::runtime_error("LoadWallet failed");
332
333 std::string strError;
334 TopUpKeyPool();
335 int nRandomPoolSize = 0;
336 CRITICAL_BLOCK(cs_keyPool)
337 nRandomPoolSize = (int)mapKeyPool.size();
338 nFail += Check(nRandomPoolSize == KEYPOOL_SIZE,
339 "a wallet can start with a random key pool") ? 0 : 1;
340
341 // The address the wallet was showing before any of this. It has to
342 // survive under a name that says it is not covered by the phrase.
343 std::vector<unsigned char> vchPreSeedKey = keyUser.GetPubKey();
344 std::string strPreSeedAddr = PubKeyToAddress(keyUser.GetPubKey());
345
346 nFail += Check(SetHDSeedFromMnemonic(strPhraseA, strError),
347 "a valid phrase installs a seed") ? 0 : 1;
348 nFail += Check(HaveHDSeed(), "the wallet reports having a seed") ? 0 : 1;
349 nFail += Check(nHDKeySchema == HD_SCHEMA_BIP44,
350 "a new recovery phrase records the BIP44 HD schema") ? 0 : 1;
351 nFail += Check(nHDCoinType == HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL,
352 "a new recovery phrase records the provisional BIP44 coin type") ? 0 : 1;
353 nFail += Check(nHDReceiveNext == 1 && nHDChangeNext == 0,
354 "BIP44 receive/change counters reserve the default receive key") ? 0 : 1;
355 std::vector<unsigned int> vBIP44Path = HDBIP44Path(HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL,
356 HD_BIP44_ACCOUNT,
357 HD_BIP44_CHAIN_RECEIVE,
358 0);
359 nFail += Check(vBIP44Path.size() == 5 &&
360 vBIP44Path[0] == (HD_BIP44_PURPOSE | bitflash::BIP32_HARDENED) &&
361 vBIP44Path[1] == (HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL | bitflash::BIP32_HARDENED) &&
362 vBIP44Path[2] == (HD_BIP44_ACCOUNT | bitflash::BIP32_HARDENED) &&
363 vBIP44Path[3] == HD_BIP44_CHAIN_RECEIVE &&
364 vBIP44Path[4] == 0,
365 "the BIP44 helper builds m/44'/coin_type'/account'/change/index") ? 0 : 1;
366
367 int nPoolAfterSeed = 0;
368 CRITICAL_BLOCK(cs_keyPool)
369 nPoolAfterSeed = (int)mapKeyPool.size();
370 nFail += Check(nPoolAfterSeed == 0,
371 "installing a phrase clears the old random key pool") ? 0 : 1;
372 nFail += Check(nHDNext == 0,
373 "installing a BIP44 phrase leaves the legacy counter unused") ? 0 : 1;
374
375 CKey keyFirstDerived;
376 if (!DeriveHDKey(0, keyFirstDerived, strError))
377 throw std::runtime_error("default derivation failed: " + strError);
378 bitflash::BIP32PrivateNode hdParent;
379 hdParent.privateKey = vchHDMaster;
380 hdParent.chainCode = vchHDChainCode;
381 bitflash::BIP32PrivateNode hdBIP44ReceiveChild;
382 if (!bitflash::BIP32DerivePath(hdParent,
383 HDBIP44Path(HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL,
384 HD_BIP44_ACCOUNT,
385 HD_BIP44_CHAIN_RECEIVE,
386 0),
387 hdBIP44ReceiveChild,
388 strError))
389 throw std::runtime_error("BIP44 receive path derivation failed: " + strError);
390 CKey keyBIP44ReceivePath;
391 if (!keyBIP44ReceivePath.SetSecret(hdBIP44ReceiveChild.privateKey))
392 throw std::runtime_error("BIP44 receive path produced an unusable key");
393 nFail += Check(keyBIP44ReceivePath.GetPubKey() == keyFirstDerived.GetPubKey(),
394 "the default HD key path is m/44'/coin_type'/0'/0/0") ? 0 : 1;
395
396 bitflash::BIP32PrivateNode hdLegacyChild;
397 int nSchemaForLegacyCheck = nHDKeySchema;
398 nHDKeySchema = HD_SCHEMA_LEGACY;
399 CKey keyLegacyDerived;
400 if (!DeriveHDKey(0, keyLegacyDerived, strError))
401 throw std::runtime_error("legacy derivation failed: " + strError);
402 nHDKeySchema = nSchemaForLegacyCheck;
403 if (!bitflash::BIP32DerivePath(hdParent, HDLegacyPath(0), hdLegacyChild, strError))
404 throw std::runtime_error("legacy path derivation failed: " + strError);
405 CKey keyLegacyPath;
406 if (!keyLegacyPath.SetSecret(hdLegacyChild.privateKey))
407 throw std::runtime_error("legacy path produced an unusable key");
408 nFail += Check(keyLegacyPath.GetPubKey() == keyLegacyDerived.GetPubKey(),
409 "the legacy HD key path remains m/index'") ? 0 : 1;
410
411 std::vector<unsigned char> vchDefaultKey;
412 bool fDefaultRead = CWalletDB("r").ReadDefaultKey(vchDefaultKey);
413 nFail += Check(fDefaultRead && vchDefaultKey == keyFirstDerived.GetPubKey(),
414 "the default receiving key is derived from the phrase") ? 0 : 1;
415 nFail += Check(WalletHasPrivateKey(vchDefaultKey),
416 "the derived default key is stored in wallet.dat") ? 0 : 1;
417
418 std::string strNewAddr = PubKeyToAddress(vchDefaultKey);
419 nFail += Check(strPreSeedAddr != strNewAddr,
420 "the visible address stops being the pre-seed one") ? 0 : 1;
421 nFail += Check(mapAddressBook.count(strPreSeedAddr) > 0 &&
422 mapAddressBook[strPreSeedAddr] !=
423 mapAddressBook[strNewAddr],
424 "the pre-seed address is kept, named apart from the new one") ? 0 : 1;
425
426 std::vector<unsigned char> vchBefore = vchHDMaster;
427 int nSchemaBefore = nHDKeySchema;
428 unsigned int nCoinTypeBefore = nHDCoinType;
429 nFail += Check(!SetHDSeedFromMnemonic("not a mnemonic at all", strError),
430 "an invalid phrase is refused") ? 0 : 1;
431 nFail += Check(vchHDMaster == vchBefore,
432 "a refused phrase leaves the existing seed alone") ? 0 : 1;
433 nFail += Check(nHDKeySchema == nSchemaBefore,
434 "a refused phrase leaves the derivation schema alone") ? 0 : 1;
435 nFail += Check(nHDCoinType == nCoinTypeBefore,
436 "a refused phrase leaves the BIP44 coin type alone") ? 0 : 1;
437
438 // The property the whole feature exists for: the same words give back
439 // the same keys, in the same order.
440 std::vector<std::string> first;
441 for (unsigned int i = 0; i < 5; i++)
442 {
443 CKey key;
444 if (!DeriveHDKey(i, key, strError))
445 throw std::runtime_error("derivation failed: " + strError);
446 first.push_back(HexStrLocal(key.GetPubKey()));
447 }
448
449 SetHDSeedFromMnemonic(strPhraseA, strError);
450 std::vector<std::string> again;
451 for (unsigned int i = 0; i < 5; i++)
452 {
453 CKey key;
454 if (!DeriveHDKey(i, key, strError))
455 throw std::runtime_error("derivation failed: " + strError);
456 again.push_back(HexStrLocal(key.GetPubKey()));
457 }
458 nFail += Check(first == again,
459 "the same phrase derives the same keys in the same order") ? 0 : 1;
460
461 SetHDSeedFromMnemonic(strPhraseB, strError);
462 std::vector<std::string> other;
463 for (unsigned int i = 0; i < 5; i++)
464 {
465 CKey key;
466 if (!DeriveHDKey(i, key, strError))
467 throw std::runtime_error("derivation failed: " + strError);
468 other.push_back(HexStrLocal(key.GetPubKey()));
469 }
470 nFail += Check(first != other, "a different phrase derives different keys") ? 0 : 1;
471
472 std::set<std::string> distinct(first.begin(), first.end());
473 nFail += Check(distinct.size() == first.size(),
474 "consecutive indices give distinct keys") ? 0 : 1;
475
476 // With a seed installed the pool must be derived from it, and the
477 // counter must move exactly once per key.
478 SetHDSeedFromMnemonic(strPhraseA, strError);
479 unsigned int nReceiveNextBefore = nHDReceiveNext;
480 TopUpKeyPool();
481
482 int nPool = 0;
483 CRITICAL_BLOCK(cs_keyPool)
484 nPool = (int)mapKeyPool.size();
485 nFail += Check(nPool == KEYPOOL_SIZE, "the derived pool fills") ? 0 : 1;
486 nFail += Check(nHDReceiveNext == nReceiveNextBefore + (unsigned int)KEYPOOL_SIZE,
487 "the BIP44 receive counter advances once per pooled key") ? 0 : 1;
488 nFail += Check(nHDChangeNext == 0,
489 "filling the receive pool leaves the BIP44 change counter alone") ? 0 : 1;
490 nFail += Check(!RestoreScanReachedDepth(HD_SCHEMA_BIP44, 600, 400, 600, 600),
491 "BIP44 restore depth is not satisfied by receive plus legacy alone") ? 0 : 1;
492 nFail += Check(RestoreScanReachedDepth(HD_SCHEMA_BIP44, 600, 600, 600, 600),
493 "BIP44 restore depth is satisfied on each branch") ? 0 : 1;
494 nFail += Check(RestoreScanReachedDepth(HD_SCHEMA_LEGACY, 0, 0, 600, 600),
495 "legacy restore depth still follows the legacy counter") ? 0 : 1;
496
497 std::set<std::string> derived;
498 for (unsigned int i = nReceiveNextBefore; i < nHDReceiveNext; i++)
499 {
500 CKey key;
501 if (!DeriveHDKey(i, key, strError))
502 throw std::runtime_error("derivation failed: " + strError);
503 derived.insert(HexStrLocal(key.GetPubKey()));
504 }
505
506 bool fStored = true;
507 bool fFromSeed = true;
508 CRITICAL_BLOCK(cs_keyPool)
509 {
510 for (map<int64, vector<unsigned char> >::const_iterator mi = mapKeyPool.begin();
511 mi != mapKeyPool.end(); ++mi)
512 {
513 if (!WalletHasPrivateKey(mi->second))
514 fStored = false;
515 if (!derived.count(HexStrLocal(mi->second)))
516 fFromSeed = false;
517 }
518 }
519 nFail += Check(fStored, "every derived pooled key is stored in wallet.dat") ? 0 : 1;
520 nFail += Check(fFromSeed, "every pooled key came from the seed, not from chance") ? 0 : 1;
521
522 // A recovery phrase is only useful if the wallet can tell the user
523 // what today's spendable balance would actually come back from it.
524 CKey keyAuditDerived;
525 if (!DeriveHDKey(0, keyAuditDerived, strError))
526 throw std::runtime_error("audit derivation failed: " + strError);
527 CKey keyAuditChange;
528 if (!DeriveHDChangeKey(0, keyAuditChange, strError))
529 throw std::runtime_error("audit change derivation failed: " + strError);
530 nFail += Check(keyAuditChange.GetPubKey() != keyAuditDerived.GetPubKey(),
531 "BIP44 receive and change chains derive different keys") ? 0 : 1;
532 if (!AddKey(keyAuditChange))
533 throw std::runtime_error("could not store the audit change key");
534 nHDChangeNext = 1;
535 int nSchemaForLegacyAudit = nHDKeySchema;
536 nHDKeySchema = HD_SCHEMA_LEGACY;
537 CKey keyAuditLegacyHD;
538 if (!DeriveHDKey(0, keyAuditLegacyHD, strError))
539 throw std::runtime_error("audit legacy compatibility derivation failed: " + strError);
540 nHDKeySchema = nSchemaForLegacyAudit;
541 if (!AddKey(keyAuditLegacyHD))
542 throw std::runtime_error("could not store the audit legacy compatibility key");
543 nHDNext = 1;
544
545 CWalletTx wtxLegacy;
546 wtxLegacy.vout.push_back(CTxOut(5 * COIN, CScript() << vchPreSeedKey << OP_CHECKSIG));
547 CWalletTx wtxDerived;
548 wtxDerived.vout.push_back(CTxOut(7 * COIN, CScript() << keyAuditDerived.GetPubKey() << OP_CHECKSIG));
549 CWalletTx wtxChange;
550 wtxChange.vout.push_back(CTxOut(13 * COIN, CScript() << keyAuditChange.GetPubKey() << OP_CHECKSIG));
551 CWalletTx wtxLegacyHD;
552 wtxLegacyHD.vout.push_back(CTxOut(17 * COIN, CScript() << keyAuditLegacyHD.GetPubKey() << OP_CHECKSIG));
553 CWalletTx wtxImmatureLegacy;
554 wtxImmatureLegacy.vin.push_back(CTxIn());
555 wtxImmatureLegacy.vout.push_back(CTxOut(11 * COIN, CScript() << vchPreSeedKey << OP_CHECKSIG));
556
557 CRITICAL_BLOCK(cs_mapWallet)
558 {
559 mapWallet.clear();
560 mapWallet[wtxLegacy.GetHash()] = wtxLegacy;
561 mapWallet[wtxDerived.GetHash()] = wtxDerived;
562 mapWallet[wtxChange.GetHash()] = wtxChange;
563 mapWallet[wtxLegacyHD.GetHash()] = wtxLegacyHD;
564 mapWallet[wtxImmatureLegacy.GetHash()] = wtxImmatureLegacy;
565 }
566
567 WalletRecoveryAudit audit = GetWalletRecoveryAudit();
568 nFail += Check(audit.fHaveSeed, "the recovery audit reports the phrase") ? 0 : 1;
569 nFail += Check(audit.nSchema == HD_SCHEMA_BIP44,
570 "the recovery audit reports the derivation schema") ? 0 : 1;
571 nFail += Check(audit.nCoinType == HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL,
572 "the recovery audit reports the BIP44 coin type") ? 0 : 1;
573 nFail += Check(audit.nReceiveNext == nHDReceiveNext && audit.nChangeNext == 1,
574 "the recovery audit reports receive/change counters") ? 0 : 1;
575 nFail += Check(audit.nLegacyCredit == 5 * COIN,
576 "the recovery audit finds wallet.dat-only balance") ? 0 : 1;
577 nFail += Check(audit.nRecoverableCredit == 37 * COIN,
578 "the recovery audit finds BIP44 and legacy-HD phrase balance") ? 0 : 1;
579 nFail += Check(audit.nLegacyTx == 1 && audit.nRecoverableTx == 3,
580 "the recovery audit counts wallet.dat-only and phrase-backed transactions") ? 0 : 1;
581 nFail += Check(audit.nLegacyImmatureCredit == 11 * COIN,
582 "the recovery audit finds wallet.dat-only immature mining rewards") ? 0 : 1;
583 nFail += Check(audit.nLegacyImmatureTx == 1,
584 "the recovery audit counts wallet.dat-only immature mining rewards") ? 0 : 1;
585 }
586 catch (const std::exception& e)
587 {
588 printf(" FAIL exception: %s\n", e.what());
589 nFail++;
590 }
591 catch (...)
592 {
593 printf(" FAIL unknown exception\n");
594 nFail++;
595 }
596
597 DBFlush(true);
598 SetCurrentDir(cwd);
599 RemoveTree(tmp);
600 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
601 nFail, nFail == 1 ? "" : "s");
602 fflush(stdout);
603 return nFail == 0 ? 0 : 1;
604}
605
606static int RunWalletFormatSelfTest()
607{
608 fflush(stdout);
609 printf("wallet-format self-test\n");
610
611 std::string tmp;
612 std::string cwd;
613 if (!MakeTempDir(tmp))
614 {
615 printf(" FAIL could not create a temporary data directory\n");
616 return 1;
617 }
618 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
619 {
620 printf(" FAIL could not move into the temporary data directory\n");
621 RemoveTree(tmp);
622 return 1;
623 }
624
625 int nFail = 0;
626 strSetDataDir = tmp;
627 printf(" temp datadir: %s\n", tmp.c_str());
628
629 try
630 {
631 class CWalletDBRaw : public CWalletDB
632 {
633 public:
634 CWalletDBRaw(const char* pszMode="r+") : CWalletDB(pszMode) { }
635 bool WriteStringRecord(const string& strType, int nValue)
636 {
637 return Write(strType, nValue);
638 }
639 bool EraseStringRecord(const string& strType)
640 {
641 return Erase(strType);
642 }
643 };
644
645 if (!LoadWallet())
646 throw std::runtime_error("LoadWallet failed");
647
648 {
649 CWalletDB walletdb;
650 nFail += Check(walletdb.WriteWalletMinVersion(WALLET_FORMAT_SUPPORTED),
651 "the current wallet format marker can be written") ? 0 : 1;
652 }
653
654 std::vector<unsigned char> vchDefaultKey;
655 nFail += Check(CWalletDB("r").LoadWallet(vchDefaultKey),
656 "the current wallet format marker loads") ? 0 : 1;
657
658 {
659 CWalletDBRaw walletdb;
660 nFail += Check(walletdb.WriteStringRecord("mkey", 1),
661 "a malformed encrypted master-key record can be written") ? 0 : 1;
662 }
663
664 vchDefaultKey.clear();
665 nFail += Check(!CWalletDB("r").LoadWallet(vchDefaultKey),
666 "a malformed encrypted master-key record is refused") ? 0 : 1;
667
668 {
669 CWalletDBRaw walletdb;
670 nFail += Check(walletdb.EraseStringRecord("mkey"),
671 "the malformed encrypted master-key record can be removed") ? 0 : 1;
672 }
673
674 {
675 CWalletDB walletdb;
676 nFail += Check(walletdb.WriteWalletMinVersion(WALLET_FORMAT_SUPPORTED + 1),
677 "a future wallet format marker can be written") ? 0 : 1;
678 }
679
680 vchDefaultKey.clear();
681 nFail += Check(!CWalletDB("r").LoadWallet(vchDefaultKey),
682 "a future wallet format marker is refused") ? 0 : 1;
683 }
684 catch (const std::exception& e)
685 {
686 printf(" FAIL exception: %s\n", e.what());
687 nFail++;
688 }
689 catch (...)
690 {
691 printf(" FAIL unknown exception\n");
692 nFail++;
693 }
694
695 DBFlush(true);
696 SetCurrentDir(cwd);
697 RemoveTree(tmp);
698 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
699 nFail, nFail == 1 ? "" : "s");
700 fflush(stdout);
701 return nFail == 0 ? 0 : 1;
702}
703
704static void ClearWalletRuntimeForTest()
705{
706 CRITICAL_BLOCK(cs_mapKeys)
707 {
708 mapKeys.clear();
709 mapCryptedKeys.clear();
710 mapPubKeys.clear();
711 mapMasterKeys.clear();
712 vWalletMasterKey.clear();
713 vchCryptedHDMaster.clear();
714 vchCryptedHDChainCode.clear();
715 fWalletEncrypted = false;
716 fWalletLocked = true;
717 }
718 CRITICAL_BLOCK(cs_keyPool)
719 mapKeyPool.clear();
720 vchHDMaster.clear();
721 vchHDChainCode.clear();
722 nHDNext = 0;
723}
724
725static int RunWalletCryptoSelfTest()
726{
727 fflush(stdout);
728 printf("wallet-crypto self-test\n");
729
730 std::string tmp;
731 std::string cwd;
732 if (!MakeTempDir(tmp))
733 {
734 printf(" FAIL could not create a temporary data directory\n");
735 return 1;
736 }
737 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
738 {
739 printf(" FAIL could not move into the temporary data directory\n");
740 RemoveTree(tmp);
741 return 1;
742 }
743
744 int nFail = 0;
745 strSetDataDir = tmp;
746 printf(" temp datadir: %s\n", tmp.c_str());
747
748 try
749 {
750 const string strPassphrase = "btf-test-passphrase";
751 const string strWrongPassphrase = "btf-test-passphraser";
752
753 CKey key;
754 key.MakeNewKey();
755 vector<unsigned char> vchPubKey = key.GetPubKey();
756 CPrivKey vchPrivKey = key.GetPrivKey();
757
758 CKeyingMaterial vchMasterKey;
759 for (int i = 0; i < 32; i++)
760 vchMasterKey.push_back((unsigned char)(i + 1));
761
762 CWalletMasterKey kMasterKey;
763 kMasterKey.vchSalt.clear();
764 for (int i = 0; i < 8; i++)
765 kMasterKey.vchSalt.push_back((unsigned char)(0xa0 + i));
766 kMasterKey.nDeriveIterations = 2500;
767
768 CKeyingMaterial vchPassKey;
769 vector<unsigned char> vchPassIV;
770 nFail += Check(DeriveWalletPassphraseKey(strPassphrase, kMasterKey.vchSalt,
771 kMasterKey.nDeriveIterations,
772 vchPassKey, vchPassIV),
773 "a passphrase key can be derived") ? 0 : 1;
774 nFail += Check(EncryptSecret(vchPassKey,
775 vector<unsigned char>(vchMasterKey.begin(), vchMasterKey.end()),
776 vchPassIV, kMasterKey.vchCryptedKey),
777 "the wallet master key can be encrypted") ? 0 : 1;
778
779 vector<unsigned char> vchCryptedKey;
780 nFail += Check(EncryptSecret(vchMasterKey,
781 vector<unsigned char>(vchPrivKey.begin(), vchPrivKey.end()),
782 WalletKeyIV(vchPubKey), vchCryptedKey),
783 "a private key can be encrypted under the wallet master key") ? 0 : 1;
784
785 {
786 CWalletDB walletdb("cr");
787 nFail += Check(walletdb.WriteWalletMinVersion(WALLET_FORMAT_ENCRYPTED),
788 "an encrypted wallet format marker can be written") ? 0 : 1;
789 nFail += Check(walletdb.WriteMasterKey(0, kMasterKey),
790 "the encrypted master key can be written") ? 0 : 1;
791 nFail += Check(walletdb.WriteCryptedKey(vchPubKey, vchCryptedKey),
792 "the encrypted private key can be written") ? 0 : 1;
793 nFail += Check(walletdb.WriteDefaultKey(vchPubKey),
794 "the encrypted wallet default key can be written") ? 0 : 1;
795 }
796
797 ClearWalletRuntimeForTest();
798
799 vector<unsigned char> vchDefaultKey;
800 nFail += Check(CWalletDB("r").LoadWallet(vchDefaultKey),
801 "an encrypted wallet can be loaded") ? 0 : 1;
802 nFail += Check(vchDefaultKey == vchPubKey,
803 "the encrypted wallet default public key loads") ? 0 : 1;
804 nFail += Check(IsWalletEncrypted() && IsWalletLocked(),
805 "an encrypted wallet loads locked") ? 0 : 1;
806 nFail += Check(WalletCanSpendKey(vchPubKey),
807 "an encrypted key is recognized as wallet-owned while locked") ? 0 : 1;
808
809 CPrivKey vchDecrypted;
810 string strError;
811 nFail += Check(!GetWalletPrivKey(vchPubKey, vchDecrypted, strError),
812 "a locked encrypted key cannot be read") ? 0 : 1;
813 nFail += Check(!UnlockWallet(strWrongPassphrase, strError),
814 "the wrong-passphrase does not unlock") ? 0 : 1;
815 nFail += Check(UnlockWallet(strPassphrase, strError),
816 "the right passphrase unlocks") ? 0 : 1;
817 nFail += Check(!IsWalletLocked(),
818 "the wallet reports unlocked") ? 0 : 1;
819 nFail += Check(GetWalletPrivKey(vchPubKey, vchDecrypted, strError) &&
820 vchDecrypted == vchPrivKey,
821 "an unlocked encrypted key decrypts to the original private key") ? 0 : 1;
822 LockWallet();
823 nFail += Check(IsWalletLocked(),
824 "locking clears the unlocked state") ? 0 : 1;
825 }
826 catch (const std::exception& e)
827 {
828 printf(" FAIL exception: %s\n", e.what());
829 nFail++;
830 }
831 catch (...)
832 {
833 printf(" FAIL unknown exception\n");
834 nFail++;
835 }
836
837 DBFlush(true);
838 SetCurrentDir(cwd);
839 RemoveTree(tmp);
840 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
841 nFail, nFail == 1 ? "" : "s");
842 fflush(stdout);
843 return nFail == 0 ? 0 : 1;
844}
845
846static bool FileContainsBytes(const string& strPath, const vector<unsigned char>& vchNeedle)
847{
848 if (vchNeedle.empty())
849 return false;
850 FILE* pf = fopen(strPath.c_str(), "rb");
851 if (!pf)
852 return false;
853
854 vector<unsigned char> vchHaystack;
855 unsigned char buf[4096];
856 size_t n = 0;
857 while ((n = fread(buf, 1, sizeof(buf), pf)) > 0)
858 vchHaystack.insert(vchHaystack.end(), buf, buf + n);
859 fclose(pf);
860
861 if (vchHaystack.size() < vchNeedle.size())
862 return false;
863 return search(vchHaystack.begin(), vchHaystack.end(),
864 vchNeedle.begin(), vchNeedle.end()) != vchHaystack.end();
865}
866
867static bool FileContainsText(const string& strPath, const string& strNeedle)
868{
869 FILE* pf = fopen(strPath.c_str(), "rb");
870 if (!pf)
871 return false;
872
873 string strHaystack;
874 char buf[4096];
875 size_t n = 0;
876 while ((n = fread(buf, 1, sizeof(buf), pf)) > 0)
877 strHaystack.append(buf, buf + n);
878 fclose(pf);
879
880 return strHaystack.find(strNeedle) != string::npos;
881}
882
883static bool WriteTextFile(const string& strPath, const string& strText)
884{
885 FILE* pf = fopen(strPath.c_str(), "wb");
886 if (!pf)
887 return false;
888 bool fOk = fwrite(strText.data(), 1, strText.size(), pf) == strText.size();
889 if (fclose(pf) != 0)
890 fOk = false;
891 return fOk;
892}
893
894static string QuoteCommandArg(const string& str)
895{
896 string out = "\"";
897 for (size_t i = 0; i < str.size(); i++)
898 {
899 if (str[i] == '"')
900 out += "\\\"";
901 else
902 out += str[i];
903 }
904 out += "\"";
905 return out;
906}
907
908static int RunBitflashChild(const string& strExe,
909 const vector<string>& vArgs,
910 const string* pStdinFile = NULL)
911{
912#ifdef _WIN32
913 vector<char*> argv;
914 argv.push_back((char*)strExe.c_str());
915 for (size_t i = 0; i < vArgs.size(); i++)
916 argv.push_back((char*)vArgs[i].c_str());
917 argv.push_back(NULL);
918
919 int nOldOut = _dup(1);
920 int nOldErr = _dup(2);
921 int nOldIn = _dup(0);
922 int nNull = _open("NUL", _O_WRONLY);
923 int nIn = -1;
924 if (nNull >= 0)
925 {
926 _dup2(nNull, 1);
927 _dup2(nNull, 2);
928 }
929 if (pStdinFile)
930 {
931 nIn = _open(pStdinFile->c_str(), _O_RDONLY);
932 if (nIn >= 0)
933 _dup2(nIn, 0);
934 }
935 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]);
936 if (nIn >= 0)
937 _close(nIn);
938 if (nNull >= 0)
939 _close(nNull);
940 if (nOldIn >= 0)
941 {
942 _dup2(nOldIn, 0);
943 _close(nOldIn);
944 }
945 if (nOldOut >= 0)
946 {
947 _dup2(nOldOut, 1);
948 _close(nOldOut);
949 }
950 if (nOldErr >= 0)
951 {
952 _dup2(nOldErr, 2);
953 _close(nOldErr);
954 }
955 return nRet;
956#else
957 string strCmd = QuoteCommandArg(strExe);
958 for (size_t i = 0; i < vArgs.size(); i++)
959 strCmd += " " + QuoteCommandArg(vArgs[i]);
960 if (pStdinFile)
961 strCmd += " < " + QuoteCommandArg(*pStdinFile);
962 strCmd += " > /dev/null 2>&1";
963 return system(strCmd.c_str());
964#endif
965}
966
967static int RunWalletEncryptSelfTest()
968{
969 fflush(stdout);
970 printf("wallet-encrypt self-test\n");
971
972 std::string tmp;
973 std::string cwd;
974 if (!MakeTempDir(tmp))
975 {
976 printf(" FAIL could not create a temporary data directory\n");
977 return 1;
978 }
979 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
980 {
981 printf(" FAIL could not move into the temporary data directory\n");
982 RemoveTree(tmp);
983 return 1;
984 }
985
986 int nFail = 0;
987 bool fWalletEnvClosed = false;
988 strSetDataDir = tmp;
989 printf(" temp datadir: %s\n", tmp.c_str());
990
991 try
992 {
993 if (!LoadWallet())
994 throw std::runtime_error("LoadWallet failed");
995
996 string strError;
997 const string strMnemonic =
998 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
999 nFail += Check(SetHDSeedFromMnemonic(strMnemonic, strError),
1000 "a phrase can be installed before encryption") ? 0 : 1;
1001 TopUpKeyPool();
1002
1003 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
1004 CPrivKey vchDefaultPrivKey;
1005 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
1006 "the default private key is readable before encryption") ? 0 : 1;
1007 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(), vchDefaultPrivKey.end());
1008 vector<unsigned char> vchPlainHDMaster = vchHDMaster;
1009 vector<unsigned char> vchPlainHDChainCode = vchHDChainCode;
1010
1011 string strBackup;
1012 nFail += Check(EncryptWallet("btf-test-passphrase", strBackup, strError),
1013 "wallet.dat can be rewritten encrypted") ? 0 : 1;
1014 fWalletEnvClosed = true;
1015 nFail += Check(FileExists(strBackup.c_str()),
1016 "the original wallet is preserved as an explicit backup") ? 0 : 1;
1017
1018 string strWalletPath = GetAppDir() + "/wallet.dat";
1019 nFail += Check(!FileContainsBytes(strWalletPath, vchDefaultPrivBytes),
1020 "the active wallet no longer contains the default private key bytes") ? 0 : 1;
1021 nFail += Check(!FileContainsBytes(strWalletPath, vchPlainHDMaster),
1022 "the active wallet no longer contains the HD master key bytes") ? 0 : 1;
1023 nFail += Check(!FileContainsBytes(strWalletPath, vchPlainHDChainCode),
1024 "the active wallet no longer contains the HD chain-code bytes") ? 0 : 1;
1025
1026#ifdef _WIN32
1027 string strExe = cwd + "\\bitflash.exe";
1028#else
1029 string strExe = cwd + "/bitflash-node";
1030#endif
1031 string strWrongDump = tmp + "/wrong-pass-dump.txt";
1032 string strRightDump = tmp + "/right-pass-dump.txt";
1033 string strLiteralDump = tmp + "/literal-pass-dump.txt";
1034 string strStdinDump = tmp + "/stdin-pass-dump.txt";
1035 string strWrongPassFile = tmp + "/wrong-pass.txt";
1036 string strRightPassFile = tmp + "/right-pass.txt";
1037 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") &&
1038 WriteTextFile(strRightPassFile, "btf-test-passphrase\n"),
1039 "passphrase files can be written for restarted commands") ? 0 : 1;
1040
1041 vector<string> vWrongArgs;
1042 vWrongArgs.push_back("-datadir=" + tmp);
1043 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
1044 vWrongArgs.push_back("-dumpwallet=" + strWrongDump);
1045 vWrongArgs.push_back("-nogui");
1046 int nWrongRet = RunBitflashChild(strExe, vWrongArgs);
1047 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
1048 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1;
1049
1050 vector<string> vLiteralArgs;
1051 vLiteralArgs.push_back("-datadir=" + tmp);
1052 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase");
1053 vLiteralArgs.push_back("-dumpwallet=" + strLiteralDump);
1054 vLiteralArgs.push_back("-nogui");
1055 int nLiteralRet = RunBitflashChild(strExe, vLiteralArgs);
1056 nFail += Check(nLiteralRet != 0 && !FileExists(strLiteralDump.c_str()),
1057 "literal passphrases on the command line are refused") ? 0 : 1;
1058
1059 vector<string> vRightArgs;
1060 vRightArgs.push_back("-datadir=" + tmp);
1061 vRightArgs.push_back("-walletpassphrase=@" + strRightPassFile);
1062 vRightArgs.push_back("-dumpwallet=" + strRightDump);
1063 vRightArgs.push_back("-nogui");
1064 int nRightRet = RunBitflashChild(strExe, vRightArgs);
1065 nFail += Check(nRightRet == 0 && FileExists(strRightDump.c_str()),
1066 "a restarted wallet unlocks with the right passphrase") ? 0 : 1;
1067 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
1068 "the restarted wallet can decrypt and dump the original key") ? 0 : 1;
1069
1070 vector<string> vStdinArgs;
1071 vStdinArgs.push_back("-datadir=" + tmp);
1072 vStdinArgs.push_back("-walletpassphrase");
1073 vStdinArgs.push_back("-dumpwallet=" + strStdinDump);
1074 vStdinArgs.push_back("-nogui");
1075 int nStdinRet = RunBitflashChild(strExe, vStdinArgs, &strRightPassFile);
1076 nFail += Check(nStdinRet == 0 && FileExists(strStdinDump.c_str()),
1077 "a restarted wallet can read the passphrase from stdin") ? 0 : 1;
1078 }
1079 catch (const std::exception& e)
1080 {
1081 printf(" FAIL exception: %s\n", e.what());
1082 nFail++;
1083 }
1084 catch (...)
1085 {
1086 printf(" FAIL unknown exception\n");
1087 nFail++;
1088 }
1089
1090 if (!fWalletEnvClosed)
1091 DBFlush(true);
1092 SetCurrentDir(cwd);
1093 RemoveTree(tmp);
1094 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1095 nFail, nFail == 1 ? "" : "s");
1096 fflush(stdout);
1097 return nFail == 0 ? 0 : 1;
1098}
1099
1100static int RunNetMessageSelfTest()
1101{
1102 fflush(stdout);
1103 printf("net-message self-test\n");
1104
1105 int nFail = 0;
1106 try
1107 {
1108 CNode complete(INVALID_SOCKET, CAddress("127.0.0.1"));
1109 complete.nVersion = VERSION;
1110 complete.vRecv << CMessageHeader("ping", 0);
1111 nFail += Check(ProcessMessages(&complete), "a complete empty message processes") ? 0 : 1;
1112 nFail += Check(complete.vRecv.empty(), "a complete message is consumed") ? 0 : 1;
1113 nFail += Check(!complete.fDisconnect, "a valid ping does not disconnect the peer") ? 0 : 1;
1114
1115 CNode partial(INVALID_SOCKET, CAddress("127.0.0.1"));
1116 partial.nVersion = VERSION;
1117 partial.vRecv << CMessageHeader("block", 100);
1118 unsigned int nPartialBefore = partial.vRecv.size();
1119 nFail += Check(ProcessMessages(&partial), "an incomplete message returns cleanly") ? 0 : 1;
1120 nFail += Check(partial.vRecv.size() == nPartialBefore,
1121 "an incomplete message keeps one header in the buffer") ? 0 : 1;
1122 nFail += Check(partial.nIncompleteMessageStart != 0,
1123 "an incomplete message starts a timeout clock") ? 0 : 1;
1124 nFail += Check(!partial.fDisconnect,
1125 "a fresh incomplete message does not disconnect immediately") ? 0 : 1;
1126
1127 CNode stale(INVALID_SOCKET, CAddress("127.0.0.1"));
1128 stale.nVersion = VERSION;
1129 stale.vRecv << CMessageHeader("block", 100);
1130 stale.nIncompleteMessageStart = GetTime() - BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS - 1;
1131 stale.nIncompleteMessageSize = 100;
1132 stale.strIncompleteMessageCommand = "block";
1133 ProcessMessages(&stale);
1134 nFail += Check(stale.fDisconnect,
1135 "a stale incomplete message disconnects the peer") ? 0 : 1;
1136
1137 CNode oversized(INVALID_SOCKET, CAddress("127.0.0.1"));
1138 oversized.nVersion = VERSION;
1139 oversized.vRecv << CMessageHeader("block", MAX_PROTOCOL_MESSAGE_SIZE + 1);
1140 ProcessMessages(&oversized);
1141 nFail += Check(oversized.fDisconnect,
1142 "an oversized message header disconnects the peer") ? 0 : 1;
1143 }
1144 catch (const std::exception& e)
1145 {
1146 printf(" FAIL exception: %s\n", e.what());
1147 nFail++;
1148 }
1149 catch (...)
1150 {
1151 printf(" FAIL unknown exception\n");
1152 nFail++;
1153 }
1154
1155 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1156 nFail, nFail == 1 ? "" : "s");
1157 fflush(stdout);
1158 return nFail == 0 ? 0 : 1;
1159}
1160
1161static CBlock MakeSizedConsensusBlock(size_t nScriptBytes)
1162{
1163 CTransaction tx;
1164 tx.vin.push_back(CTxIn(COutPoint(), CScript() << 1 << 1));
1165 tx.vout.push_back(CTxOut(0, CScript()));
1166 tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nScriptBytes, 0);
1167
1168 CBlock block;
1169 block.vtx.push_back(tx);
1170 block.hashMerkleRoot = block.BuildMerkleTree();
1171 block.nVersion = 1;
1172 block.nTime = GetAdjustedTime();
1173 block.nBits = bnProofOfWorkLimit.GetCompact();
1174 block.nNonce = 0;
1175 return block;
1176}
1177
1178static int RunConsensusLimitsSelfTest()
1179{
1180 fflush(stdout);
1181 printf("consensus-limits self-test\n");
1182
1183 int nFail = 0;
1184 try
1185 {
1186 nFail += Check(MAX_BLOCK_SIZE == 1000000,
1187 "the consensus block-size cap is 1 MB") ? 0 : 1;
1188 nFail += Check(MAX_BLOCK_SIZE < MAX_SIZE,
1189 "the block cap is tighter than the serializer cap") ? 0 : 1;
1190
1191 CBlock small = MakeSizedConsensusBlock(100);
1192 nFail += Check(small.CheckSizeLimits(),
1193 "a small block is within the consensus size limit") ? 0 : 1;
1194
1195 CBlock oversized = MakeSizedConsensusBlock(MAX_BLOCK_SIZE);
1196 unsigned int nSerialized = ::GetSerializeSize(oversized, SER_DISK);
1197 nFail += Check(nSerialized > MAX_BLOCK_SIZE && nSerialized <= MAX_SIZE,
1198 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1;
1199 nFail += Check(!oversized.CheckSizeLimits(),
1200 "a block above 1 MB is outside the consensus size limit") ? 0 : 1;
1201 }
1202 catch (const std::exception& e)
1203 {
1204 printf(" FAIL exception: %s\n", e.what());
1205 nFail++;
1206 }
1207 catch (...)
1208 {
1209 printf(" FAIL unknown exception\n");
1210 nFail++;
1211 }
1212
1213 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1214 nFail, nFail == 1 ? "" : "s");
1215 fflush(stdout);
1216 return nFail == 0 ? 0 : 1;
1217}
1218
1219int RunSelfTest(const std::string& name)
1220{
1221 AttachTerminal();
1222
1223 if (name == "wallet-keypool")
1224 return RunWalletKeyPoolSelfTest();
1225 if (name == "wallet-hd")
1226 return RunWalletHDSelfTest();
1227 if (name == "wallet-format")
1228 return RunWalletFormatSelfTest();
1229 if (name == "wallet-crypto")
1230 return RunWalletCryptoSelfTest();
1231 if (name == "wallet-encrypt")
1232 return RunWalletEncryptSelfTest();
1233 if (name == "net-message")
1234 return RunNetMessageSelfTest();
1235 if (name == "consensus-limits")
1236 return RunConsensusLimitsSelfTest();
1237
1238 printf("Unknown self-test '%s'\n", name.c_str());
1239 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, net-message, consensus-limits\n");
1240 return 1;
1241}