源码地址:PHP从零实现区块链(二)工作量证明 – 简书

注:本例只是从网页版实现一下原理,源码非本人所写,只是将原帖的源码更改了一下,变成网页版

因为运行环境问题,本例暂时从windows转到ubuntu下,因为后面例子使用了gmp库的gmp_pow和gmp_cmp函数,而php在windows下暂时没有找到使用gmp的解决方案。

所以直接用ubuntu系统来运行本例,支持的比较友好。(后面看情况是否转回windows)

关于怎么在ubuntu下搭建php运行环境,可以参考我这篇文章:

ubuntu下安装php运行环境-CSDN博客

好了,接下来都是在ubuntu下操作。

因为要使用gmp_pow和gmp_cmp函数,我们需要安装gmp库。

打开终端输入如下命令安装:

udo apt-get install php-gmp

然后将gmp和php关联一下,如下命令:

sudo phpenmod gmp

但是显示文件模块不在php 8.1下。我一看是下了8.2相关的。

所以我们得加上版本号(你们看情况选择自己的版本号 php -v可查看你当前的php版本)

1. sudo apt-get install php8.1-gmp

OK,然后运行

2. sudo phpenmod gmp

接着再重启一下apache

3.sudo /etc/init.d/apache2 restart

好,正常的话,上面三步就能搞定了。

接着我们来个例子调用一下gmp_pow函数测试一下,求2的10次方,如下:

<?php

$pow1 = gmp_pow(“2”, 10);

echo $pow1;

?>

结果:

OK,接下来我们正式来研究这个例子.

首先我们新增ProofOfWork.php文件,代码如下:

prevBlockHash = $prevBlockHash;$this->data = $data;$this->timestamp = time();$pow = new ProofOfWork($this);list($nonce, $hash) = $pow->run();$this->nonce = $nonce;$this->hash = $hash;}public function setHash(): string{return hash('sha256', implode('', [$this->timestamp, $this->prevBlockHash, $this->data]));}}

开头引用一下require_once ‘ProofOfWork.php’;

解释:这里添加了block的三个数据后,跟原先相比,要添加哈希值,不是采用setHash直接添加了。而是调用$pow->run找到符合要求的哈希值后,才进行添加。

OK,接着修改一下app.php如下:

addBlock('i am 2 block');$bc->addBlock('i am 3 block');$time2 = time();$spend = $time2 - $time1;foreach ($bc->blocks as $block){print_r($block);echo('
'); }echo('花费时间(s):'.$spend);

接下来运行一下,但是很久没响应,然后报这个错:

原是程序执行超时了,因为一直在进行哈希运算寻找符合要求的哈希值,然后一直没找到。

这个我们可以去把超时时间设置的大一点即可。

不过我们也可以把难度调小一些, 就是将define(‘targetBits’,24);改为define(‘targetBits’,16);

意思,只是找前4位为0的哈希值就行了,不用前6位为0。

更改后OK,运行如下:

可以看到哈希值前4位都为0,说明程序运行正常。

接着我们改一下超时时间,将难度重新调为24,找一 下前6位为0的哈希值。

将php.ini下两句都改成300,最大时间5分钟。

max_execution_time=300

max_input_time=300

改完后重启一下apache。接着再次打开app.php,找到了需要的哈希值:

总算找到了,花费3分多钟,这个有点看运气,我测试了几次,有时候20秒也找到了。

PS:再讲一下这个$this->target = gmp_pow(‘2’, (256 – targetBits));难度控制逻辑吧。

这里写的有点乱,凑合看一下吧。

首先这算的是一个求2的平方,我们来看看,2的二进制,是怎么表示的:

0010

好,这样一个二进制数字,它的平方是什么?两个0010相加,就是0100。

然后又是乘2平方,1000。

什么意思呢,就是2的n次方,他的二进制就是正好有一个1,然后其它全是0。

不是1011,也不是0101。每多一次就是把1往前进一位。

那么可以想到,2的256次方肯定是100000000…这样开始的。

那么2的248,肯定也是跟2的256相比,它的前八位肯定是0。

二进制前八位是0,我们知道八位一个字节,一个字节的范围0到256

一个字节的16进制表示,就是“00”到“FF”。

那么二进制前八位是00,那么换成十六进制两位肯定也是00了。

那么难度24,设定的目标值,就是2的232次方,他的二进制前三个字节是全是0,

那么意思就是16进制的前6位都是0。

而哈希值我们知道是16进制64个字符的,那么两个字符一个字节,就是32个字节。

八位一个字节,就是32*8,256位二进制。

而2的256次方,正好也要256位二进制表示。就是10000000000…..开头。

那么得到的哈希值,如果要小于2的256次方,是不是有很多?太容易找到了。

因为2的256次方差不多已经是最大哈希值了,所以我设定减24,那么符合2的232次方的范围变小了。这个值也难找一些。

所以targetBits难度值每加1,那么二进制里的1就会右移一位,变小。

大概就是这样。