LaravelZero 从零实现区块链(二)工作量证明
工作量证明
上一节我们提到区块链的核心技术点之一就是需要一种分布式一致性算法,或者说需要一种共识机制,来实现区块链这个分布式系统的对外一致性。
在比特币中,采用的这种共识机制被称为工作量证明(POW)。
简单来说,想要将区块(block)添加到区块链中,需要付出一定的成本。对于比特币而言,需要做的就是为当前要添加的区块计算一个满足难度条件的Hash值,
这是一个困难的过程,会消耗大量的机器计算能力。当然,完成这个工作也会获得奖励,这个过程也被称为挖矿。也正是由于这种困难的工作,才保证了区块链的安全和一致。
实现
首先,让我们来定义挖矿的难度值,在config目录下新建一个 blockchain.php。
<?php
return [
// 定义挖矿难度
'targetBits' => 24
];
我们知道SHA256方法计算出来的结果是一个256位的二进制数,这里的 24 指的是算出来的哈希前 24 位必须是 0,如果用 16 进制表示,就是前 6 位必须是 0,目前我们并不会实现一个动态调整目标的算法,所以将难度定义为一个配置即可。
新建一个 ProofOfWork.php
<?php
namespace App\Services;
use GMP;
class ProofOfWork
{
/**
* @var Block $block
*/
public $block;
/**
* 目标值(计算结果要小于这个目标值才有效)
* @var GMP $target
*/
public $target;
public function __construct(Block $block)
{
$targetBits = config('blockchain.targetBits');
$this->target = gmp_pow('2', (256 - $targetBits));
$this->block = $block;
}
}
由于数字很大,所以使用PHP的GMP来存放与比较。
// 当 targetBits = 0 时,targe(目标)值
0x1000000000000000000000000000000000000000000000000000000000000000
// 当 targetBits = 24 时,targe(目标)值
0x0000010000000000000000000000000000000000000000000000000000000000
可以看到,当 targetBits 越来越大,target 会越来越小,这导致要寻找一个小于 target 的哈希会变得越来越困难。比特币会根据全网出块的时间动态调整挖矿难度,当全网算力变大,难度也会逐步加大。
下面,继续为 ProofOfWork 添加两个方法:
public function prepareData(int $nonce): string
{
return implode('', [
$this->block->prevBlockHash,
$this->block->data,
$this->block->timestamp,
$this->block->data,
$nonce
]);
}
public function run(): array
{
$nonce = 0;
$hash = '';
while (true) {
$data = $this->prepareData($nonce);
$hash = hash('sha256', $data);
if (gmp_cmp('0x' . $hash, $this->target) == -1) {
break;
}
$nonce++;
}
return [$nonce, $hash];
}
prepareData 方法是准备要哈希的数据,run 则是寻找小于 target 的哈希。这里我们引入了新变量 nonce,也就是不断修改 nonce 的值,来看匹配出有效的哈希。
最后我们要修改一下 Block 的构造函数
public function __construct(string $data, string $prevBlockHash)
{
$this->prevBlockHash = $prevBlockHash;
$this->data = $data;
$this->timestamp = time();
$pow = new ProofOfWork($this);
list($nonce, $hash) = $pow->run();
$this->nonce = $nonce;
$this->hash = $hash;
}
测试
下面我们来测试一下之前的修改
$time1 = time();
$bc = BlockChain::NewGenesisBlock();
$bc->addBlock('i am second block');
$bc->addBlock('i am third block');
$time2 = time();
$spend = $time2 - $time1;
dd($bc->blocks, '花费时间(s):' . $spend);
// 结果:
array:3 [
0 => App\Services\Block {#161
+timestamp: 1587802143
+data: "Genesis Block"
+prevBlockHash: ""
+hash: "000000c983da2e30e3110e40c8a9acbc321a93cef2a8409000deaeb784778f1c"
+nonce: 3898561
}
1 => App\Services\Block {#159
+timestamp: 1587802149
+data: "i am second block"
+prevBlockHash: "000000c983da2e30e3110e40c8a9acbc321a93cef2a8409000deaeb784778f1c"
+hash: "000000d22a3ed3aed46da3b566e0f0705d3e540fc2061ca12a9a79369711f595"
+nonce: 1018747
}
2 => App\Services\Block {#166
+timestamp: 1587802151
+data: "i am third block"
+prevBlockHash: "000000d22a3ed3aed46da3b566e0f0705d3e540fc2061ca12a9a79369711f595"
+hash: "0000002b2867b3fd52ea94c41950ed1a827fcb3515e5305f9b310c7571a89db9"
+nonce: 10739207
}
]
"花费时间(s):26"
可以看到,现在加入区块已经需要一些时间了,在我的电脑上算出三个有效块需要26s,并且算出来的 hash值都是小于目标值的。
0x0000010000000000000000000000000000000000000000000000000000000000 // target
0x000000c983da2e30e3110e40c8a9acbc321a93cef2a8409000deaeb784778f1c // Genesis Block
0x000000d22a3ed3aed46da3b566e0f0705d3e540fc2061ca12a9a79369711f595 // second block
0x0000002b2867b3fd52ea94c41950ed1a827fcb3515e5305f9b310c7571a89db9 // third block
Yeah!
下一篇,我们会一起实现区块链数据持久化,并且构建出命令行。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: