1前言
上一篇文章我们介绍了区块链的最基本数据结构-区块,而且还构建了一个最原始的区块链。但是现在我们很容易就可以向区块链中添加区块,这样有可能导致大量的区块在同时添加到区块链中,从而导致广播风暴。而且在分布式环境中,如果并发量太大会导致很多问题,例如拜占庭问题。
为了解决这个问题,比特币使用了工作量证明机制。
2工作量证明
由于现在添加一个区块的成本很低,所以必须找到一个增加添加区块成本的方法,而在比特币中使用的是工作量证明,我们也效仿比特币使用工作量证明。
工作量证明(POW,Proof-of-Work)是一个用于阻止拒绝服务攻击和类似垃圾邮件等服务错误问题的协议,它在 1993 年被 Cynthia Dwork 和 Moni Naor 提出。
那么什么是工作量证明呢?其实很简单,就是找到一个符合某一规定的Hash值。例如我们规定区块的Hash值的前 20 个位必须为 0,要符合这样的区块才能添加到区块链中,那么工作量证明就是要找到符合这样规定的Hash值。
由于找到这样的Hash值是非常困难的,所以会给予找到合适Hash值的人相应奖励(比特币),找到符合规定Hash值的过程被称为“挖矿”,而挖矿的机器被称为“矿工”。
3Hash值计算
如前面所说,我们需要找到一个符合规定的Hash值才能将区块添加到区块链中,而在我们的前一篇文章中计算区块Hash值的方法是直接序列化区块,然后使用SHA-256来计算其Hash值。那么有个问题是,区块的内容是不变的,所以计算出来的Hash值也是固定的,那么有什么办法来改变区块的Hash值呢?这里我们使用Hashcash 算法,步骤如下:
1. 取一些公开的数据(比如区块头)。
2. 给这个公开数据添加一个计数器。计数器默认从 0 开始。
3. 将数据和计数器组合到一起,获得一个Hash值。
4. 检查Hash值是否符合规定的条件:
1) 如果符合条件,结束
2) 如果不符合,增加计数器,重复步骤 3-4
可以看出这个是一个暴力计算的过程:改变计数器,计算新的Hash值,检查是否符合条件,直到找到符合条件的Hash值。
在 Hashcash 算法中,它的要求是“一个Hash值的前 20 位必须是 0”。条件越苛刻,找到符合条件的Hash值就越难。下图详细说明了这个过程:
在上图中,129022是计数器的值,而符合条件的Hash值是前 16 个位为 0 (上图中表现为Hash值的前4个字符为0)。
4实现
在上一篇文章中的Block对象的实现中,添加一个findBlockHash()的方法用于找到符合条件的Hash值:
[code]=inherit
=inherit=inheritclass =inheritBlock
{
...
=inheritpublic $nonce;
=inheritprivate =inherit=inheritfunction =inheritprepareData=inherit($nonce)
{
=inheritreturn json_encode([
=inherit$this->prevHash,
=inherit$this->timeStamp,
=inherit$this->data,
$nonce,
]);
}
=inheritpublic =inherit=inheritfunction =inheritfindBlockHash=inherit()
{
$found = =inheritfalse;
$condition = =inherit'0000'; =inherit// Hash值前N个字符必须等于$condition
$condlength = strlen($condition);
printf(=inherit"Mining the block containing /"%s/"/n", =inherit$this->data);
=inheritfor ($nonce = =inherit0; $nonce < PHP_INT_MAX; $nonce++) {
$data = =inherit$this->prepareData($nonce);
$hash = hash(=inherit'sha256', $data);
printf(=inherit"/r%d: %s", $nonce, $hash);
=inheritif (substr($hash, =inherit0, $condlength) === $condition) {
$found = =inherittrue;
=inheritbreak;
}
}
=inheritprint(=inherit"/n/n");
=inheritif ($found) {
=inherit$this->nonce = $nonce;
=inherit$this->hash = $hash;
}
=inheritreturn $found;
}
}
[/code]
在上面的代码中,我们为Block类添加了一个nonce的成员变量,用于记录计数器。而在findBlockHash()函数中,我们不断增加计数器的值,计算出Hash值,然后比较Hash值是否符合条件(前N个字符是否等于变量$condition)。如果找到合适的Hash值就退出循环,否则增加计数器的值计算下一个Hash值。而prepareData()方法用于序列化要计算Hash值的数据。
最后,我们在Block类的构造函数中调用findBlockHash()方法:
[code]=inherit
=inherit=inheritclass =inheritBlock
{
...
=inheritpublic =inherit=inheritfunction =inherit__construct=inherit($prevHash, $data)
{
=inherit$this->prevHash = $prevHash;
=inherit$this->timeStamp = time();
=inherit$this->data = $data;
=inherit$this->findBlockHash();
}
...
}
从运行结果可以看到,算出来的Hash值都符合我们规定的条件。当然,你可以修改更苛刻的条件来增加挖矿的难度。
最后还有一件事需要做,就是验证区块是否合法:
[code]=inherit
=inherit=inheritclass =inheritBlock
{
...
=inheritpublic =inherit=inheritfunction =inheritvalidate=inherit()
{
$condition = =inherit'0000';
$condlength = strlen($condition);
$data = =inherit$this->prepareData(=inherit$this->nonce);
=inheritreturn substr(hash(=inherit'sha256', $data), =inherit0, $condlength) === $condition;
}
}
[/code]
验证的方法很简单,就是计算出区块的Hash值,然后比较Hash值是否符合条件。然后我们在打印区块链的时候验证区块是否合法:
[code]=inherit
=inheritinclude(=inherit'blockchain.php');
$bc = =inheritnew Blockchain();
$bc->addBlock(=inherit'This is block1');
$bc->addBlock(=inherit'This is block2');
=inheritforeach ($bc->blocks as $block) {
...
printf(=inherit"PoW: %s/n", $block->validate() ? =inherit'true' : =inherit'false');
...
}
结果符合我们的预期,代码在:https://github.com/liexusong/b … /v2.0
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/257425.html