深入理解比特幣數(shù)據(jù)存儲(chǔ)

本文由冉小龍和張勇合作完成允趟,致謝:張勇,轉(zhuǎn)載請(qǐng)注明出處

當(dāng)我們打開 bitcoincash 目錄時(shí)码倦,我們會(huì)看到如下的文件目錄,這些文件究竟是什么锭碳,具體存儲(chǔ)了哪些內(nèi)容呢袁稽?下面我們將一一揭開其神秘面紗。

bitcoincash 文件夾:

bitcoincash.png

blocks 文件夾:

blocks.png

index 文件夾:

index.png

chainstate文件夾:


chainstate.png

通過(guò)觀察 bitcoincash 目錄擒抛,我們可以發(fā)現(xiàn)推汽, 比特幣總共存儲(chǔ)了以下內(nèi)容:

文件名稱 文件描述 存儲(chǔ)形式
chainstate 存儲(chǔ) utxo 相關(guān)的數(shù)據(jù) leveldb數(shù)據(jù)庫(kù)
blocks/index 存儲(chǔ) blocks 的元數(shù)據(jù)信息 leveldb數(shù)據(jù)庫(kù)
blocks/blk?????.dat 存儲(chǔ) blocks 相關(guān)的數(shù)據(jù)信息,主要包括 block header 和 txs 磁盤文件
blocks/rev?????.dat 存儲(chǔ) blocks undo 的數(shù)據(jù)歧沪,主要包括每筆交易所花費(fèi)的 out 的信息歹撒。 磁盤文件

其中 block 的數(shù)據(jù)和 block 的 undo 數(shù)據(jù)是直接存儲(chǔ)到disk上面的,block 的 index 數(shù)據(jù)和 utxo 的數(shù)據(jù)是寫到 leveldb 數(shù)據(jù)庫(kù)中诊胞。

leveldb

為了方便理解 leveldb 的目錄存儲(chǔ)結(jié)構(gòu)暖夭,下面簡(jiǎn)述一下 leveldb 的原理。

leveldb.png

leveldb 使用的是 LSMTree 的存儲(chǔ)結(jié)構(gòu)撵孤,其存儲(chǔ)的邏輯大致如上圖所示迈着,具體步驟如下:

  • 當(dāng)往 leveldb 中寫入一條數(shù)據(jù)的時(shí)候,首先會(huì)將數(shù)據(jù)寫入 log 文件邪码,log 文件完成之后裕菠,再將數(shù)據(jù)寫入內(nèi)存(memtable)中。
  • 當(dāng) memtable 中的數(shù)據(jù)寫滿之后闭专,.log 文件會(huì)被鎖定奴潘,同時(shí)生成 Immutable table 文件,該文件只支持讀操作影钉,不支持寫和刪除画髓,這個(gè)時(shí)候,會(huì)重新生成 .log文 件和memtable 文件斧拍,新寫入一條數(shù)據(jù)的時(shí)候雀扶,會(huì)重新寫入空的 .log 文件和 memtable 中。
  • LevelDb 后臺(tái)調(diào)度會(huì)將 Immutable Memtable 的數(shù)據(jù)導(dǎo)出到磁盤肆汹,形成一個(gè)新的SSTable 文件愚墓。SSTable 就是由內(nèi)存中的數(shù)據(jù)不斷導(dǎo)出并進(jìn)行 Compaction 操作后形成的,而且 SSTable 的所有文件是一種層級(jí)結(jié)構(gòu)昂勉,第一層為 Level 0浪册,第二層為 Level 1,依次類推岗照,層級(jí)逐漸增高村象,這也是為何稱之為L(zhǎng)evelDb的原因笆环。

各個(gè)文件的含義:

Current文件:

