Proof Of Work 工作量证明
https://www.cnblogs.com/zhang-qc/p/10451817.html
借鉴了 哈希现金(Hashcash)-1997年 英国密码学专家亚当.贝克(Adam Back)
用工作量证明系统解决了互联网垃圾邮件问题,它要求计算机在获得发送信息权限之前做一定的计算工作,这对正常的信息传播来讲,几乎很难察觉,但是对向全网大量散步垃圾信息的计算机来说,就成为了巨大的工作量和负担。
通过进行一定的运算和消耗一定的时间来计算一个符合规则的值,并提供给服务方快速做验证。
比特币中的POW共识
比特币 - 去中心化的点对点电子交易系统 :维护分布式去中心化的账本
分布式无信任条件下的账本一致 ---》共识
POW解决的是拜占庭下的共识,保证分布式账本的最终一致性,解决双花攻击;同时也建立和维护了一个分布式的时钟
PoW系统的主要特征是计算的不对称性。(SHA256)
工作端需要做一定难度的工作得出一个结果,验证方却很容易通过结果来检查工作端是不是做了相应的工作。
作弊行为的前提在于花费大量的资源,一旦某人无法成功达成恶意目标就意味着其付出了巨大的且不可挽回的沉没成本。(这也是pow的优势所在,作恶有代价)
核心技术:散列函数 SHA256
比特币节点pow大致流程:
- 生成coinbase交易,并与其他所有准备打包进区块的交易组成交易列表,通过Merkle树算法生成Merkle根哈希;
- 把Merkle根哈希及其他相关字段组装成区块头,将区块头的80字节数据作为工作量证明的输入;
- 不停地变更区块头中的随机数,即nonce的数值,并对每次变更后的区块头做双重SHA256运算,将结果值与当前网络的目标值做对比,如果小于目标值则解题成功,工作量证明完成。
比特币区块头结构
1
2
3
4
5
6
7
8
9
10
11
12
|
class
CBlockHeader
{
public
:
// header
int32_t nVersion;
uint256 hashPrevBlock;
uint256 hashMerkleRoot;
uint32_t nTime;
uint32_t nBits;
uint32_t nNonce;
//...
};<br>
// 代码位置src/primitives/block.h<br>
|
比特币区块结构:
1
2
3
4
5
6
7
8
9
|
class
CBlock :
public
CBlockHeader
{
public
:
// 交易的列表
std::vector<CTransactionRef> vtx;
//...
}
//代码位置src/primitives/block.h
|
如上区块头长度为80字节,因此执行SHA256算法,分割成 64B和16B+填充的48B两段进行运算
挖矿的过程就是寻找符合规则的 nNonce ,使如下等式成立:
1
|
SHA256(SHA256(version + prev_hash + merkle_root + ntime + nbits + nNonce + 填充 )) < TARGET
|
nNonce的范围为 0~2^32,当 nNonce 溢出仍然没有符合的值时,修改区块 coinbase 里面的 ExtraNonce
pow算法中生成coinbase交易以及创建区块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
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;<br>
//版本号
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);<br>
//时间戳
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());
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);<br>
//挖矿奖励 GetBlockSubsidy()和手续费
coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;<br>
//第一笔交易即为矿工获得奖励和手续费的特殊交易
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());
<br>
//随机数 nNonce 先重置为0
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
|
POW的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript,
int
nGenerate, uint64_t nMaxTries,
bool
keepScript)
{
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(coinbaseScript->reserveScript));
if
(!pblocktemplate.get())
throw
JSONRPCError(RPC_INTERNAL_ERROR,
"Couldn't create new block"
);
CBlock *pblock = &pblocktemplate->block;
{
LOCK(cs_main);<br>
//用于更改 coinbase交易中的 ExtraNonce
<strong>IncrementExtraNonce</strong>(pblock, chainActive.Tip(), nExtraNonce);
}
//不断变更区块头中的随机数 Nonce
//对变更后的区块头做双重SHA256哈希运算
//CheckProofOfWork 函数 与当前难度的目标值做比对,如果小于目标难度,即Pow完成
//uint64_t nMaxTries = 1000000;即重试100万次<br>
while
(nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !<strong>CheckProofOfWork(</strong>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);
//<strong>ProcessNewBlock</strong> 函数验证合法性
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 wallet
if
(keepScript)
{
coinbaseScript->KeepScript();
}
}
return
blockHashes;
}
//代码位置src/rpc/mining.cpp
|
双SHA256验证过程:
区块头长度为80字节,因此执行SHA256算法,分割成 64B和16B+填充的48B两段进行运算
挖矿的过程就是寻找符合规则的 nNonce ,使如下等式成立:
1
|
SHA256(SHA256(version + prev_hash + merkle_root + ntime + nbits + nNonce + 填充 )) < TARGET
|
源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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
;
}
//代码位置 src/pow.cpp
|
nNonce的范围为 0~2^32,当 nNonce 溢出仍然没有符合的值时,使用IncrementExtraNonce()函数修改区块 coinbase 里面的 ExtraNonce
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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;
//加 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)) + COINBASE_FLAGS;
assert
(txCoinbase.vin[0].scriptSig.size() <= 100);
//重新计算 pBlock 区块头中的 hashMerkleRoot
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
}
//代码位置 src/miner.cpp
|
难度值计算 - 源码见 GetNextWorkRequired 函数,位置 src/pow.cpp
规则大致为每创建2016个块后将计算新的难度,此后的2016个块使用新的难度。计算步骤如下:
- 找到前2016个块的第一个块,计算生成这2016个块花费的时间。即最后一个块的时间与第一个块的时间差。时间差不小于3.5天,不大于56天。
- 计算前2016个块的难度总和,即单个块的难度x总时间。
- 计算新的难度,即2016个块的难度总和/14天的秒数,得到每秒的难度值。
- 要求新的难度,难度不低于参数定义的最小难度。
POW算法被批评的点:
PoW机制造成了巨大的能源浪费;
算力集中导致的中心化问题(矿池)。