php区块链存储地址,PHP从零实现区块链(三)数据持久化与CLI

引言

到目前为止,我们已经构建了一个有工作量证明机制的区块链。在这篇文章中,我们会将区块链持久化,而不是只在内存中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作,代码变动较大,点击这里查看

持久化方案

Bitcoin Core ,最初由中本聪发布,现在是比特币的一个参考实现,它使用的是 LevelDB。我们为了方便,使用 Laravel提供的基于文件的缓存,大家也可以换成其他 K-V 数据库。

在 config 目录下新建一个 cache.php 配置文件。

return [

'default' => 'file',

'stores' => [

'file' => [

'driver' => 'file',

'path' => storage_path(),

],

],

'prefix' => 'bc_'

];

数据库结构

在开始实现持久化的逻辑之前,我们首先需要决定到底要如何在数据库中进行存储。为此,我们可以参考 Bitcoin Core 的做法:

简单来说,Bitcoin Core 使用两个 “bucket” 来存储数据:

其中一个 bucket 是 blocks,它存储了描述一条链中所有块的元数据。

另一个 bucket 是 chainstate,存储了一条链的状态,也就是当前所有的未花费的交易输出,和一些元数据。

此外,出于性能的考虑,Bitcoin Core 将每个区块(block)存储为磁盘上的不同文件。如此一来,就不需要仅仅为了读取一个单一的块而将所有(或者部分)的块都加载到内存中。但是,为了简单起见,我们并不会实现这一点。

在 blocks 中,key -> value 为:

key

value

b + 32 字节的 block hash

block index record

f + 4 字节的 file number

文件编号

l + 4 字节的 file number

最后一个块记录

R + 1 字节的 boolean

是否正在重新索引

F + 1 字节的 flag name length + flag name string

1 byte boolean: various flags that can be on or off

t + 32 字节的 transaction hash

transaction index record

在 chainstate,key -> value 为:

key

value

c + 32 字节的 transaction hash

unspent transaction output record for that transaction

B

32 字节的 block hash: the block hash up to which the database represents the unspent transaction outputs

因为目前还没有交易,所以我们只需要 blocks bucket。

另外,正如上面提到的,我们会将整个数据库存储为单个文件,而不是将区块存储在不同的文件中。所以,我们也不会需要文件编号(file number)相关的东西。最终,我们会用到的键值对有:

32 字节的 block-hash -> block 结构

l -> 链中最后一个块的 hash

这就是实现持久化机制所有需要了解的内容了。

持久化

下面修改 BlockChain.php 以及 Block.php

class BlockChain

{

/**

* // 存放最后一个块的hash

* @var string $tips

*/

public $tips;

public function __construct(string $tips)

{

$this->tips = $tips;

}

// 加入一个块到区块链中

public function addBlock(string $data)

{

// 获取最后一个块

$prevBlock = unserialize(Cache::get($this->tips));

$newBlock = new Block($data, $prevBlock->hash);

// 存入最后一个块到数据库,并更新 l 和 tips

Cache::put($newBlock->hash, serialize($newBlock));

Cache::put('l', $newBlock->hash);

$this->tips = $newBlock->hash;

}

// 新建区块链

public static function NewBlockChain(): BlockChain

{

if (Cache::has('l')) {

// 存在区块链

$tips = Cache::get('l');

} else {

$genesis = Block::NewGenesisBlock();

Cache::put($genesis->hash, serialize($genesis));

Cache::put('l', $genesis->hash);

$tips = $genesis->hash;

}

return new BlockChain($tips);

}

}

class Block

{

public static function NewGenesisBlock()

{

return $block = new Block('Genesis Block', '');

}

}

现在我们的 BlockChain 不再需要 $blocks 这个数组变量,而是使用一个 $tips,存放最后一个区块的Hash。

然后移除原来的 NewGenesisBlock 方法,新建一个 NewBlockChain,他的作用是:

如果数据库已存在l,也就是有最后一个块的哈希,那就取出并创建一个 Blockchain 实例;

