Bitcoin — Phần 2: Proof of work quá trình điều chỉnh độ khó

Dũng Trần
tradahacking
Published in
6 min readJul 28, 2017

Tiếp theo bài viết trước Bitcoin — Phần 1: Bitcoin proof of work những điều chưa kể, giờ chúng ta cùng tìm hiểu rõ hơn về giá trị Difficulty, và cách tính nBits cho block kế tiếp.

Hồi giờ nghe nói về mining coin thì người ta nói Difficulty cao thì khó đào. Vậy tại sao lại có Difficulty?.

https://en.bitcoin.it/wiki/Difficulty

Trong wiki của Bitcoin có nói rất rõ về Difficulty nó được lưu là Bits (source code viết là nBits) trong block header. Đây là công thức tính Difficulty từ giá trị nBits:

/*
File: /src/rpc/blockchain.cpp
Line: 46
*/
double GetDifficulty(const CBlockIndex* blockindex)
{
// Floating point number that is a multiple of the minimum difficulty,
// minimum difficulty = 1.0.
if (blockindex == NULL)
{
if (chainActive.Tip() == NULL)
return 1.0;
else
blockindex = chainActive.Tip();
}
int nShift = (blockindex->nBits >> 24) & 0xff;double dDiff =
(double)0x0000ffff / (double)(blockindex->nBits & 0x00ffffff);
while (nShift < 29)
{
dDiff *= 256.0;
nShift++;
}
while (nShift > 29)
{
dDiff /= 256.0;
nShift--;
}
return dDiff;
}

Dựa vào code trong Bitcoin Wiki và mình viết lại bằng JavaScript:

function GetDifficulty(nBits)
{
let nShift = (nBits >> 24) & 0xff;
let dDiff = parseFloat(0x0000ffff) / parseFloat(nBits & 0x00ffffff);
while (nShift < 29)
{
dDiff *= 256.0;
nShift++;
}
while (nShift > 29)
{
dDiff /= 256.0;
nShift--;
}
return dDiff;
}
function GetDifficulty2(Bits){
return Math.exp(Math.log(65535) - Math.log(Bits % Math.pow(2, 25)) + Math.log(256) * (29 - Math.floor(Bits/Math.pow(2, 24))));
}
//Block #371623 https://blockexplorer.com/block/0000000000000000079c58e8b5bce4217f7515a74b170049398ed9b8428beb4a
console.log('Calcucate difficulty from nBits (Bitcoin core):', GetDifficulty(0x181443c4));
console.log('Calcucate difficulty from nBits (Bitcoin wiki):', GetDifficulty2(0x181443c4));

Kết quả ta có:

[email protected]:~/labs/010-proof-of-work$ node testDiff.js Calcucate difficulty from nBits (Bitcoin core): 54256630327.88996 Calcucate difficulty from nBits (Bitcoin wiki): 54256630327.88995

Có sai lệch chút đỉnh, công thức trên dùng bitwise operation, bên dưới thì sử dụng các công thức toán học. Ta có công thức tính Difficulty từ nBits:

nBits chính là giá trị Difficulty thực sự được lưu trong block header. Theo bài trước ta thấy rõ từ nBits ta tính được bnTarget đó chính là giá trị dùng để đánh giá một block có thỏa Bitcoin proof-of-work hay không. Ta xét quá trình update timestamp của một block khi mining bằng cách xem xét hàm UpdateTime():

/*
File: /src/miner.cpp
Line: 58
*/
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev)
{
int64_t nOldTime = pblock->nTime;
int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
if (nOldTime < nNewTime)
pblock->nTime = nNewTime;
// Updating time can change work required on testnet:
if (consensusParams.fPowAllowMinDifficultyBlocks)
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams);
return nNewTime - nOldTime;
}

Độ khó mới sẽ được gán vào pblock->nBits (của block đang được “đào”).

pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams);

Ta xét chi tiết function GetNextWorkRequired() dùng để xác định yêu cầu về độ khó cho block kế tiếp:

/*
File: /src/pow.cpp
Line: 13
*/
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();
// Genesis block
if (pindexLast == NULL)
return nProofOfWorkLimit;
// Only change once per difficulty adjustment interval
if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0)
{
if (params.fPowAllowMinDifficultyBlocks)
{
// Special difficulty rule for testnet:
// If the new block's timestamp is more than 2* 10 minutes
// then allow mining of a min-difficulty block.
if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2)
return nProofOfWorkLimit;
else
{
// Return the last non-special-min-difficulty-rules-block
const CBlockIndex* pindex = pindexLast;
while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)
pindex = pindex->pprev;
return pindex->nBits;
}
}
return pindexLast->nBits;
}
// Go back by what we want to be 14 days worth of blocks
int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);
assert(nHeightFirst >= 0);
const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);
assert(pindexFirst);
return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);
}

Để hiểu thì ta cần phải nắm vài parameters được định nghĩa dành cho mainnet của Bitcoin:

