關于比特幣難度調整部分見《精通比特幣》8.7.3節(jié)
先介紹一下CBigNum。
CBigNum
CBigNum是openssl庫中定義的BIGNUM的包裝類。公鑰密碼學需要能夠處理非常大的整數。標準的數據類型無法滿足要求。BIGNUM可以存放任意長度的整型。
CBigNum類的結構并不復雜。它是由一堆不同類型構造BIGNUM的構造器組成,包括char,short,int,long,int64,int256,它們unsigned版本和vector<unsigned char>等。它同樣重構操作符,例如加、減、乘、除、位操作等。所有的實際工作代理給了BIGNUM類行。大部分CBigNum的代碼僅僅是為BIGNUM的函數準備輸入數據。
難度調整
這部分源代碼比較簡單,如下:
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast)
{
const unsigned int nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
const unsigned int nTargetSpacing = 10 * 60;
const unsigned int nInterval = nTargetTimespan / nTargetSpacing;
// Genesis block
if (pindexLast == NULL)
return bnProofOfWorkLimit.GetCompact();
// Only change once per interval
if ((pindexLast->nHeight+1) % nInterval != 0)
return pindexLast->nBits;
// Go back by what we want to be 14 days worth of blocks
const CBlockIndex* pindexFirst = pindexLast;
for (int i = 0; pindexFirst && i < nInterval-1; i++)
pindexFirst = pindexFirst->pprev;
assert(pindexFirst);
// Limit adjustment step
unsigned int nActualTimespan = pindexLast->nTime - pindexFirst->nTime;
printf(" nActualTimespan = %d before bounds\n", nActualTimespan);
if (nActualTimespan < nTargetTimespan/4)
nActualTimespan = nTargetTimespan/4;
if (nActualTimespan > nTargetTimespan*4)
nActualTimespan = nTargetTimespan*4;
// Retarget
CBigNum bnNew;
bnNew.SetCompact(pindexLast->nBits);
bnNew *= nActualTimespan;
bnNew /= nTargetTimespan;
if (bnNew > bnProofOfWorkLimit)
bnNew = bnProofOfWorkLimit;
/// debug print
printf("\n\n\nGetNextWorkRequired RETARGET *****\n");
printf("nTargetTimespan = %d nActualTimespan = %d\n", nTargetTimespan, nActualTimespan);
printf("Before: %08x %s\n", pindexLast->nBits, CBigNum().SetCompact(pindexLast->nBits).getuint256().ToString().c_str());
printf("After: %08x %s\n", bnNew.GetCompact(), bnNew.getuint256().ToString().c_str());
return bnNew.GetCompact();
}
nTargetTimespan 為兩個星期的秒數,nTargetSpacing 為10分鐘的秒數。nInterval = nTargetTimespan / nTargetSpacing;即為2016個區(qū)塊。
難度的調整是在每個完整節(jié)點中獨立自動發(fā)生的。每2,016個區(qū)塊中的所有節(jié)點都會調整難度。難度的調整公式是由最新2,016個區(qū)塊的花費時長與20,160分鐘(兩周,即這些區(qū)塊以10分鐘一個速率所期望花費的時長)比較得出的。難度是根據實際時長與期望時長的比值進行相應調整的(或變難或變易)。簡單來說,如果網絡發(fā)現(xiàn)區(qū)塊產生速率比10分鐘要快時會增加難度。如果發(fā)現(xiàn)比10分鐘慢時則降低難度。
首先函數會計算當前區(qū)塊是否到達下個難度周期,如果沒有達到,則難度位即為上一區(qū)塊的難度位。return pindexLast->nBits;
如果已經達到一個新的難度周期,則通過循環(huán):
for (int i = 0; pindexFirst && i < nInterval-1; i++)
pindexFirst = pindexFirst->pprev;
向前尋找2016個區(qū)塊,此時pindexFirst指向上一難度周期的第一個區(qū)塊。
nActualTimespan = pindexLast->nTime - pindexFirst->nTime
即利用上一難度周期的最后一個塊的時間戳減去第一個區(qū)塊的時間戳,其結果為上一難度周期總共花費的時間nActualTimespan 。
為了防止難度的變化過快,每個周期的調整幅度必須小于一個因子(值為4)。如果要調整的幅度大于4倍,則按4倍調整。由于在下一個2,016區(qū)塊的周期不平衡的情況會繼續(xù)存在,所以進一步的難度調整會在下一周期進行。因此平衡哈希計算能力和難度的巨大差異有可能需要花費幾個2,016區(qū)塊周期才會完成。
這部分便是將上一難度周期消耗的時間與期望的時間作比較,并根據結果進行調整。如果調整幅度大于4倍,則按4倍調整。
if (nActualTimespan < nTargetTimespan/4)
nActualTimespan = nTargetTimespan/4;
if (nActualTimespan > nTargetTimespan*4)
nActualTimespan = nTargetTimespan*4;
如果小于4倍則根據比例進行調整。
bnNew *= nActualTimespan;
bnNew /= nTargetTimespan;
此時便完成了一個周期的難度調整。