1. 程式人生 > >cpp 區塊鏈模擬示例(五) 序列化

cpp 區塊鏈模擬示例(五) 序列化

sdn art 標識符 事情 nth end 數據序列化 需要 targe

有了區塊和區塊鏈的基本結構,有了工作量證明,我們已經可以開始挖礦了。剩下就是最核心的功能-交易,但是在開始實現交易這一重大功能之前,我們還要預先做一些鋪墊,比如數據的序列化和啟動命令解析。

根據用 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;
}

運行結果如下:

參考博文:

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

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

cpp 區塊鏈模擬示例(五) 序列化