第二章 工作量证明和挖矿

  • 概览
  • 工作量证明拼图和难易度
  • 挖矿
  • 难易度共识
  • 时间戳校验
  • 累积难易度
  • 验证测试
  • 小结

概览

本章节我们将会在我们的玩具版区块链的基础上加入工作量证明(POW)的支持。在第一章节的版本中, 任何人都都可以在没有任何工作量证明的情况下添加一个区块到区块链中。 当我们引入工作量证明机制之后,一个节点必须要解开一个有相当计算量的拼图(POW Puzzle)之后,才能往区块链上添加一个新的区块。而去解开该拼图,通常就被称为挖矿。

引入工作量证明机制之后,我们还可以对一个新区块的产出时间作出大致的控制。大概的做法就是动态的改变拼图的难易程度来达到控制的效果:如果最近的区块产生的太快了,那么就将拼图的难度提升,反之,则将拼图的难度降低。

需要点出来的时,本章节中我们还没有引入 交易(Transaction) 这个概念。这就意味着矿工挖出一个区块后,并不会获得相应的奖励。 一般来说,在加密货币中,如果矿工挖到了一个区块,是应该获得一定量的币作为激励的。

工作量证明拼图和难易度

在上一个章节的区块链的基础上,我们将会为区块结构加入两个新的属性:difficulty和nonce。要弄清楚这俩货是干嘛用的,我们必须先对工作量证明拼图作一些必要的阐述。

工作量证明拼图是一个什么样的任务呢?其实就是去计算出一个满足条件的区块哈希。怎么才算满足条件呢?如果这个计算出来的哈希的前面的0的数目满足指定的个数,那么就算满足条件。那么这个个数又是谁指定的呢?对,就是上面的这个difficulty指定的。如果difficulty指定说哈希前面要有4个0,但你计算出来的哈希前面只有3个0,那么这个哈希就不是个有效的哈希。

下图展示的就是不同难易程度的情况下,哈希是否有效:

以下代码用来检查指定difficulty下哈希是否有效:

const hashMatchesDifficulty = (hash: string, difficulty: number): boolean => {
    const hashInBinary: string = hexToBinary(hash);
    const requiredPrefix: string = '0'.repeat(difficulty);
    return hashInBinary.startsWith(requiredPrefix);
};

区块的哈希是通过对区块的内容算sha256来获得的,通过相同的区块内容做哈希,我们是无法算出符合指定difficulty的哈希, 因为内容一直没有变,哈希也就一直不变, 这就是为什么我们引入了nonce这个属性到区块中,我们可以控制nonce的改变来获得不同的哈希值。只要我们的内容有任何一点点的改变,算出来的哈希就肯定是不一样的。一旦相应的nonce修改后让我们获得到指定difficulty的哈希,我们挖矿也就成功了!

既然加入了difficulty和nonce到区块,我们还是先看看区块结构现在长什么样吧:

class Block {

    public index: number;
    public hash: string;
    public previousHash: string;
    public timestamp: number;
    public data: string;
    public difficulty: number;
    public nonce: number;

    constructor(index: number, hash: string, previousHash: string,
                timestamp: number, data: string, difficulty: number, nonce: number) {
        this.index = index;
        this.previousHash = previousHash;
        this.timestamp = timestamp;
        this.data = data;
        this.hash = hash;
        this.difficulty = difficulty;
        this.nonce = nonce;
    }
}

当然,我们的创世区块是硬编码的,记得把它也给更新成相应的结构哦。

挖矿

如上所述, 为了解开我们的工作量证明拼图这个任务,我们需要不停的修改区块中的nonce然后计算修改后的哈希,直到算出满足条件的哈希。这个满足条件的哈希什么时候才会跑出来则完全是个随机的事情。所以我们要做的就是给nonce一个初始值,然后不停的循环,修改,计算哈希,直到满足条件的心仪的它的出现。

const findBlock = (index: number, previousHash: string, timestamp: number, data: string, difficulty: number): Block => {
    let nonce = 0;
    while (true) {
        const hash: string = calculateHash(index, previousHash, timestamp, data, difficulty, nonce);
        if (hashMatchesDifficulty(hash, difficulty)) {
            return new Block(index, hash, previousHash, timestamp, data, difficulty, nonce);
        }
        nonce++;
    }
};

一旦满足条件的区块的哈希给找到了,挖矿成功!然后我们就需要将该区块广播到网络上,让其他节点接受我们的区块,并更新最新的账本。这个和第一章节的情况并无二致。

难易度共识

