python区块链持久化和命令行接口实现简版


Posted in Python onMay 25, 2022

说明

本文根据https://github.com/liuchengxu/blockchain-tutorial的内容,用python实现的,但根据个人的理解进行了一些修改,大量引用了原文的内容。文章末尾有"本节完整源码实现地址"。

引言

到目前为止,我们已经构建了一个有工作量证明机制的区块链。有了工作量证明,挖矿也就有了着落。虽然目前距离一个有着完整功能的区块链越来越近了,但是它仍然缺少了一些重要的特性。在今天的内容中,我们会将区块链持久化到一个数据库中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作。本质上,区块链是一个分布式数据库,不过,我们暂时先忽略 “分布式” 这个部分,仅专注于 “存储” 这一点。

选择数据库

目前,我们的区块链实现里面并没有用到数据库,而是在每次运行程序时,简单地将区块链存储在内存中。那么一旦程序退出,所有的内容就都消失了。我们没有办法再次使用这条链,也没有办法与其他人共享,所以我们需要把它存储到磁盘上。

那么,我们要用哪个数据库呢?实际上,任何一个数据库都可以。在 比特币原始论文 中,并没有提到要使用哪一个具体的数据库,它完全取决于开发者如何选择。 Bitcoin Core ,最初由中本聪发布,现在是比特币的一个参考实现,它使用的是 LevelDB。而我们将要使用的是…

couchdb

因为它:

  • 简单易用
  • 有一个web的UI界面,方便我们查看
  • 丰富的查询支持
  • 良好的python支持

couchdb的安装

docker版couchdb安装,使用docker-compose安装couchdb

# couchdb.yaml
version: '2'
services:
  couchdb:
    image: hyperledger/fabric-couchdb
    ports:
    - 5984:5984

执行docker-compose -f couchdb.yaml up -d即可安装。
使用http://ip:5984/_utils即可访问couchdb的后台管理系统。

数据库结构

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

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

  • 其中一个 bucket 是 blocks,它存储了描述一条链中所有块的元数据
  • 另一个 bucket 是 chainstate,存储了一条链的状态,也就是当前所有的未花费的交易输出,和一些元数据

此外,出于性能的考虑,Bitcoin Core 将每个区块(block)存储为磁盘上的不同文件。如此一来,就不需要仅仅为了读取一个单一的块而将所有(或者部分)的块都加载到内存中。而我们直接使用couchdb。

在 blocks 中,key -> value 为:

key value
b + 32 字节的 block hash block index record
f + 4 字节的 file number file information record
l + 4 字节的 file number the last block file number used
R + 1 字节的 boolean 是否正在 reindex
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(转换为16进制字符串) -> block 结构
  • l -> 链中最后一个块的 hash(转换为16进制字符串)

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

序列化

为了方便我们查看,这里我们不直接使用二进制数据,而将其转换为16进制字符串。所以我们需要对区块内容进行序列化。

让我们来实现 Block 的 Serialize 方法:

# class Block
    def serialize(self):
        return {
            "magic_no": self._magic_no,
            "block_header": self._block_header.serialize(),
            "transactions": self._transactions
        }

直接返回我们需要的数据构成的字典即可,而block_header则需要进一步序列化。它的序列化同样也只需要返回具体的数据字典即可,如下:

# class BlockHeader
    def serialize(self):
        return self.__dict__

反序列化则是把信息转换为区块对象。

# class Block
    @classmethod
    def deserialize(cls, data):
        block_header_dict = data['block_header']
        block_header = BlockHeader.deserialize(block_header_dict)
        transactions = data["transactions"]
        return cls(block_header, transactions)

首先反序列化块,然后构造成一个对象,反序列化Header:

# class BlockHeader
    @classmethod
    def deserialize(cls, data):
        timestamp = data.get('timestamp', '')
        prev_block_hash = data.get('pre_block_hash', '')
        # hash = data.get('hash', '')
        hash_merkle_root = data.get('hash_merkle_root', '')
        height = data.get('height', '')
        nonce = data.get('nonce', '')
        block_header = cls(hash_merkle_root, height, prev_block_hash)
        block_header.timestamp = timestamp
        block_header.nonce = nonce
        return block_header

持久化

持久化要做的事情就是把区块数据写入到数据库中,则我们要做的事情有:

  • 检查数据库是否已经有了一个区块链
  • 如果没有则创建一个,创建创世块并将l指向这个块的哈希
  • 添加一个区块,将l指向新添加的区块哈希

创建创世块如下:

# class BlockChain:
    def new_genesis_block(self):
        if 'l' not in self.db:
            genesis_block = Block.new_genesis_block('genesis_block')
            genesis_block.set_header_hash()
            self.db.create(genesis_block.block_header.hash, genesis_block.serialize())
            self.set_last_hash(genesis_block.block_header.hash)

