本章源代碼地址:[https://github.com/daleboy/blockchain3]
# 前言
前面兩個版本的區塊鏈都是在內存中運行,本質上區塊鏈是一個分布式數據庫,我們在本版中只關注存儲,即將區塊鏈存儲到數據庫中,而分布式在后面的版本中實現。
# 數據選擇
比特幣選擇的數據庫是LevelDB,我們將選擇BoltDB:
1. 非常簡單和簡約
2. 用 Go 實現
3. 不需要運行一個服務器
4. 能夠允許我們構造想要的數據結構
BoltDB使用鍵值存儲,其API僅限于值的獲取或存儲。
BoltDB沒有數據類型,鍵和值都是字節數組(byte array),為了存儲區塊(Block),需要將struct序列化為byte array,取出時候,還需要將byte array反序列化為struct。
# 數據庫結構
數據庫結構決定了我們存儲什么到數據庫,以及存儲的形式。我們參考比特幣的數據庫結構:
Bitcoin Core 使用兩個 “bucket” 來存儲數據:
1. 其中一個 bucket 是**blocks**,它存儲了描述一條鏈中所有塊的元數據
2. 另一個 bucket 是**chainstate**,存儲了一條鏈的狀態,也就是當前所有的未花費的交易輸出,和一些元數據
此外,出于性能的考慮,Bitcoin Core 將每個區塊(block)存儲為磁盤上的不同文件。如此一來,就不需要僅僅為了讀取一個單一的塊而將所有(或者部分)的塊都加載到內存中。但是,為了簡單起見,我們并不會實現這一點。
因為目前還沒有交易,所以我們只需要**blocks**bucket。另外,正如上面提到的,我們會將整個數據庫存儲為單個文件,而不是將區塊存儲在不同的文件中。所以,我們也不會需要文件編號(file number)相關的東西。最終,我們會用到的鍵值對有:
1. 32 字節的 block-hash -> block 的結構,目的性不言而喻
2. 鏈中最后一塊的hash,目的是為了挖新區塊時候需要用到
# 序列化
我們用[encoding/gob](https://link.jianshu.com/?t=https://golang.org/pkg/encoding/gob/)來完成序列化。

這里需要注意,由于在go中,struct是一個值類型,所以創建實例時候直接按值類型的方式創建,而為了更好利用內存,通過引用(指針)來使用實例(無論是作為函數的左值還是作為函數的參數)。
# 持久化
持久化在blockchain.go之中實現,從NewBlockchain改造開始:
1. 打開一個數據庫文件
2. 檢查文件里面是否已經存儲了一個區塊鏈
3. 如果已經存儲了一個區塊鏈:
1. 創建一個新的`Blockchain`實例
2. 設置`Blockchain`實例的 tip 為數據庫中存儲的最后一個塊的哈希
4. 如果沒有區塊鏈:
1. 創建創世塊
2. 存儲到數據庫
3. 將創世塊哈希保存為最后一個塊的哈希
4. 創建一個新的`Blockchain`實例,其 tip 指向創世塊(tip 有尾部,尖端的意思,在這里 tip 存儲的是最后一個塊的哈希)

注意的是,存儲到數據庫的最后一個區塊的哈希鍵值對:鍵為字符串"1"的二進制數組,值為哈希值的二進制數組。
## 區塊鏈結構

序列化后,新的區塊鏈結構不再存儲所有的區塊,而只存儲區塊鏈最后一個區塊的哈希(tip),另外,存儲了一個數據庫連接,這樣方便后面隨時使用。
## AddBlock
將普通區塊存入數據庫比較簡單:
1. 從數據庫讀取最后一個區塊的哈希;
2. 挖出符合要求的區塊;
3. 將新區塊寫入數據庫表;
4. 修改數據庫表中的tip;
5. 更新當前區塊實例的tip。

# 區塊鏈迭代器
使用區塊鏈迭代器的好處是,我們在讀取區塊鏈中的區塊時候,不必將所有的塊都加載到內存中(因為我們的區塊鏈數據庫可能很大!或者現在可以假裝它可能很大),而是一個一個地讀取它們。

實際上,**選擇一個 tip 就是意味著給一條鏈“投票”**。一條鏈可能有多個分支,最長的那條鏈會被認為是主分支。在獲得一個 tip (可以是鏈中的任意一個塊)之后,我們就可以重新構造整條鏈,找到它的長度和需要構建它的工作。這同樣也意味著,一個 tip 也就是區塊鏈的一種標識符。
# CLI命令行
到目前為止,我們的實現還沒有提供一個與程序交互的接口:目前只是在`main`函數中簡單執行了`NewBlockchain`和`bc.AddBlock`。是時候改變了!現在我們想要擁有這些命令:
~~~
blockchain_go addblock "Pay 0.031337 for a coffee"
blockchain_go printchain
~~~
CLI的具體實現在cli.go中,見github。
# main.go
是時候檢驗我們的目標是否實現了:

注意,struct實例化的寫法:
(1)以key:value的方式構建struct實例是更嚴謹的寫法
(2)如果是在包內使用,直接寫value是可以的
(3)如果在包外使用,直接寫value會出錯:composite?literal?uses?unkeyed?fields
注意,在命令行執行前,區塊鏈已經創建好了,當然創始區塊也挖好了。
運行結果:

- 重要更新說明
- linechain發布
- linechain新版設計
- 引言一
- 引言二
- 引言三
- vs-code設置及開發環境設置
- BoltDB數據庫應用
- 關于Go語言、VS-code的一些Tips
- 區塊鏈的架構
- 網絡通信與區塊鏈
- 單元測試
- 比特幣腳本語言
- 關于區塊鏈的一些概念
- 區塊鏈組件
- 區塊鏈第一版:基本原型
- 區塊鏈第二版:增加工作量證明
- 區塊鏈第三版:持久化
- 區塊鏈第四版:交易
- 區塊鏈第五版:實現錢包
- 區塊鏈第六版:實現UTXO集
- 區塊鏈第七版:網絡
- 階段小結
- 區塊鏈第八版:P2P
- P2P網絡架構
- 區塊鏈網絡層
- P2P區塊鏈最簡體驗
- libp2p建立P2P網絡的關鍵概念
- 區塊鏈結構層設計與實現
- 用戶交互層設計與實現
- 網絡層設計與實現
- 建立節點發現機制
- 向區塊鏈網絡請求區塊信息
- 向區塊鏈網絡發布消息
- 運行區塊鏈
- LineChain
- 系統運行流程
- Multihash
- 區塊鏈網絡的節點發現機制深入探討
- DHT
- Bootstrap
- 連接到所有引導節點
- Advertise
- 搜索其它peers
- 連接到搜到的其它peers
- 區塊鏈網絡的消息訂發布-訂閱機制深入探討
- LineChain:適用于智能合約編程的腳本語言支持
- LineChain:解決分叉問題
- LineChain:多重簽名
- libp2p升級到v0.22版本
- 以太坊基礎
- 重溫以太坊的樹結構
- 世界狀態樹
- (智能合約)賬戶存儲樹
- 交易樹
- 交易收據樹
- 小結
- 以太坊的存儲結構
- 以太坊狀態數據庫
- MPT
- 以太坊POW共識算法
- 智能合約存儲
- Polygon Edge
- block結構
- transaction數據結構
- 數據結構小結
- 關于本區塊鏈的一些說明
- UML工具-PlantUML
- libp2p介紹
- JSON-RPC
- docker制作:啟動多個應用系統
- Dockerfile
- docker-entrypoint.sh
- supervisord.conf
- docker run
- nginx.conf
- docker基礎操作整理
- jupyter計算交互環境
- git技巧一
- git技巧二
- 使用github項目的最佳實踐
- windows下package管理工具