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

目录
  • 说明
  • 引言
  • 选择数据库
  • couchdb
    • couchdb的安装
  • 数据库结构
  • 序列化
  • 持久化
  • 区块链迭代器
  • CLI
  • 测试一下

说明

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

引言

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

选择数据库

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

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

couchdb

因为它:

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

couchdb的安装

直接安装,参考https://www.jb51.net/article/202914.htm

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区块链持久化和命令行接口实现简版的详细内容,更多关于区块链持久化命令行接口的资料请关注我们其它相关文章!

(0)

相关推荐

  • python区块链实现简版工作量证明

    目录 说明 回顾 工作量证明 哈希计算 Hashcash 实现 说明 本文根据https://github.com/liuchengxu/blockchain-tutorial的内容,用python实现的,但根据个人的理解进行了一些修改,大量引用了原文的内容.文章末尾有"本节完整源码实现地址". 回顾 在上一节,我们构造了一个非常简单的数据结构 – 区块,它也是整个区块链数据库的核心.目前所完成的区块链原型,已经可以通过链式关系把区块相互关联起来:每个块都与前一个块相关联. 但是,当前

  • Python区块链块的添加教程

    目录 添加第一个区块 添加更多块 转储整个区块链 每个矿工将从先前创建的交易池中获取交易.要跟踪已挖掘的消息数量,我们必须创建一个全局变量 : last_transaction_index = 0 我们现在让我们的第一个矿工在区块链中添加一个区块. 添加第一个区块 到添加一个新块,我们首先创建一个 Block 类的实例 block = Block() 我们从队列中选取前3个交易并减去; for i in range(3):    temp_transaction = transactions[l

  • python区块链简易版交易完善挖矿奖励示例

    目录 说明 引言 奖励 UTXO 集 Merkle 树 P2PKH 总结 说明 本文根据https://github.com/liuchengxu/blockchain-tutorial的内容,用python实现的,但根据个人的理解进行了一些修改,大量引用了原文的内容.文章末尾有"本节完整源码实现地址". 引言 在这个系列文章的一开始,我们就提到了,区块链是一个分布式数据库.不过在之前的文章中,我们选择性地跳过了“分布式”这个部分,而是将注意力都放到了“数据库”部分.到目前为止,我们几

  • python区块链简易版交易实现示例

    目录 说明 引言 比特币交易 交易输出 发送币 余额查看 总结 说明 本文根据https://github.com/liuchengxu/blockchain-tutorial的内容,用python实现的,但根据个人的理解进行了一些修改,大量引用了原文的内容.文章末尾有"本节完整源码实现地址". 引言 交易(transaction)是比特币的核心所在,而区块链唯一的目的,也正是为了能够安全可靠地存储交易.在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它.今天,我们将会开始

  • Python区块链范围结论及Genesis Block的添加教程

    目录 Genesis Block添加 结论 Genesis Block添加 将块添加到区块链包括将创建的块附加到我们的 TPCoins 列表. TPCoins.append (block0) 请注意,与系统中的其余块不同,genesis块只包含一个由TPCoins系统的发起者启动的事务.现在,您将通过调用我们的全局函数转储区块链的内容 dump_blockchain : dump_blockchain(TPCoins) 执行此功能时,您将看到以下输出 : Number of blocks in 

  • python区块链基本原型简版实现示例

    目录 说明 引言 区块 区块头 区块链 总结 说明 本文根据https://github.com/liuchengxu/blockchain-tutorial的内容,用python实现的,但根据个人的理解进行了一些修改,大量引用了原文的内容.文章末尾有"本节完整源码实现地址". 引言 区块链是 21 世纪最具革命性的技术之一,它仍然处于不断成长的阶段,而且还有很多潜力尚未显现. 本质上,区块链只是一个分布式数据库而已. 不过,使它独一无二的是,区块链是一个公开的数据库,而不是一个私人数

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

    目录 说明 引言 选择数据库 couchdb couchdb的安装 数据库结构 序列化 持久化 区块链迭代器 CLI 测试一下 说明 本文根据https://github.com/liuchengxu/blockchain-tutorial的内容,用python实现的,但根据个人的理解进行了一些修改,大量引用了原文的内容.文章末尾有"本节完整源码实现地址". 引言 到目前为止,我们已经构建了一个有工作量证明机制的区块链.有了工作量证明,挖矿也就有了着落.虽然目前距离一个有着完整功能的区

  • python区块链实现简版网络

    目录 说明 引言 区块链网络 kademlia发现协议 简化协议 消息 TCP服务端 TCP客户端 P2P服务器 连接节点 RPC 测试 区块同步方式 问题 总结 说明 本文根据https://github.com/liuchengxu/blockchain-tutorial的内容,用python实现的,但根据个人的理解进行了一些修改,大量引用了原文的内容.文章末尾有"本节完整源码实现地址". 引言 到目前为止,我们所构建的原型已经具备了区块链所有的关键特性:匿名,安全,随机生成的地址

  • python区块链地址的简版实现

    说明 本文根据https://github.com/liuchengxu/blockchain-tutorial 的内容,用python实现的,但根据个人的理解进行了一些修改,大量引用了原文的内容.文章末尾有"本节完整源码实现地址". 引言 在上一篇文章中,我们已经初步实现了交易.相信你应该了解了交易中的一些天然属性,这些属性没有丝毫“个人”色彩的存在:在比特币中,没有用户账户,不需要也不会在任何地方存储个人数据(比如姓名,护照号码或者 SSN).但是,我们总要有某种途径识别出你是交易

  • python区块链创建多个交易教程

    目录 创建多个交易 显示事务 交易队列 创建多个客户端 创建第一个事务 添加更多交易 转储交易 创建多个交易 各个客户进行的交易在系统中排队;矿工从这个队列中获取交易并将其添加到块中.然后他们将挖掘区块,获胜的矿工将有权将区块添加到区块链中,从而为自己赚取一些钱. 我们将在稍后讨论这个挖掘过程区块链的创建.在我们为多个事务编写代码之前,让我们添加一个小实用程序函数来打印给定事务的内容. 显示事务 display_transaction 函数接受事务类型的单个参数.接收到的事务中的字典对象被复制到

  • Python区块链客户端类开发教程

    目录 开发客户端 客户端类 客户端 测试客户端 开发客户端 客户是持有TPCoins并从网络上的其他供应商处交换商品/服务的客户,包括他自己的.我们应该为此目的定义 Client 类.要为客户端创建全局唯一标识,我们使用PKI(公钥基础结构).在本章中,让我们详细讨论一下. 客户应该能够将钱包从另一个已知的人那里汇款.同样,客户应该能够接受来自第三方的钱.对于花钱,客户将创建一个指定发件人姓名和支付金额的交易.为了收款,客户将向第三方提供他的身份 : 本质上是钱的发送者.我们不存储客户持有的钱包

  • Python区块链交易类教程

    目录 创建 Transaction 类 测试交易类 创建 Transaction 类 在本章中,让我们创建一个 Transaction 类,以便客户能够向某人汇款.请注意,客户可以是货币的发件人或收件人.当您想要收款时,其他一些发件人将创建一个交易并在其中指定您的公开地址.我们将事务类的初始化定义如下 : def __init__(self, sender, recipient, value):    self.sender = sender    self.recipient = recipi

随机推荐