比特幣POW算法分析


基本信息

官網:https://github.com/bitcoin/bitcoin

 

比特幣節點pow大致流程

  • 生成coinbase交易,並與其他所有准備打包進區塊的交易組成交易列表,通過Merkle樹算法生成Merkle根哈希;
  • 把Merkle根哈希及其他相關字段組裝成區塊頭,將區塊頭的80字節數據作為工作量證明的輸入;
  • 不停地變更區塊頭中的隨機數,即nonce的數值,並對每次變更后的區塊頭做雙重SHA256運算,將結果值與當前網絡的目標值做對比,如果小於目標值則解題成功,工作量證明完成。

 

源碼解析

比特幣區塊頭結構

src/primitives/block.h

區塊頭長度為80字節

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:
    // network and disk(交易列表)
    std::vector<CTransactionRef> vtx;

    // memory only
    mutable bool fChecked;

  

如上區塊頭長度為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交易以及創建區塊

src/miner.cpp

std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn)
{
    int64_t nTimeStart = GetTimeMicros();

    resetBlock();

    pblocktemplate.reset(new CBlockTemplate());

    if(!pblocktemplate.get())
        return nullptr;
    CBlock* const 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, m_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(); // Decide whether to include witness transactions // This is only needed in case the witness softfork activation is reverted // (which would require a very deep reorganization). // Note that the mempool would accept transactions with witness data before // IsWitnessEnabled, but we would only ever mine blocks after IsWitnessEnabled // unless there is a massive block reorganization with the witness softfork // not activated. // TODO: replace this with a call to main to assess validity of a mempool // transaction (which in most cases can be a no-op). 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); coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
//挖礦獎勵 GetBlockSubsidy()和手續費 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()); pblock->nNonce = 0; //隨機數 nNonce 先重置為0 pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } 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); }

POW的實現

src/rpc/mining.cpp

static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash)
{
    block_hash.SetNull();

    {
        LOCK(cs_main);
        IncrementExtraNonce(&block, ::ChainActive().Tip(), extra_nonce);
    }

    CChainParams chainparams(Params());

    //不斷變更區塊頭中的隨機數 Nonce

      //對變更后的區塊頭做雙重SHA256哈希運算     

//CheckProofOfWork 函數 與當前難度的目標值做比對,如果小於目標難度,即Pow完成
       //uint64_t nMaxTries = 1000000;即重試100萬次<br>
    while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus()) && !ShutdownRequested()) {
        ++block.nNonce;
        --max_tries;
    }
    if (max_tries == 0 || ShutdownRequested()) {
        return false;
    }
    if (block.nNonce == std::numeric_limits<uint32_t>::max()) {
        return true;
    }
//函數驗證合法性 std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); if (!chainman.ProcessNewBlock(chainparams, shared_pblock, true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } block_hash = block.GetHash(); return true; } static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries) { int nHeightEnd = 0; int nHeight = 0; { // Don't keep cs_main locked LOCK(cs_main); ////用於更改 coinbase交易中的 ExtraNonce nHeight = ::ChainActive().Height(); nHeightEnd = nHeight+nGenerate; } unsigned int nExtraNonce = 0; UniValue blockHashes(UniValue::VARR); while (nHeight < nHeightEnd && !ShutdownRequested()) { std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(mempool, Params()).CreateNewBlock(coinbase_script)); if (!pblocktemplate.get()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); CBlock *pblock = &pblocktemplate->block; uint256 block_hash; if (!GenerateBlock(chainman, *pblock, nMaxTries, nExtraNonce, block_hash)) { break; } if (!block_hash.IsNull()) { ++nHeight; blockHashes.push_back(block_hash.GetHex()); } } return blockHashes; }

  

 

 雙SHA256驗證過程

區塊頭長度為80字節,因此執行SHA256算法,分割成 64B和16B+填充的48B兩段進行運算(??)

挖礦的過程就是尋找符合規則的 nNonce ,使如下等式成立:

SHA256(SHA256(version + prev_hash + merkle_root + ntime + nbits + nNonce + 填充 )) < TARGET

src/pow.cpp

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;
}

  

修改區塊 coinbase 里面的 ExtraNonce

nNonce的范圍為 0~2^32,當 nNonce 溢出仍然沒有符合的值時,使用IncrementExtraNonce()函數修改區塊 coinbase 里面的 ExtraNonce

 src/miner.cpp

void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
    // Update 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)); assert(txCoinbase.vin[0].scriptSig.size() <= 100); //重新計算 pBlock 區塊頭中的 hashMerkleRoot pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); }

 

難度值計算 - 源碼見 GetNextWorkRequired 函數,位置 src/pow.cpp

規則大致為每創建2016個塊后將計算新的難度,此后的2016個塊使用新的難度。計算步驟如下: 

  1. 找到前2016個塊的第一個塊,計算生成這2016個塊花費的時間。即最后一個塊的時間與第一個塊的時間差。時間差不小於3.5天,不大於56天。
  2. 計算前2016個塊的難度總和,即單個塊的難度x總時間。
  3. 計算新的難度,即2016個塊的難度總和/14天的秒數,得到每秒的難度值。
  4. 要求新的難度,難度不低於參數定義的最小難度。


簡易模式

 

參考資料

https://www.cnblogs.com/zhang-qc/p/10451817.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM