• cpp 区块链模拟示例(五) 序列化


    有了区块和区块链的基本结构,有了工作量证明,我们已经可以开始挖矿了。剩下就是最核心的功能-交易,但是在开始实现交易这一重大功能之前,我们还要预先做一些铺垫,比如数据的序列化和启动命令解析。

    根据用 Go 构建一个区块链》的目录, 本章节的区块数据的序列化存储会使用一款KV数据库。其中比特币中是使用的是谷歌出品、c++编写的 LevelDB数据库,go语言示例中使用的是BoltDB。

    我本来考虑使用redis和json来进行我们的数据序列化存储。使用boost库的program_options 解析命令行参数。但是考虑代码过于复杂也许会偏离演示区块链的属性这一目的。最后进行了精简,最终的方案是舍去命令行参数解析,数据序列化改为使用map容器作为哈希与区块block指针的映射记录。

    我们代替序列化的数据结构为map<string, Block*> g_db;

    该结构是一个哈希值与区块指针的映射,由于每个区块的哈希值的都是独一无二的,一定程度上哈希就相当于KV数据库中KEY。

    我们通过独一无二的哈希值可以快速的map容器中查找的到区块指针,这一过程与使用kv数据的增删改查接口是基本相同的。所以使用map能达到使用kv数据一样的演示效果。

    一 创建区块链与加入创世块

    让我们从 NewBlockchain 函数开始。在之前的实现中,它会创建一个新的 Blockchain 实例,并向其中加入创世块。而现在,我们希望它做的事情有

    1. 创建创世块
    2. 存储到map中
    3. 将创世块哈希保存为最后一个块的哈希
    4. 创建一个新的 Blockchain 实例,其 tip 指向创世块(tip 有尾部,尖端的意思,在这里 tip 存储的是最后一个块的哈希
      代码大概是这样:
     1 Blockchain* NewBlockchain() {
     2     string tip;
     3     Block* genesis = NewGenesisBlock();
     4     g_db[genesis->hash] = genesis;
     5     g_db["l"] = genesis;
     6     tip = genesis->hash;
     7 
     8     Blockchain* bc = new Blockchain{ tip, &g_db };
     9 
    10     return bc;
    11 }

    我们创建了一个创世块,并且记录到全局map中,创始块哈希映射创始块的指针。 “l”字符串映射目前最后一个区块指针,恰好也是目前唯一的一个区块-创世块。 tp记录最后一个区块的哈希,也是目前唯一的一个区块-创世块的哈希

    Blockchain 的结构现在看起来是这样:

    typedef struct blockchain {
        string tip;
        map<string, Block*>* db;
    }Blockchain;

    接下来我们想要更新的是 AddBlock 方法:现在向链中加入区块,就不是像之前向一个数组中加入一个元素那么简单了。从现在开始,我们会将区块存储在数据库里面:

    void AddBlock(string  data, Blockchain* bc) {
        string lastHash;
    
        Block* p = g_db["l"];
        if (p == NULL)
            return;
        lastHash = p->hash;
    
        Block* newBlock = NewBlock(data, lastHash);
    
        (*bc->db)[newBlock->hash] = newBlock;
        (*bc->db)["l"] = newBlock;
    
        bc->tip = newBlock->hash;
    }

    二检查区块链

    现在,产生的所有块都会被保存到一个数据库里面,所以我们可以重新打开一个链,然后向里面加入新块。但是在实现这一点后,我们失去了之前一个非常好的特性:我们再也无法打印区块链的区块了,因为现在不是将区块存储在一个数组,而是放到了数据库里面。让我们来解决这个问题!

    BoltDB 允许对一个 bucket 里面的所有 key 进行迭代,但是所有的 key 都以字节序进行存储,而且我们想要以区块能够进入区块链中的顺序进行打印。此外,因为我们不想将所有的块都加载到内存中(因为我们的区块链数据库可能很大!或者现在可以假装它可能很大),我们将会一个一个地读取它们。故而,我们需要一个区块链迭代器(BlockchainIterator):

    typedef struct blockchainiterator {
        string currentHash;
        map<string, Block*>* db;
    }BlockchainIterator;

    每当要对链中的块进行迭代时,我们就会创建一个迭代器,里面存储了当前迭代的块哈希(currentHash)和数据库的连接(db)。通过 db,迭代器逻辑上被附属到一个区块链上(这里的区块链指的是存储了一个数据库连接的 Blockchain 实例),并且通过 Blockchain 方法进行创建:

    BlockchainIterator* Iterator(Blockchain* bc) {
        BlockchainIterator* bci = new BlockchainIterator{ bc->tip,bc->db };
        return bci;
    }

    注意,迭代器的初始状态为链中的 tip,因此区块将从头到尾,也就是从最新的到最旧的进行获取。实际上,选择一个 tip 就是意味着给一条链“投票”。一条链可能有多个分支,最长的那条链会被认为是主分支。在获得一个 tip (可以是链中的任意一个块)之后,我们就可以重新构造整条链,找到它的长度和需要构建它的工作。这同样也意味着,一个 tip 也就是区块链的一种标识符。

    BlockchainIterator 只会做一件事情:返回链中的下一个块

    Block* Next(BlockchainIterator* i){
        Block* block;
        if (i->currentHash == "")
            return NULL;
        block = (*i->db)[i->currentHash];
        i->currentHash = block->prevBlockHash;
        return block;
    }

    这就是数据库部分的内容了!

    我们在main函数中测试创建区块链和添加区块 并且打印结果 代码如下

    int main()
    {
        Blockchain* bc = NewBlockchain();
        printChain(bc);
    
        AddBlock("Send 1 BTC to Ivan", bc);
        printChain(bc);
    
        AddBlock("Pay 0.31337 BTC for a coffee", bc);
        printChain(bc);
    
        return 0;
    }

    运行结果如下:

    Mining the block containing Genesis Block


    Prev. hash:
    Data: Genesis Block
    Hash: 000000e8b0be7b9518b23c6cfbfc7ff19ec8395141e37cfdb87e7e448cf1d8c0

    Mining the block containing Send 1 BTC to Ivan


    Prev. hash: 000000e8b0be7b9518b23c6cfbfc7ff19ec8395141e37cfdb87e7e448cf1d8c0
    Data: Send 1 BTC to Ivan
    Hash: 0000005abaa800aba85a0d0ae8bd11077af9bfdf41cbc96c217638b6990988aa

    Prev. hash:
    Data: Genesis Block
    Hash: 000000e8b0be7b9518b23c6cfbfc7ff19ec8395141e37cfdb87e7e448cf1d8c0

    Mining the block containing Pay 0.31337 BTC for a coffee


    Prev. hash: 0000005abaa800aba85a0d0ae8bd11077af9bfdf41cbc96c217638b6990988aa
    Data: Pay 0.31337 BTC for a coffee
    Hash: 000000401cdc481e2c6698a801917e65dbc1ab0168aed077feef04623f8e1280

    Prev. hash: 000000e8b0be7b9518b23c6cfbfc7ff19ec8395141e37cfdb87e7e448cf1d8c0
    Data: Send 1 BTC to Ivan
    Hash: 0000005abaa800aba85a0d0ae8bd11077af9bfdf41cbc96c217638b6990988aa

    Prev. hash:
    Data: Genesis Block
    Hash: 000000e8b0be7b9518b23c6cfbfc7ff19ec8395141e37cfdb87e7e448cf1d8c0

    请按任意键继续. . .

     工程代码见群下载  文件名为CppBlockchain_part3.zip

    参考博文:

    https://blog.csdn.net/simple_the_best/article/details/78157303

    https://jeiwan.cc/posts/building-blockchain-in-go-part-3/

    作 者: itdef
    欢迎转帖 请保持文本完整并注明出处
    技术博客 http://www.cnblogs.com/itdef/
    B站算法视频题解
    https://space.bilibili.com/18508846
    qq 151435887
    gitee https://gitee.com/def/
    欢迎c c++ 算法爱好者 windows驱动爱好者 服务器程序员沟通交流
    如果觉得不错,欢迎点赞,你的鼓励就是我的动力
    阿里打赏 微信打赏
  • 相关阅读:
    ionic + cordova+angularJs 搭建的H5 App完整版总结
    在DevExpress程序中使用GridView直接录入数据的时候,增加列表选择的功能
    【Web动画】SVG 线条动画入门
    闲来无聊,研究一下Web服务器 的源程序
    PHP实现RTX发送消息提醒
    关于AngularJS(1)
    项目总结12:bootstrap-select下拉框模糊搜索
    JAVA读取XML文件并解析获取元素、属性值、子元素信息
    项目总结11:Centos部署JDK+Tomcat+MySQL文档(阿里云-网易云-华为云)
    项目总结10:通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题
  • 原文地址:https://www.cnblogs.com/itdef/p/9436705.html
Copyright © 2020-2023  润新知