关于比特币挖矿部分的原理,参见《精通比特币》第八章。
本文源代码来自最原始版本的比特币源代码original-bitcoin。
挖矿
挖矿部分源代码位于main.cpp文件下,函数如下,我们将逐步分析。
bool BitcoinMiner()
{
printf("BitcoinMiner started\n");
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
CKey key;
key.MakeNewKey();
CBigNum bnExtraNonce = 0;
while (fGenerateBitcoins)
{
Sleep(50);
CheckForShutdown(3);
while (vNodes.empty())
{
Sleep(1000);
CheckForShutdown(3);
}
unsigned int nTransactionsUpdatedLast = nTransactionsUpdated;
CBlockIndex* pindexPrev = pindexBest;
unsigned int nBits = GetNextWorkRequired(pindexPrev);
//
// Create coinbase tx
//
CTransaction txNew;
txNew.vin.resize(1);
txNew.vin[0].prevout.SetNull();
txNew.vin[0].scriptSig << nBits << ++bnExtraNonce;
txNew.vout.resize(1);
txNew.vout[0].scriptPubKey << key.GetPubKey() << OP_CHECKSIG;
//
// Create new block
//
auto_ptr pblock(new CBlock());
if (!pblock.get())
return false;
// Add our coinbase tx as first transaction
pblock->vtx.push_back(txNew);
// Collect the latest transactions into the block
int64 nFees = 0;
CRITICAL_BLOCK(cs_main)
CRITICAL_BLOCK(cs_mapTransactions)
{
CTxDB txdb("r");
map mapTestPool;
vector vfAlreadyAdded(mapTransactions.size());
bool fFoundSomething = true;
unsigned int nBlockSize = 0;
while (fFoundSomething && nBlockSize < MAX_SIZE/2)
{
fFoundSomething = false;
unsigned int n = 0;
for (map::iterator mi = mapTransactions.begin(); mi != mapTransactions.end(); ++mi, ++n)
{
if (vfAlreadyAdded[n])
continue;
CTransaction& tx = (*mi).second;
if (tx.IsCoinBase() || !tx.IsFinal())
continue;
// Transaction fee requirements, mainly only needed for flood control
// Under 10K (about 80 inputs) is free for first 100 transactions
// Base rate is 0.01 per KB
// 交易手续费,前100个满足条件的交易不需要手续费
int64 nMinFee = tx.GetMinFee(pblock->vtx.size() < 100);
map mapTestPoolTmp(mapTestPool);
if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), 0, nFees, false, true, nMinFee))
continue;
swap(mapTestPool, mapTestPoolTmp);
pblock->vtx.push_back(tx);
nBlockSize += ::GetSerializeSize(tx, SER_NETWORK);
vfAlreadyAdded[n] = true;
fFoundSomething = true;
}
}
}
pblock->nBits = nBits;
// CoinBase交易产生的数额
pblock->vtx[0].vout[0].nValue = pblock->GetBlockValue(nFees);
printf("\n\nRunning BitcoinMiner with %d transactions in block\n", pblock->vtx.size());
//
// Prebuild hash buffer
//
struct unnamed1
{
struct unnamed2
{
int nVersion;
uint256 hashPrevBlock;
uint256 hashMerkleRoot;
unsigned int nTime;
unsigned int nBits;
unsigned int nNonce;
}
block;
unsigned char pchPadding0[64];
uint256 hash1;
unsigned char pchPadding1[64];
}
tmp;
tmp.block.nVersion = pblock->nVersion;
tmp.block.hashPrevBlock = pblock->hashPrevBlock = (pindexPrev ? pindexPrev->GetBlockHash() : 0);
tmp.block.hashMerkleRoot = pblock->hashMerkleRoot = pblock->BuildMerkleTree();
tmp.block.nTime = pblock->nTime = max((pindexPrev ? pindexPrev->GetMedianTimePast()+1 : 0), GetAdjustedTime());
tmp.block.nBits = pblock->nBits = nBits;
tmp.block.nNonce = pblock->nNonce = 1;
unsigned int nBlocks0 = FormatHashBlocks(&tmp.block, sizeof(tmp.block));
unsigned int nBlocks1 = FormatHashBlocks(&tmp.hash1, sizeof(tmp.hash1));
//
// Search
//
unsigned int nStart = GetTime();
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
uint256 hash;
loop
{
BlockSHA256(&tmp.block, nBlocks0, &tmp.hash1);
BlockSHA256(&tmp.hash1, nBlocks1, &hash);
if (hash <= hashTarget)
{
pblock->nNonce = tmp.block.nNonce;
assert(hash == pblock->GetHash());
//// debug print
printf("BitcoinMiner:\n");
printf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
pblock->print();
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
CRITICAL_BLOCK(cs_main)
{
// Save key
if (!AddKey(key))
return false;
key.MakeNewKey();
// Process this block the same as if we had received it from another node
if (!ProcessBlock(NULL, pblock.release()))
printf("ERROR in BitcoinMiner, ProcessBlock, block not accepted\n");
}
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
Sleep(500);
break;
}
// Update nTime every few seconds
if ((++tmp.block.nNonce & 0x3ffff) == 0)
{
CheckForShutdown(3);
if (tmp.block.nNonce == 0)
break;
if (pindexPrev != pindexBest)
break;
if (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)
break;
if (!fGenerateBitcoins)
break;
tmp.block.nTime = pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
}
}
}
return true;
}
开始部分是一些线程以及初始化的操作。GetNextWorkRequired用于获取当前挖矿的难度值,这个函数在前面的文章已经详细分析过了。随后便是创建一笔coinbase交易:
//
// Create coinbase tx
//
CTransaction txNew;
txNew.vin.resize(1);
txNew.vin[0].prevout.SetNull();
txNew.vin[0].scriptSig << nBits << ++bnExtraNonce;
txNew.vout.resize(1);
txNew.vout[0].scriptPubKey << key.GetPubKey() << OP_CHECKSIG;
coinbase交易不引用UTXO,其输出为指向矿工的地址。关于交易的数据结构可以参考https://blog.csdn.net/pure_lady/article/details/77771392
然后开始创建一个新区块,并将coinbase交易作为第一笔交易。
//
// Create new block
//
auto_ptr pblock(new CBlock());
if (!pblock.get())
return false;
// Add our coinbase tx as first transaction
pblock->vtx.push_back(txNew);
创建完成后便会将 收到的符合条件的交易依次加入到区块中。
while (fFoundSomething && nBlockSize < MAX_SIZE/2)
{
fFoundSomething = false;
unsigned int n = 0;
for (map::iterator mi = mapTransactions.begin(); mi != mapTransactions.end(); ++mi, ++n)
{
if (vfAlreadyAdded[n])
continue;
CTransaction& tx = (*mi).second;
if (tx.IsCoinBase() || !tx.IsFinal())
continue;
// Transaction fee requirements, mainly only needed for flood control
// Under 10K (about 80 inputs) is free for first 100 transactions
// Base rate is 0.01 per KB
// 交易手续费,前100个满足条件的交易不需要手续费
int64 nMinFee = tx.GetMinFee(pblock->vtx.size() < 100);
map mapTestPoolTmp(mapTestPool);
if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), 0, nFees, false, true, nMinFee))
continue;
swap(mapTestPool, mapTestPoolTmp);
pblock->vtx.push_back(tx);
nBlockSize += ::GetSerializeSize(tx, SER_NETWORK);
vfAlreadyAdded[n] = true;
fFoundSomething = true;
}
}
对于mapTransactions中的每个交易,首先检查是否为coinbase交易,if (tx.IsCoinBase() || !tx.IsFinal())
,然后计算交易手续费nMinFee,其函数为GetMinFee(),具体计算代码如下:
int64 GetMinFee(bool fDiscount=false) const
{
unsigned int nBytes = ::GetSerializeSize(*this, SER_NETWORK);
if (fDiscount && nBytes < 10000)
return 0;
return (1 + (int64)nBytes / 1000) * CENT;
}
可以看到前100个且nBytes<10000的交易其手续费为0,对于除此之外的交易,其手续费为按照交易的长度计算,可以看到,比特币的手续费与交易的金额无关而与交易的字节长度有关。
随着时间的过去,交易费的计算方式和交易费在交易优先级上的影响一直在发展。起初,交易费是网络中的一个固定常数。渐渐地,交易费的结构被放宽了,以便被市场基于网络容量和交易量而强制影响。目前最小交易费被固定在每千字节0.0001比特币,或者说是每千字节万分之一比特币,最近一次改变是从千分之一比特币减少到这个数值的。大多数交易少于一千字节,但是那些包含多个输入和输出的交易尺寸可能更大。在未来的比特币协议修订版中,钱包应用预计会使用统计学分析,基于最近的几笔交易的平均费用,来计算最恰当的费用并附在交易上。
之后会对交易进行一系列检查,出现问题则会打印相应提示,通过则会改变交易在内存和硬盘中的状态,随后将交易加入区块中,然后计算序列化后区块长度。保证区块大小符合要求。在打包完所有符合要求的交易后,程序进入下一阶段。
pblock->nBits = nBits;
// CoinBase交易产生的数额
pblock->vtx[0].vout[0].nValue = pblock->GetBlockValue(nFees);
printf("\n\nRunning BitcoinMiner with %d transactions in block\n", pblock->vtx.size());
对于coinbase交易加交易费,即本次挖矿能够获取的比特币数量,计算公式如下:
int64 CBlock::GetBlockValue(int64 nFees) const
{
int64 nSubsidy = 50 * COIN;
// Subsidy is cut in half every 4 years
nSubsidy >>= (nBestHeight / 210000);
return nSubsidy + nFees;
}
最开始时每个区块可以获得50个比特币,之后奖励每210000个区块后将会减半,(根据挖矿难度调整,210000个区块大约需要4年)。最后矿工的收益为coinbase加上交易费的和。
然后是一个结构体:
struct unnamed1
{
struct unnamed2
{
int nVersion;
uint256 hashPrevBlock;
uint256 hashMerkleRoot;
unsigned int nTime;
unsigned int nBits;
unsigned int nNonce;
}
block;
unsigned char pchPadding0[64];
uint256 hash1;
unsigned char pchPadding1[64];
}
tmp;
对应的block为区块头的结构。然后会对区块头进行相应的赋值操作,包括版本号,前区块哈希,默克尔根,时间戳,难度目标,nonce等。
赋值完成后便开始寻找满足条件的nonce解,
//
// Search
//
unsigned int nStart = GetTime();
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
uint256 hash;
loop
{
BlockSHA256(&tmp.block, nBlocks0, &tmp.hash1);
BlockSHA256(&tmp.hash1, nBlocks1, &hash);
if (hash <= hashTarget)
{
pblock->nNonce = tmp.block.nNonce;
assert(hash == pblock->GetHash());
//// debug print
printf("BitcoinMiner:\n");
printf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
pblock->print();
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
CRITICAL_BLOCK(cs_main)
{
// Save key
if (!AddKey(key))
return false;
key.MakeNewKey();
// Process this block the same as if we had received it from another node
if (!ProcessBlock(NULL, pblock.release()))
printf("ERROR in BitcoinMiner, ProcessBlock, block not accepted\n");
}
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
Sleep(500);
break;
}
计算目标便是找到一个解使得区块头哈希小于hashTarget ,其值可由难度位计算得来:
CBigNum& SetCompact(unsigned int nCompact)
{
unsigned int nSize = nCompact >> 24;
std::vector vch(4 + nSize);
vch[3] = nSize;
if (nSize >= 1) vch[4] = (nCompact >> 16) & 0xff;
if (nSize >= 2) vch[5] = (nCompact >> 8) & 0xff;
if (nSize >= 3) vch[6] = (nCompact >> 0) & 0xff;
BN_mpi2bn(&vch[0], vch.size(), this);
return *this;
}
每次将nonce的值增加1,并计算头部哈希值,与目标值进行比较,如果没有达到小于目标值,则继续计算,同时定期更新nTime。直到找到一个满足条件的解nonce,此时表明挖矿成功,打印相关信息并保存秘钥。