比特币源码剖析(十四)
本篇主要分析 Step 8: load wallet 第八步加载钱包的详细过程。
源码剖析
3.11.8.第八步,加载钱包。这部分代码实现在“init.cpp”文件的 AppInit2(…) 函数中。
bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // 3.11.程序初始化,共 12 步
{
...
// ********************************************************* Step 8: load wallet // 若启用钱包功能,则加载钱包
#ifdef ENABLE_WALLET // 1.钱包有效的宏
if (fDisableWallet) { // 默认 false
pwalletMain = NULL; // 钱包指针置空
LogPrintf("Wallet disabled!\n");
} else {
// needed to restore wallet transaction meta data after -zapwallettxes // 需要在 -zapwallettxes 后恢复钱包交易元数据
std::vector<CWalletTx> vWtx; // 钱包交易列表
if (GetBoolArg("-zapwallettxes", false)) { // 分离钱包交易选项,默认关闭
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
pwalletMain = new CWallet(strWalletFile); // 1.1.创建并初始化钱包对象
DBErrors nZapWalletRet = pwalletMain->ZapWalletTx(vWtx); // 从钱包中分离所有交易到钱包交易列表
if (nZapWalletRet != DB_LOAD_OK) {
uiInterface.InitMessage(_("Error loading wallet.dat: Wallet corrupted"));
return false;
}
delete pwalletMain; // 删除钱包对象
pwalletMain = NULL; // 指针置空
}
uiInterface.InitMessage(_("Loading wallet...")); // 开始加载钱包
nStart = GetTimeMillis(); // 获取当前时间
bool fFirstRun = true; // 首次运行标志,初始为 true
pwalletMain = new CWallet(strWalletFile); // 1.2.创建新的钱包对象
DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun); // 加载钱包到内存(键值对)
if (nLoadWalletRet != DB_LOAD_OK) // 加载钱包状态错误
{
if (nLoadWalletRet == DB_CORRUPT)
strErrors << _("Error loading wallet.dat: Wallet corrupted") << "\n";
else if (nLoadWalletRet == DB_NONCRITICAL_ERROR)
{
InitWarning(_("Error reading wallet.dat! All keys read correctly, but transaction data"
" or address book entries might be missing or incorrect."));
}
else if (nLoadWalletRet == DB_TOO_NEW)
strErrors << _("Error loading wallet.dat: Wallet requires newer version of Bitcoin Core") << "\n";
else if (nLoadWalletRet == DB_NEED_REWRITE)
{
strErrors << _("Wallet needed to be rewritten: restart Bitcoin Core to complete") << "\n";
LogPrintf("%s", strErrors.str());
return InitError(strErrors.str());
}
else
strErrors << _("Error loading wallet.dat") << "\n";
} // 加载钱包成功
if (GetBoolArg("-upgradewallet", fFirstRun)) // 1.3.升级钱包选项,首次运行标志在这里应该为 false
{
int nMaxVersion = GetArg("-upgradewallet", 0);
if (nMaxVersion == 0) // the -upgradewallet without argument case
{
LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); // 60000
nMaxVersion = CLIENT_VERSION; // 最大版本为当前客户端版本
pwalletMain->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately // 这里设置的是最小版本
}
else
LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
if (nMaxVersion < pwalletMain->GetVersion()) // 若最大版本小于当前钱包版本
strErrors << _("Cannot downgrade wallet") << "\n";
pwalletMain->SetMaxVersion(nMaxVersion); // 设置最大版本
}
if (fFirstRun) // 1.4.若是首次运行
{
// Create new keyUser and set as default key // 创建新用户密钥并设置为默认密钥
RandAddSeedPerfmon(); // 随机数种子
CPubKey newDefaultKey; // 新公钥对象
if (pwalletMain->GetKeyFromPool(newDefaultKey)) { // 从钥匙池取一个公钥
pwalletMain->SetDefaultKey(newDefaultKey); // 设置该公钥为默认公钥
if (!pwalletMain->SetAddressBook(pwalletMain->vchDefaultKey.GetID(), "", "receive")) // 设置默认公钥到地址簿默认账户 "" 下,并设置目的为接收
strErrors << _("Cannot write default address") << "\n";
}
pwalletMain->SetBestChain(chainActive.GetLocator()); // 主钱包设置最佳链,记录最佳块的位置
}
LogPrintf("%s", strErrors.str());
LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); // 记录钱包加载的时间
RegisterValidationInterface(pwalletMain); // 注册一个钱包用于接收 bitcoin core 的升级
CBlockIndex *pindexRescan = chainActive.Tip(); // 获取链尖区块索引
if (GetBoolArg("-rescan", false)) // 1.5.再扫描选项,默认关闭
pindexRescan = chainActive.Genesis(); // 获取当前链的创世区块索引
else
{
CWalletDB walletdb(strWalletFile); // 通过钱包文件名创建钱包数据库对象
CBlockLocator locator;
if (walletdb.ReadBestBlock(locator)) // 获取最佳区块的位置
pindexRescan = FindForkInGlobalIndex(chainActive, locator); // 在激活的链和最佳区块间找最新的一个公共块
else
pindexRescan = chainActive.Genesis();
}
if (chainActive.Tip() && chainActive.Tip() != pindexRescan) // 链尖非创世区块也非分叉区块
{
//We can't rescan beyond non-pruned blocks, stop and throw an error // 我们无法再扫描超出非修剪的区块,停止或抛出一个错误。
//this might happen if a user uses a old wallet within a pruned node // 如果用户在已修剪节点中使用旧钱包或者在更长时间中运行的 -disablewallet,
// or if he ran -disablewallet for a longer time, then decided to re-enable // 则可能发生这种情况。
if (fPruneMode) // 修剪模式标志,默认为 false
{
CBlockIndex *block = chainActive.Tip(); // 获取激活的链尖区块索引
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block) // 找到 pindexRescan 所对应区块
block = block->pprev;
if (pindexRescan != block)
return InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
}
uiInterface.InitMessage(_("Rescanning...")); // 开始再扫描
LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); // 记录从 pindexRescan->nHeight 开始再扫描的区块个数
nStart = GetTimeMillis(); // 记录再扫描的开始时间
pwalletMain->ScanForWalletTransactions(pindexRescan, true); // 再扫描钱包交易
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); // 记录再扫描的用时
pwalletMain->SetBestChain(chainActive.GetLocator()); // 设置最佳链(内存、数据库)
nWalletDBUpdated++; // 钱包数据库升级次数加 1
// Restore wallet transaction metadata after -zapwallettxes=1 // 在 zapwallettxes 选项设置模式 1 后,恢复钱包交易元数据
if (GetBoolArg("-zapwallettxes", false) && GetArg("-zapwallettxes", "1") != "2")
{ // 该选项设置会删除所有钱包交易且只恢复在启动时通过使用 -rescan 再扫描选项的部分区块链(模式)
CWalletDB walletdb(strWalletFile); // 创建钱包数据库对象
BOOST_FOREACH(const CWalletTx& wtxOld, vWtx) // 遍历钱包交易列表
{
uint256 hash = wtxOld.GetHash(); // 获取钱包交易哈希
std::map<uint256, CWalletTx>::iterator mi = pwalletMain->mapWallet.find(hash); // 在钱包映射交易映射列表中查找该交易
if (mi != pwalletMain->mapWallet.end()) // 若找到
{ // 更新该笔钱包交易
const CWalletTx* copyFrom = &wtxOld;
CWalletTx* copyTo = &mi->second;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
copyTo->nTimeReceived = copyFrom->nTimeReceived;
copyTo->nTimeSmart = copyFrom->nTimeSmart;
copyTo->fFromMe = copyFrom->fFromMe;
copyTo->strFromAccount = copyFrom->strFromAccount;
copyTo->nOrderPos = copyFrom->nOrderPos;
copyTo->WriteToDisk(&walletdb); // 写入钱包数据文件中
}
}
}
}
pwalletMain->SetBroadcastTransactions(GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); // 设置广播交易,默认为 true
} // (!fDisableWallet)
#else // 2.!ENABLE_WALLET
LogPrintf("No wallet support compiled in!\n");
#endif // !ENABLE_WALLET
...
}
1.若开启钱包功能
1.1.创建并初始化钱包对象,获取钱包交易列表后,删除该钱包对象。
1.2.创建新的钱包对象,并加载钱包到内存中。
1.3.钱包版本号升级。
1.4.若是首次运行,创建新的用户密钥并设置为默认密钥。
1.5.重新扫描钱包。
1.6.设置钱包交易广播。
2.若关闭钱包功能,记录日志跳过此步。
1.1.调用 pwalletMain->ZapWalletTx(vWtx) 获取钱包数据库中的钱包交易列表。 该函数声明在“wallet/wallet.h”文件的 CWallet 类中,实现在“wallet/wallet.cpp”文件中。
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
if (!fFileBacked) // 若钱包未备份
return DB_LOAD_OK; // 返回 0
DBErrors nZapWalletTxRet = CWalletDB(strWalletFile,"cr+").ZapWalletTx(this, vWtx); // 打开钱包数据库
if (nZapWalletTxRet == DB_NEED_REWRITE)
{
if (CDB::Rewrite(strWalletFile, "\x04pool")) // 重写钱包数据库文件
{
LOCK(cs_wallet); // 钱包上锁
setKeyPool.clear(); // 密钥池集合清空
// Note: can't top-up keypool here, because wallet is locked. // 注:这里不能填充密钥池,因为钱包已锁定
// User will be prompted to unlock wallet the next operation // 在需要新密钥的下一个操作时解锁钱包,
// that requires a new key. // 系统将提示用户。
}
}
if (nZapWalletTxRet != DB_LOAD_OK)
return nZapWalletTxRet;
return DB_LOAD_OK;
}
内部调用 CWalletDB(strWalletFile,”cr+”).ZapWalletTx(this, vWtx) 实现获取钱包数据库中的钱包交易列表的功能。 该函数声明在“wallet/walletdb.h”文件的 CWalletDB 类中,实现在“wallet/walletdb.cpp”文件中。
DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet, vector<CWalletTx>& vWtx)
{
// build list of wallet TXs // 构建钱包交易列表
vector<uint256> vTxHash;
DBErrors err = FindWalletTx(pwallet, vTxHash, vWtx); // 查询并获取指定钱包的钱包交易哈希和交易对象
if (err != DB_LOAD_OK)
return err;
// erase each wallet TX // 擦除每笔钱包交易
BOOST_FOREACH (uint256& hash, vTxHash) { // 遍历钱包交易哈希列表
if (!EraseTx(hash)) // 擦除数据库中的钱包交易
return DB_CORRUPT;
}
return DB_LOAD_OK;
}
1.2.调用 pwalletMain->LoadWallet(fFirstRun) 从数据库加载钱包到内存中,该函数声明在“wallet.h”文件的 CWallet 中。
class CWallet : public CCryptoKeyStore, public CValidationInterface
{
...
DBErrors LoadWallet(bool& fFirstRunRet); // 加载钱包到内存
...
};
实现在“wallet.cpp”文件中,入参为:首次运行标志(初始为 true)。
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
if (!fFileBacked) // 若非首次运行
return DB_LOAD_OK; // 返回 0,表示加载完毕
fFirstRunRet = false; // 首次运行,把该标志置为 false
DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); // 从钱包文件中加载钱包到内存
if (nLoadWalletRet == DB_NEED_REWRITE) // 5 需要重写
{
if (CDB::Rewrite(strWalletFile, "\x04pool"))
{
LOCK(cs_wallet); // 对钱包上锁
setKeyPool.clear(); // 清空钥匙池
// Note: can't top-up keypool here, because wallet is locked. // 注:不能在这里充值钥匙池,因为钱包被锁了。
// User will be prompted to unlock wallet the next operation // 用户将被提示下一个操作需要一个新密钥来解锁钱包。
// that requires a new key.
}
}
if (nLoadWalletRet != DB_LOAD_OK) // 若加载错误
return nLoadWalletRet; // 直接返回加载状态
fFirstRunRet = !vchDefaultKey.IsValid(); // 否则验证默认公钥是否有效,若有效,设置第一次运行状态为 false
uiInterface.LoadWallet(this); // UI 交互,加载钱包
return DB_LOAD_OK; // 0
}
1.4.调用 pwalletMain->GetKeyFromPool(newDefaultKey) 从密钥池中获取一个新密钥(公钥)。 该函数声明在“wallet.h”文件的 CWallet 类中。
class CWallet : public CCryptoKeyStore, public CValidationInterface
{
...
bool GetKeyFromPool(CPubKey &key); // 从密钥池中获取一个密钥的公钥
...
void SetBestChain(const CBlockLocator& loc); // 设置最佳链
...
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); // 设置地址簿(地址,所属账户,地址用途)
...
bool SetDefaultKey(const CPubKey &vchPubKey); // 设置默认密钥
...
};
实现在“wallet.cpp”文件中,入参为:待获取的新密钥(公钥)。
bool CWallet::GetKeyFromPool(CPubKey& result)
{
int64_t nIndex = 0;
CKeyPool keypool; // 密钥池条目
{
LOCK(cs_wallet);
ReserveKeyFromKeyPool(nIndex, keypool); // 从密钥池中预定一个密钥,若获取失败,nIndex 为 -1
if (nIndex == -1) // -1 表示当前 keypool 为空
{
if (IsLocked()) return false;
result = GenerateNewKey(); // 创建新的私钥,并用椭圆曲线加密生成对应的公钥
return true;
}
KeepKey(nIndex); // 从钱包数据库的密钥池中移除该密钥
result = keypool.vchPubKey;
}
return true;
}
然后调用 pwalletMain->SetDefaultKey(newDefaultKey) 把上一步获取的新密钥设置为钱包的默认密钥。 该函数实现在“wallet.cpp”文件中,入参为:刚从密钥池中取出的密钥对应的公钥。
bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
{
if (fFileBacked)
{
if (!CWalletDB(strWalletFile).WriteDefaultKey(vchPubKey)) // 把默认公钥写入钱包数据库 wallet.dat 中
return false;
}
vchDefaultKey = vchPubKey; // 设置该公钥为默认公钥
return true;
}
然后调用 pwalletMain->SetAddressBook(pwalletMain->vchDefaultKey.GetID(), “”, “receive”) 把该默认密钥加入地址簿的默认账户中,用途为接收。 该函数实现在“wallet.cpp”文件中,入参为:公钥索引,帐户名,用途。
bool CWallet::SetAddressBook(const CTxDestination& address, const string& strName, const string& strPurpose)
{
bool fUpdated = false; // 标记钱包地址簿是否更新,指地址已存在更新其用途,新增地址不算
{
LOCK(cs_wallet); // mapAddressBook
std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address); // 首先在地址簿中查找该地址
fUpdated = mi != mapAddressBook.end(); // 查找到的话,升级标志置为 true
mapAddressBook[address].name = strName; // 账户名,若地址已存在,直接改变账户名,否则插入该地址
if (!strPurpose.empty()) /* update purpose only if requested */ // 用途非空
mapAddressBook[address].purpose = strPurpose; // 升级该已存在地址的用途
}
NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO,
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) ); // 通知地址簿已改变
if (!fFileBacked) // 文件未备份
return false;
if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose)) // 用途非空时,写入钱包数据库该地址对应的用途
return false;
return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName); // 最后写入地址对应的账户名到钱包数据库
}
最后调用 pwalletMain->SetBestChain(chainActive.GetLocator()) 来设置最佳链。 该函数实现在“wallet.cpp”文件中,入参为:激活的链的区块位置。
void CWallet::SetBestChain(const CBlockLocator& loc)
{
CWalletDB walletdb(strWalletFile); // 创建钱包数据库局部对象
walletdb.WriteBestBlock(loc); // 写入最佳块位置到钱包数据库文件
}
1.6.调用 pwalletMain->SetBroadcastTransactions(GetBoolArg(“-walletbroadcast”, DEFAULT_WALLETBROADCAST)); 设置钱包交易广播标志。 该函数实现在“wallet.h”文件的 CWallet 类中。
static const bool DEFAULT_WALLETBROADCAST = true; // 钱包交易广播,默认开启
...
class CWallet : public CCryptoKeyStore, public CValidationInterface
{
...
bool fBroadcastTransactions; // 广播交易的标志
...
DBErrors LoadWallet(bool& fFirstRunRet); // 加载钱包到内存 /** Set whether this wallet broadcasts transactions. */ // 设置该钱包是否广播交易。
void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; }
...
};