Current 文件是干什么的呢?這個(gè)文件的內(nèi)容只有一個(gè)信息厚者,就是記載當(dāng)前的 manifest 文件名躁劣。因?yàn)樵?LevleDb 的運(yùn)行過(guò)程中,隨著 Compaction 的進(jìn)行库菲,SSTable 文件會(huì)發(fā)生變化账忘,會(huì)有新的文件產(chǎn)生,老的文件被廢棄熙宇,Manifest 也會(huì)跟著反映這種變化鳖擒,此時(shí)往往會(huì)新生成Manifest 文件來(lái)記載這種變化,而 Current 則用來(lái)指出哪個(gè) Manifest 文件才是我們關(guān)心的那個(gè) Manifest 文件烫止。

Manifest文件

Manifest 文件存儲(chǔ)的是 xxx.ldb 文件的元數(shù)據(jù)信息蒋荚,因?yàn)椋覀冎挥?xxx.ldb 文件馆蠕,我們并不知道它具體屬于哪一個(gè) level期升。這也是 Manifest 文件的作用,每次打開 DB 的時(shí)候互躬,leveldb 都會(huì)去創(chuàng)建這樣一個(gè)文件并在其尾部追加后綴標(biāo)識(shí)吓妆。該文件是以 append 的方式寫入 disk 的。

LOG文件

leveldb 運(yùn)行時(shí)的日志文件吨铸,方便用戶查看行拢。

LOCK文件

它是使用文件實(shí)現(xiàn)的一個(gè) DB 鎖,告知用戶诞吱,一個(gè) leveldb 的實(shí)例在一個(gè)進(jìn)程范圍內(nèi)只允許被打開一次舟奠。

xxx.ldb文件

這個(gè)文件是記錄 leveldb 的數(shù)據(jù)文件(區(qū)別與元數(shù)據(jù)文件),按照 KV 有序的形式寫入數(shù)據(jù)庫(kù)中房维。

level-0 的文件大小就是 memtable 文件做 compaction 之后的大小沼瘫,level-1 10MB、level-2 100MB咙俩、level-3 1000MB 以此類推耿戚。

xxx.log 文件

我們上面說(shuō)過(guò),為了保證數(shù)據(jù)不丟失阿趁,在寫數(shù)據(jù)之前會(huì)先寫入 .log 文件膜蛔,.log 文件存儲(chǔ)的是一系列最近的更新,每個(gè)更新以 append 的方式追加到當(dāng)前的 log 文件中脖阵,當(dāng) log 文件達(dá)到 4MB時(shí)會(huì)轉(zhuǎn)化為一個(gè)有序的文件皂股,并創(chuàng)建新的 log 文件來(lái)記錄最近的更新。這個(gè) log 文件中與上文中提到的 memtable 文件是互相映射的命黔,當(dāng) memtable 文件被寫入 level-0 后呜呐,對(duì)應(yīng)的 log 文件會(huì)被刪除就斤,新的 log 文件會(huì)重新創(chuàng)建,對(duì)應(yīng)新的 memtable蘑辑,以此類推洋机。

綜上所述,我們可以看出洋魂,leveldb 是存儲(chǔ)模型中一個(gè)典型的數(shù)據(jù)與元數(shù)據(jù)分離存儲(chǔ)的數(shù)據(jù)庫(kù)槐秧。

chainstate 文件夾

chainstate 是一個(gè)leveldb的數(shù)據(jù)庫(kù),主要存儲(chǔ)一些 utxo 和 tx 的元數(shù)據(jù)信息忧设。存儲(chǔ) chainstate 的數(shù)據(jù)主要是用來(lái)去驗(yàn)證新進(jìn)來(lái)的 blocks 和 tx 是否是合法的。如果沒(méi)有這個(gè)操作颠通,就意味著對(duì)于每一個(gè)被花費(fèi)的 out 你都需要去進(jìn)行全表掃描來(lái)驗(yàn)證址晕。

chainstate_file.png

