您的位置:首页 > 其它

比特币源码解析(20) - 可执行程序 - Bitcoind

2018-02-13 14:40 639 查看

0x01 AppInitMain Step 5: verify wallet database integrity

#ifdef ENABLE_WALLET
if (!WalletVerify())
return false;
#endif
1
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  区块链 比特币 源码