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
實例,並向其中加入創世塊。而現在,我們希望它做的事情有
- 創建創世塊
- 存儲到map中
- 將創世塊哈希保存為最後一個塊的哈希
- 創建一個新的
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 區塊鏈模擬示例(五) 序列化