Proof Of Work 工作量證明
借鑒了 哈希現金(Hashcash)-1997年 英國密碼學專家亞當.貝克(Adam Back)
用工作量證明系統解決了互聯網垃圾郵件問題,它要求計算機在獲得發送信息權限之前做一定的計算工作,這對正常的信息傳播來講,幾乎很難察覺,但是對向全網大量散步垃圾信息的計算機來說,就成為了巨大的工作量和負擔。
通過進行一定的運算和消耗一定的時間來計算一個符合規則的值,並提供給服務方快速做驗證。
比特幣中的POW共識
比特幣 - 去中心化的點對點電子交易系統 :維護分布式去中心化的賬本
分布式無信任條件下的賬本一致 ---》共識
POW解決的是拜占庭下的共識,保證分布式賬本的最終一致性,解決雙花攻擊;同時也建立和維護了一個分布式的時鍾
PoW系統的主要特征是計算的不對稱性。(SHA256)
工作端需要做一定難度的工作得出一個結果,驗證方卻很容易通過結果來檢查工作端是不是做了相應的工作。
作弊行為的前提在於花費大量的資源,一旦某人無法成功達成惡意目標就意味着其付出了巨大的且不可挽回的沉沒成本。(這也是pow的優勢所在,作惡有代價)
核心技術:散列函數 SHA256