我们现在已经拥有找出和验证满足指定难易度的区块的哈希的手段了,但是,这个难易程度是如何决定的呢?必须要有一个方法让全网各个节点一致认同这个决定。不然我的难易度是要挖半天才能出来,别人的是几毫秒就出来,那这个区块链网络就有问题。

所以,这里必须要有一个方法来让所有的节点都一致认同当前挖矿的难易度。为了做到这一点,我们首先引入一些用于计算当前难易度的规则。

先定义以下的一些常量:

  • BLOCK_GENERATION_INTERVAL: 定义 一个区块产出的 频率(比特币中该值是设置成10分钟的, 即每10分钟产出一个区块)
  • DIFFICULTY_ADJUSTMENT_INTERVAL: 定义 修改难易度以适应网络不断增加或者降低的网络算力(hashRate, 即每秒能计算的哈希的数量)的 频率(比特币中是设置成2016个区块, 即每2016个区块,然后根据这些区块生成的耗时,做一次难易度调整)

这里我们将会把区块产出间隔设置成10秒,难易度调整间隔设置成10个区块。这些常量是不会随着时间而改变的,所以我们将其硬编码如下:

// in seconds
const BLOCK_GENERATION_INTERVAL: number = 10;

// in blocks
const DIFFICULTY_ADJUSTMENT_INTERVAL: number = 10;

有了这些规则,我们就可以在我们的网络上达成难易度的一致性了。每产出10个区块之后,我们就去检查生成所有这10个区块所消耗的时间,然后和预期时间进行对比,然后对难易度进行动态的调整。

这里的预期时间是这样指定的:BLOCK_GENERATION_INTERVAL * DIFFICULTY_ADJUSTMENT_INTERVAL。 该预期时间代表了当前网络的算力刚刚好和当前的难易度吻合。

如果耗时超过或者不足预期时间的两倍,那么我们就会将difficulty进行对应的减1或者加1。该算法如下:

const getDifficulty = (aBlockchain: Block[]): number => {
    const latestBlock: Block = aBlockchain[blockchain.length - 1];
    if (latestBlock.index % DIFFICULTY_ADJUSTMENT_INTERVAL === 0 && latestBlock.index !== 0) {
        return getAdjustedDifficulty(latestBlock, aBlockchain);
    } else {
        return latestBlock.difficulty;
    }
};

const getAdjustedDifficulty = (latestBlock: Block, aBlockchain: Block[]) => {
    const prevAdjustmentBlock: Block = aBlockchain[blockchain.length - DIFFICULTY_ADJUSTMENT_INTERVAL];
    const timeExpected: number = BLOCK_GENERATION_INTERVAL * DIFFICULTY_ADJUSTMENT_INTERVAL;
    const timeTaken: number = latestBlock.timestamp - prevAdjustmentBlock.timestamp;
    if (timeTaken < timeExpected / 2) {
        return prevAdjustmentBlock.difficulty + 1;
    } else if (timeTaken > timeExpected * 2) {
        return prevAdjustmentBlock.difficulty - 1;
    } else {
        return prevAdjustmentBlock.difficulty;
    }
};

时间戳校验

第一章节中的区块链版本中,区块结构中的时间戳属性是没有任何意义的,因为我们不会对其作任何校验的工作,也就是说,我们其实可以给该时间戳赋以任何内容。 但现在情况变了,因为我们这里引入了难易度的动态调整,而该调整是基于前10个区块产出的耗时总长度的。所以时间戳我们就不能像之前一样随意赋值了。

既然时间戳的大小会影响区块产出难易度的调整,所以别有用心的人就会考虑去恶意设置一个错误的时间戳来尝试操纵我们的难易度,以实现对我们的区块链网络进行攻击。为了规避这种风险,我们需要引入以下的规则:

  • 新区块时间戳比当前时间最晚不晚过1分钟,那么我们认为该区块有效。
  • 新区块时间戳比前一区块的时间戳最早不早过1分钟,那么我们任务该区块在区块链中是有效的。
const isValidTimestamp = (newBlock: Block, previousBlock: Block): boolean => {
    return ( newBlock.timestamp - 60 < getCurrentTimestamp() )
        && ( previousBlock.timestamp - 60 < newBlock.timestamp );
};

天地会珠海分舵注:这里为什么要有一分钟的缓冲呢?估计是为了既考虑一定程度的容错,也减缓了时间戳恶意修改的攻击。如果还有其他原因的,请不吝指教。

累积难易度

还记得第一章节的区块链的版本中,我们定的规则是:一旦发生冲突,我们总是选择最长的区块链作为有效的链进行更新,以示对更多区块生产者的努力的肯定。因为我们现在引入了挖矿的难易度,所以我们不应该再这样子做了,我们更应该对投入资源更多计算而出的那条链进行肯定。也就是说,现在正确的链,不是最长的那条链,而是累积难易度最大的那条链。什么意思呢?换个说法就是,正确的链,应该是那条消耗了最多计算资源(网络算力*耗时)而产生出来的链。