如上圖所示,utxo的數(shù)據(jù)主要存儲(chǔ)于chainstate這個(gè)文件目錄顿锰,由于要存儲(chǔ)到leveldb中谨垃,所以肯定是按照 key、value 的格式將數(shù)據(jù)準(zhǔn)備好硼控。

coin

coin.png
coin_db_key.png

如上所示:key總共包含三部分內(nèi)容刘陶,1 字節(jié)的大寫 C , 32 字節(jié)的 hash,4 字節(jié)的序列號(hào)牢撼。

value 是 coin 被序列化之后的值匙隔,具體如下:

image.png

coin 又包含了 txout 結(jié)構(gòu),具體如下:

image.png

對(duì) nValue 和 scriptPubKey 采用了不同的壓縮方式來(lái)進(jìn)行序列化熏版,如下:

image.png

best block

image.png

比特幣還往 chainstate 中記錄了另一部分信息纷责,首先去判斷當(dāng)前 block 的 hash 是否為 null,不為 null 的話撼短,以 1 字節(jié)的大寫 B 為 key再膳,32 字節(jié)的 block hash 為value,寫入 coin 數(shù)據(jù)庫(kù)中曲横。

總結(jié):utxo 寫入 disk 的數(shù)據(jù)庫(kù)為:chainstate喂柒,寫入數(shù)據(jù)分為兩部分,第一部分:key是outpoin, 由<txid>+<tx out index>組成禾嫉,其中txid是32字節(jié)灾杰,tx out index 是用var int的編碼方式序列化value 為 coin 序列化之后的大小。第二部分:寫入的 key 為 1 字節(jié)的DB_BEST_BLOCK 標(biāo)識(shí)熙参,value 為 32 字節(jié)的 block hash吭露。

image.png

bitcoin core 0.17 的時(shí)候, chainstate 目錄做了改動(dòng)尊惰,多寫了一部分?jǐn)?shù)據(jù)進(jìn)去讲竿,圖示如下:

image.png

Note:

在0.17的結(jié)構(gòu)中泥兰,第一部分并不會(huì)存在很長(zhǎng)時(shí)間,它只會(huì)在觸發(fā)BatchWrite第一步寫入题禀,在整個(gè)coinsmap寫完之后將這部分刪除鞋诗。

index 文件夾:

image.png

index 文件夾下記錄的主要是 blocks 的 index 信息,block index 是block的元數(shù)據(jù)信息迈嘹,其中包含和block header信息削彬,高度,以及chain的信息秀仲;按照 utxo 存儲(chǔ)的思路融痛,我們?cè)偃ふ?blocks 中 index 的 key 和 value。

reindex

image.png

index 中寫的第一部分?jǐn)?shù)據(jù):key 是 1 字節(jié)的 DB_REINDEX_FLAG神僵,value 是 1 字節(jié)的布爾值雁刷。用來(lái)標(biāo)識(shí)是否需要進(jìn)行 reindex 操作。

txindex

image.png

index 中寫的第二部分?jǐn)?shù)據(jù):key 是 1 字節(jié)的 DB_TXINDEX 加 32 字節(jié)的 hash保礼,value 是序列化之后的 CDiskTxPos沛励,它只有一個(gè)成員是,int 類型的 nTxOffset炮障。這些是可選的目派,只有當(dāng)'txindex' 被啟用時(shí)才存在。 每個(gè)記錄存儲(chǔ):

  • 交易存儲(chǔ)在哪個(gè)塊文件號(hào)碼中胁赢。
  • 哪個(gè)文件中的交易所屬的塊被抵消存儲(chǔ)在企蹭。
  • 從該塊的開始到該交易本身被存儲(chǔ)的位置的偏移量。

blockfileinfo

image.png

index 中寫的第三部分?jǐn)?shù)據(jù):這部分?jǐn)?shù)據(jù)是比較重要的智末,

fileinfo

