48

区块链扫盲篇之使用 PHP 实现区块链(三):区块链持久化

 5 years ago
source link: https://mp.weixin.qq.com/s/P4gyfOn6UBgHhByO_PwYeA?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1 引言

前一篇文章我们已经介绍了怎么为区块链加上工作量证明,但离可用的区块链还差很远。我们现在的区块链保存在内存中,进程退出或者机器重启都会导致区块链数据丢失。所以这一篇文章主要介绍怎么持久化我们的区块链到磁盘中,这样重启电脑也不会丢失我们的区块链。

2 数据库选择

市面上有很多数据库可以选择,例如:MySQL、MongoDB、LevelDB等。而在比特币的实现中,使用的是LevelDB。但由于LevelDB要另外安装,所以稍微有点麻烦。

为了简便起见,我自己使用PHP实现了一个简单的文件数据库“CuteDB”,地址是:https://github.com/liexusong/CuteDB。CuteDB只实现了简单“get”、“set”和“delete”操作,所以使用起来非常简单。CuteDB使用HashTable作为存储算法,有兴趣可以查看源码。

3 存储结构

我们使用区块的Hash值作为键,将区块序列化后作为值来存储。而使用“lasthash”作为键来保存最后一个区块的Hash值,这样的话就可以通过最后一个区块的Hash值来不断回溯整个区块链的所有区块。

现在我们需要修改Blockchain类的构造函数:

<?php

include('block.php');
include('CuteDB.php');

class Blockchain
{
    const dbFile = 'blockchain';
    const lastHashField = 'lasthash';

    private $_db = null;
    private $_lastHash = null;

    public function __construct()
    {
        $this->_db = new CuteDB();

        if (!$this->_db->open(Blockchain::dbFile)) {
            exit("Failed to create/open blockchian database");
        }

        $this->_lastHash = $this->_db->get(Blockchain::lastHashField);
        if (!$this->_lastHash) {
            $block = new Block('', 'Genesis Block');
            $hash = $block->getBlockHash();
            $this->_db->set($hash, serialize($block));
            $this->_db->set(Blockchain::lastHashField, $hash);
            $this->_lastHash = $hash;
        }
    }
    ...
}

在构造函数中,我们首先打开区块链的数据库,然后去数据库查看最后一个区块的Hash值是否存在,如果不存在说明我们的区块链还没有创建,所以需要创建一个创世区块,然后保存到数据库中,最后保存最后一个区块的Hash值到数据库。

在上面的过程中,我们会把最后一个区块的Hash值保存到Blockchain对象的“_lastHash”字段中,这样方便我们以后创建新区块时指定上一个区块的Hash值。

Blockchain类的addBlock()方法需要作如下修改:

<?php

include('block.php');
include('CuteDB.php');

class Blockchain
{
    ...

    public function addBlock($data)
    {
        $newBlock = new Block($this->_lastHash, $data);

        $hash = $newBlock->getBlockHash();

        $this->_db->set($hash, serialize($newBlock));
        $this->_db->set(Blockchain::lastHashField, $hash);

        $this->_lastHash = $hash;
    }
}

因为“_lastHash”字段保存了最后一个区块的Hash值,所以在新创建区块时把这个Hash值作为前一个区块的Hash值传入到参数即可。在保存区块时,首先使用区块的Hash值作为键,然后序列化区块后作为值,保存到数据库中,最后更新最后一个区块的Hash值。

然后我们新创建一个方法打印整条区块链:

<?php

include('block.php');
include('CuteDB.php');

class Blockchain
{
    ...

    public function printBlockchain()
    {
        $lastHash = $this->_lastHash;

        while (true) {
            $block = $this->_db->get($lastHash);
            if (!$block) {
                break;
            }

            $block = unserialize($block);

            printf("PrevHash: %s\n", $block->prevHash);
            printf("Hash: %s\n", $block->hash);
            printf("Data: %s\n", $block->data);
            printf("Nonce: %s\n\n\n", $block->nonce);

            $lastHash = $block->prevHash;
        }
    }
}

这个方法很简单,就是使用最后一个区块的Hash值来不断回溯整条区块链。

最后,我们通过测试代码来测试一下我们的结果:

<?php

include('blockchain.php');

$bc = new Blockchain();

$bc->addBlock('This is block1');
$bc->addBlock('This is block2');

$bc->printBlockchain();

结果输出如下:

7ZN3myJ.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK