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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012183589/article/details/78502801

0x01 AppInitMain Step 5: verify wallet database integrity

#ifdef ENABLE_WALLET
    if (!WalletVerify())
        return false;
#endif

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;
}

这个函数首先看命令行中是否禁用了钱包,如果禁用了那么就直接返回,这里的-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;
}

该函数传入钱包文件名和钱包的绝对路径,首先检查钱包文件名是否是不包含路径的纯文件名,接下来调用一个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 |
                             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;
}

这个函数首先检查数据库文件是否存在,不存在就立即创建;然后设置日志文件,并且通过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;
}

此函数有四个参数,分别表示如下含义:

  • 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);
}

函数的主体又是调用Db中的Verify函数来验证数据库文件的完整性,这个函数的介绍可以参考http://web.mit.edu/jhawk/mnt/spo/subversion/docs/api_cxx/db_verify.html。如果验证通过的话那么就直接返回VERIFY_OK;否则就先看是否设置了恢复函数,如果没有就返回RECOVER_FAIL;如果设置了,那么就调用恢复函数,并返回恢复函数执行的结果。再回到原来的调用的地方可以发现恢复函数被设置成了CWalletDB::Recover函数,所以这里都会调用这个函数。

猜你喜欢

转载自blog.csdn.net/u012183589/article/details/78502801