比特币挖矿与源码解析
- 2021 年 12 月 20 日
本文字数:19245 字
阅读完需:约 63 分钟
作者:chirpyli
来源:恒生LIGHT云社区
挖矿流程概述
比特币挖矿,其实就是比特币节点,对交易进行打包出块,获取记账权的同时得到比特币激励。挖矿主要流程如下:
收集网络上广播的交易,进行验证,加入到交易池中;
构造新块——将交易池中的交易打包,选择一条最长的区块链,计算最新块头哈希值作为新块(候选块,还未获得记账权)前一区块哈希;
工作量证明,计算难题,竞争记账权,如果获得记账权,则将新块广播出去,如果收到其他节点广播的新块,则验证该新块,如果新块通过验证,则重新开始挖矿流程。
随着专用矿机的出现,CPU 挖矿基本已经被废弃了,这里的 CPU 挖矿代码只是用来学习以及测试用,实际矿工并不会使用下面的代码来挖矿。开启 CPU 挖矿的话,可通过一个 generatetoaddress
RPC 调用开启挖矿到一个特定的地址。
一个比特币节点可以选择挖矿也可以选择不挖矿,如果想挖矿的话,输入开始挖矿的命令 bitcoin-cli generatetoaddress
开启挖矿流程。下面我们来分析相关源代码:
// 开启挖矿流程后,进入这里,需要一个交易输出,及Coinbase交易要将挖矿奖励给矿工,需要锁定脚本
static UniValue generatetoaddress(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
throw std::runtime_error(
RPCHelpMan{"generatetoaddress",
"\nMine blocks immediately to a specified address (before the RPC call returns)\n",
{
{"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, //设置准备挖多少个区块
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."}, //挖矿奖励输出地址
{"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."},
},
RPCResult{
"[ blockhashes ] (array) hashes of blocks generated\n"
},
RPCExamples{
"\nGenerate 11 blocks to myaddress\n"
+ HelpExampleCli("generatetoaddress", "11 \"myaddress\"")
+ "If you are running the bitcoin core wallet, you can get a new address to send the newly generated bitcoin to with:\n"
+ HelpExampleCli("getnewaddress", "")
},
}.ToString());
int nGenerate = request.params[0].get_int();
uint64_t nMaxTries = 1000000;
if (!request.params[2].isNull()) {
nMaxTries = request.params[2].get_int();
}
CTxDestination destination = DecodeDestination(request.params[1].get_str());
if (!IsValidDestination(destination)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address");
}
CScript coinbase_script = GetScriptForDestination(destination); //用于给矿工奖励的锁定脚本
return generateBlocks(coinbase_script, nGenerate, nMaxTries); //产生新块,竞争记账权
}
static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
{
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(coinbase_script)); //构造新块
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); //默克尔根哈希是在这里面计算并赋值的。
}
//不断改变nNonce值,计算难题
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);
if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
++nHeight;
blockHashes.push_back(pblock->GetHash().GetHex());
}
return blockHashes;
}
后面我们展开这里面的代码,详细分析一下这里面的具体流程。先从收集交易开始。
验证交易加入到交易池
比特币钱包构造交易并发布到比特币区块链网络中,其他节点收到网络上接收到的交易,先进行验证,验证交易后,比特币节点会将这些交易添加到自己的内存池中同时将交易广播给其他节点。内存池也称作交易池,用来暂存尚未被确认(加入到区块)的交易记录。这里的验证规则如下(好长,且有可能会发生变化):
交易的语法和数据结构必须正确。
交易输入和输出均不能为空。
交易字节数的大小必须小于 MAX_BLOCK_SIZE。
每个交易输出的汇总价值必须在允许范围内(小于 2100 万比特币,大于 0)。
交易输入的哈希值不能为零,不应该转播 Coinbase 交易。
nLockTime 小于或等于 INT_MAX。
交易字节数必须大于或等于 100。
交易中签名操作的数量必须小于签名操作的限制值。
解锁脚本(scriptSig)只能将数字压入堆栈,锁定脚本(scriptPubKey)必须匹配 isStandard 格式(这将拒绝“非标准”交易)。
交易池或者主分支的区块中必须存在匹配的交易。
对于每个输入,如果引用的输出在交易池的其他交易中存在,交易必须被拒绝。
对于每个输入,需要在主分支和交易池中查找被引用的输出交易。如果任何输入对应的输出交易不存在,那么这就是个孤儿交易。如果其对应的交易不在孤儿交易池中,将其加入孤儿交易池。
对于每个输入,如果引用的输出交易是一个铸币交易的输出,必须至少经过 COINBASE_MATURITY(100)确认。
使用输出交易计算输入价值,检查每个输入价值以及汇总值,看其是否超过允许范围(小于 2100 万比特币,大于 0)。
如果输入价值汇总小于输出价值,拒绝这笔交易。
如果交易费用太小以致无法加入一个空的区块,拒绝这笔交易。
每个输入的解锁脚本必须基于相应的输出锁定脚本进行验证。
构造新块
前面收集网络上的交易并验证,加入交易池,这一过程其实是挖矿流程的预备工作,构造新块并进行难题计算才是挖矿流程的核心步骤。
构造过程
构造新块,其实你看一下区块结构,把相关字段设置好就可以了。
class CBlock : public CBlockHeader
{
public:
// network and disk
std::vector<CTransactionRef> vtx;
//...省略部分代码...
}
从交易池中按交易优先级选取部分交易加入到区块交易列表中,并开始构造区块头,最主要的是前一区块哈希和默克尔树根哈希值。这里还需要构造一笔 Coinbase 交易,并作为整个区块的第一笔交易。
class CBlockHeader
{
public:
// header
int32_t nVersion; // 版本号,指定验证规则(indicates which set of block validation rules to follow)
uint256 hashPrevBlock; // 前一区块哈希(实际计算时取得是前一区块头哈希)(a reference to the parent/previous block in the blockchain)
uint256 hashMerkleRoot; // 默克尔根哈希(a hash (root hash) of the merkle tree data structure containing a block's transactions)
uint32_t nTime; // 时戳(seconds from Unix Epoch)
uint32_t nBits; // 区块难度(aka the difficulty target for this block)
uint32_t nNonce; // 工作量证明nonce(value used in proof-of-work)
// ...部分代码省略...
}
前一区块哈希值是选取当前区块链最长的一条链条的最高区块头部做哈希,计算出前一区块哈希。默克尔根哈希值根据交易列表计算得出。下面我们看一下构造新块的源代码:
// 这个类负责构造新块(准确的说应该是候选块,因为还没有经过工作量证明)
/** Generate a new block, without valid proof-of-work */
class BlockAssembler
{
private:
// The constructed block template
std::unique_ptr<CBlockTemplate> pblocktemplaclass CBlock : public CBlockHeader
{
public:
// network and disk
std::vector<CTransactionRef> vtx;te;
// A convenience pointer that always refers to the CBlock in pblocktemplate
CBlock* pblock;
// Configuration parameters for the block size
bool fIncludeWitness;
unsigned int nBlockMaxWeight;
CFeeRate blockMinFeeRate;
// Information on the current status of the block
uint64_t nBlockWeight;
uint64_t nBlockTx;
uint64_t nBlockSigOpsCost;
CAmount nFees;
CTxMemPool::setEntries inBlock;
// Chain context for the block
int nHeight;
int64_t nLockTimeCutoff;
const CChainParams& chainparams;
public:
struct Options {
Options();
size_t nBlockMaxWeight;
CFeeRate blockMinFeeRate;
};
explicit BlockAssembler(const CChainParams& params);
BlockAssembler(const CChainParams& params, const Options& options);
/** Construct a new block template with coinbase to scriptPubKeyIn */
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); //最重要的是这个成员函数,创建新块
static Optional<int64_t> m_last_block_num_txs;
static Optional<int64_t> m_last_block_weight;
private:
// utility functions
/** Clear the block's state and prepare for assembling a new block */
void resetBlock();
/** Add a tx to the block */
void AddToBlock(CTxMemPool::txiter iter);
// Methods for how to add transactions to a block.
/** Add transactions based on feerate including unconfirmed ancestors
* Increments nPackagesSelected / nDescendantsUpdated with corresponding
* statistics from the package selection (for logging statistics). */
void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs);
// helper functions for addPackageTxs()
/** Remove confirmed (inBlock) entries from given set */
void onlyUnconfirmed(CTxMemPool::setEntries& testSet);
/** Test if a new package would "fit" in the block */
bool TestPackage(uint64_t packageSize, int64_t packageSigOpsCost) const;
/** Perform checks on each transaction in a package:
* locktime, premature-witness, serialized size (if necessary)
* These checks should always succeed, and they're here
* only as an extra check in case of suboptimal node configuration */
bool TestPackageTransactions(const CTxMemPool::setEntries& package);
/** Return true if given transaction from mapTx has already been evaluated,
* or if the transaction's cached data in mapTx is incorrect. */
bool SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs);
/** Sort the package in an order that is valid to appear in a block */
void SortForBlock(const CTxMemPool::setEntries& package, std::vector<CTxMemPool::txiter>& sortedEntries);
/** Add descendants of given transactions to mapModifiedTx with ancestor
* state updated assuming given transactions are inBlock. Returns number
* of updated descendants. */
int UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set &mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs);
};
我们接下来看一下 BlockAssembler
类最重要的成员函数 CreateNewBlock
的源码:
//参数scriptPubKeyIn表示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();
// 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(); //Coinbase交易是没有输入,只有输出的
coinbaseTx.vout.resize(1);
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)); //Coinbase交易放到交易列表的第一位,区块中的第一笔交易
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; //随机数,初始置为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);
}
可以看到,除了默克尔根哈希字段,其他主要字段都已经构造完成,而默克尔根哈希字段的构造在下面代码中会构造。
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;
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->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); // 计算默克尔根哈希
}
uint256 BlockMerkleRoot(const CBlock& block, bool* mutated)
{
// 计算交易哈希值
std::vector<uint256> leaves;
leaves.resize(block.vtx.size());
for (size_t s = 0; s < block.vtx.size(); s++) {
leaves[s] = block.vtx[s]->GetHash();
}
return ComputeMerkleRoot(std::move(leaves), mutated);
}
uint256 ComputeMerkleRoot(std::vector<uint256> hashes, bool* mutated) {
bool mutation = false;
while (hashes.size() > 1) { // 逐层向上计算,一直计算到根哈希
if (mutated) {
for (size_t pos = 0; pos + 1 < hashes.size(); pos += 2) {
if (hashes[pos] == hashes[pos + 1]) mutation = true;
}
}
if (hashes.size() & 1) { // 奇数个数交易,最后一个交易与自己配对
hashes.push_back(hashes.back());
}
SHA256D64(hashes[0].begin(), hashes[0].begin(), hashes.size() / 2);
hashes.resize(hashes.size() / 2);
}
if (mutated) *mutated = mutation;
if (hashes.size() == 0) return uint256();
return hashes[0];
}
挖矿难度
为了避免随着算力的波动造成比特币出块时间的不稳定,比特币中挖矿难度值是动态调整的,具体难度值的计算公式如下:New Difficulty = Old Difficulty * (Actual Time of Last 2016 Blocks / 20160 minutes)
。每 2016 个区块调整一次,这样比特币的出块间隔就被稳定在 10 分种左右。
难度值计算源代码如下(很啰嗦,但实际上知道上面那个公式就好了):
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
assert(pindexLast != nullptr);
unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();
// Only change once per difficulty adjustment interval
// 块高是否是2016个区块的整数倍,如果不是就不用调整难度,就沿用用当前区块链顶点区块的难度值
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);
}
// 计算下一个难度值
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();
}
挖矿奖励
矿工竞争记账权,可获得交易费及挖矿奖励,挖矿所得会在 Coinbase 的交易输出给矿工。那么挖矿奖励是怎么计算的呢?源代码如下:
//根据块高度计算奖励值,最开始是奖励50比特币,随后每210000块递减一半
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams)
{
int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;
// Force block reward to zero when right shift is undefined.
if (halvings >= 64)
return 0;
CAmount nSubsidy = 50 * COIN;
// Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years.
nSubsidy >>= halvings;
return nSubsidy;
}
这个可以在比特币区块链浏览器查到的,第 210000 个区块之前,Coinbase 挖矿奖励值(不计算交易费)都是 50 比特币,而第 210000 个区块奖励值为 25 比特币,之后依次类推。
接下来就要进行难题计算,竞争记账权了。
工作量证明
工作量证明的过程如下,就是不断改变 nNonce
值,直到成功或者收到其他节点产生的新块从而开始下一个新区块工作量证明的过程。
static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
{
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(coinbase_script)); //构造新块
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); //默克尔根哈希是在这里面计算并赋值的。
}
//不断改变nNonce值,计算难题
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);
if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
++nHeight;
blockHashes.push_back(pblock->GetHash().GetHex());
}
return blockHashes;
}
可以想象,这种 CPU 挖矿代码在 GPU 或者专用矿机面前已经没有成功挖矿可能性,而且一些比特币实现已经将挖矿这部分代码移除。这里分析这段代码只是为了对比特币工作量证明挖矿的过程有个清晰的认识,加深对比特币工作流程的理解。
另外这里要再扩展一下,最开始挖矿,难度值很低,只需要不断修改随机数
nNonce
值就可以了,现在随着算力的提高,难度越来越大,其实矿工可以修改的不只是nNonce
,矿工还可以修改nTime
时戳的值,因为只要nTime
满足要求即可,允许一定的误差(且时间不可能完全同步),还可以修改 Coinbase 交易中交易输入中的脚本来间接修改默克尔根哈希值hashMerkleRoot
,从而达到扩展随机源的目的。所以实际的挖矿程序与上面的 CPU 挖矿程序不同。且其中最耗时的操作就是哈希运算的过程,挖矿矿机一般会专门针对哈希算法做优化的。
接下来我们继续分析:
// 检查是否满足难度条件
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;
}
如果满足上面的难度条件,成功的竞争到了新块的记账权,则进入下面的流程:新块处理流程,主要内容是验证新块,验证通过就存入磁盘并通过网络广播出去,选择加入最长链。
// 新块处理流程
bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool *fNewBlock)
{
AssertLockNotHeld(cs_main);
{
CBlockIndex *pindex = nullptr;
if (fNewBlock) *fNewBlock = false;
CValidationState state;
// CheckBlock() does not support multi-threaded block validation because CBlock::fChecked can cause data race.
// Therefore, the following critical section must include the CheckBlock() call as well.
LOCK(cs_main);
// Ensure that CheckBlock() passes before calling AcceptBlock, as
// belt-and-suspenders.
bool ret = CheckBlock(*pblock, state, chainparams.GetConsensus()); //校验区块,如果校验通过就存入磁盘
if (ret) {
// Store to disk
ret = ::ChainstateActive().AcceptBlock(pblock, state, chainparams, &pindex, fForceProcessing, nullptr, fNewBlock);
}
if (!ret) {
GetMainSignals().BlockChecked(*pblock, state);
return error("%s: AcceptBlock FAILED (%s)", __func__, FormatStateMessage(state));
}
}
NotifyHeaderTip();
CValidationState state; // Only used to report errors, not invalidity - ignore it
if (!::ChainstateActive().ActivateBestChain(state, chainparams, pblock)) //选择加入最长链
return error("%s: ActivateBestChain failed (%s)", __func__, FormatStateMessage(state));
return true;
}
首先要对新区块进行验证
// 验证新区块
bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot)
{
// These are checks that are independent of context.
if (block.fChecked)
return true;
// Check that the header is valid (particularly PoW). This is mostly
// redundant with the call in AcceptBlockHeader.
if (!CheckBlockHeader(block, state, consensusParams, fCheckPOW))
return false;
// Check the merkle root.
if (fCheckMerkleRoot) {
bool mutated;
uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated); //检查默克尔根哈希是否一致
if (block.hashMerkleRoot != hashMerkleRoot2)
return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txnmrklroot", "hashMerkleRoot mismatch");
// Check for merkle tree malleability (CVE-2012-2459): repeating sequences
// of transactions in a block without affecting the merkle root of a block,
// while still invalidating it.
if (mutated)
return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txns-duplicate", "duplicate transaction");
}
// All potential-corruption validation must be done before we do any
// transaction validation, as otherwise we may mark the header as invalid
// because we receive the wrong transactions for it.
// Note that witness malleability is checked in ContextualCheckBlock, so no
// checks that use witness data may be performed here.
// Size limits 区块大小在长度限制之内
if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-length", "size limits failed");
// First transaction must be coinbase, the rest must not be 第一个交易(且只有第一个)是coinbase交易
if (block.vtx.empty() || !block.vtx[0]->IsCoinBase())
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-missing", "first tx is not coinbase");
for (unsigned int i = 1; i < block.vtx.size(); i++)
if (block.vtx[i]->IsCoinBase())
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-multiple", "more than one coinbase");
// Check transactions使用检查清单验证区块内的交易并确保它们的有效性
for (const auto& tx : block.vtx)
if (!CheckTransaction(*tx, state, true))
return state.Invalid(state.GetReason(), false, state.GetRejectCode(), state.GetRejectReason(),
strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage()));
unsigned int nSigOps = 0;
for (const auto& tx : block.vtx)
{
nSigOps += GetLegacySigOpCount(*tx);
}
if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST)
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-sigops", "out-of-bounds SigOpCount");
if (fCheckPOW && fCheckMerkleRoot)
block.fChecked = true;
return true;
}
// 验证是否满足工作量证明
static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true)
{
// Check proof of work matches claimed amount
if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams))
return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "high-hash", "proof of work failed");
return true;
}
// 验证校验交易
bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fCheckDuplicateInputs)
{
// Basic checks that don't depend on any context
if (tx.vin.empty())
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vin-empty");
if (tx.vout.empty())
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-empty");
// Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability)
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-oversize");
// Check for negative or overflow output values
CAmount nValueOut = 0;
for (const auto& txout : tx.vout)
{
if (txout.nValue < 0)
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-negative");
if (txout.nValue > MAX_MONEY)
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-toolarge");
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
}
// Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock
if (fCheckDuplicateInputs) {
std::set<COutPoint> vInOutPoints;
for (const auto& txin : tx.vin)
{
if (!vInOutPoints.insert(txin.prevout).second)
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
}
}
if (tx.IsCoinBase())
{
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-length");
}
else
{
for (const auto& txin : tx.vin)
if (txin.prevout.IsNull())
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-prevout-null");
}
return true;
}
上面新块验证通过后,将新块存入磁盘中:
/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock)
{
const CBlock& block = *pblock;
if (fNewBlock) *fNewBlock = false;
AssertLockHeld(cs_main);
CBlockIndex *pindexDummy = nullptr;
CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy;
if (!AcceptBlockHeader(block, state, chainparams, &pindex))
return false;
// Try to process all requested blocks that we don't have, but only
// process an unrequested block if it's new and has enough work to
// advance our tip, and isn't too many blocks ahead.
bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA;
bool fHasMoreOrSameWork = (m_chain.Tip() ? pindex->nChainWork >= m_chain.Tip()->nChainWork : true);
// Blocks that are too out-of-order needlessly limit the effectiveness of
// pruning, because pruning will not delete block files that contain any
// blocks which are too close in height to the tip. Apply this test
// regardless of whether pruning is enabled; it should generally be safe to
// not process unrequested blocks.
bool fTooFarAhead = (pindex->nHeight > int(m_chain.Height() + MIN_BLOCKS_TO_KEEP));
// TODO: Decouple this function from the block download logic by removing fRequested
// This requires some new chain data structure to efficiently look up if a
// block is in a chain leading to a candidate for best tip, despite not
// being such a candidate itself.
// TODO: deal better with return value and error conditions for duplicate
// and unrequested blocks.
if (fAlreadyHave) return true;
if (!fRequested) { // If we didn't ask for it:
if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned
if (!fHasMoreOrSameWork) return true; // Don't process less-work chains
if (fTooFarAhead) return true; // Block height is too high
// Protect against DoS attacks from low-work chains.
// If our tip is behind, a peer could try to send us
// low-work blocks on a fake chain that we would never
// request; don't process these.
if (pindex->nChainWork < nMinimumChainWork) return true;
}
if (!CheckBlock(block, state, chainparams.GetConsensus()) ||
!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) {
assert(IsBlockReason(state.GetReason()));
if (state.IsInvalid() && state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID;
setDirtyBlockIndex.insert(pindex);
}
return error("%s: %s", __func__, FormatStateMessage(state));
}
// Header is valid/has work, merkle tree and segwit merkle tree are good...RELAY NOW
// (but if it does not build on our best tip, let the SendMessages loop relay it)
if (!IsInitialBlockDownload() && m_chain.Tip() == pindex->pprev)
GetMainSignals().NewPoWValidBlock(pindex, pblock); // 如果是最新块,广播出去
// Write block to history file
if (fNewBlock) *fNewBlock = true;
try {
FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, chainparams, dbp);
if (blockPos.IsNull()) {
state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__));
return false;
}
ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus());
} catch (const std::runtime_error& e) {
return AbortNode(state, std::string("System error: ") + e.what());
}
FlushStateToDisk(chainparams, state, FlushStateMode::NONE);
CheckBlockIndex(chainparams.GetConsensus());
return true;
}
实际上,广播的是区块头,源码如下:
/**
* Maintain state about the best-seen block and fast-announce a compact block
* to compatible peers.
*/
void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) {
std::shared_ptr<const CBlockHeaderAndShortTxIDs> pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs> (*pblock, true);
const CNetMsgMaker msgMaker(PROTOCOL_VERSION);
LOCK(cs_main);
static int nHighestFastAnnounce = 0;
if (pindex->nHeight <= nHighestFastAnnounce)
return;
nHighestFastAnnounce = pindex->nHeight;
bool fWitnessEnabled = IsWitnessEnabled(pindex->pprev, Params().GetConsensus());
uint256 hashBlock(pblock->GetHash());
{
LOCK(cs_most_recent_block);
most_recent_block_hash = hashBlock;
most_recent_block = pblock;
most_recent_compact_block = pcmpctblock;
fWitnessesPresentInMostRecentCompactBlock = fWitnessEnabled;
}
// 遍历连接节点,发送出去
connman->ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) {
AssertLockHeld(cs_main);
// TODO: Avoid the repeated-serialization here
if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect)
return;
ProcessBlockAvailability(pnode->GetId());
CNodeState &state = *State(pnode->GetId());
// If the peer has, or we announced to them the previous block already,
// but we don't think they have this one, go ahead and announce it
if (state.fPreferHeaderAndIDs && (!fWitnessEnabled || state.fWantsCmpctWitness) &&
!PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) {
LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerLogicValidation::NewPoWValidBlock",
hashBlock.ToString(), pnode->GetId());
connman->PushMessage(pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock));
state.pindexBestHeaderSent = pindex;
}
});
}
再下面就是选择加入最长链了,这就涉及到比特币区块链分叉的问题及相关的处理逻辑,这里不再细述,感兴趣的可以自己阅读比特币源码。并且,不是所有的区块链都有分叉这个概念的,在许可链等采用 PBFT 共识算法的区块链中,每个块一旦形成共识,就是确定性的,而比特币因 Pow 共识算法的原因,新块产生后并不是确定性的,从而有了分叉这个概念。
至此,比特币挖矿的流程基本就这些了,可以看到 Pow 共识算法在公有链中是非常简单高效的,它相比传统共识算法 PBFT、Raft 等最大的特点是共识过程无需与其他节点交互,无论是网络中只有 1 个节点还是成千上万个节点(目前比特币的共识(挖矿)节点规模大概在 1 万左右),因此它对网络动态变化的适应性非常高。但代价就是需要巨大算力的支持,而传统共识算法没有这种问题。
版权声明: 本文为 InfoQ 作者【恒生LIGHT云社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/b8ad1518824f4efdde24df468】。文章转载请联系作者。
恒生LIGHT云社区
还未添加个人签名 2018.11.07 加入
还未添加个人简介
评论