BIP9允許部署多個向後相容的軟分叉,通過曠工在一個目標周期內投票,如果達到啟用閾值nRuleChangeActivationThreshold,就能成功的啟用該升級。在實現方面,通過重定義區塊頭資訊中的version欄位,將version欄位解釋為bit vector,每一個bit可以用來跟蹤一個獨立的部署,在滿足啟用條件之後,該部署將會生效,同時該bit可以被其他部署使用。目前通過BIP9成功進行軟分叉有BIP68, 112, 113, 於2016-07-04 ,高度:419328成功啟用. BIP9部署設定
每一個進行部署的BIP9,都必須設定bit位、開始時間、到期時間。
struct BIP9Deployment { int bit; int64_t nStartTime; int64_t nTimeout;};// namespace:Consensusstruct Params { ... uint32_t nRuleChangeActivationThreshold; uint32_t nMinerConfirmationWindow; BIP9Deployment vDeployments[MAX_VERSION_BITS_DEPLOYMENTS]; // BIP9 uint256 powLimit; bool fPowAllowMinDifficultyBlocks; bool fPowNoRetargeting; int64_t nPowTargetSpacing; int64_t nPowTargetTimespan; ... };
bit通過1 << bit方式轉換成一個uint32_t的整數,在檢驗一個BIP9部署是否成功啟用的時候使用了Condition(...)函數,來驗證一個區塊是否贊成該部署。
bool Condition(const CBlockIndex *pindex, const Consensus::Params ¶ms) const { return (( (pindex->nVersion & VERSIONBITS_TOP_MASK) ==VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask(params)) != 0);}uint32_t Mask(const Consensus::Params ¶ms) const { return ((uint32_t)1) << params.vDeployments[id].bit;}
邏輯分析 首先驗證該version是有效version設定(001) 驗證塊的版本號碼中是否設定了指定的bit位 Mask()函數通過將1左移BIP9部署中設定的bit,產生一個該區塊代表的version
開始時間和到期時間主要為了在檢查BIP9部署狀態時,提供狀態判斷的依據和臨界值。比如如果區塊的中位元時間超過了到期時間nTimeTimeout,則判斷該BIP9部署已經失敗(後面會詳細拆解)。
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) { stateNext = THRESHOLD_FAILED;} else if (pindexPrev->GetMedianTimePast() >= nTimeStart) { stateNext = THRESHOLD_STARTED;}if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) { stateNext = THRESHOLD_FAILED; break;}
部署狀態轉換
BIP9部署中定義了所有軟分叉升級的初始狀態均為THRESHOLD_DEFINED,並定義創始區塊狀態為THRESHOLD_DEFINED, 另外如果在程式中遇到blockIndex為nullptr時,均返回THRESHOLD_DEFINED狀態。
具體轉換過程如下:THRESHOLD_DEFINED為軟分叉的初始狀態,如果過去中位元時間(MTP)大於nStartTIme,則狀態轉換為THRESHOLD_STARTED,如果MTP大於等於nTimeout,則狀態轉換成THRESHOLD_FAILED;如果在一個目標周期(2016個區塊)內贊成升級的區塊數量佔95%以上(大約1915個區塊),則狀態轉換成THRESHOLD_LOCKED_IN,否則轉換成THRESHOLD_FAILED;在THRESHOLD_LOCKED_IN之後的下一個目標周期,狀態轉換成THRESHOLD_ACTIVE,同時該部署將保持該狀態。
enum ThresholdState { THRESHOLD_DEFINED, THRESHOLD_STARTED, THRESHOLD_LOCKED_IN, THRESHOLD_ACTIVE, THRESHOLD_FAILED,};
商務邏輯
基類AbstractThresholdConditionChecker定義了通過共識規則檢查BIP9部署的狀態。有如下方法,其中最後兩個方法在基類中實現,子類繼承了該方法的實現: Condition(...)檢測一個區塊是否贊成一個軟分叉升級:首先驗證該區塊version是否有效version格式, 然後檢測該version是否設定了相應個bit位 BeginTime(...)返回共識規則中的開始投票時間(採用MTP驗證 pindexPrev->GetMedianTimePast() >= nTimeStart) EndTime(...)返回共識規則中的設定的到期時間 Period(...)返回共識規則中的一個目標周期(當前主鏈的目標周期為2016個區塊) Threshold(...)返回nRuleChangeActivationThreshold,表示滿足軟分叉升級的最低要求 GetStateFor(...)在提供共識規則、開始檢索的區塊索引、以及之前緩衝的狀態資料判斷當前部署的狀態(後面會詳細分析其邏輯) GetStateSinceHeightFor(...)函數的作用是尋找從哪個區塊高度開始,該部署的狀態就已經和當前一致
class AbstractThresholdConditionChecker {protected: virtual bool Condition(const CBlockIndex *pindex, const Consensus::Params ¶ms) const = 0; virtual int64_t BeginTime(const Consensus::Params ¶ms) const = 0; virtual int64_t EndTime(const Consensus::Params ¶ms) const = 0; virtual int Period(const Consensus::Params ¶ms) const = 0; virtual int Threshold(const Consensus::Params ¶ms) const = 0; public: ThresholdState GetStateFor(const CBlockIndex *pindexPrev, const Consensus::Params ¶ms, ThresholdConditionCache &cache) const; int GetStateSinceHeightFor(const CBlockIndex *pindexPrev, const Consensus::Params ¶ms, ThresholdConditionCache &cache) const;};
類VersionBitsConditionChecker繼承了AbstractThresholdConditionChecker。實現了: BeginTime(const Consensus::Params ¶ms) EndTime(const Consensus::Params ¶ms) Period(const Consensus::Params ¶ms) Threshold(const Consensus::Params ¶ms) Condition(const CBlockIndex *pindex, const Consensus::Params ¶ms)
class VersionBitsConditionChecker : public AbstractThresholdConditionChecker {private:// maybe: DEPLOYMENT_TESTDUMMY,DEPLOYMENT_CSV,MAX_VERSION_BITS_DEPLOYMENTS const Consensus::DeploymentPos id;protected: int64_t BeginTime(const Consensus::Params ¶ms) const { return params.vDeployments[id].nStartTime; } int64_t EndTime(const Consensus::Params ¶ms) const { return params.vDeployments[id].nTimeout; } int Period(const Consensus::Params ¶ms) const { return params.nMinerConfirmationWindow; } int Threshold(const Consensus::Params ¶ms) const { return params.nRuleChangeActivationThreshold; } bool Condition(const CBlockIndex *pindex, const Consensus::Params ¶ms) const { return (( (pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask(params)) != 0); } ...}
另個一重要的類VersionBitsCache,包括一個方法和一個數組。該數組作為記憶體緩衝使用,該數組的成員是一個map,當檢查一個BIP9部署的狀態時,如果在檢查過程中判斷出部署狀態,該map會以區塊索引為索引值,以狀態資訊(int)為值,緩衝起來,在下次檢查時可以在該區塊位置直接得到其狀態資訊,對程式起到了最佳化的作用,避免重複的檢索。
struct VersionBitsCache { ThresholdConditionCache caches[Consensus::MAX_VERSION_BITS_DEPLOYMENTS]; void Clear();};typedef std::map<const CBlockIndex *, ThresholdState> ThresholdConditionCache;
另外WarningBitsConditionChecker類也繼承了AbstractThresholdConditionChecker類,實現了對未知升級的追蹤與警告。一旦nVersion中有未預料到的位被設定成1,mask將會產生非零的值。當未知升級被檢測到處THRESHOLD_LOCKED_IN狀態,軟體應該警告使用者即將到來未知的軟分叉。在下一個目標周期,處於THRESHOLD_ACTIVE狀態是,更應該強調警告使用者。
需要說明的是:未知升級只有處於LOCKED_IN或ACTIVE的條件下才會發出警告
...WarningBitsConditionChecker checker(bit);ThresholdState state = checker.GetStateFor(pindex, chainParams.GetConsensus(), warningcache[bit]);if (state == THRESHOLD_ACTIVE || state == THRESHOLD_LOCKED_IN) { if (state == THRESHOLD_ACTIVE) { std::string strWarning = strprintf(_("Warning: unknown new rules activated (versionbit %i)"), bit); SetMiscWarning(strWarning); if (!fWarned) { AlertNotify(strWarning); fWarned = true; } } else { warningMessages.push_back( strprintf("unknown new rules are about to activate (versionbit %i)", bit)); }}...
代碼拆解 GetAncestor(int height)函數在整個模組中的使用率非常高,其作用就是為了返回指定高度的區塊索引,作用非常簡單但是其代碼邏輯不太好理解。可以把整個區塊鏈簡單的看成就是一個鏈表結構,為了獲得指定高度的節點資訊,一般通過依次移動指標到指定區塊即可。在該模組中,使用CBlockIndex類中的pskip欄位,配合GetSkipHeight(int height)函數,能夠快速定位到指定高度的區塊,最佳化了執行的效率。
CBlockIndex *CBlockIndex::GetAncestor(int height) { if (height > nHeight || height < 0) { return nullptr; } CBlockIndex *pindexWalk = this; int heightWalk = nHeight; while (heightWalk > height) { int heightSkip = GetSkipHeight(heightWalk); int heightSkipPrev = GetSkipHeight(heightWalk - 1); if (pindexWalk->pskip != nullptr && (heightSkip == height || (heightSkip > height && !(heightSkipPrev < heightSkip - 2 && heightSkipPrev >= height)))) { pindexWalk = pindexWalk->pskip; heightWalk = heightSkip; } else { assert(pindexWalk->pprev); pindexWalk = pindexWalk->pprev; heightWalk--; } } return pindexWalk;}static inline int GetSkipHeight(int height) { if (height < 2) { return 0; } return (height & 1) ? InvertLowestOne(InvertLowestOne(height - 1)) + 1 : InvertLowestOne(height);} 在整個模組中進行時間比較判斷是都使用了GetMedianTimePast(), 其作用就是找出當前區塊前的10個區塊,排序後,返回第5個元素的nTime
enum { nMedianTimeSpan = 11 };int64_t GetMedianTimePast() const { int64_t pmedian[nMedianTimeSpan]; int64_t *pbegin = &pmedian[nMedianTimeSpan]; int64_t *pend = &pmedian[nMedianTimeSpan]; const CBlockIndex *pindex = this; for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev) { *(--pbegin) = pindex->GetBlockTime(); } std::sort(pbegin, pend); return pbegin[(pend - pbegin) / 2];}
邏輯如下: 建立包含11個元素的數組,包括該區塊和之前的10個區塊 pbegin、pend兩個遊標(數組遊標)指向數組末端 遍曆11個區塊,pindex遊標不斷地向前移動 數組遊標向前移動,並將pindex擷取的時間戳記賦值給數組 對數組排序(排序的原因是:區塊時間戳記是不可靠的欄位,其大小與建立區塊順序可能不一致) 11個區塊去中間的元素,也就是數組下標為5的元素,因為是奇數個元素,所以不用進行判斷下標無效的問題 GetStateFor(...)函數在整個模組中至關重要,負責擷取BIP9部署的狀態資訊。首先說明的是在一個目標周期之內,一個BIP9部署的狀態是相同的,也就是說部署狀態只會在難度目標發生改變之後才會更新。GetStateFor(...)函數擷取的是上一個目標周期的最後一個區塊的狀態,如果該狀態可以判斷出部署狀態則得出結果,並將結果儲存在VersionBitsCache結構體中;如果該狀態已經存在於緩衝中則直接返回結果;最後如果該區塊無法得出狀態資訊,則會依次尋找(pindexPrev.nHeight - nPeriod)高度的狀態資訊,直到能夠得出結果。如果直到nullptr也沒有,則返回THRESHOLD_DEFINED。其中比較重要的是,如果一個區塊表明該部署狀態處於THRESHOLD_STARTED,則會進行更為詳細的判斷,以證明其狀態是否以及失敗或者可以進入LOCKED_IN階段。
ThresholdState AbstractThresholdConditionChecker::GetStateFor(...){...if (pindexPrev != nullptr) { pindexPrev = pindexPrev->GetAncestor( pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod)); } std::vector<const CBlockIndex *> vToCompute; while (cache.count(pindexPrev) == 0) { if (pindexPrev