添加一个区块如下:

def add_block(self, transactions):
        """
        add a block to block_chain
        """
        last_block = self.get_last_block()
        prev_hash = last_block.get_header_hash()
        height = last_block.block_header.height + 1
        block_header = BlockHeader('', height, prev_hash)
        block = Block(block_header, transactions)
        block.mine()
        block.set_header_hash()
        self.db.create(block.block_header.hash, block.serialize())
        last_hash = block.block_header.hash
        self.set_last_hash(last_hash)

对couchdb的操作的简单封装如下:

class DB(Singleton):
    def __init__(self, db_server_url, db_name='block_chain'):
        self._db_server_url = db_server_url
        self._server = couchdb.Server(self._db_server_url)
        self._db_name = db_name
        self._db = None
    @property
    def db(self):
        if not self._db:
            try:
                self._db = self._server[self._db_name]
            except couchdb.ResourceNotFound:
                self._db = self._server.create(self._db_name)
        return self._db
    def create(self, id, data):
        self.db[id] = data
        return id
    def __getattr__(self, name):
        return getattr(self.db, name)
    def __contains__(self, name):
        return self.db.__contains__(name)
    def __getitem__(self, key):
        return self.db[key]
    def __setitem__(self, key, value):
        self.db[key] = value

区块链迭代器

由于我们现在使用了数据库存储,不再是数组,那么我们便失去了迭代打印区块链的特性,我们需要重写__getitem__以获得该特性,实现如下:

# class BlockChain(object):
    def __getitem__(self, index):
        last_block = self.get_last_block()
        height = last_block.block_header.height
        if index <= height:
            return self.get_block_by_height(index)
        else:
            raise IndexError('Index is out of range')
# class BlockChain(object):
    def get_block_by_height(self, height):
        """
        Get a block by height
        """
        query = {"selector": {"block_header": {"height": height}}}
        docs = self.db.find(query)
        block = Block(None, None)
        for doc in docs:
            block.deserialize(doc)
            break
        return block

根据区块高度获取对应的区块,此处是利用了couchdb的mongo_query的富查询来实现。

CLI

到目前为止,我们的实现还没有提供一个与程序交互的接口。是时候加上交互了:
这里我们使用argparse来解析参数:

def new_parser():
    parser = argparse.ArgumentParser()
    sub_parser = parser.add_subparsers(help='commands')
    # A print command
    print_parser = sub_parser.add_parser(
        'print', help='Print all the blocks of the blockchain')
    print_parser.add_argument('--print', dest='print', action='store_true')
    # A add command
    add_parser = sub_parser.add_parser(
        'addblock', help='Print all the blocks of the blockchain')
    add_parser.add_argument(
        '--data', type=str, dest='add_data', help='block data')
    return parser
def print_chain(bc):
    for block in bc:
        print(block)
def add_block(bc, data):
    bc.add_block(data)
    print("Success!")
def main():
    parser = new_parser()
    args = parser.parse_args()
    bc = BlockChain()
    if hasattr(args, 'print'):
        print_chain(bc)
    if hasattr(args, 'add_data'):
        add_block(bc, args.add_data)
if __name__ == "__main__":
    main()

测试一下

# 创世块创建
$python3 main.py
Mining a new block
Found nonce == 19ash_hex == 047f213bcb01f1ffbcdfafad57ffeead0e86924cf439594020da47ff2508291c
<Document 'l'@'191-2f44a1493638684d9e000d8dd105192a' {'hash': 'e4f7adac65bcbb304af21be52a1b52bb28c0205a3746d63453d9e8c182de927a'}>
Mining a new block
Found nonce == 1ash_hex == 0df1ac18c84a8e524d6fe49cb04aae9af02dd85addc4ab21ac13f9d0d7ffe769
<Document 'l'@'192-168ff7ea493ca53c66690985deb5b7ac' {'hash': '01015004e21d394b1a6574eb81896e1c800f18aa22997e96b79bca22f7821a67'}>
Block(_block_header=BlockHeader(timestamp='1551317137.2814202', hash_merkle_root='', prev_block_hash='', hash='f20f3c74c831d03aaa2291af23e607896a61809b5ced222483b46795a456a1c5', nonce=None, height=0))
Block(_block_header=BlockHeader(timestamp='1551317137.358466', hash_merkle_root='', prev_block_hash='f20f3c74c831d03aaa2291af23e607896a61809b5ced222483b46795a456a1c5', hash='e4f7adac65bcbb304af21be52a1b52bb28c0205a3746d63453d9e8c182de927a', nonce=19, height=1))
Block(_block_header=BlockHeader(timestamp='1551317137.4621542', hash_merkle_root='', prev_block_hash='e4f7adac65bcbb304af21be52a1b52bb28c0205a3746d63453d9e8c182de927a', hash='01015004e21d394b1a6574eb81896e1c800f18aa22997e96b79bca22f7821a67', nonce=1, height=2))
$python3 cli.py addblock --data datas
Mining a new block
Found nonce == 6ash_hex == 0864df4bfbb2fd115eeacfe9ff4d5813754198ba261c469000c29b74a1b391c5
<Document 'l'@'193-92e02b894d09dcd64f8284f141775920' {'hash': '462ac519b6050acaa78e1be8c2c8de298b713a2e138d7139fc882f7ae58dcc88'}>
Success!

