Proof Of Work 工作量证明

https://www.cnblogs.com/zhang-qc/p/10451817.html

借鉴了 哈希现金(Hashcash)-1997年 英国密码学专家亚当.贝克(Adam Back)

用工作量证明系统解决了互联网垃圾邮件问题,它要求计算机在获得发送信息权限之前做一定的计算工作,这对正常的信息传播来讲,几乎很难察觉,但是对向全网大量散步垃圾信息的计算机来说,就成为了巨大的工作量和负担。

通过进行一定的运算和消耗一定的时间来计算一个符合规则的值,并提供给服务方快速做验证。

比特币中的POW共识

比特币 - 去中心化的点对点电子交易系统 :维护分布式去中心化的账本

分布式无信任条件下的账本一致 ---》共识

POW解决的是拜占庭下的共识,保证分布式账本的最终一致性,解决双花攻击;同时也建立和维护了一个分布式的时钟

PoW系统的主要特征是计算的不对称性。(SHA256)

工作端需要做一定难度的工作得出一个结果,验证方却很容易通过结果来检查工作端是不是做了相应的工作。

作弊行为的前提在于花费大量的资源,一旦某人无法成功达成恶意目标就意味着其付出了巨大的且不可挽回的沉没成本。(这也是pow的优势所在,作恶有代价)

核心技术:散列函数 SHA256

比特币节点pow大致流程:

  1. 生成coinbase交易,并与其他所有准备打包进区块的交易组成交易列表,通过Merkle树算法生成Merkle根哈希;
  2. 把Merkle根哈希及其他相关字段组装成区块头,将区块头的80字节数据作为工作量证明的输入;
  3. 不停地变更区块头中的随机数,即nonce的数值,并对每次变更后的区块头做双重SHA256运算,将结果值与当前网络的目标值做对比,如果小于目标值则解题成功,工作量证明完成。
----------------------------------------------------------
 
Bitcoin Core 0.17.1 挖矿部分源码的解析:

比特币区块头结构

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, falsefalse)) {
        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, truenullptr))
            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个块使用新的难度。计算步骤如下: 

  1. 找到前2016个块的第一个块,计算生成这2016个块花费的时间。即最后一个块的时间与第一个块的时间差。时间差不小于3.5天,不大于56天。
  2. 计算前2016个块的难度总和,即单个块的难度x总时间。
  3. 计算新的难度,即2016个块的难度总和/14天的秒数,得到每秒的难度值。
  4. 要求新的难度,难度不低于参数定义的最小难度。

 

POW算法被批评的点

PoW机制造成了巨大的能源浪费;

算力集中导致的中心化问题(矿池)。

[转帖]Proof Of Work 工作量证明的更多相关文章

  1. 区块链共识机制之工作量证明(POW)

    像比特币.以太坊.NXT.Bitshares等这些区块链系统,其本质上是一种加密经济组织,它建立在点对点网络上,是去中心化.无管辖的,由密码学.经济学和社会共识来共同维护.这些加密网络因各种原因有着多 ...

  2. 用python阐释工作量证明(proof of work)

    了解比特币的都知道挖矿非常耗电,这是由于比特币用到了工作量证明. 工作量证明是指系统为达到某目标而设置的工作度量方法.一開始是用在网络攻防上,大大提高攻击者的计算量,攻击成本也就上去了. 工作量证明须 ...

  3. cpp 区块链模拟示例(四) 区块链工作量证明

    本文主要在之前的区块链原形上添加了工作量证明,并且为后继的交易功能做好准备. 上一个章节我们已经创建了区块链的基本原形,但是区块的哈希计算和加入太过于简单,如果按照这种速度添加区块那么区块链估计一个小 ...

  4. Go实现Pow工作量证明

    之前使用python编写了一段代码实现了工作量证明机制,近期由于参与以太坊智能合约开发钱包的工作接触到golang语言,所以借此以go来实现Pow(Proof of work). 实现代码如下: // ...

  5. 详解POW工作量证明原理

    原文地址  来自 微信公众号 区块链大师 POW工作量证明(英文全称为Proof of Work)早在比特币出现之前就已经有人探索,常见的是利用HASH运算的复杂度进行CPU运算实现工作量确定,当然你 ...

  6. 【区块链Go语言实现】Part 2:工作量证明机制POW

    0x00 介绍 在上一篇文章中,我们建立了一个非常简单的数据结构,它是区块链数据库的本质.并且,我们实现了以类似链条关系的方式向其中添加区块的功能:每个区块都会链接到前一区块.然而,我们实现的区块链有 ...

  7. [GO]使用go语言实现比特币的工作量证明

    之前的博文已经实现了区块连的基本的工作原理,但在比特币系统中有一个很重要的概念:工作量证明POW,在比特币系统中它的作用就是在十分钟左右的时间内只有一个有能够记帐并得到奖励 在之前的博文中,区块的哈希 ...

  8. [转]工作量证明(PoW)权益证明(PoS)和委任权益证明(DPoS)区别

    原文链接 Both in the glossary and in some of our previous posts we've touched on mining and the two main ...

  9. Python实现一条基于POS算法的区块链

    区块链中的共识算法 在比特币公链架构解析中,就曾提到过为了实现去中介化的设计,比特币设计了一套共识协议,并通过此协议来保证系统的稳定性和防攻击性. 并且我们知道,截止目前使用最广泛,也是最被大家接受的 ...

