首页 > PoW挖矿算法原理及其在比特币、以太坊中的实现-fcj

比特币挖矿机源码,PoW挖矿算法原理及其在比特币、以太坊中的实现-fcj

互联网 2021-03-08 23:53:26

PoW,全称Proof of Work,即工作量证明,又称挖矿。大部分公有链或虚拟货币,如比特币、以太坊,均基于PoW算法,来实现其共识机制。即根据挖矿贡献的有效工作,来决定货币的分配。 

比特币区块

 比特币区块由区块头和该区块所包含的交易列表组成。区块头大小为80字节,其构成包括: 4字节:版本号32字节:上一个区块的哈希值32字节:交易列表的Merkle根哈希值4字节:当前时间戳4字节:当前难度值4字节:随机数Nonce值 此80字节长度的区块头,即为比特币Pow算法的输入字符串。交易列表附加在区块头之后,其中第一笔交易为矿工获得奖励和手续费的特殊交易。 bitcoin-0.15.1源码中区块头和区块定义: 

class CBlockHeader{public://版本号int32_t nVersion;//上一个区块的哈希值uint256 hashPrevBlock;//交易列表的Merkle根哈希值uint256 hashMerkleRoot;//当前时间戳uint32_t nTime;//当前挖矿难度,nBits越小难度越大uint32_t nBits;//随机数Nonce值uint32_t nNonce;//其它代码略};class CBlock : public CBlockHeader{public://交易列表std::vector vtx;//其它代码略};//代码位置src/primitives/block.h

 

比特币Pow算法原理