比特幣節點pow大致流程:
- 生成coinbase交易,並與其他所有准備打包進區塊的交易組成交易列表,通過Merkle樹算法生成Merkle根哈希;
- 把Merkle根哈希及其他相關字段組裝成區塊頭,將區塊頭的80字節數據作為工作量證明的輸入;
- 不停地變更區塊頭中的隨機數,即nonce的數值,並對每次變更后的區塊頭做雙重SHA256運算,將結果值與當前網絡的目標值做對比,如果小於目標值則解題成功,工作量證明完成。
比特幣區塊頭結構
class CBlockHeader
{
public:
// header
int32_t nVersion;
uint256 hashPrevBlock;
uint256 hashMerkleRoot;
uint32_t nTime;
uint32_t nBits;
uint32_t nNonce;
//...
};
// 代碼位置src/primitives/block.h
比特幣區塊結構:
class CBlock : public CBlockHeader
{
public:
// 交易的列表
std::vector<CTransactionRef> vtx;
//...
}
//代碼位置src/primitives/block.h
如上區塊頭長度為80字節,因此執行SHA256算法,分割成 64B和16B+填充的48B兩段進行運算
挖礦的過程就是尋找符合規則的 nNonce ,使如下等式成立:
SHA256(SHA256(version + prev_hash + merkle_root + ntime + nbits + nNonce + 填充 )) < TARGET
nNonce的范圍為 0~2^32,當 nNonce 溢出仍然沒有符合的值時,修改區塊 coinbase 里面的 ExtraNonce
pow算法中生成coinbase交易以及創建區塊:
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn)
{
int64_t nTimeStart = GetTimeMicros();
resetBlock();
pblocktemplate.reset(new CBlockTemplate());
if(!pblocktemplate.get())
return nullptr;
pblock = &pblocktemplate->block; // pointer for convenience
// Add dummy coinbase tx as first transaction
pblock->vtx.emplace_back();
pblocktemplate->vTxFees.push_back(-1); // updated at end
pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end
LOCK2(cs_main, mempool.cs);
CBlockIndex* pindexPrev = chainActive.Tip();
assert(pindexPrev != nullptr);
nHeight = pindexPrev->nHeight + 1;
//版本號
pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus());
// -regtest only: allow overriding block.nVersion with
// -blockversion=N to test forking scenarios
if (chainparams.MineBlocksOnDemand())
pblock->nVersion = gArgs.GetArg("-blockversion", pblock->nVersion);
//時間戳
pblock->nTime = GetAdjustedTime();
const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast();
nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST)
? nMedianTimePast
: pblock->GetBlockTime();
fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus());
int nPackagesSelected = 0;
int nDescendantsUpdated = 0;
addPackageTxs(nPackagesSelected, nDescendantsUpdated);
int64_t nTime1 = GetTimeMicros();
m_last_block_num_txs = nBlockTx;
m_last_block_weight = nBlockWeight;
// Create coinbase transaction. 創建coinbase交易
CMutableTransaction coinbaseTx;
coinbaseTx.vin.resize(1);
coinbaseTx.vin[0].prevout.SetNull();
coinbaseTx.vout.resize(1);
//挖礦獎勵 GetBlockSubsidy()和手續費
coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
//第一筆交易即為礦工獲得獎勵和手續費的特殊交易
pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());
pblocktemplate->vTxFees[0] = -nFees;
LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost);
// Fill in header 將區塊頭數據補齊
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev);
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
//隨機數 nNonce 先重置為0
pblock->nNonce = 0;
pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]);
CValidationState state;
if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) {
throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state)));
}
int64_t nTime2 = GetTimeMicros();
LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart));
return std::move(pblocktemplate);
}
//代碼位置 src/miner.cpp
POW的實現
UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript)
{
static const int nInnerLoopCount = 0x10000;
int nHeightEnd = 0;
int nHeight = 0;
{ // Don't keep cs_main locked
LOCK(cs_main);
nHeight = chainActive.Height();
nHeightEnd = nHeight+nGenerate;
}
unsigned int nExtraNonce = 0;
UniValue blockHashes(UniValue::VARR);
while (nHeight < nHeightEnd && !ShutdownRequested())
{
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(Params()).CreateNewBlock(coinbaseScript->reserveScript));
if (!pblocktemplate.get())
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
CBlock *pblock = &pblocktemplate->block;
{
LOCK(cs_main);
//用於更改 coinbase交易中的 ExtraNonce
IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce);
}
//不斷變更區塊頭中的隨機數 Nonce
//對變更后的區塊頭做雙重SHA256哈希運算
//CheckProofOfWork 函數 與當前難度的目標值做比對,如果小於目標難度,即Pow完成
//uint64_t nMaxTries = 1000000;即重試100萬次
while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
++pblock->nNonce;
--nMaxTries;
}
if (nMaxTries == 0) {
break;
}
if (pblock->nNonce == nInnerLoopCount) {
continue;
}
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
//ProcessNewBlock 函數驗證合法性
if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
++nHeight;
blockHashes.push_back(pblock->GetHash().GetHex());
//mark script as important because it was used at least for one coinbase output if the script came from the wallet
if (keepScript)
{
coinbaseScript->KeepScript();
}
}
return blockHashes;
}
//代碼位置src/rpc/mining.cpp
雙SHA256驗證過程:
區塊頭長度為80字節,因此執行SHA256算法,分割成 64B和16B+填充的48B兩段進行運算
挖礦的過程就是尋找符合規則的 nNonce ,使如下等式成立:
SHA256(SHA256(version + prev_hash + merkle_root + ntime + nbits + nNonce + 填充 )) < TARGET
源碼:
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
{
bool fNegative;
bool fOverflow;
arith_uint256 bnTarget;
bnTarget.SetCompact(nBits, &fNegative, &fOverflow);
// Check range
if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit))
return false;
// Check proof of work matches claimed amount
if (UintToArith256(hash) > bnTarget)
return false;
return true;
}
//代碼位置 src/pow.cpp
nNonce的范圍為 0~2^32,當 nNonce 溢出仍然沒有符合的值時,使用IncrementExtraNonce()函數修改區塊 coinbase 里面的 ExtraNonce
void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
// Update nExtraNonce 更新
static uint256 hashPrevBlock;
if (hashPrevBlock != pblock->hashPrevBlock)
{
nExtraNonce = 0;
hashPrevBlock = pblock->hashPrevBlock;
}
++nExtraNonce; //加 1
unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2
CMutableTransaction txCoinbase(*pblock->vtx[0]);
//重新生成簽名
txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS;
assert(txCoinbase.vin[0].scriptSig.size() <= 100);
//重新計算 pBlock 區塊頭中的 hashMerkleRoot
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
}
//代碼位置 src/miner.cpp
難度值計算 - 源碼見 GetNextWorkRequired 函數,位置 src/pow.cpp
規則大致為每創建2016個塊后將計算新的難度,此后的2016個塊使用新的難度。計算步驟如下:
- 找到前2016個塊的第一個塊,計算生成這2016個塊花費的時間。即最后一個塊的時間與第一個塊的時間差。時間差不小於3.5天,不大於56天。
- 計算前2016個塊的難度總和,即單個塊的難度x總時間。
- 計算新的難度,即2016個塊的難度總和/14天的秒數,得到每秒的難度值。
- 要求新的難度,難度不低於參數定義的最小難度。
POW算法被批評的點:
PoW機制造成了巨大的能源浪費;
算力集中導致的中心化問題(礦池)。