首先寫入 fileinfo 數(shù)據(jù)练对,key 是 1 字節(jié)的 DB_BLOCK_FILES 加上 4 字節(jié)的文件編號(hào),value 是 CBlockFileInfo 序列化后的數(shù)據(jù)吹害。

lastFile

其次寫入 lastFile 信息螟凭,key 是 1 字節(jié)的 DB_LAST_BLOCK,value 是 4 字節(jié)的 nLastFile它呀。

blockindex

最后寫入 blockindex 的信息螺男,key 是 1 字節(jié)的 DB_BLOCK_INDEX 加上 32 字節(jié)的 blockhash value是CDiskBlockIndex序列化之后的數(shù)據(jù)。

flag
image.png

index 中寫的第四部分?jǐn)?shù)據(jù):key 是 1 字節(jié)的 DB_FLAG 加上 flag 的名字纵穿,value 是 1 字節(jié)的布爾值(1 為 true下隧,0 為 false),可以打開或關(guān)閉各種類型的標(biāo)志谓媒,目前定義的比如:TxIndex(是否啟動(dòng)交易索引)淆院。

image.png

block 文件夾

block 文件夾下主要存在兩種文件,一種是 blk???.dat句惯,用于存儲(chǔ) block土辩,另一種是 rev???.dat支救,用于存儲(chǔ) undo block。 主要存儲(chǔ)格式如下:

blk?????.dat

存儲(chǔ) block 序列化的數(shù)據(jù)拷淘。

image.png

存儲(chǔ)格式如下(按照先后順序):

image.png
MessageStart

MessageMagic 在啟動(dòng)程序時(shí)定義各墨,并且在不同網(wǎng)絡(luò)中定義不同,MessageMagic 分為 netMagic 和 diskMagic :

Mainnet:
image.png
TestNet:
image.png
RegTestNet:
image.png

MessageMagic 是一個(gè) 4 byte 的數(shù)組启涯,在寫入數(shù)據(jù)的時(shí)候調(diào)用 FLATDATA 這個(gè)宏定義贬堵,具體如下:

image.png

FLATDATA 會(huì)將vector或者map這種數(shù)據(jù)結(jié)構(gòu)中的元素按照數(shù)組的原始序列dump到disk上。

image.png

write() 函數(shù)的第一個(gè)參數(shù)代表要寫入的數(shù)據(jù)的起始位置结洼,第二個(gè)參數(shù)代表要寫入數(shù)據(jù)的大小黎做,pbegin 指向 vector 的起始位置,pend指向末尾元素 +1 的位置松忍,所以在這里先寫入了 4 byte 的 messageStart蒸殿。

image.png

BlockSize

BlockSize主要描述 Block 被序列化后的長(zhǎng)度,為 4 byte挽铁。

image.png

Block 序列化

block 序列化主要序列化兩部分,一部分是 BlockHeader 結(jié)構(gòu)敞掘,一部分為 transaction 的一個(gè)共享指針 vtx:

image.png

第一部分是 BlockHeader:

image.png

第二部分是 vtx:

image.png

CTransaction 主要序列化以下內(nèi)容:

image.png

總結(jié):

blk????.dat 文件首先寫入 4 byte 的 messageMagic叽掘,其次寫入 4 byte 的 block size,最后寫入 block 被序列化之后的數(shù)據(jù)玖雁。

image.png

rev?????.dat

存儲(chǔ) undoblock 序列化的數(shù)據(jù)更扁。

image.png

MessageStart 和 UndoBlockSize 與 Block 中的相同。

BlockUndo 序列化

BlockUndo 序列化只有 vtxundo 一個(gè)對(duì)象赫冬,vtxundo 是 CTxUndo 的一個(gè) vector 浓镜,對(duì)其進(jìn)行序列化操作如下:

image.png

CTxUndo 的序列化操作如下,其中 prevout 是一個(gè) Coin 的 vector:

image.png

Coin的序列化操作如下:

image.png

