比特币源码研读之二十二
我们今天将继续深入AppInitMain函数,下面我们一起来对其进行详细分析。
本文将着重分析钱包数据库文件验证与钱包文件恢复的代码。本文主要涉及的源码文件包括:
src/bitcond.cpp、src/init.h、src/init.cpp、src/util.h、src/util.cpp、src/wallet/wallet.h、src/wallet/wallet.cpp、src/wallet/db.h、src/wallet/db.cpp、src/wallet/walletdb.h、src/wallet/walletdb.cpp
这部分代码位于src/wallet/wallet.h中的CWallet类中,我们首先来看Verify函数在头文件中的声明:
//! Verify the wallet database and perform salvage ifrequired
static bool Verify();
该函数为静态全局函数,其作用是验证钱包数据库并在必要时执行钱包恢复操作。它是如何验证和执行钱包恢复的,我们将从其函数的代码中详细解读。
一、 钱包关闭参数
Verify()函数中首选进行钱包功能是否关闭判断,其判断依据为DEFAULT_DISABLE_WALLET参数,该参数定义于wallet.h中:
static const bool DEFAULT_DISABLE_WALLET = false;
从其定义可以看出钱包默认是开启的,但如果我们在启动比特币全节点钱包时,设置了disablewallet参数的话,钱包功能将被关闭,此时也就无需对钱包进行验证了,程序将退出。
二、钱包文件验证
比特币钱包数据库使用的是BerkeleyDB,那有人会说,我在用比特币全节点钱包的时候,并没有让我安装这个数据库啊,那这数据库功能怎么起作用的,如何实现钱包的存储功能呢?我们先来看下BerkeleyDB的功能介绍:
Berkeley DB是一个开源的文件数据库,介于关系数据库与内存数据库之间,使用方式与内存数据库类似,它提供的是一系列直接访问数据库的函数,而不是像关系数据库那样需要网络通讯、SQL解析等步骤。
从上述介绍我们可以看出,BerkeleyDB并不是Oracle、SQL Server或者MySQL那样是独立的关系型数据库,而是类似内存型数据库,直接在内存中进行数据库操作,主要应用在UNIX/LINUX操作系统上,其设计思想是简单、小巧、可靠、高性能。从这也可以看出中本聪在设计比特币时尽量以实用为宗旨进行实现,不会依赖多余的外部软件,这样可以更好地保证比特币节点的独立性。
现在我们再来看下钱包文件验证相关的具体代码:
1、LogPrintf("UsingBerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
2、std::stringwalletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);
3、LogPrintf("Usingwallet %s\n", walletFile);
4、uiInterface.InitMessage(_("Verifyingwallet..."));
// Wallet file must be a plain filename without adirectory
5、If (walletFile!= boost::filesystem::basename(walletFile) +boost::filesystem::extension(walletFile))
6、return InitError(strprintf(_("Wallet %s resides outside data directory %s"), walletFile, GetDataDir().string()));
上述代码中主要包含6行代码:
(1)在日志中打印BerkeleyDB的版本信息,其版本信息通过DbEnv::version(0,0,0)获得,DBEnv我们在wallet.h和wallet.cpp中并没有找到其定义,那么它是在哪定义的呢?DBEnv是berkeleydb的全局环境类,version函数是#include 中定义,我在berkeleydb的API介绍文档中找到了version的定义:
从该文档可以看出其返回值为berkeleydb的版本信息,返回的版本信息包括主版本、次版本以及修订版本3个信息。我们知道berkeleydb的版本信息通过LogPrintf函数将其打印至日志文件中,我们打开日志文件看下其打印信息:
绿色选中部分即为berkeleydb的版本打印信息,为4.8.3。
(2)第二行代码是获取代码文件,代码文件默认为DEFAULT_WALLET_DAT,其定义于wallet.cpp中,其定义如下:
const char * DEFAULT_WALLET_DAT ="wallet.dat";
虽然在这行代码中有-wallet参数,表示我们在启动比特币钱包时可以设置钱包文件的名称,但一般来说,大家还是默认使用wallet.dat文件。
(3)第3行和第4行代码中,分别在日志中打印了钱包文件名,同时会显示当前钱包状态为“Verifying wallet……”;
(4)第5行和第6行代码中验证钱包名称,通过其注释以及错误提示可以看出,钱包名称必须是且仅是文件名,不包含任何路径信息,而且钱包文件必须是在数据目录下。
三、 打开钱包数据库
此处将通过bitdb.open()打开钱包数据库,其中bitdb变量为CDBEnv的对象,该变量为全局变量,在src/wallet/db.h中声明,定义于db.cpp中:
extern CDBEnv bitdb; //db.h
CDBEnv bitdb; //db.cpp
CDBEnv中的open函数实现代码为用户db.cpp中,具体代码大家可以去该文件中查看,在这个函数中主要实现了database文件夹与db.log文件的创建,同时对钱包数据库环境进行初始化,设置相应参数。
如果数据库环境创建失败,程序将通过boost::filesystem::rename(pathDatabase,
pathDatabaseBak);把当前创建的database文件夹重命名,命名后的文件夹名称包含当前时间。
然后程序再次尝试创建钱包数据库,对其进行初始化,如果这次再次失败,程序将提示钱包数据库环境创建失败,并退出程序。
四、 钱包文件恢复
当我们的钱包文件出现损坏时,我们是可以尝试恢复的,恢复方式为在钱包文件启动时,设置salvgewallet参数,该参数的意义是:
-salvgewallet:参数的功能为试图在比特币客户端启动时从损坏的钱包中恢复私钥。
这个参数我们在《比特币源码研读之十六》中也解释过,大家有兴趣可以再回头看下第十六篇记录。
如果设置了salvgewallet参数后,程序此时将进行钱包恢复操作,其恢复操作是进入CWalletDB的Recover函数中。在该函数的实现中,我们可以看到在该函数的开始处就描述了其恢复过程:
(1)重命令钱包文件名称为:wallet.timestamp.bak
(2)通过调用CDBEnv的Salvage函数,并且参数fAggressive=true的情况下恢复尽可能多的数据;
(3)将恢复的数据重写至新的钱包文件中;
(4)通过-rescan参数对区块进行重新扫描,进而恢复丢失的交易。
五、 钱包文件验证
程序将通过bitdb对钱包文件(无论是已有的还是刚恢复的)进行验证,如果验证出错,该Verify函数中的回调函数将尝试恢复钱包文件,如果恢复成功则返回RECOVER_OK,否则返回RECOVER_FAIL。
以上即为钱包验证函数的代码分析,从本文的分析我们可以看出比特币的钱包文件为wallet.dat,钱包信息存储用的数据库为BerkeleyDB,同时我们还可以通过salvagewallet参数对钱包进行恢复操作。
区块链研习社比特币源码研读班 菜菜子
本文由【区块链研习社】优质内容计划支持,更多关于区块链的深度好文,请点击【区块链研习社】简书专栏:http://www.jianshu.com/c/b17f09dc2831