源码地址: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就会右移一位,变小。
大概就是这样。