那么怎么算出一条链的累积难易度呢?我们首先对区块链中每个区块的difficulty进行2^difficulty计算,然后将每个区块的计算结果加起来,最终就是这条链的累积难易度。

这里为什么我们用2^difficulty来计算呢?因为difficulty代表了哈希的二进制表示中前面的0的位数。试想下,一个difficulty是11的块和一个difficulty是5的块相比,总的来说,我们需要多计算2^(11-5) = 2^6 次哈希才能获得想要的结果。因为每一位在二进制中都有0和1两种变化,他们之间相差6个位,固有2^6个变化。

下图中,虽然链A比链B长,但因为链B的累积难易度比链A大,所以链B才是正确的链.

同时我们还要注意的是,我们累积难易度只和区块的difficulty属性有关系,和区块的真实哈希即其前面的位数是没有任何关系的。 拿一个difficulty为4的区块来说,假如它的哈希是000000a34c...(该哈希同时也满足difficulty为5和6的情况),我们计算累积难易度是还是会以2^4来算,而不是2^5或者2^6, 即使它前面有6个0.

这种根据难易度选择正确的链的策略也叫做中本聪共识(Nakamoto consensus), 这也是中本聪的比特币中最重要的一个发明之一。一旦账本出现分叉,矿工们就必须选择一条链来投入资源继续进行挖矿,因为这个关系到矿工挖矿后的激励,所以选择正确的链必须全网达成共识。

验证测试

  • 安装运行
npm install
npm run node1
npm run node2
  • 挖矿

我们可以尝试通过curl或者postman调用/mineBlock这个api接口来进行挖矿,并查看对应的输出.

  • 难易度调整

在通过不同的时间间隔调用完10次/mineBlock这个接口之后,难易度就会相应的进行变化

  • 选择最大累积难易度的链

这个真实情况不好模拟出来。 但是我们可以去验证对应的算法。我们可以先开一个节点,创建3个以上的区块之后,再开第二个节点。这时节点2就会去获取节点1的所有区块,然后运行对应的逻辑去选择最大累积难易度的链。

小结

工作量证明拼图的一个重要特点就是难以解开但易于验证。所以不断调整nonce以算出一个满足一定难度的SHA256哈希,然后简单的验证前面几位是否是0,往往就是这种问题最简单的解决方案。

有了工作量证明机制后,节点现在就需要挖矿,也就是解决工作量证明拼图,才能往区块链中新增加一个区块了。在下一章节中,我们将会为我们的区块链引入交易(Transaction)功能。

本章节代码请查看这里

第三章

本文由天地会珠海分舵编译,转载需授权,喜欢点个赞,吐槽请评论,如能给Github上的项目给个星,将不胜感激.

原文地址:https://www.cnblogs.com/techgogogo/p/11072545.html

时间: 2024-10-09 20:12:47

第二章 工作量证明和挖矿的相关文章

比特币如何挖矿(挖矿原理)-工作量证明