 Pow的过程,即为不断调整Nonce值,对区块头做双重SHA256哈希运算,使得结果满足给定数量前导0的哈希值的过程。其中前导0的个数,取决于挖矿难度,前导0的个数越多,挖矿难度越大。 具体如下: 1、生成铸币交易,并与其它所有准备打包进区块的交易组成交易列表,生成Merkle根哈希值。2、将Merkle根哈希值,与区块头其它字段组成区块头,80字节长度的区块头作为Pow算法的输入。3、不断变更区块头中的随机数Nonce,对变更后的区块头做双重SHA256哈希运算,与当前难度的目标值做比对,如果小于目标难度,即Pow完成。 Pow完成的区块向全网广播,其他节点将验证其是否符合规则,如果验证有效,其他节点将接收此区块,并附加在已有区块链之后。之后将进入下一轮挖矿。 bitcoin-0.15.1源码中Pow算法实现:

UniValue generateBlocks(std::shared_ptr coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript){static const int nInnerLoopCount = 0x10000;int nHeightEnd = 0;int nHeight = 0;{ // Don't keep cs_main lockedLOCK(cs_main);nHeight = chainActive.Height();nHeightEnd = nHeight+nGenerate;}unsigned int nExtraNonce = 0;UniValue blockHashes(UniValue::VARR);while (nHeight < nHeightEnd){std::unique_ptr 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);IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce);}//不断变更区块头中的随机数Nonce//对变更后的区块头做双重SHA256哈希运算//与当前难度的目标值做比对,如果小于目标难度,即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 shared_pblock = std::make_shared(*pblock);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 walletif (keepScript){coinbaseScript->KeepScript();}}return blockHashes;}//代码位置src/rpc/mining.cpp

 另附bitcoin-0.15.1源码中生成铸币交易和创建新块: 

std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx){int64_t nTimeStart = GetTimeMicros();resetBlock();pblocktemplate.reset(new CBlockTemplate());if(!pblocktemplate.get())return nullptr;pblock = &pblocktemplate->block; // pointer for conveniencepblock->vtx.emplace_back();pblocktemplate->vTxFees.push_back(-1); // updated at endpblocktemplate->vTxSigOpsCost.push_back(-1); // updated at endLOCK2(cs_main, mempool.cs);CBlockIndex* pindexPrev = chainActive.Tip();nHeight = pindexPrev->nHeight + 1;//版本号pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus());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()) && fMineWitnessTx;int nPackagesSelected = 0;int nDescendantsUpdated = 0;addPackageTxs(nPackagesSelected, nDescendantsUpdated);int64_t nTime1 = GetTimeMicros();nLastBlockTx = nBlockTx;nLastBlockWeight = nBlockWeight;//创建铸币交易CMutableTransaction coinbaseTx;coinbaseTx.vin.resize(1);coinbaseTx.vin[0].prevout.SetNull();coinbaseTx.vout.resize(1);//挖矿奖励和手续费coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());coinbaseTx.vin[0].scriptSig = CScript() 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);//上一个区块的哈希值pblock->hashPrevBlock= pindexPrev->GetBlockHash();UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev);//当前挖矿难度pblock->nBits= GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());//随机数Nonce值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

 

比特币挖矿难度计算

 每创建2016个块后将计算新的难度,此后的2016个块使用新的难度。计算步骤如下: 1、找到前2016个块的第一个块,计算生成这2016个块花费的时间。即最后一个块的时间与第一个块的时间差。时间差不小于3.5天,不大于56天。2、计算前2016个块的难度总和,即单个块的难度x总时间。3、计算新的难度,即2016个块的难度总和/14天的秒数,得到每秒的难度值。4、要求新的难度,难度不低于参数定义的最小难度。 bitcoin-0.15.1源码中计算挖矿难度代码如下: 

//nFirstBlockTime即前2016个块的第一个块的时间戳unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params){if (params.fPowNoRetargeting)return pindexLast->nBits;//计算生成这2016个块花费的时间int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;//不小于3.5天if (nActualTimespan < params.nPowTargetTimespan/4)nActualTimespan = params.nPowTargetTimespan/4;//不大于56天if (nActualTimespan > params.nPowTargetTimespan*4)nActualTimespan = params.nPowTargetTimespan*4;// Retargetconst arith_uint256 bnPowLimit = UintToArith256(params.powLimit);arith_uint256 bnNew;bnNew.SetCompact(pindexLast->nBits);//计算前2016个块的难度总和//即单个块的难度*总时间bnNew *= nActualTimespan;//计算新的难度//即2016个块的难度总和/14天的秒数bnNew /= params.nPowTargetTimespan;//bnNew越小,难度越大//bnNew越大,难度越小//要求新的难度,难度不低于参数定义的最小难度if (bnNew > bnPowLimit)bnNew = bnPowLimit;return bnNew.GetCompact();}//代码位置src/pow.cpp

 

以太坊区块

 以太坊区块由Header和Body两部分组成。 其中Header部分成员如下:ParentHash,父区块哈希UncleHash,叔区块哈希,具体为Body中Uncles数组的RLP哈希值。RLP哈希,即某类型对象RLP编码后做SHA3哈希运算。Coinbase,矿工地址。Root,StateDB中state Trie根节点RLP哈希值。TxHash,Block中tx Trie根节点RLP哈希值。ReceiptHash,Block中Receipt Trie根节点的RLP哈希值。Difficulty,区块难度,即当前挖矿难度。Number,区块序号,即父区块Number+1。GasLimit,区块内所有Gas消耗的理论上限,创建时指定,由父区块GasUsed和GasLimit计算得出。GasUsed,区块内所有Transaction执行时消耗的Gas总和。Time,当前时间戳。Nonce,随机数Nonce值。 有关叔区块:叔区块,即孤立的块。以太坊成块速度较快,导致产生孤块。以太坊会给发现孤块的矿工以回报,激励矿工在新块中引用孤块,引用孤块使主链更重。在以太坊中,主链是指最重的链。 有关state Trie、tx Trie和Receipt Trie:state Trie,所有账户对象可以逐个插入一个Merkle-PatricaTrie(MPT)结构中,形成state Trie。tx Trie:Block中Transactions中所有tx对象,逐个插入MPT结构中,形成tx Trie。Receipt Trie:Block中所有Transaction执行后生成Receipt数组,所有Receipt逐个插入MPT结构中,形成Receipt Trie。 Body成员如下:Transactions,交易列表。Uncles,引用的叔区块列表。 go-ethereum-1.7.3源码中区块头和区块定义: 

type Header struct {//父区块哈希ParentHashcommon.Hash//叔区块哈希UncleHash common.Hash//矿工地址Coinbasecommon.Address//StateDB中state Trie根节点RLP哈希值Rootcommon.Hash//Block中tx Trie根节点RLP哈希值TxHashcommon.Hash//Block中Receipt Trie根节点的RLP哈希值ReceiptHash common.HashBloom Bloom//区块难度Difficulty*big.Int//区块序号Number*big.Int//区块内所有Gas消耗的理论上限GasLimit*big.Int//区块内所有Transaction执行时消耗的Gas总和GasUsed *big.Int//当前时间戳Time*big.IntExtra []byteMixDigest common.Hash//随机数Nonce值Nonce BlockNonce}type Body struct {//交易列表Transactions []*Transaction//引用的叔区块列表Uncles []*Header}//代码位置core/types/block.go

 

以太坊Pow算法原理

 以太坊Pow算法可以表示为如下公式:RAND(h, n)

免责声明:非本网注明原创的信息,皆为程序自动获取自互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件24小时内删除。

相关阅读