簡介:
到目前為止肛著,我們實(shí)現(xiàn)了一個(gè)簡單的區(qū)塊鏈基礎(chǔ)模型,建立了工作量證明的機(jī)制跺讯。本文將與大家一起實(shí)現(xiàn)區(qū)塊鏈的持久化存儲(chǔ)枢贿,以及實(shí)現(xiàn)一個(gè)簡單的命令行對(duì)區(qū)塊鏈進(jìn)行操作。我們先忽略區(qū)塊鏈“分布式”的特征刀脏,專注實(shí)現(xiàn)其數(shù)據(jù)庫的特征局荚。
數(shù)據(jù)庫:
目前為止,我們沒有數(shù)據(jù)庫愈污,我們將區(qū)塊鏈存在內(nèi)存中耀态。對(duì)于實(shí)現(xiàn)的區(qū)塊,我們再次使用暂雹,不能和別人分享該區(qū)塊首装,因此我們要實(shí)現(xiàn)區(qū)塊鏈的持久化。
比特幣白皮書中并沒有指定特定的數(shù)據(jù)庫類型杭跪,它取決于開發(fā)者的選擇仙逻,在實(shí)際使用中比特幣使用leveldb作為數(shù)據(jù)庫。大家來了解一種新的數(shù)據(jù)庫:
BoltDB:
1.簡單
2.使用go語言
3.不需要啟動(dòng)服務(wù)
4.滿足我們需要的數(shù)據(jù)結(jié)構(gòu)需求
From the BoltDB’s?README on Github:
Bolt is a pure Go key/value store inspired by Howard Chu’s LMDB project. The goal of the project is to provide a simple, fast, and reliable database for projects that don’t require a full database server such as Postgres or MySQL.
Since Bolt is meant to be used as such a low-level piece of functionality, simplicity is key. The API will be small and only focus on getting values and setting values. That’s it.
簡單的key/value涧尿,api專注于setting和getting系奉。
需要注意的是,boltDB存儲(chǔ)二進(jìn)制類型數(shù)據(jù)姑廉,因此為了存儲(chǔ)區(qū)塊缺亮,我們必須進(jìn)行序列化,我們使用golibarary中的encoding/gob作為序列化和反序列化函數(shù)庄蹋。
數(shù)據(jù)庫結(jié)構(gòu):
我們參考比特幣的存儲(chǔ)結(jié)構(gòu)(將區(qū)塊鏈數(shù)據(jù)分為兩個(gè)bucket):
1.blocks區(qū)塊存儲(chǔ)區(qū)塊鏈中區(qū)塊的元數(shù)據(jù)瞬内。
2.chainstate 存儲(chǔ)鏈的狀態(tài),存儲(chǔ)未話費(fèi)交易輸出和一些元數(shù)據(jù)限书。
在blocks區(qū)虫蝶,其key/value結(jié)構(gòu)如下:
1.'b' + 32位區(qū)塊哈希 作為區(qū)塊的索引
2.'f' + 4位文件句柄 -> 文件信息記錄
3.'l' -> 4位文件句柄??->上一個(gè)區(qū)塊的文件信息
4.'R' -> 1位布爾型: 是否在索引中
5.'F' + 1位標(biāo)記長度 + 標(biāo)記名 -> 1 byte boolean: various flags that can be on or off
6.'t' + 32位交易哈希 -> 交易記錄
在chainstate區(qū),其key/value結(jié)構(gòu)如下:
1.'c' + 32位交易哈希 -> 本次交易的未花費(fèi)交易輸出
2.'B' -> 32位交易哈希: 到目前為止未消費(fèi)交易的區(qū)塊哈希
由于倦西,我們本文并不實(shí)現(xiàn)交易能真,同時(shí)我們不分開文件存儲(chǔ),所以數(shù)據(jù)庫總的區(qū)塊結(jié)構(gòu)可以簡化為:
1.32位區(qū)塊哈希>區(qū)塊結(jié)構(gòu)(序列化的)
2.‘l’>上一個(gè)區(qū)塊的哈希
序列化:
由于boltDB只能存儲(chǔ)[]byte類型數(shù)據(jù),為了存儲(chǔ)區(qū)塊數(shù)據(jù)粉铐,我們需要實(shí)現(xiàn)序列化接口疼约。我們使用go libarary中提供的encoding/gob函數(shù)實(shí)現(xiàn)區(qū)塊的序列化。
另外我們需要一個(gè)函數(shù)蝙泼,將db中的block反序列化為Block結(jié)構(gòu)程剥,以再次讀取。
持久化:
從NewBlockChain函數(shù)開始汤踏,新建了一個(gè)BlockChain實(shí)例织鲸,并且將GeneiusBlock加入其中。
持久化步驟如下:
1.打開一個(gè)數(shù)據(jù)文件
2.檢查是否該文件是否有保存的區(qū)塊鏈信息
3.如果存在區(qū)塊鏈信息:新建一個(gè)區(qū)塊鏈實(shí)例溪胶,設(shè)置該區(qū)塊鏈tip指向db中存儲(chǔ)的區(qū)塊鏈的最后一個(gè)區(qū)塊哈希
4.如果不存在區(qū)塊鏈:
? ? ? ? 1.新建區(qū)塊鏈實(shí)例
? ? ? ? 2.存儲(chǔ)在數(shù)據(jù)庫中
? ? ? ? 3.將創(chuàng)世區(qū)塊的哈希作為最后一個(gè)區(qū)塊哈希
? ? ? ? 4.新建一個(gè)區(qū)塊鏈實(shí)例搂擦,tip指向創(chuàng)世區(qū)塊
同時(shí)我們需要更新區(qū)塊的結(jié)構(gòu)如下:
type BlockChainstruct {
????????tip []byte
? ? ? ? db? *bolt.DB
}
接下來我們需要更新AddBlock方法:
現(xiàn)在我們實(shí)現(xiàn)了添加新區(qū)塊的功能,但是出現(xiàn)一個(gè)問題哗脖,我們無法獲取已經(jīng)保存的區(qū)塊鏈的信息瀑踢。因此,我們需要增加新的功能才避,從db中讀取區(qū)塊鏈信息橱夭。
查看區(qū)塊鏈:
至此為止,所有的區(qū)塊均存儲(chǔ)在數(shù)據(jù)庫中工扎,我們可以重新打開區(qū)塊鏈文件新增區(qū)塊徘钥。但是我們?nèi)鄙僖粋€(gè)重要功能,一次瀏覽每個(gè)區(qū)塊的內(nèi)容肢娘。
BoltDB提供api可以循環(huán)一個(gè)bucket中內(nèi)容呈础,但是其key是字節(jié)排序的,我們希望按區(qū)塊添加的順序依次打印區(qū)塊信息橱健。由于我們不希望一次將所有區(qū)塊的信息加載入內(nèi)存而钞,因此需要實(shí)現(xiàn)一個(gè)迭代功能。
CLI:
目前拘荡,我們實(shí)現(xiàn)了NewBlockChain臼节、AddBlock方法,現(xiàn)在就開始實(shí)現(xiàn)簡單的命令行操作方法吧珊皿。
1.blockchain_go AddBlock "A pays B two yuan"
2.blockchain_go PrintChain
所有的命令行操作方法网缝,我們都由CLI struct實(shí)現(xiàn)。
該結(jié)構(gòu)的命令行由Run方法維護(hù)輸入蟋定。
其中粉臊,os.Args返回參數(shù)中,第一個(gè)參數(shù)返回為可執(zhí)行命令文件路徑:
例如: C://goblock/main.exe? addblock -data test
上述命令使用os.Args返回參數(shù)數(shù)組驶兜,os.Args[0]為C://goblock/main.exe扼仲,os.Args[1]為addblock远寸,os.Args[2]為data,os.Args[3]為test屠凶。具體的使用可查看flag包用法驰后,后文會(huì)介紹。
到目前為止矗愧,我們實(shí)現(xiàn)了命令行向區(qū)塊db中添加區(qū)塊灶芝,以及查看區(qū)塊信息的功能。