比特币源码解析(20) - 可执行程序 - Bitcoind
2018-02-13 14:40
639 查看
0x01 AppInitMain Step 5: verify wallet database integrity
#ifdef ENABLE_WALLET if (!WalletVerify()) return false; #endif1
2
3
4
Step 5主要是验证钱包数据库的完整性,从而避免钱包内容被本地错误的修改。钱包的启用是通过一个宏定义来进行实现的,如果启用了这个宏那么就会进行钱包数据的完整性校验,再来看看
WalletVerify的实现,
// src/wallet/init.cpp line 174 bool WalletVerify() { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) return true; uiInterface.InitMessage(_("Verifying wallet(s)...")); // Keep track of each wallet absolute path to detect duplicates. std::set<fs::path> wallet_paths; for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { if (boost::filesystem::path(walletFile).filename() != walletFile) { return InitError(strprintf(_("Error loading wallet %s. -wallet parameter must only specify a filename (not a path)."), walletFile)); } if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) { return InitError(strprintf(_("Error loading wallet %s. Invalid characters in -wallet filename."), walletFile)); } fs::path wallet_path = fs::absolute(walletFile, GetDataDir()); if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) { return InitError(strprintf(_("Error loading wallet %s. -wallet filename must be a regular file."), walletFile)); } if (!wallet_paths.insert(wallet_path).second) { return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), walletFile)); } std::string strError; if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError)) { return InitError(strError); } if (gArgs.GetBoolArg("-salvagewallet", false)) { // Recover readable keypairs: CWallet dummyWallet; std::string backup_filename; if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter, backup_filename)) { return false; } } std::string strWarning; bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError); if (!strWarning.empty()) { InitWarning(strWarning); } if (!dbV) { InitError(strError); return false; } } return true; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
3334
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
这个函数首先看命令行中是否禁用了钱包,如果禁用了那么就直接返回,这里的
-disablewallet和前面的宏定义是在不同的情况下执行的,宏定义是在编译的时候执行的,而命令行的参数中是在程序执行是才判断的。接下来对于命令行中传入的每一个钱包路径,首先检查文件的路径、是否包含非法字符、是否是regular file或者链接、是否有相同的文件等等。
验证钱包环境
验证完文件基本的属性之后,开始验证上下文环境,通过CWalletDB中的
VerifyEnvironment函数 ,而
CWalletDB中的
VerifyEnvironment又是调用
CDB中的
VerifyEnvironment,这函数的实现如下:
// src/wallet/db.cpp line 229 bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr) { LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); LogPrintf("Using wallet %s\n", walletFile); // Wallet file must be a plain filename without a directory if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) { errorStr = strprintf(_("Wallet %s resides outside data directory %s"), walletFile, dataDir.string()); return false; } if (!bitdb.Open(dataDir)) { // try moving the database env out of the way fs::path pathDatabase = dataDir / "database"; fs::path pathDatabaseBak = dataDir / strprintf("database.%d.bak", GetTime()); try { fs::rename(pathDatabase, pathDatabaseBak); LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string()); } catch (const fs::filesystem_error&) { // failure is ok (well, not really, but it's not worse than what we started with) } // try again if (!bitdb.Open(dataDir)) { // if it still fails, it probably means we can't even create the database env errorStr = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir()); return false; } } return true; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
该函数传入钱包文件名和钱包的绝对路径,首先检查钱包文件名是否是不包含路径的纯文件名,接下来调用一个
CDBEnv类型变量
bitdb中的
open函数,该函数实现如下:
// src/wallet/db.cpp line 67 bool CDBEnv::Open(const fs::path& pathIn) { if (fDbEnvInit) return true; boost::this_thread::interruption_point(); strPath = pathIn.string(); fs::path pathLogDir = pathIn / "database"; TryCreateDirectories(pathLogDir); fs::path pathErrorFile = pathIn / "db.log"; LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string()); unsigned int nEnvFlags = 0; if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB)) nEnvFlags |= DB_PRIVATE; dbenv->set_lg_dir(pathLogDir.string().c_str()); dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet dbenv->set_lg_bsize(0x10000); dbenv->set_lg_max(1048576); dbenv->set_lk_max_locks(40000); dbenv->set_lk_max_objects(40000); dbenv->set_errfile(fsbridge::fopen(pathErrorFile, "a")); /// debug dbenv->set_flags(DB_AUTO_COMMIT, 1); dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1); dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1); int ret = dbenv->open(strPath.c_str(), DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | 10864 DB_THREAD | DB_RECOVER | nEnvFlags, S_IRUSR | S_IWUSR); if (ret != 0) { dbenv->close(0); return error("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret)); } fDbEnvInit = true; fMockDb = false; return true; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
这个函数首先检查数据库文件是否存在,不存在就立即创建;然后设置日志文件,并且通过
DbEnv类型变量指针
dbenv设置了一系列数据库运行的相关参数,这些函数的介绍参考http://web.mit.edu/jhawk/mnt/spo/subversion/docs/api_cxx/c_index.html。回到
VerifyEnvironment函数,
open函数执行成功的话,就直接返回
true;否则就进入
if语句,之后首先将原来的钱包数据库文件进行备份,然后再次尝试调用
open函数,创建数据库文件。
恢复私钥
-salvgewallet:从损坏的钱包文件中尝试回复私钥。接下来的一个
if语句通过调用
CWalletDB::Recover()转而调用
CDB::Recover(),该函数的实现如下:
// src/wallet/db.cpp line 163 bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename) { // Recovery procedure: // move wallet file to walletfilename.timestamp.bak // Call Salvage with fAggressive=true to // get as much data as possible. // Rewrite salvaged data to fresh wallet file // Set -rescan so any missing transactions will be // found. int64_t now = GetTime(); newFilename = strprintf("%s.%d.bak", filename, now); int result = bitdb.dbenv->dbrename(nullptr, filename.c_str(), nullptr, newFilename.c_str(), DB_AUTO_COMMIT); if (result == 0) LogPrintf("Renamed %s to %s\n", filename, newFilename); else { LogPrintf("Failed to rename %s to %s\n", filename, newFilename); return false; } std::vector<CDBEnv::KeyValPair> salvagedData; bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData); if (salvagedData.empty()) { LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); return false; } LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); std::unique_ptr<Db> pdbCopy(new Db(bitdb.dbenv, 0)); int ret = pdbCopy->open(nullptr, // Txn pointer filename.c_str(), // Filename "main", // Logical db name DB_BTREE, // Database type DB_CREATE, // Flags 0); if (ret > 0) { LogPrintf("Cannot create database file %s\n", filename); pdbCopy->close(0); return false; } DbTxn* ptxn = bitdb.TxnBegin(); for (CDBEnv::KeyValPair& row : salvagedData) { if (recoverKVcallback) { CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) continue; } Dbt datKey(&row.first[0], row.first.size()); Dbt datValue(&row.second[0], row.second.size()); int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); if (ret2 > 0) fSuccess = false; } ptxn->commit(0); pdbCopy->close(0); return fSuccess; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
此函数有四个参数,分别表示如下含义:
filename:待恢复的钱包文件名;
callbackDataIn:恢复数据写入对象;
recoverKVcallback:回调函数,用来将恢复数据写入
callbackDataIn;
newFilename:备份文件名。
私钥恢复的步骤是首先备份原来的钱包文件,然后调用
CDBEnv类中的
Salvage函数,这个函数实现的功能是从文件中将公私钥读取出来并保存在到
salvagedData中。恢复完之后就将恢复的数据写入到本地数据库中,这个写入的过程都是通过
pdbCopy对象来进行的,同时如果定义了
recoverKVcallback函数,那么还同时写入到
callbackDataIn对象中,用于传给上层调用函数。
验证数据库文件
回到WalletVerify()函数,剩下最后一个函数
VerifyDatabaseFile,这个函数又调用
CDBEnv类中的
Verify函数,这个函数的实现如下:
// src/wallet/db.cpp line 146 CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename) { LOCK(cs_db); assert(mapFileUseCount.count(strFile) == 0); Db db(dbenv, 0); int result = db.verify(strFile.c_str(), nullptr, nullptr, 0); if (result == 0) return VERIFY_OK; else if (recoverFunc == nullptr) return RECOVER_FAIL; // Try to recover: bool fRecovered = (*recoverFunc)(strFile, out_backup_filename); return (fRecovered ? RECOVER_OK : RECOVER_FAIL); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
函数的主体又是调用
Db中的
Verify函数来验证数据库文件的完整性,这个函数的介绍可以参考http://web.mit.edu/jhawk/mnt/spo/subversion/docs/api_cxx/db_verify.html。如果验证通过的话那么就直接返回
VERIFY_OK;否则就先看是否设置了恢复函数,如果没有就返回
RECOVER_FAIL;如果设置了,那么就调用恢复函数,并返回恢复函数执行的结果。再回到原来的调用的地方可以发现恢复函数被设置成了
CWalletDB::Recover函数,所以这里都会调用这个函数。
转自:http://blog.csdn.net/pure_lady/article/details/78502801
相关文章推荐
- 比特币源码解析(20) - 可执行程序 - Bitcoind
- 比特币源码解析(16) - 可执行程序 - Bitcoind
- 比特币源码解析(17) - 可执行程序 - Bitcoind
- 比特币源码解析(12) - 可执行程序 - Bitcoind
- 比特币源码解析(21) - 可执行程序 - Bitcoind
- 比特币源码解析(21) - 可执行程序 - Bitcoind
- 比特币源码解析(18) - 可执行程序 - Bitcoind
- 比特币源码解析(22) - 可执行程序 - Bitcoind
- 比特币源码解析(14) - 可执行程序 - Bitcoind
- 比特币源码解析(11) - 可执行程序 - Bitcoind
- 比特币源码解析(16) - 可执行程序 - Bitcoind
- 比特币源码解析(11) - 可执行程序 - Bitcoind
- 比特币源码解析(12) - 可执行程序 - Bitcoind
- 比特币源码解析(22) - 可执行程序 - Bitcoind
- 比特币源码解析(19) - 可执行程序 - Bitcoind
- 比特币源码解析(15) - 可执行程序 - Bitcoind
- 比特币源码解析(14) - 可执行程序 - Bitcoind
- 比特币源码解析(9) - 可执行程序 - Bitcoind
- 比特币源码解析(9) - 可执行程序 - Bitcoind
- 比特币源码解析(15) - 可执行程序 - Bitcoind