在区块链记账原理 一篇,我们了解到记账是把交易记录.交易时间.账本序号.上一个Hash值等信息计算Hash打包的过程.我们知道所有的计算和存贮是需要消耗计算机资源的,既然要付出成本,那节点为什么还要参与记账呢?在中本聪(比特币之父)的设计里,完成记账的节点可以获得系统给与的一定数量的比特币奖励,这个奖励的过程也就是比特币的发行过程,因此大家形象的把记账称为"挖矿",本文将详细讨论这个过程. 记账工作 由于记账是有奖励的,每次记账都可以给自己凭空增加一定数量的个比特币(当前是12.5比特

浅谈无需工作量证明的加密货币

浅谈无需工作量证明的加密货币 Iddo Bentov1,Ariel Gabizon2,Alex Mizrahi (Computer Science Dept., Technion; chromawallet.com) 译者:shylocks ([email protected]) 摘要:本文研究了那些并没有使用 PoW(工作量证明)协议的加密货币.这些协议通常采用 PoS(权益证明)协议,也就是采用了一种使担任验证工作的人获得在系统中相关权限的协议.我们对拥有较多矿工的系统进行了分析.最后,提出

《大道至简》第二章阅读笔记

<大道至简>这本书在第二章中的主要内容是“懒人创造方法”!因为一个勤勤恳恳.老实工作的人是不太可能会懂得创新的,因为他只知道认真仔细的工作,一点一滴.一丝不苟.按部就班的按照上司交给他的内容,因为他认真负责,不容许自己出现一点纰漏.而懒人则不一样了,因为工作量庞大,所以他们自己因为懒惰而各种寻找方法,从而减轻自己的工作量,动脑筋让自己的实际工作量减到最小,而这时就需要开动脑筋,让自己想出一个可行的办法,从而实现自己的目的. 在这本书的第二章开头,还是延续了这本书的惯例,用一个寓言小故事来引入本

大道至简第二章—懒人的方法

大道至简第二章—懒人的方法 僰道有故蜀王兵阑亦有神,作大滩江中.其崖崭峻,不可凿:乃积薪烧之.故其处悬崖有赤白五色. ----华阳国志卷三-蜀志 在第一章中作者引用愚公移山的典故向我们介绍了编程的精义,以愚公为例向我们介绍了个编程人员应具备的素质.而在第二章,作者通过蜀郡太守李冰烧石破山建造都江堰的故事告诉我们我们只是勤奋是不够的.如果李冰像愚公那样日复一日的敲石碎山,就不会有空闲时间去观察,去思考了.那也不会有“积薪烧之”的事情了.所以李冰乃是闲人一枚. 人的精力是有限的.愚公而愚公可以多吃点

大道至简(周爱民)第二章-----读后感

今天把周爱民大道至简的第二章关于是懒人造就了方法读了几遍,作者通过战国时李冰凿山与愚公移山的比较来阐述懒人早就方法主题,以前听历史老师讲课的时候正是因为懒人才会有那么多可以节省人们力气和时间的发明,但懒人并不是真的懒,只是把更多的时间用到了思考上面与观察生活细节上面,正如文中作者所说愚公太勤快了,勤快的今天可以比昨天凿出一倍的石头,以致没有了机会去寻找更快的方法,人的精力终归是有极限的.提出新的方法,解决的将是做事成效的根本问题.而愚公可以多吃点饭,多加点班,但却突破不了人的精力极限.   文中

读大道至简第二章有感

大道至简第二章的题目是"是懒人造就了方法"而开头也写到李冰的开山并与愚公移山做了比较,无非就是想用具体的例子来证明懒人造就方法的观点.而其深层的含义便是要学会观察,学会思考,同样是一件事,很多人都能做,有人用的时间长资源多,而有的人则截然相反,这就说明了观察思考的重要性,而相对于编程来说,一个简单的比较大小的问题,有的人比较五次,而有的人只比较四次就能运行出结果,这就是程序的优化,也是思考的结果 一百万行代码是可以写在一个文件里的.这反映了一个很常见的问题,很多初学者比如我们总是在关注

大道至简(第二章)读后感

在读过<大道至简>第一章后给了我学好编程的信心,明白了编程并不是学不会只是自己的态度不对.之后我又开始阅读第二章,第二章的题目一下子吸引了我,因为我正是懒人一枚,看到题目后感觉好亲切(窃喜,懒人还是蛮有价值的嘛). 如同第一章一样,作者还是以典故开篇,引人入胜,开篇为<华阳志>里面的一段话,“樊道有蜀王兵阑,亦有神作大江滩中.其崖斩竣不可破,(冰)乃积薪烧之.”作者通过典故说服了我,原来真的是懒人创造了方法,如果李冰如同愚公一般勤劳,他肯定是不会想到积薪烧之的.我也通过这个故事看到

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

了解比特币的都知道挖矿很耗电,这是因为比特币用到了工作量证明. 工作量证明是指系统为达到某目标而设置的工作度量方法.一开始是用在网络攻防上,大大提高攻击者的计算量,攻击成本也就上去了. 工作量证明需要由工作者和验证者两方共同完成.它有两层含义. 1.工作者需要完成的工作必须有一定的量,这个量由验证者给出. 2.验证者可以迅速的检验工作量是否达标,注意这里的检验完成过程必须简单. 举几个例子 A跟B说,你给我还原这个魔方,B还原魔方需要很多时间,而A验证却很快,只需要看一眼就行了. A跟B说,你给

统计学习方法 笔记&lt;第二章 感知机&gt;

第二章 感知机 感觉感知机这东西还是很简单的,随便写点. 感知机(perceptron)是二分类的线性分类器. 输入x表示实例的特征向量,输出y为实例的类别,由如下函数表示: 其中w为权值(weight)或权值向量(weight vector),b表示偏置(bias),sign为符号函数,里面的东西大于0就是1,否则是-1. 感知机属于判别模型(直接寻找输入到输出的映射函数,不关心联合概率什么的). 感知机的解释:wx + b = 0 对应于特征空间中的一个超平面S(超平面这个东西在二维上表示为