一切正常工作。

参考:
[1] persistence-and-cli

[2] 完整实现源码

以上就是python区块链持久化和命令行接口实现简版的详细内容!


Tags in this post...

Python 相关文章推荐
Python微信库:itchat的用法详解
Aug 14 Python
python使用pyqt写带界面工具的示例代码
Oct 23 Python
python 通过类中一个方法获取另一个方法变量的实例
Jan 22 Python
基于Python实现用户管理系统
Feb 26 Python
举例讲解Python常用模块
Mar 08 Python
Python基础之循环语句用法示例【for、while循环】
Mar 23 Python
Python如何筛选序列中的元素的方法实现
Jul 15 Python
Django实现发送邮件功能
Jul 18 Python
Django使用Channels实现WebSocket的方法
Jul 28 Python
Matplotlib scatter绘制散点图的方法实现
Jan 02 Python
python中图像通道分离与合并实例
Jan 17 Python
python为Django项目上的每个应用程序创建不同的自定义404页面(最佳答案)
Mar 09 Python
python区块链实现简版工作量证明
May 25 #Python
pycharm无法安装cv2模块问题
May 20 #Python
python中 Flask Web 表单的使用方法
May 20 #Python
Python OpenGL基本配置方式
May 20 #Python
Python面试不修改数组找出重复的数字
May 20 #Python
Python 中面向接口编程
May 20 #Python
人工智能深度学习OpenAI baselines的使用方法
May 20 #Python
You might like
setcookie中Cannot modify header information-headers already sent by错误的解决方法详解
2013/05/08 PHP
PHP批量生成静态HTML的简单原理和方法
2014/04/20 PHP
php中current、next与reset函数用法实例
2014/11/17 PHP
Laravel框架中VerifyCsrfToken报错问题的解决
2017/08/30 PHP
PHP面向对象程序设计中的self、static、parent关键字用法分析
2019/08/14 PHP
js控制div及网页相关属性的代码
2009/12/19 Javascript
购物车选中得到价格实现示例
2014/01/26 Javascript
Seajs的学习笔记
2014/03/04 Javascript
js实现jquery的offset()方法实例
2015/01/10 Javascript
jquery实现textarea 高度自适应
2015/03/11 Javascript
为什么JavaScript没有块级作用域
2016/05/22 Javascript
文件上传,iframe跨域数据提交的实现
2016/11/18 Javascript
jQuery获取this当前对象子元素对象的方法
2016/11/29 Javascript
深入理解在JS中通过四种设置事件处理程序的方法
2017/03/02 Javascript
CSS3结合jQuery实现动画效果及回调函数的实例
2017/12/27 jQuery
selenium+java中用js来完成日期的修改
2019/10/31 Javascript
JSONP 的原理、理解 与 实例分析
2020/05/16 Javascript
vue print.js打印支持Echarts图表操作
2020/11/13 Javascript
[00:06]Yes,it worked!小卡尔成功穿越时空加入战场!
2019/07/20 DOTA
Python从MP3文件获取id3的方法
2015/06/15 Python
Python字典实现简单的三级菜单(实例讲解)
2017/07/31 Python
python多进程提取处理大量文本的关键词方法
2018/06/05 Python
通过python将大量文件按修改时间分类的方法
2018/10/17 Python
解决PyCharm的Python.exe已经停止工作的问题
2018/11/29 Python
详解python读取和输出到txt
2019/03/29 Python
python七夕浪漫表白源码
2019/04/05 Python
Django自定义模板过滤器和标签的实现方法
2019/08/21 Python
NET程序员上机面试题
2015/05/23 面试题
公司端午节活动方案
2014/02/04 职场文书
贸易跟单员英文求职信
2014/04/19 职场文书
2014年党员承诺书范文
2014/05/20 职场文书
企业读书活动总结
2014/06/30 职场文书
教师创先争优承诺书
2015/04/27 职场文书
2016年秋季趣味运动会开幕词
2016/03/04 职场文书
python用海龟绘图写贪吃蛇游戏
2021/06/18 Python
MySQL实现配置主从复制项目实践
2022/03/31 MySQL