如果不存在l,就创建一个创世区块,并更新数据库,在创建出 Blockchain 实例。

注意我们是将 Block 序列化 serialize() 以后才存入数据库的,再取出时需要进行反序列化 unserialize()。

最后修改 addBlock 方法,适配持久化逻辑。

遍历区块链

现在我们的区块链数据存放于数据库中,不方便打印区块信息了。现在我们来解决这个问题。

PHP提供了迭代器接口,让我们能自己实现遍历逻辑,修改 BlockChain.php

// 实现迭代器接口

class BlockChain implements \Iterator

{

// ......

/**

* 迭代器指向的当前块Hash

* @var string $iteratorHash

*/

private $iteratorHash;

/**

* 迭代器指向的当前块Hash

* @var Block $iteratorBlock

*/

private $iteratorBlock;

/**

* @inheritDoc

*/

public function current()

{

return $this->iteratorBlock = unserialize(Cache::get($this->iteratorHash));

}

/**

* @inheritDoc

*/

public function next()

{

return $this->iteratorHash = $this->iteratorBlock->prevBlockHash;

}

/**

* @inheritDoc

*/

public function key()

{

return $this->iteratorHash;

}

/**

* @inheritDoc

*/

public function valid()

{

return $this->iteratorHash != '';

}

/**

* @inheritDoc

*/

public function rewind()

{

$this->iteratorHash = $this->tips;

}

}

添加两个成员变量,$iteratorHash 记录当前遍历的Hash,$iteratorBlock 是当前区块;然后实现迭代器方法。

CLI

下面我们来实现命令行操作,让我们能使用命令行与程序进行交互!

在项目根目录下使用下面的命令

$ php blockchain make:command InitBlockChain

$ php blockchain make:command PrintChain

$ php blockchain make:command AddBlock

你的项目名也许和我的(blockchain)不一样,可以使用

php application app:rename blockchain 来修改项目名。

完成命令创建后可以在 app/Commands 下找到创建的文件。

class InitBlockChain extends Command

{

/**

* The name and signature of the console command.

*

* @var string

*/

protected $signature = 'init-blockchain';

/**

* The console command description.

*

* @var string

*/

protected $description = '初始化一个区块链,如果没有则创建';

/**

* Create a new command instance.

*

* @return void

*/

public function __construct()

{

parent::__construct();

}

/**

* Execute the console command.

*

* @return mixed

*/

public function handle()

{

$this->task('init blockchain', function () {

BlockChain::NewBlockChain();

return true;

});

}

}

class AddBlock extends Command

{

/**

* The name and signature of the console command.

*

* @var string

*/

protected $signature = 'addblock {data : 区块记录的数据}';

/**

* The console command description.

*

* @var string

*/

protected $description = '向区块链中添加一个区块';

/**

* Create a new command instance.

*

* @return void

*/

public function __construct()

{

parent::__construct();

}

/**

* Execute the console command.

*

* @return mixed

*/

public function handle()

{

$data = $this->argument('data');

$this->task('mining block:', function () use ($data) {

$bc = BlockChain::NewBlockChain();

$bc->addBlock($data);

return true;

});

}

}

class PrintChain extends Command

{

/**

* The name and signature of the console command.

*

* @var string

*/

protected $signature = 'printchain';

/**

* The console command description.

*

* @var string

*/

protected $description = '格式化打印出所有块信息';

/**

* Create a new command instance.

*

* @return void

*/

public function __construct()

{

parent::__construct();

}

/**

* Execute the console command.

*

* @return mixed

*/

public function handle()

{

$bc = BlockChain::NewBlockChain();

foreach ($bc as $block) {

$this->info('-----------------');

$this->info(' hash: ' . $block->hash);

$this->info(' prev hash: ' . $block->prevBlockHash);

$this->info(' timestamp: ' . $block->timestamp);

$this->info(' data: ' . $block->data);

}

}

}

下面来测试一下:

e84b60384eec

cli-test.png

在根目录下,我们也能看到多出来了缓存使用的storage目录,里面就是持久化的区块链数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值