PHP从零实现区块链(网页版二)工作量证明

源码地址: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文件,代码如下:

<?php

define('targetBits',24);
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;
    }
    public function prepareData(int $nonce): string
    {
        return implode('', [
            $this->block->timestamp,
            $this->block->prevBlockHash,
            $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];
    }
}

在里面,我做了一些更改,(config()因为要安装laravel框架来使用,这里我暂时先用常量代替。在下一章中再介绍安装。)

用下面两句代替config():

define('targetBits',24);

$this->target = gmp_pow('2', (256 - targetBits));

ProofOfWork 类解释:

block在得到前三个变量数据后,就把block通过ProofOfWork的构造函数传进去。

然后在ProofOfWork里的run()函数产生一个变动的数字nonce。再调用prepareData函数,把这个nonce和block的三个数据合在一起得到data返回去。

run()得到data后,就对data进行哈希运算,然后把哈希值和提前设定的哈希值比较,

如果小于设定的哈希值,那么就符合要求了。如果不符合,则nonce+1,把新数字放进去再进行哈希运算,如此循环,直到得到符合要求的哈希值,然后就把符合要求的nonce和哈希值传回去。

接下来就是block.php,我们要更改block类的构造函数。换proofOfwork来得到哈希值。

完整代码如下:

<?php
require_once 'ProofOfWork.php';
class Block
{
    /**
     * 当前时间戳,也就是区块创建的时间
     * @var int $timestamp
     */
    public $timestamp;

    /**
     * 区块存储的信息,也就是交易
     * @var string $data
     */
    public $data;

    /**
     * 前一个块的哈希,即父哈希
     * @var string $prevBlockHash
     */
    public $prevBlockHash;



    /**
     * 当前块的哈希 
     * @var string $hash
     */
    public $hash;
    public $nonce;

    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;
    }

    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如下:

<?php
require_once 'BlockChain.php';
$time1 = time();
$bc = BlockChain::NewGenesisBlock();
$bc->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('<hr>');
 }
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就会右移一位,变小。

大概就是这样。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bczheng1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值