從零開始學習比特幣開發(十一)-建立錢包
比特幣使用者最關心除了交易之外就是地址、錢包、私鑰了,交易、地址、錢包、私鑰這些不同概念之間具有內在的聯絡,要了解交易必須先要了解地址、錢包、私鑰這幾個概念,從本章開始,我們開始學習這一部分內容。
建立錢包整體流程
前面我們提到 RPC 的概念,RPC 是 remote process call 這個過程的縮寫,也就是遠端過程呼叫。比特幣核心提供了很多 RPC 來供客戶端呼叫,其中一個就是我們這裡要講的 createwallet
建立一個錢包,通過這個 RPC ,我們就可以生成一個新的錢包。
createwallet
RPC 可以接收兩個引數,第一個錢包名稱,第二個是否禁止私鑰。第一個引數是必填引數,第二個是可選引數,預設為假。
下面我們通過這個 RPC 來看下怎麼生成一個錢包。
-
從引數中取得錢包的名稱。
std::string wallet_name = request.params[0].get_str(); std::string error; std::string warning;
-
如果提供了第2個引數則取得是否禁止私鑰。
bool disable_privatekeys = false; if (!request.params[1].isNull()) { disable_privatekeys = request.params[1].get_bool(); }
-
通過錢包名稱加上其儲存的路徑來判斷錢包名稱是否已經存在。
fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir()); if (fs::symlink_status(wallet_path).type() != fs::file_not_found) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already exists."); }
-
檢查錢包檔案的路徑可以建立、不與別的錢包重複、路徑是一個目錄,同時為了向後相容,錢包路徑要在
-walletdir
if (!CWallet::Verify(wallet_name, false, error, warning)) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); }
-
建立錢包,如果建立失敗,則丟擲異常。
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()), (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); }
CreateWalletFromFile
是我們這部分內部的主體,所以放在下面進行詳細說明,此處略過不講。 -
呼叫
AddWallet
方法,把錢包物件加入vpwallets
向量中。 -
呼叫錢包物件的
postInitProcess
方法,進行一些後置的處理。 -
返回錢包名字和警告資訊組成的物件。
建立錢包方法 CreateWalletFromFile
上面一節的第5部提到CreateWalletFromFile
這個方法。下面我們詳細講解它,它接收 3 個引數,第一個引數是錢包的名稱,第二個引數是錢包的絕對路徑,第三個引數是錢包的標誌。具體邏輯如下:
-
生成兩個變數,一個是錢包檔案,一個是錢包的交易元資料,用來在清除交易之後恢復錢包交易。
const std::string& walletFile = name; std::vector<CWalletTx> vWtx;
-
如果啟動引數指定了
-zapwallettxes
,那麼生成一個臨時錢包,並呼叫其ZapWalletTx
方法來清除交易。如果出現錯誤,則返回空指標。if (gArgs.GetBoolArg("-zapwallettxes", false)) { std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(name, WalletDatabase::Create(path)); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); return nullptr; } }
生成錢包時,需要指定錢包檔案的路徑,從而可以讀取錢包中儲存的交易資訊。
下面,我們來看下
ZapWalletTx
這個方法。方法的主體是生成一個可以訪問錢包的資料庫的物件,並呼叫後者的ZapWalletTx
方法來完成清理交易的。在後者的方法定義如下:DBErrors WalletBatch::ZapWalletTx(std::vector<CWalletTx>& vWtx) { std::vector<uint256> vTxHash; DBErrors err = FindWalletTx(vTxHash, vWtx); if (err != DBErrors::LOAD_OK) return err; for (const uint256& hash : vTxHash) { if (!EraseTx(hash)) return DBErrors::CORRUPT; } return DBErrors::LOAD_OK; }
在上面的方法中,呼叫
FindWalletTx
方法,從錢包資料庫中讀取所有的交易雜湊和交易本身,並生成對應的CWalletTx
物件。然後,遍歷所有的交易雜湊,呼叫EraseTx
方法來刪除對應的交易。為什麼要清理錢包資料庫中的交易呢?因為有交易費用過低,導致無法打包到區塊中,對於這些交易我們需要從錢包檔案中清理出去。
-
生成兩個變數,一個是當前時間,一個是表示第一次執行錢包的標誌。
int64_t nStart = GetTimeMillis(); bool fFirstRun = true;
-
根據錢包名稱和絕對路徑生成錢包物件。
std::shared_ptr<CWallet> walletInstance(new CWallet(name, WalletDatabase::Create(path)), ReleaseWallet);
-
呼叫錢包物件的
LoadWallet
方法載入錢包。如果出現錯誤,則進行相應的處理。DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { if (nLoadWalletRet == DBErrors::CORRUPT) { InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); return nullptr; } else if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) { InitWarning(strprintf(_("Error reading %s! All keys read correctly, but transaction data or address book entries might be missing or incorrect."), walletFile)); } else if (nLoadWalletRet == DBErrors::TOO_NEW) { InitError(strprintf(_("Error loading %s: Wallet requires newer version of %s"), walletFile, _(PACKAGE_NAME))); return nullptr; } else if (nLoadWalletRet == DBErrors::NEED_REWRITE) { InitError(strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME))); return nullptr; } else { InitError(strprintf(_("Error loading %s"), walletFile)); return nullptr; } }
錢包物件
LoadWallet
方法的主體是生成一個可以訪問錢包的資料庫的物件,從而呼叫錢包資料庫物件的的LoadWallet
方法來載入錢包。從錢包資料庫中載入錢包的方法在下面詳細說明,此處略過。DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { LOCK2(cs_main, cs_wallet); fFirstRunRet = false; DBErrors nLoadWalletRet = WalletBatch(*database,"cr+").LoadWallet(this); if (nLoadWalletRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { setInternalKeyPool.clear(); setExternalKeyPool.clear(); m_pool_key_to_index.clear(); } } { LOCK(cs_KeyStore); fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } if (nLoadWalletRet != DBErrors::LOAD_OK) return nLoadWalletRet; return DBErrors::LOAD_OK; }
-
如果啟動引數
-upgradewallet
為真,或者沒有指定啟動引數-upgradewallet
為假,但是第一次執行建立這個錢包,那麼進行如下處理:-
獲取啟動引數
-upgradewallet
的值,預設為0,儲存到變數nMaxVersion
中。int nMaxVersion = gArgs.GetArg("-upgradewallet", 0);
-
如果變數
nMaxVersion
為0,即沒有指定啟動引數,那麼設定nMaxVersion
等於FEATURE_LATEST(目前為 169900)
,即支援 HD 分割,同時呼叫錢包物件的SetMinVersion
方法,設定錢包最小版本為這個版本。if (nMaxVersion == 0) // the -upgradewallet without argument case { walletInstance->WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); nMaxVersion = FEATURE_LATEST; walletInstance->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately } else walletInstance->WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
-
如果變數
nMaxVersion
小於錢包當前的版本,則直接返回空指標。int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); if (nMaxVersion < walletInstance->GetVersion()) { InitError(_("Cannot downgrade wallet")); return nullptr; }
-
呼叫錢包物件的
SetMinVersion
方法,設定錢包最大版本。walletInstance->SetMaxVersion(nMaxVersion);
-
-
如果沒有指定啟動引數
-upgradewallet
,或者指定了但為假,那麼升級到 HD 錢包。具體處理如下:-
如果錢包不支援
FEATURE_HD_SPLIT
,並且錢包的版本大於等於 139900 (FEATURE_HD_SPLIT
)且小於等於 169900 (FEATURE_PRE_SPLIT_KEYPOOL
),則返回空指標。int max_version = walletInstance->nWalletVersion; if (!walletInstance->CanSupportFeature(FEATURE_HD_SPLIT) && max_version >=FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) { return nullptr; }
-
如果錢包支援
FEATURE_HD
,且當前沒有啟用 HD,那麼呼叫SetMinVersion
方法,設定最小版本為FEATURE_HD
,同時呼叫GenerateNewSeed
生成新的隨機種子,然後呼叫SetHDSeed
方法來設定隨機種子。bool hd_upgrade = false; bool split_upgrade = false; if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) { walletInstance->SetMinVersion(FEATURE_HD); CPubKey masterPubKey = walletInstance->GenerateNewSeed(); walletInstance->SetHDSeed(masterPubKey); hd_upgrade = true; }
-
如果錢包支援
FEATURE_HD_SPLIT
,那麼呼叫SetMinVersion
方法,設定最小版本為FEATURE_HD_SPLIT
。如果FEATURE_HD_SPLIT
大於先前的版本,則設定變數split_upgrade
為真。if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) { walletInstance->WalletLogPrintf("Upgrading wallet to use HD chain split\n"); walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); split_upgrade = FEATURE_HD_SPLIT > prev_version; }
-
如果變數
split_upgrade
為真,則呼叫MarkPreSplitKeys
方法,將當前位於金鑰池中的所有金鑰標記為預拆分。if (split_upgrade) { walletInstance->MarkPreSplitKeys(); }
-
如果是 HD 升級,那麼重新生成金鑰池。
if (hd_upgrade) { if (!walletInstance->TopUpKeyPool()) { InitError(_("Unable to generate keys")); return nullptr; } }
-
-
如果是第一次執行,即第一次建立這個錢包,那麼:
-
設定錢包最小版本為
FEATURE_LATEST
,當前為FEATURE_PRE_SPLIT_KEYPOOL
。walletInstance->SetMinVersion(FEATURE_LATEST);
-
如果建立引數指定不能包含私鑰,那麼設定錢包這個標記;否則,呼叫
GenerateNewSeed
方法,生成新的隨機種子,然後呼叫SetHDSeed
方法,儲存隨機種子。if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } else { CPubKey seed = walletInstance->GenerateNewSeed(); walletInstance->SetHDSeed(seed); }
-
如果建立引數沒有指定不能包含私鑰,那麼填充金鑰池。如果失敗,即不能生成初始金鑰,則返回空指標。
if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) { return nullptr; }
-
重新整理到資料庫。
walletInstance->ChainStateFlushed(chainActive.GetLocator());
-
-
否則,如果建立引數指定不能包含私鑰,那麼返回 NULL。
-
如果指定了啟動引數
-addresstype
,但是解析失敗,則返回空指標。 -
如果指定了啟動引數
-changetype
,但是解析失敗,則返回空指標。 -
如果設定了最小交易費用,則解析並設定錢包的最小交易費用。
if (gArgs.IsArgSet("-mintxfee")) { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) { return nullptr; } walletInstance->m_min_fee = CFeeRate(n); }
-
根據不同網路取得是否啟用回退費用。如果啟動引數設定了
-fallbackfee
用以在估算費用不足時將使用的費率,那麼就解析設定的費率,如果解析錯誤,則列印錯誤日誌,並返回空指標,如果解析OK,但是大於規定的最大值,則列印警告日誌。如果兩者都沒有問題,則設定錢包的回退費率。walletInstance->m_allow_fallback_fee = Params().IsFallbackFeeEnabled(); if (gArgs.IsArgSet("-fallbackfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) { InitError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { InitWarning(AmountHighWarn("-fallbackfee") + " " + _("This is the transaction fee you may pay when fee estimates are not available.")); } walletInstance->m_fallback_fee = CFeeRate(nFeePerK); walletInstance->m_allow_fallback_fee = nFeePerK != 0; //disable fallback fee in case value was set to 0, enable if non-null value }
-
如果啟動引數設定了
-discardfee
用以規定在費用小於多少時捨棄,那麼就解析設定的費率,如果解析錯誤,則列印錯誤日誌,並返回空指標,如果解析OK,但是大於規定的最大值,則列印警告日誌。如果兩者都沒有問題,則設定錢包的丟棄費率。if (gArgs.IsArgSet("-discardfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) { InitError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { InitWarning(AmountHighWarn("-discardfee") + " " + _("This is the transaction fee you may discard if change is smaller than dust at this level")); } walletInstance->m_discard_rate = CFeeRate(nFeePerK); }
-
如果啟用引數設定了
-paytxfee
指定交易費用,那麼就解析設定的費率,如果解析錯誤,則列印錯誤日誌,並返回空指標,如果解析OK,但是大於規定的最大值,則列印警告日誌。如果兩者都沒有問題,則設定錢包的交易費用。如果交易費用小於規定的最小值,則認為交易費用為為,則列印警告日誌,並返回空指標。if (gArgs.IsArgSet("-paytxfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) { InitError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { InitWarning(AmountHighWarn("-paytxfee") + " " + _("This is the transaction fee you will pay if you send a transaction.")); } walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000); if (walletInstance->m_pay_tx_fee < ::minRelayTxFee) { InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), gArgs.GetArg("-paytxfee", ""), ::minRelayTxFee.ToString())); return nullptr; } }
-
設定交易確認的平均區塊數(預設為6)、是否傳送未確認的變更、是否啟用 full-RBF opt-in。
walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET); walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); walletInstance->m_signal_rbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
-
接下來填充金鑰池,如果錢包被鎖定,則不進行任何操作。
walletInstance->TopUpKeyPool();
-
獲取區塊鏈的創世區塊索引。如果沒有指定重新掃描區塊鏈,或指定不掃描,那麼進行下面的操作。
生成一個可以訪問錢包資料庫的物件,然後從資料庫讀取最佳區塊,即關鍵字為
bestblock
的區塊。如果可以找到這個區塊,那麼呼叫FindForkInGlobalIndex
方法,返回分叉的區塊。最佳區塊是一個區塊定位器,它描述了塊鏈中到另一個節點的位置,這樣如果另一個節點沒有相同的分支,它就可以找到最近的公共中繼。
CBlockIndex *pindexRescan = chainActive.Genesis(); if (!gArgs.GetBoolArg("-rescan", false)) { WalletBatch batch(*walletInstance->database); CBlockLocator locator; if (batch.ReadBestBlock(locator)) pindexRescan = FindForkInGlobalIndex(chainActive, locator); }
pindexRescan
為重新掃描區塊鏈的區塊,預設從創世區塊開始,下面會進行重新設定。FindForkInGlobalIndex
方法根據區塊定位器中包含的區塊雜湊在區塊索引集合mapBlockIndex
中查詢對應的區塊。如果這個區塊索引存在,進一步如果當前區塊鏈中包含這個區塊索引,則返回這個區塊索引,否則如果這個區塊索引的祖先是當前區塊鏈的頂部區塊,則返回當前區塊鏈的頂部區塊。最後,如果找不到這樣的區塊,則返回當前區塊鏈的創世區塊。CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) { AssertLockHeld(cs_main); for (const uint256& hash : locator.vHave) { CBlockIndex* pindex = LookupBlockIndex(hash); if (pindex) { if (chain.Contains(pindex)) return pindex; if (pindex->GetAncestor(chain.Height()) == chain.Tip()) { return chain.Tip(); } } } return chain.Genesis(); }
-
設定錢包最後一個處理的區塊為當前區塊鏈頂部的區塊。
walletInstance->m_last_block_processed = chainActive.Tip();
-
如果當前區塊鏈頂部的區塊存在,且不等於前一步中我們找到的
pindexRescan
對應的區塊,那麼進行下面的處理:-
如果當前是修剪模式,從區塊鏈頂部的區塊開始向下遍歷,一直找到某個區塊的前一區塊的資料不在區塊資料庫檔案或,或者前一個區塊的交易數為0,或者這個區塊是
pindexRescan
。如果最終找到的區塊不是pindexRescan
,那麼列印錯誤訊息,並返回空指標。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; } }
-
從
pindexRescan
區塊開始向區塊鏈頂部遍歷,找到第一個區塊建立時間小於錢包建立時間與TIMESTAMP_WINDOW
之差的區塊。TIMESTAMP_WINDOW
當前規定為 2個小時。while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) { pindexRescan = chainActive.Next(pindexRescan); }
-
生成一個
WalletRescanReserver
物件,並呼叫其reserve
方法。如果該方法返回假,則列印錯誤日誌,並返回空指標。否則,呼叫錢包物件的ScanForWalletTransactions
方法,掃描錢包的所有交易。reserve
方法內部檢查錢包的fScanningWallet
屬性,如果這個屬性已經為真,那麼直接返回假;否則,設定其為真,並設定變數m_could_reserve
也為真,然後返回真。具體程式碼如下:bool reserve() { assert(!m_could_reserve); std::lock_guard<std::mutex> lock(m_wallet->mutexScanning); if (m_wallet->fScanningWallet) { return false; } m_wallet->fScanningWallet = true; m_could_reserve = true; return true; }
ScanForWalletTransactions
方法,具體處理邏輯如下:-
首先,進行些變數初始化及校驗,不詳述。
int64_t nNow = GetTime(); const CChainParams& chainParams = Params(); assert(reserver.isReserved()); if (pindexStop) { assert(pindexStop->nHeight >= pindexStart->nHeight); } CBlockIndex* pindex = pindexStart; CBlockIndex* ret = nullptr;
-
呼叫
GuessVerificationProgress
方法,預估驗證的進度。fAbortRescan = false; CBlockIndex* tip = nullptr; double progress_begin; double progress_end; { LOCK(cs_main); progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex); if (pindexStop == nullptr) { tip = chainActive.Tip(); progress_end = GuessVerificationProgress(chainParams.TxData(), tip); } else { progress_end = GuessVerificationProgress(chainParams.TxData(), pindexStop); } }
-
只要
pindex
不為空,且沒有終止當前的掃描,且沒有收到關閉的請求,就沿著區塊鏈向棧頂遍歷,從硬碟上讀取區塊,如果可以讀取到區塊,並且當前活躍區塊鏈包含當前的區塊,那麼從這個區塊中同步所有的交易,如果當前活躍區塊鏈不包含當前的區塊,那麼設定當前區塊索引為退出索引,並且退出迴圈;如果不能從硬碟中讀取,同樣設定當前區塊索引為退出索引。如果當前區塊等於退出區塊的索引,那麼退出迴圈。double progress_current = progress_begin; while (pindex && !fAbortRescan && !ShutdownRequested()) { if (GetTime() >= nNow + 60) { nNow = GetTime(); } CBlock block; if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { LOCK2(cs_main, cs_wallet); if (pindex && !chainActive.Contains(pindex)) { ret = pindex; break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { SyncTransaction(block.vtx[posInBlock], pindex, posInBlock, fUpdate); } } else { ret = pindex; } if (pindex == pindexStop) { break; } { LOCK(cs_main); pindex = chainActive.Next(pindex); progress_current = GuessVerificationProgress(chainParams.TxData(), pindex); if (pindexStop == nullptr && tip != chainActive.Tip()) { tip = chainActive.Tip(); progress_end = GuessVerificationProgress(chainParams.TxData(), tip); } } }
-
返回退出區塊索引。
-
-
呼叫錢包物件的
ChainStateFlushed
方法,把區塊定位器作為最佳區塊儲存到錢包資料庫中。walletInstance->ChainStateFlushed(chainActive.GetLocator());
-
呼叫錢包資料庫物件的
IncrementUpdateCounter
方法,增加資料庫物件的更新次數。 -
如果啟動引數指定了
-zapwallettxes
,且等於1,那麼需要重新把錢包的元資料儲存到錢包資料庫中。錢包交易前面取出來儲存在vWtx
變數中。if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2") { WalletBatch batch(*walletInstance->database); for (const CWalletTx& wtxOld : vWtx) { uint256 hash = wtxOld.GetHash(); std::map<uint256, CWalletTx>::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->nOrderPos = copyFrom->nOrderPos; batch.WriteTx(*copyTo); } } }
-
-
呼叫全域性方法
RegisterValidationInterface
註冊錢包繫結方法作為訊號處理器。 -
呼叫錢包物件的
SetBroadcastTransactions
方法,根據啟動引數設定是否廣播交易。 -
返回錢包物件。
從資料庫中讀取錢包的 LoadWallet 方法
在這裡,我們仔細看下錢包資料庫的 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。
-
-
返回真。