接下来主要是分析Bitcoin Core初始化中的加载钱包过程,对应的源码是src/init.cpp的AppInitMain()方法的Step 8: load wallet这部分,详见:https://github.com/bitcoin/bitcoin/blob/v0.16.1/src/init.cpp
// ********************************************************* Step 8: load wallet
#ifdef ENABLE_WALLET
if (!OpenWallets())
return false;
#else
LogPrintf("No wallet support compiled in!\n");
#endif
关键的就是OpenWallets()方法:
bool OpenWallets()
{
if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
LogPrintf("Wallet disabled!\n");
return true;
}
for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
CWallet * const pwallet = CWallet::CreateWalletFromFile(walletFile);
if (!pwallet) {
return false;
}
vpwallets.push_back(pwallet);
}
return true;
}
根据"-wallet"参数设置的钱包文件路径读取相应文件从而创建钱包,而"-wallet"参数由WalletParameterInteraction()方法设定:
bool WalletParameterInteraction()
{
if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
for (const std::string& wallet : gArgs.GetArgs("-wallet")) {
LogPrintf("%s: parameter interaction: -disablewallet -> ignoring -wallet=%s\n", __func__, wallet);
}
return true;
}
gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT);
......
可以看到"-wallet"参数默认设置为DEFAULT_WALLET_DAT,而DEFAULT_WALLET_DAT定义在src/wallet/wallet.cpp中:
const char * DEFAULT_WALLET_DAT = "wallet.dat";
也就是说默认是从wallet.dat中加载钱包文件从而创建钱包。
WalletParameterInteraction()方法由之前讲过的AppInitParameterInteraction()方法调用。
接下来主要是分析关键的CreateWalletFromFile()方法,定义在src/wallet/wallet.cpp中:
CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
{
// needed to restore wallet transaction meta data after -zapwallettxes
std::vector vWtx;
接下去看源码:
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
std::unique_ptr dbw(new CWalletDBWrapper(&bitdb, walletFile));
std::unique_ptr tempWallet = MakeUnique(std::move(dbw));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DB_LOAD_OK) {
InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
return nullptr;
}
}
如果用户设置了"-zapwallettxes"选项,则清除钱包的所有的交易数据,为什么清除交易数据,这是因为可能存在交易费过低或者没有交易费的交易,通过这个选项可以清除掉这种交易。
这里是调用CWallet类的ZapWalletTx()方法清除交易的,进一步跟进去,可以发现是调用CWalletDB类的ZapWalletTx()方法清除交易的:
DBErrors CWalletDB::ZapWalletTx(std::vector& vWtx)
{
// build list of wallet TXs
std::vector vTxHash;
DBErrors err = FindWalletTx(vTxHash, vWtx);
if (err != DB_LOAD_OK)
return err;
// erase each wallet TX
for (uint256& hash : vTxHash) {
if (!EraseTx(hash))
return DB_CORRUPT;
}
return DB_LOAD_OK;
}
通过CWalletDB::FindWalletTx()方法找到所有交易对应的hash放在集合vTxHash中,然后遍历该集合,调用CWalletDB::EraseTx()方法清除交易。
uiInterface.InitMessage(_("Loading wallet..."));
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
std::unique_ptr dbw(new CWalletDBWrapper(&bitdb, walletFile));
CWallet *walletInstance = new CWallet(std::move(dbw));
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
创建了CWallet对象,然后调用LoadWallet()方法从wallet.dat文件中加载数据。
if (gArgs.GetBoolArg("-upgradewallet", fFirstRun))
{
int nMaxVersion = gArgs.GetArg("-upgradewallet", 0);
if (nMaxVersion == 0) // the -upgradewallet without argument case
{
LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
nMaxVersion = CLIENT_VERSION;
walletInstance->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately
}
else
LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
if (nMaxVersion < walletInstance->GetVersion())
{
InitError(_("Cannot downgrade wallet"));
return nullptr;
}
walletInstance->SetMaxVersion(nMaxVersion);
}
如果设置了"-upgradewallet"选项,则根据参数进行钱包升级。
if (fFirstRun)
{
// ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
if (!gArgs.GetBoolArg("-usehd", true)) {
InitError(strprintf(_("Error creating %s: You can't create non-HD wallets with this version."), walletFile));
return nullptr;
}
walletInstance->SetMinVersion(FEATURE_NO_DEFAULT_KEY);
// generate a new master key 生成主私钥
CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
if (!walletInstance->SetHDMasterKey(masterPubKey))
throw std::runtime_error(std::string(__func__) + ": Storing master key failed");
// Top up the keypool 生成子私钥,填充满密钥池
if (!walletInstance->TopUpKeyPool()) {
InitError(_("Unable to generate initial keys") += "\n");
return nullptr;
}
walletInstance->SetBestChain(chainActive.GetLocator());
}
《精通比特币》里讲过钱包就是私钥的集合,分层确定性钱包(即HD Wallets)需要先生成一个主私钥,再根据这个主私钥生成很多子私钥。
4.1 生成主私钥
看下CPubKey CWallet::GenerateNewHDMasterKey()源码
CPubKey CWallet::GenerateNewHDMasterKey()
{
//创建私钥
CKey key;
key.MakeNewKey(true);
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
//根据secp256k1椭圆曲线算法由私钥生成公钥
// calculate the pubkey
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
//标记为主密钥
// set the hd keypath to "m" -> Master, refers the masterkeyid to itself
metadata.hdKeypath = "m";
metadata.hdMasterKeyID = pubkey.GetID();
{
LOCK(cs_wallet);
// mem store the metadata
mapKeyMetadata[pubkey.GetID()] = metadata;
//将公钥私钥对写入到钱包的数据库文件中
// write the key&metadata to the database
if (!AddKeyPubKey(key, pubkey))
throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed");
}
return pubkey;
}
上述关键代码已经做了注释,下面做进一步分析
4.1.1创建私钥
调用 CKey::MakeNewKey()方法使用加密PRNG生成一个私钥。
4.1.2由私钥生成公钥
调用CPubKey CKey::GetPubKey() 方法根据secp256k1椭圆曲线算法由私钥生成公钥:
CPubKey CKey::GetPubKey() const {
assert(fValid);
secp256k1_pubkey pubkey;
size_t clen = CPubKey::PUBLIC_KEY_SIZE;
CPubKey result;
int ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pubkey, begin());
assert(ret);
secp256k1_ec_pubkey_serialize(secp256k1_context_sign, (unsigned char*)result.begin(), &clen, &pubkey, fCompressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
assert(result.size() == clen);
assert(result.IsValid());
return result;
}
4.1.3标记为主密钥
调用pubkey.GetID()计算一个CKeyID ,CKeyID 其实是私钥CKey的引用,用来唯一标识一个私钥,只不过是采用私钥对应的公钥的Hash160哈希值来表示的。
看下源码:
//! Get the KeyID of this public key (hash of its serialization)
CKeyID GetID() const
{
return CKeyID(Hash160(vch, vch + size()));
}
4.1.4将公钥私钥对写入到钱包的数据库文件中
调用CWallet::AddKeyPubKey()方法将公钥私钥对写入到钱包的数据库文件中:
bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
{
CWalletDB walletdb(*dbw);
return CWallet::AddKeyPubKeyWithDB(walletdb, secret, pubkey);
}
4.2生成子私钥
看下bool CWallet::TopUpKeyPool(unsigned int kpSize)源码:
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
{
LOCK(cs_wallet);
if (IsLocked())
return false;
// Top up key pool
unsigned int nTargetSize;
if (kpSize > 0)
nTargetSize = kpSize;
else
nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
// count amount of available keys (internal, external)
// make sure the keypool of external and internal keys fits the user selected target (-keypool)
int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0);
int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0);
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
{
// don't create extra internal keys
missingInternal = 0;
}
bool internal = false;
CWalletDB walletdb(*dbw);
for (int64_t i = missingInternal + missingExternal; i--;)
{
if (i < missingInternal) {
internal = true;
}
assert(m_max_keypool_index < std::numeric_limits::max()); // How in the hell did you use so many keys?
int64_t index = ++m_max_keypool_index;
//调用GenerateNewKey()方法生成公钥私钥对
CPubKey pubkey(GenerateNewKey(walletdb, internal));
if (!walletdb.WritePool(index, CKeyPool(pubkey, internal))) {
throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
}
if (internal) {
setInternalKeyPool.insert(index);
} else {
setExternalKeyPool.insert(index);
}
m_pool_key_to_index[pubkey.GetID()] = index;
}
if (missingInternal + missingExternal > 0) {
LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size());
}
}
return true;
}
CBlockIndex *pindexRescan = chainActive.Genesis();
if (!gArgs.GetBoolArg("-rescan", false))
{
CWalletDB walletdb(*walletInstance->dbw);
CBlockLocator locator;
if (walletdb.ReadBestBlock(locator))
pindexRescan = FindForkInGlobalIndex(chainActive, locator);
}
walletInstance->m_last_block_processed = chainActive.Tip();
RegisterValidationInterface(walletInstance);
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 an old wallet within a pruned node
// or if he ran -disablewallet for a longer time, then decided to re-enable
if (fPruneMode)
{
CBlockIndex *block = chainActive.Tip();
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block)
block = block->pprev;
if (pindexRescan != block) {
InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
return nullptr;
}
}
uiInterface.InitMessage(_("Rescanning..."));
LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight);
// No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability)
while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) {
pindexRescan = chainActive.Next(pindexRescan);
}
nStart = GetTimeMillis();
{
WalletRescanReserver reserver(walletInstance);
if (!reserver.reserve()) {
InitError(_("Failed to rescan the wallet during initialization"));
return nullptr;
}
//扫描区块链
walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true);
}
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
walletInstance->SetBestChain(chainActive.GetLocator());
walletInstance->dbw->IncrementUpdateCounter();
// Restore wallet transaction metadata after -zapwallettxes=1
if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2")
{
CWalletDB walletdb(*walletInstance->dbw);
for (const CWalletTx& wtxOld : vWtx)
{
uint256 hash = wtxOld.GetHash();
std::map::iterator mi = walletInstance->mapWallet.find(hash);
if (mi != walletInstance->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;
walletdb.WriteTx(*copyTo);
}
}
}
}
如果用户设置了"-rescan"选项,则从创世区块开始,获取需要重新更新钱包交易的位置,然后开始扫描区块,以将相关交易加入到钱包中。分析下对应的ScanForWalletTransactions()方法:
/**
* Scan the block chain (starting in pindexStart) for transactions
* from or to us. If fUpdate is true, found transactions that already
* exist in the wallet will be updated.
*
* Returns null if scan was successful. Otherwise, if a complete rescan was not
* possible (due to pruning or corruption), returns pointer to the most recent
* block that could not be scanned.
*
* If pindexStop is not a nullptr, the scan will stop at the block-index
* defined by pindexStop
*
* Caller needs to make sure pindexStop (and the optional pindexStart) are on
* the main chain after to the addition of any new keys you want to detect
* transactions for.
*/
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate)
{
int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
assert(reserver.isReserved());
if (pindexStop) {
assert(pindexStop->nHeight >= pindexStart->nHeight);
}
CBlockIndex* pindex = pindexStart;
CBlockIndex* ret = nullptr;
{
fAbortRescan = false;
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
CBlockIndex* tip = nullptr;
double dProgressStart;
double dProgressTip;
{
LOCK(cs_main);
tip = chainActive.Tip();
dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
}
while (pindex && !fAbortRescan)
{
if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) {
double gvp = 0;
{
LOCK(cs_main);
gvp = GuessVerificationProgress(chainParams.TxData(), pindex);
}
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
}
if (GetTime() >= nNow + 60) {
nNow = GetTime();
LOCK(cs_main);
LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
}
CBlock block;
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
LOCK2(cs_main, cs_wallet);
if (pindex && !chainActive.Contains(pindex)) {
// Abort scan if current block is no longer active, to prevent
// marking transactions as coming from the wrong block.
ret = pindex;
break;
}
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
}
} else {
ret = pindex;
}
if (pindex == pindexStop) {
break;
}
{
LOCK(cs_main);
pindex = chainActive.Next(pindex);
if (tip != chainActive.Tip()) {
tip = chainActive.Tip();
// in case the tip has changed, update progress max
dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
}
}
}
if (pindex && fAbortRescan) {
LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
}
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
}
return ret;
}
阅读该方法的源码可以看到,所有的区块数据是存储在磁盘中的,chainActive存储的是区块的位置索引链,通过遍历这个位置索引链,调用ReadBlockFromDisk()方法从磁盘中读取区块信息,然后通过 AddToWalletIfInvolvingMe()方法将区块中的交易数据加入到钱包中。