/*
File: /src/chainparams.cpp
Line: 183
*/
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetSpacing = 10 * 60;
consensus.fPowAllowMinDifficultyBlocks = true;
consensus.fPowNoRetargeting = false;
consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains
consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing

Và xem thêm vài đoạn này:

/*
File: /src/consensus/params.h
Line: 61
*/
int64_t nPowTargetSpacing;
int64_t nPowTargetTimespan;
int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; }

DifficultyAdjustmentInterval() sẽ trả về 2016 dựa trên chain params đã cho.

nPowTargetTimespan / nPowTargetSpacing = (14 * 24 * 60 * 60)/(10 * 60) = 2016

Giờ chúng ta phân tích rõ hàm GetNextWorkRequired():

/*
File: /src/pow.cpp
Line: 13
*/
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();
// Genesis block
if (pindexLast == NULL)
return nProofOfWorkLimit;
// Only change once per difficulty adjustment interval
if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0)
{
if (params.fPowAllowMinDifficultyBlocks)
{
// Special difficulty rule for testnet:
// If the new block's timestamp is more than 2* 10 minutes
// then allow mining of a min-difficulty block.
if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2)
return nProofOfWorkLimit;
else
{
// Return the last non-special-min-difficulty-rules-block
const CBlockIndex* pindex = pindexLast;
while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)
pindex = pindex->pprev;
return pindex->nBits;
}
}
return pindexLast->nBits;
}
// Go back by what we want to be 14 days worth of blocks
int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);
assert(nHeightFirst >= 0);
const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);
assert(pindexFirst);
return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);
}

pindexLast: Pointer trỏ vào block index cuối trong chain
pblock: Con trỏ tới 1 block mới
params: Các parameters của blockchain

unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();

GetCompact ngược với SetCompact mục đích là tạo ra giá trị nBits (uint32) từ bnTarget (uint256).

(pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0

Điều kiện này để xét block kế tiếp có phải là block điều chỉnh độ khó không. Bitcoin sẽ điều chỉnh độ khó theo chu kì 2016 blocks. Ta cùng xem quá trình điều chỉnh độ khó (nBits).

// Go back by what we want to be 14 days worth of blocks
int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);
assert(nHeightFirst >= 0);
const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);

Chúng ta sẽ xét các block trong chu kì 2016 blocks gần nhất. Để tính toán lại độ khó (nBits).

/*
File: /src/pow.cpp
Line: 52
*/
unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)
{
if (params.fPowNoRetargeting)
return pindexLast->nBits;
// Limit adjustment step
int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
if (nActualTimespan < params.nPowTargetTimespan/4)
nActualTimespan = params.nPowTargetTimespan/4;
if (nActualTimespan > params.nPowTargetTimespan*4)
nActualTimespan = params.nPowTargetTimespan*4;
// Retarget
const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
arith_uint256 bnNew;
bnNew.SetCompact(pindexLast->nBits);
bnNew *= nActualTimespan;
bnNew /= params.nPowTargetTimespan;
if (bnNew > bnPowLimit)
bnNew = bnPowLimit;
return bnNew.GetCompact();
}

Đoạn code bên dưới sẽ tính toán lại thời gian thực tế để sinh ra 2016 blocks:

int64_t nActualTimespan = pindexLast->GetBlockTime() — nFirstBlockTime;

Tính thời gian thực tế dựa trên timestamp của block, bằng các lấy thời gian block gần nhất trừ cho thời gian block đầu tiên trong chu kì 2016 blocks (nhắc lại nPowTargetTimespan chính là hai tuần).

bnNew.SetCompact(pindexLast->nBits);

Đẩy giá trị nBits (chưa packed chính là bnTarget) của block mới nhất được verify của blockchain vào giá trị bnNew để tính toán tiếp.

bnNew *= nActualTimespan;
bnNew /= params.nPowTargetTimespan;

bnNew được điều chỉnh gián tiếp thông qua điều chỉnh bnTarget sau đó được pack lại bằng GetCompact():

nActualTimespan < (14*24*60*60)/4 thì bnTarget mới bằng bnTarget cũ chia cho 4
nActualTimespan > (14*24*60*60)*4 thì bnTarget mới bằng bnTarget cũ nhân với 4

if (bnNew > bnPowLimit)
bnNew = bnPowLimit;

Đảm bảo độ khó mới không vượt quá powLimit

Kết luận:

  • Quá trình mining là quá trình tìm số nNonce để block header thỏa POW
  • Từ giá trị nBits (độ khó lưu trử trong block header) chúng ta tính ra giá trị bnTarget
  • Blockhash (digest/hashes của block header) tìm được phải nhỏ hơn bnTarget
  • bnTarget luôn phải nhỏ hơn hoặc bằng powLimit
  • Difficulty là giá trị để đo độ khó khi sinh block, được lưu trong block header dưới dạng nBits
  • Độ khó nBits sẽ được điều chỉnh mỗi 2016 blocks, dựa trên timestamp của block đầu vào cuối chu kì 2016 blocks.

Originally published at fkguru.com on July 28, 2017.

--

--