随机推荐

  1. Passwords Gym - 101174E (AC自动机上DP)

    Problem E: Passwords \[ Time Limit: 1 s \quad Memory Limit: 256 MiB \] 题意 给出两个正整数\(A,B\),再给出\(n\)个字符 ...

  2. Scrapy爬虫案例 | 数据存储至MySQL

    首先,MySQL创建好数据库和表 然后编写各个模块 item.py import scrapy class JianliItem(scrapy.Item): name = scrapy.Field() ...

  3. minikube 安装试用

    目前使用k8s 要么用的物理机搭建的环境,要么就是使用docker for mac 中kubernetes 的特性,为了本地调试方便,使用下minikube minukube 包含的特性 负载均衡器 ...

  4. SpringBoot要点之使用Actuator监控

    Actuator是Springboot提供的用来对应用系统进行自省和监控的功能模块,借助于Actuator开发者可以很方便地对应用系统某些监控指标进行查看.统计等. 在pom文件中加入spring-b ...

  5. NOI2019 Day1游记

    Day1挂了,没什么好说的... 开场T1想到70分暴力就走人了,后来发现可以写到85...(听说有人写dfs过了95?233333) T2刚了2个多小时,得到每次只在中间填最大值的结论后不会区间DP ...

  6. Docker原理及使用

    虚拟化系统: 1. Type-I: 此种虚拟化是Hypervisor直接运行在硬件之上,来创建虚拟机. 2. Type-II: 这种虚拟化类似与VMware Workstations. IPC: 在相 ...

  7. js限制输入数字能输入小数点,js定义数组,js往数组中添加数据,js将字符型转为数字型,除法结果保留两位小数——js小测:计算比赛得分

    一个朋友跟我说要去给某个比赛算分: 规则:去掉最低分最高分求平均分: 最近在学习大数据可视化——图谱,用到js一些东西,所以今天就用js练练 用到知识点: js限制输入数字能输入小数点,js定义数组, ...

  8. SpringCloud:搭建基于Gateway的微服务网关(二)

    0.代码 https://github.com/fengdaizang/OpenAPI 1.引入相关依赖 pom文件如下: <?xml version="1.0" encod ...

  9. Asp.net 与 Core .net 用法区别

    1.  定义一个类 如下,注意int?这里 public class A{ public int? num{get;set;} } 2. 如果传递的参数不能转换成int类型,则core里面接受不了参数 ...

  10. unity序列化

    什么是序列化 unity的序列化在unity的开发中起着举重足轻的地位,许多核心的功能都是基于序列化和反序列化来实现的.序列化简单来讲就是就是将我们所要保存的数据进行二进制存储,然后当我们需要的时候, ...