加载钱包
写在前面
创建钱包和交易是比特币最重要的两方面,涉及到很多很多的内容,远非一篇文章能概括的完。上一篇从整体上讲解了钱包的创建流程,虽然已经很详细了,但还是漏掉了几个重要的方法,从本篇开始我们讲解这几个特别重要的方法。对钱包感兴趣的朋友一定不要错误。
WalletBatch::LoadWallet 加载钱包
本方法,从数据库中加载钱包的相关数据。方法的执行逻辑如下:
首先,初始化几个变量。
CWalletScanState wss; bool fNoncriticalErrors = false; DBErrors result = DBErrors::LOAD_OK;
从数据库中读取钱包的最小版本。如果最小版本大于当前的最新版本,则返回数据库异常;否则,调用钱包对象的
LoadMinVersion
方法,设置钱包的版本相关属性,nWalletVersion
属性为数据库读取取的nMinVersion
,nWalletMaxVersion
属性为nWalletMaxVersion
与这个值的较大者。int nMinVersion = 0; if (m_batch.Read((std::string)"minversion", nMinVersion)) { if (nMinVersion > FEATURE_LATEST) return DBErrors::TOO_NEW; pwallet->LoadMinVersion(nMinVersion); }
获取数据库游标,如果出错,则返回数据库游标错误。
Dbc* pcursor = m_batch.GetCursor(); if (!pcursor) { pwallet->WalletLogPrintf("Error getting wallet database cursor\n"); return DBErrors::CORRUPT; }
进放
while (true){ ... }
循环读取数据库中的所有数据。具体处理如下:调用
ReadAtCursor
方法从游标中读取对应的数据。如果没有读取到数据,则退出循环。如果出现错误,则调用钱包对象的WalletLogPrintf
方法打印,然后返回数据库错误。CDataStream ssKey(SER_DISK, CLIENT_VERSION); CDataStream ssValue(SER_DISK, CLIENT_VERSION); int ret = m_batch.ReadAtCursor(pcursor, ssKey, ssValue); if (ret == DB_NOTFOUND) break; else if (ret != 0) { pwallet->WalletLogPrintf("Error reading next record from wallet database\n"); return DBErrors::CORRUPT; }
调用
ReadKeyValue
方法,读取游标中当前的内容。如果读取出错,则根据错误进行相应的处理,具体不细说。如果读取数据成功,其内部根据读取到的数据进行具体处理如下:如果当前的的 Key 是
name
,那么从流中读取对应的地址到变量strAddress
中,调用DecodeDestination
方法解码这个地址,然后设置钱包对象mapAddressBook
集合对应的CAddressBookData
对象的name
属性为流中读取到的名字值。if (strType == "name") { std::string strAddress; ssKey >> strAddress; ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name; }
否则,如果前的 Key 是
purpose
,那么从流中读取对应的地址到变量strAddress
中,调用DecodeDestination
方法解码这个地址,然后设置钱包对象mapAddressBook
集合对应的CAddressBookData
对象的purpose
属性为流中读取到的名字值。else if (strType == "purpose") { std::string strAddress; ssKey >> strAddress; ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose; }
否则,如果当前的 Key 是
tx
,即交易,那么处理如下。从流中读取交易哈希和钱包交易对象,然后调用
CheckTransaction
方法,检查读取的钱包交易对象,如果出错,则返回假。uint256 hash; ssKey >> hash; CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); ssValue >> wtx; CValidationState state; if (!(CheckTransaction(*wtx.tx, state) && (wtx.GetHash() == hash) && state.IsValid())) return false;
如果钱包交易对象的
fTimeReceivedIsTxTime
属性大于等于 31404,且小于等于 31703,因为撤销序列化在 31600 中进行了变更,所以进行下面的序列化处理。if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) { if (!ssValue.empty()) { char fTmp; char fUnused; std::string unused_string; ssValue >> fTmp >> fUnused >> unused_string; strErr = strprintf("LoadWallet() upgrading tx ver=%d %d %s", wtx.fTimeReceivedIsTxTime, fTmp, hash.ToString()); wtx.fTimeReceivedIsTxTime = fTmp; } else { strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString()); wtx.fTimeReceivedIsTxTime = 0; } wss.vWalletUpgrade.push_back(hash); }
如果钱包交易对象的
nOrderPos
为-1,那么设置钱包扫描状态对象的fAnyUnordered
为真。if (wtx.nOrderPos == -1) wss.fAnyUnordered = true;
调用钱包对象的
LoadToWallet
,加载数据库中读到的钱包交易对象到钱包中。否则,如果当前的 Key 是
watchs
,那么从流中读取对应的序列化脚本,并读取其对应的值。如果其值等于1,那么调用钱包对象的LoadWatchOnly
方法,加载一个只读的脚本。wss.nWatchKeys++; CScript script; ssKey >> script; char fYes; ssValue >> fYes; if (fYes == '1') pwallet->LoadWatchOnly(script);
否则,如果当前的 Key 是
key
或者wkey
,那么处理如下。从流中读取对应的公钥,如果 Key 是
key
,那么从流中读取出对应的私钥,否则从流中读取对应的钱包私钥,从中取得对应的私钥。CPubKey vchPubKey; ssKey >> vchPubKey; if (!vchPubKey.IsValid()) { strErr = "Error reading wallet database: CPubKey corrupt"; return false; } CKey key; CPrivKey pkey; uint256 hash; if (strType == "key") { wss.nKeys++; ssValue >> pkey; } else { CWalletKey wkey; ssValue >> wkey; pkey = wkey.vchPrivKey; }
从流中读取对应的哈希值。
ssValue >> hash;
如果哈希不空,则处理如下:
if (!hash.IsNull()) { std::vector<unsigned char> vchKey; vchKey.reserve(vchPubKey.size() + pkey.size()); vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); vchKey.insert(vchKey.end(), pkey.begin(), pkey.end()); if (Hash(vchKey.begin(), vchKey.end()) != hash) { strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt"; return false; } fSkipCheck = true; }
调用
CKey
对象的Load
方法加载密钥。if (!key.Load(pkey, vchPubKey, fSkipCheck)) { strErr = "Error reading wallet database: CPrivKey corrupt"; return false; }
调用钱包对象的
LoadKey
方法,加载密钥。if (!pwallet->LoadKey(key, vchPubKey)) { strErr = "Error reading wallet database: LoadKey failed"; return false; }
否则,如果当前的 Key 是
mkey
,则从流中读取对应的主密钥,并保存在钱包mapMasterKeys
对应的位置。else if (strType == "mkey") { unsigned int nID; ssKey >> nID; CMasterKey kMasterKey; ssValue >> kMasterKey; if(pwallet->mapMasterKeys.count(nID) != 0) { strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID); return false; } pwallet->mapMasterKeys[nID] = kMasterKey; if (pwallet->nMasterKeyMaxID < nID) pwallet->nMasterKeyMaxID = nID; }
否则,如果当前的 Key 是
ckey
,则从流中读取对应的公钥、私钥,并调用钱包对象的LoadCryptedKey
方法,加载加密的密钥。else if (strType == "ckey") { CPubKey vchPubKey; ssKey >> vchPubKey; if (!vchPubKey.IsValid()) { strErr = "Error reading wallet database: CPubKey corrupt"; return false; } std::vector<unsigned char> vchPrivKey; ssValue >> vchPrivKey; wss.nCKeys++; if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) { strErr = "Error reading wallet database: LoadCryptedKey failed"; return false; } wss.fIsEncrypted = true; }
否则,如果当前的 Key 是
keymeta
,那么从流中读取对应的公钥及其元数据,调用钱包对象的LoadKeyMetadata
方法,加载密钥元数据。else if (strType == "keymeta") { CPubKey vchPubKey; ssKey >> vchPubKey; CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nKeyMeta++; pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); }
否则,如果当前的 Key 是
watchmeta
,那么从流中读取脚本和对应的元数据,并调用钱包对象的LoadScriptMetadata
方法,加载脚本的元数据。else if (strType == "watchmeta") { CScript script; ssKey >> script; CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nKeyMeta++; pwallet->LoadScriptMetadata(CScriptID(script), keyMeta); }
否则,如果当前的 Key 是
defaultkey
,那么从流中读取对应的公钥。else if (strType == "defaultkey") { CPubKey vchPubKey; ssValue >> vchPubKey; if (!vchPubKey.IsValid()) { strErr = "Error reading wallet database: Default Key corrupt"; return false; } }
否则,如果当前的 Key 是
pool
,那么从流中读取对应的密钥池,并调用钱包对象的LoadKeyPool
方法,加载密钥池。else if (strType == "pool") { int64_t nIndex; ssKey >> nIndex; CKeyPool keypool; ssValue >> keypool; pwallet->LoadKeyPool(nIndex, keypool); }
否则,如果当前的 Key 是
version
,那么从流中读取对应的版本到钱包扫描状态对象的nFileVersion
属性中。如果这个值等于 10300,那么设置钱包对象的这个属性为 300。else if (strType == "version") { ssValue >> wss.nFileVersion; if (wss.nFileVersion == 10300) wss.nFileVersion = 300; }
否则,如果当前的 Key 是
cscript
,那么从流中读取对应的脚本,并调用钱包对象的LoadCScript
方法加载脚本。else if (strType == "cscript") { uint160 hash; ssKey >> hash; CScript script; ssValue >> script; if (!pwallet->LoadCScript(script)) { strErr = "Error reading wallet database: LoadCScript failed"; return false; } }
否则,如果当前的 Key 是
orderposnext
,那么从流中读取对应的值到钱包对象的nOrderPosNext
属性中。else if (strType == "orderposnext") { ssValue >> pwallet->nOrderPosNext; }
否则,如果当前的 Key 是
destdata
,那么从流中读取对应的数据,并调用钱包对象的LoadDestData
方法,处理这些数据。else if (strType == "destdata") { std::string strAddress, strKey, strValue; ssKey >> strAddress; ssKey >> strKey; ssValue >> strValue; pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue); }
否则,如果当前的 Key 是
hdchain
,那么从流中读取 HD 链,并调用钱包对象的SetHDChain
方法,设置钱包的 HD 链中。else if (strType == "hdchain") { CHDChain chain; ssValue >> chain; pwallet->SetHDChain(chain, true); }
否则,如果当前的 Key 是
,那么调用钱包的
方法,设置其标志。else if (strType == "flags") { uint64_t flags; ssValue >> flags; if (!pwallet->SetWalletFlags(flags, true)) { strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found"; return false; } }
否则,如果当前的 Key 不是
bestblock
、bestblock_nomerkle
、minversion
、acentry
,则钱包扫描状态对象的m_unknown_records
未知道记录属性加1。
返回真。
后记
由于本人水平所限,文中错误在所难免,欢迎您踊跃指出错误,在下感激不尽。我的微信联系方式:joepeak。
原创不易,尤其寒冬,欢迎赞助我一杯咖啡。

比特币
微信
支付宝
Last updated
Was this helpful?