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判断变量是否已经定义的方法
Aug 18 Python
开始着手第一个Django项目
Jul 15 Python
Python实现类似jQuery使用中的链式调用的示例
Jun 16 Python
Python-OpenCV基本操作方法详解
Apr 02 Python
python从子线程中获得返回值的方法
Jan 30 Python
Python3 虚拟开发环境搭建过程(图文详解)
Jan 06 Python
新版Pycharm中Matplotlib不会弹出独立的显示窗口的问题
Jun 02 Python
Python实现进度条和时间预估的示例代码
Jun 02 Python
OpenCV利用python来实现图像的直方图均衡化
Oct 21 Python
python制作图形界面的2048游戏, 基于tkinter
Apr 06 Python
python ansible自动化运维工具执行流程
Jun 24 Python
PYTHON使用Matplotlib去实现各种条形图的绘制
Mar 22 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
php xml-rpc远程调用
2008/12/19 PHP
MySQL 日期时间函数常用总结
2012/06/12 PHP
php实现阳历阴历互转的方法
2015/10/28 PHP
PHP中单例模式与工厂模式详解
2017/02/17 PHP
PHP 序列化和反序列化函数实例详解
2020/07/18 PHP
JavaScript延迟加载
2021/03/09 Javascript
JS 常用校验函数
2009/03/26 Javascript
js获取图片长和宽度的代码
2009/11/24 Javascript
!DOCTYPE声明对JavaScript的影响分析
2010/04/12 Javascript
JavaScript中读取和保存文件实例
2014/05/08 Javascript
纯javascript实现四方向文本无缝滚动效果
2015/06/16 Javascript
使用Javascript实现选择下拉菜单互移并排序
2016/02/23 Javascript
jQuery事件对象总结
2016/10/17 Javascript
vue超时计算的组件实例代码
2018/07/09 Javascript
详解vue服务端渲染浏览器端缓存(keep-alive)
2018/10/12 Javascript
layui-laydate时间日历控件使用方法详解
2018/11/15 Javascript
JS事件绑定的常用方式实例总结
2019/03/02 Javascript
Vue 3.x+axios跨域方案的踩坑指南
2019/07/04 Javascript
JS中自定义事件的使用与触发操作实例分析
2019/11/01 Javascript
Vue+Node实现的商城用户管理功能示例
2019/12/23 Javascript
[39:07]LGD vs VP 2018国际邀请赛淘汰赛BO3 第二场 8.21
2018/08/22 DOTA
python 正则表达式 概述及常用字符
2009/05/04 Python
Python中使用logging模块打印log日志详解
2015/04/05 Python
在Python中使用cookielib和urllib2配合PyQuery抓取网页信息
2015/04/25 Python
在MAC上搭建python数据分析开发环境
2016/01/26 Python
python3设计模式之简单工厂模式
2017/10/17 Python
Python实现批量压缩图片
2018/01/25 Python
Python将list中的string批量转化成int/float的方法
2018/06/26 Python
django_orm查询性能优化方法
2018/08/20 Python
使用CodeMirror实现Python3在线编辑器的示例代码
2019/01/14 Python
HTML5中实现拖放效果无须借助javascript
2012/12/26 HTML / CSS
Herschel Supply Co.美国:背包、手提袋及配件
2020/11/24 全球购物
人力资源管理专业应届生求职信
2014/04/24 职场文书
举起手来观后感
2015/06/09 职场文书
大学开学感言
2015/08/01 职场文书
Python机器学习应用之工业蒸汽数据分析篇详解
2022/01/18 Python