比特币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个块使用新的难度。计算步骤如下:
- 找到前2016个块的第一个块,计算生成这2016个块花费的时间。即最后一个块的时间与第一个块的时间差。时间差不小于3.5天,不大于56天。
- 计算前2016个块的难度总和,即单个块的难度x总时间。
- 计算新的难度,即2016个块的难度总和/14天的秒数,得到每秒的难度值。
- 要求新的难度,难度不低于参数定义的最小难度。
简易模式
参考资料
https://www.cnblogs.com/zhang-qc/p/10451817.html
浙公网安备 33010602011771号