Coin包含兩部分內(nèi)容劲厌,代碼如下:

image.png

其中對(duì) TxOut 的序列化如下膛薛,對(duì) nValue 和 scriptPubKey 采用了不同的壓縮方式來(lái)進(jìn)行序列化:

image.png
BlockUndoCheckSum

具體代碼如下:

image.png

將 hashBlock 和 blockundo 的數(shù)據(jù)寫入 CHashWriter 的接口中,獲取 CHashWriter 的 hash 补鼻,并將 32 字節(jié)的 hash 值寫入 undofile 文件中哄啄。

總結(jié)

image.png

blk????.dat 和 rev????.dat 的區(qū)別:

blk???.dat 和 rev????.dat 所存儲(chǔ)的數(shù)據(jù)是不一樣的,block 存儲(chǔ)的是 block header 和 txs 序列化后的數(shù)據(jù)风范,undo block 存儲(chǔ)的是 txout 被序列化后的數(shù)據(jù)咨跌。

關(guān)于文件大小的一些問(wèn)題:

blk.dat 的默認(rèn)初始化大小是16M佛舱,最大為 128M惯殊, rev.dat 的默認(rèn)初始化大小為 1M。

image.png

在導(dǎo)入 block 時(shí)姑宽,會(huì)去檢查磁盤空間寇漫,必須大于 50M刊殉,否則就會(huì) Disk space is low

image.png

關(guān)于在 prune 時(shí)殉摔, 磁盤要求必須大于 550M:

image.png

bitcoin 要求必須保留 288 個(gè) block, 按每個(gè) block 1M 大小進(jìn)行計(jì)算冗澈, 需要 288M钦勘, 還需要額外的 15% 的空間去存儲(chǔ) UNDO 的數(shù)據(jù), 再加上以 20% 的孤塊率亚亲, 大約需要 397M 的空間彻采, 這是最低限度, 但我們還需要加上同步塊的數(shù)據(jù) blk.dat捌归, 需要128M肛响, 再加上約為 15% 的 undo data, 約為147M惜索。 所以整個(gè)需要 147M + 397M=544M特笋, 所以設(shè)置限度為 550M。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末巾兆,一起剝皮案震驚了整個(gè)濱河市猎物,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌角塑,老刑警劉巖蔫磨,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異圃伶,居然都是意外死亡堤如,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門窒朋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搀罢,“玉大人,你說(shuō)我怎么就攤上這事侥猩±浦粒” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵欺劳,是天一觀的道長(zhǎng)洛退。 經(jīng)常有香客問(wèn)我,道長(zhǎng)杰标,這世上最難降的妖魔是什么兵怯? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮腔剂,結(jié)果婚禮上媒区,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好袜漩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布绪爸。 她就那樣靜靜地躺著,像睡著了一般宙攻。 火紅的嫁衣襯著肌膚如雪奠货。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天座掘,我揣著相機(jī)與錄音递惋,去河邊找鬼。 笑死溢陪,一個(gè)胖子當(dāng)著我的面吹牛萍虽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播形真,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼杉编,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了咆霜?” 一聲冷哼從身側(cè)響起邓馒,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛾坯,沒(méi)想到半個(gè)月后光酣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偿衰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年挂疆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了改览。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片下翎。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宝当,靈堂內(nèi)的尸體忽然破棺而出视事,到底是詐尸還是另有隱情,我是刑警寧澤庆揩,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布俐东,位于F島的核電站,受9級(jí)特大地震影響订晌,放射性物質(zhì)發(fā)生泄漏虏辫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一锈拨、第九天 我趴在偏房一處隱蔽的房頂上張望砌庄。 院中可真熱鬧,春花似錦、人聲如沸娄昆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萌焰。三九已至哺眯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扒俯,已是汗流浹背奶卓。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陵珍,地道東北人寝杖。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像互纯,于是被迫代替她去往敵國(guó)和親瑟幕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容