如何解析Ethereum數(shù)據(jù):讀取LevelDB數(shù)據(jù)

舊文重發(fā),之前的文章被簡書封了添诉,迷之尷尬

感謝 Gary大佬的細(xì)心解答

之前轉(zhuǎn)過一篇文章 如何解析 Bitcoin 的數(shù)據(jù)潭陪,Bitcoin 將 p2p 網(wǎng)絡(luò)同步來的數(shù)據(jù),保存在了LevelDB數(shù)據(jù)庫中培己。我們可以通過 rpc 的方式請求數(shù)據(jù),這些請求來的數(shù)據(jù)是從LevelDB(以下簡稱 ldb)獲取的胚泌。如果我們能直接讀取 ldb 的數(shù)據(jù)省咨,就可以繞過 rpc請求,直接讀取诸迟。

為什么繞過 rpc 茸炒,直接讀取 ldb 呢

一個字 快,二個字真快阵苇。
我們可以通過 tcp 直接讀取 mysql 數(shù)據(jù),也可以通過
navicat這樣的 GUI 工具讀取感论。有時候為了快速讀取數(shù)據(jù)绅项,我們會舍棄GUI這樣美觀的工具,而用更直接的方式比肄。
直接讀取 Bitcoin 的 ldb 數(shù)據(jù)庫快耿, 就是舍棄了 美觀的 rpc 工具,顯而易見的好處是快芳绩。

由于 Bitcoin 使用 Ldb 保存區(qū)塊鏈數(shù)據(jù)掀亥,之后的很多鏈沿用了這個技術(shù)路線,Ethereum也同樣使用 Ldb 保存數(shù)據(jù)妥色。

什么是 LevelDB

Ldb是Google 工程師Jeff Dean和Sanjay Ghemawat開發(fā)的NoSQL 存儲引擎庫搪花,是現(xiàn)代分布式存儲領(lǐng)域的一枚原子彈。在它的基礎(chǔ)之上嘹害,F(xiàn)acebook 開發(fā)出了另一個 NoSQL 存儲引擎庫 RocksDB撮竿,沿用了 Ldb 的先進(jìn)技術(shù)架構(gòu)的同時還解決了 LevelDB 的一些短板。現(xiàn)代開源市場上有很多數(shù)據(jù)庫都在使用 RocksDB 作為底層存儲引擎笔呀,比如大名鼎鼎的 TiDB幢踏。

在使用 Ldb 時,我們可以將它看成一個 Key/Value 內(nèi)存數(shù)據(jù)庫许师。它提供了基礎(chǔ)的 Get/Set API房蝉,我們在代碼里可以通過這個 API 來讀寫數(shù)據(jù)僚匆。你還可以將它看成一個無限大小的高級 HashMap,我們可以往里面塞入無限條 Key/Value 數(shù)據(jù)搭幻,只要磁盤可以裝下咧擂。

Ldb有多種儲存結(jié)構(gòu),這些結(jié)構(gòu)并不是平坦的粗卜,而是分層組織(Level)的屋确,這也是LevelDB名字的來源。如下所示:

Ldb基本使用示例

Ethereum 使用最廣的開發(fā)版本是 go-eth续扔,我們自然用 golang做些基本的代碼事例气破。

// 讀或?qū)憯?shù)據(jù)庫
db, err := leveldb.OpenFile("path/db", nil)
...
defer db.Close()
...

///////////////////////
// 讀或?qū)憯?shù)據(jù)庫,返回數(shù)據(jù)結(jié)果不能修改
data, err := db.Get([]byte("key"), nil)
...
err = db.Put([]byte("key"), []byte("value"), nil)
...
err = db.Delete([]byte("key"), nil

///////////////////////
// 數(shù)據(jù)庫遍歷
iter := db.NewIterator(nil, nil)
for iter.Next() {
    key := iter.Key()
    value := iter.Value()
    ...
}
iter.Release()
err = iter.Error()
...

如何讀取 Eth 的 Ldb 數(shù)據(jù)呢辆它?

我們先研究下 v1.8.7版本缀去,這個版本有很多裸露的線索。在示例中讀取 ldb 需要有 key识脆,Eth有key 這種東西么设联?
https://github.com/ethereum/go-ethereum/blob/v1.8.7/core/database_util.go#L53

headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
numSuffix    = []byte("n") // headerPrefix + num (uint64 big endian) + numSuffix -> hash
bodyPrefix   = []byte("b") // bodyPrefix + num (uint64 big endian) + hash -> block body

從這句話中headerPrefix + num (uint64 big endian) + hash -> header 推斷 headerPrefix + x + y 組成了一個 key, 用代碼試試

// 先連接ldb數(shù)據(jù)庫
db, _ := leveldb.OpenFile("/mnt/eth/geth/chaindata", nil)

num := 46147 // 任意的區(qū)塊高度
blkNum := make([]byte, 8)
binary.BigEndian.PutUint64(blkNum, uint64(num))  // 把num變?yōu)?uint64 big endian類型的數(shù)據(jù)

hashKey := append(headerPrefix, blkNum...) // headerPrefix + blkNum
hashKey = append(hashKey, numSuffix...) // blkNum + headerPrefix + numSuffix

// 查找hashKey 對應(yīng)的 value
blkHash, _ := db.Get(hashKey, nil)

從 Ldb 讀取出blkHash 是真有數(shù)據(jù)灼捂, 替換不同的 num 得到的數(shù)據(jù)不同±肜現(xiàn)實中我是看 GetCanonicalHash函數(shù)得到的提示。

得到了 blkHash key , 下一步得到 headerKey key

headerKey := append(headerPrefix, blkNum...) // headerPrefix + blkNum
headerKey = append(headerKey, blkHash...)    // headerPrefix + blkNum + blkHash

blkHeaderData, _ := db.Get(headerKey, nil) // headerKey是新的key

_byteData := bytes.NewReader(blkHeaderData)

blkHeader := new(types.Header)
rlp.Decode(_byteData, blkHeader)

fmt.Printf("Block Hash: %x \n", blkHeader.Hash())
fmt.Printf("Block Coinbase: %x \n", blkHeader.Coinbase)

rlp.Decode(_byteData, blkHeader) 這里解釋下悉稠,如果大家讀Eth的一些文章宫蛆,ldb的保存數(shù)據(jù)是用rlp編碼后,這里是解碼為types.Header結(jié)構(gòu)的數(shù)據(jù)的猛。

更詳細(xì)代碼請看 ethLeveldb.go

------------------------------ 華麗麗的分割線-------------------------------

go-eth 在 2019 年 7 月推出了v1.9.x版本耀盗,在同步數(shù)據(jù)、讀取Ldb方面做了大量的封裝卦尊,使用起來更為方便了叛拷。 使用 syncmode=archive同步方式的時候, 同步時間從 62 天變成了 13 天岂却,5 倍 忿薇。

上面的代碼在讀取1.9.x版本數(shù)據(jù)的時候,讀取不成功淌友。
1.9.x在數(shù)據(jù)方面做了重新整理煌恢,大概有以下兩個非兼容的改動:

  1. 歷史的區(qū)塊鏈數(shù)據(jù)(header,body震庭, receipts等)被挪到一個flaten file存儲中瑰抵,因為這部分?jǐn)?shù)據(jù)已經(jīng)是不會更改的了

  2. 更改了部分?jǐn)?shù)據(jù)結(jié)構(gòu)的scheme,例如receipt器联。原先很多字段不需要存到db二汛,是可以在read之后重新計算出來的婿崭。這部分會占據(jù)大量的存儲空間,在1.9把這些字段刪去了肴颊。

有一個 inspect 命令氓栈,分別統(tǒng)計下 fast 、full 不同模式的數(shù)據(jù)庫詳細(xì)信息

geth inspect --datadir fastdata

+-----------------+--------------------+------------+
|    DATABASE     |      CATEGORY      |    SIZE    |
+-----------------+--------------------+------------+
| Key-Value store | Headers            | 211.40 KiB |
| Key-Value store | Bodies             | 44.00 B    |
| Key-Value store | Receipts           | 42.00 B    |
| Key-Value store | Difficulties       | 19.07 KiB  |
| Key-Value store | Block number->hash | 17.24 KiB  |
| Key-Value store | Block hash->number | 845.67 KiB |
| Key-Value store | Transaction index  | 0.00 B     |
| Key-Value store | Bloombit index     | 0.00 B     |
| Key-Value store | Trie nodes         | 4.79 MiB   |
| Key-Value store | Trie preimages     | 547.13 KiB |
| Key-Value store | Clique snapshots   | 0.00 B     |
| Key-Value store | Singleton metadata | 149.00 B   |
| Ancient store   | Headers            | 5.97 MiB   |
| Ancient store   | Bodies             | 851.64 KiB |
| Ancient store   | Receipts           | 182.32 KiB |
| Ancient store   | Difficulties       | 279.10 KiB |
| Ancient store   | Block number->hash | 769.77 KiB |
| Light client    | CHT trie nodes     | 0.00 B     |
| Light client    | Bloom trie nodes   | 0.00 B     |
+-----------------+--------------------+------------+
|                         TOTAL        | 14.40 MIB  |
+-----------------+--------------------+------------+
geth inspect --datadir fulldata

+-----------------+--------------------+------------+
|    DATABASE     |      CATEGORY      |    SIZE    |
+-----------------+--------------------+------------+
| Key-Value store | Headers            | 42.89 MiB  |
| Key-Value store | Bodies             | 8.90 MiB   |
| Key-Value store | Receipts           | 3.76 MiB   |
| Key-Value store | Difficulties       | 3.93 MiB   |
| Key-Value store | Block number->hash | 3.33 MiB   |
| Key-Value store | Block hash->number | 3.07 MiB   |
| Key-Value store | Transaction index  | 544.56 KiB |
| Key-Value store | Bloombit index     | 1.60 MiB   |
| Key-Value store | Trie nodes         | 4.03 MiB   |
| Key-Value store | Trie preimages     | 1.01 MiB   |
| Key-Value store | Clique snapshots   | 0.00 B     |
| Key-Value store | Singleton metadata | 139.00 B   |
| Ancient store   | Headers            | 6.00 B     |
| Ancient store   | Bodies             | 6.00 B     |
| Ancient store   | Receipts           | 6.00 B     |
| Ancient store   | Difficulties       | 6.00 B     |
| Ancient store   | Block number->hash | 6.00 B     |
| Light client    | CHT trie nodes     | 0.00 B     |
| Light client    | Bloom trie nodes   | 0.00 B     |
+-----------------+--------------------+------------+
|                         TOTAL        | 73.05 MIB  |
+-----------------+--------------------+------------+


+-----------------+--------------------+------------+
|    DATABASE     |      CATEGORY      |    SIZE    |
+-----------------+--------------------+------------+
| Key-Value store | Headers            | 50.70 MiB  |
| Key-Value store | Bodies             | 15.60 MiB  |
| Key-Value store | Receipts           | 6.51 MiB   |
| Key-Value store | Difficulties       | 4.74 MiB   |
| Key-Value store | Block number->hash | 3.93 MiB   |
| Key-Value store | Block hash->number | 6.77 MiB   |
| Key-Value store | Transaction index  | 3.03 MiB   |
| Key-Value store | Bloombit index     | 3.54 MiB   |
| Key-Value store | Trie nodes         | 6.83 MiB   |
| Key-Value store | Trie preimages     | 1.50 MiB   |
| Key-Value store | Clique snapshots   | 0.00 B     |
| Key-Value store | Singleton metadata | 139.00 B   |
| Ancient store   | Headers            | 23.94 MiB  |
| Ancient store   | Bodies             | 4.97 MiB   |
| Ancient store   | Receipts           | 1.43 MiB   |
| Ancient store   | Difficulties       | 1.12 MiB   |
| Ancient store   | Block number->hash | 3.01 MiB   |
| Light client    | CHT trie nodes     | 0.00 B     |
| Light client    | Bloom trie nodes   | 0.00 B     |
+-----------------+--------------------+------------+
|                         TOTAL        | 137.62 MIB |
+-----------------+--------------------+------------+

這里的flaten file存儲婿着,其實是把歷史數(shù)據(jù)挪到了 ancient 文件夾授瘦,不在用 Ldb,而用普通的二進(jìn)制格式儲存數(shù)據(jù)竟宋。

.
├── 000034.ldb
├── MANIFEST-000162
└── ancient
    ├── 000006.log
    ├── bodies.0000.cdat
    ├── bodies.cidx
    ├── diffs.0000.rdat
    ├── diffs.ridx
    ├── hashes.0000.rdat
    ├── hashes.ridx
    ├── headers.0000.cdat
    ├── headers.cidx
    ├── receipts.0000.cdat
    └── receipts.cidx

那讀取是不是更麻煩了呢提完?非也

dbPath      = "/mnt/eth/geth/chaindata"
ancientPath = dbPath + "/ancient" // 必須是絕對路徑

ancientDb, _ := rawdb.NewLevelDBDatabaseWithFreezer(dbPath, 16, 1, ancientPath, "")
 
for i := 1; i <= 10; i++ {
    // ReadCanonicalHash retrieves the hash assigned to a canonical block number.
    blkHash := rawdb.ReadCanonicalHash(ancientDb, uint64(i))

    if blkHash == (common.Hash{}) {
        fmt.Printf("i: %v\n", i)
    } else {
        fmt.Printf("blkHash: %x\n", blkHash)
    }

    // ReadBody retrieves the block body corresponding to the hash.
    blkHeader := rawdb.ReadHeader(ancientDb, blkHash, uint64(i))
    fmt.Printf("blkHeader Coinbase: 0x%x\n", blkHeader.Coinbase)
}

更詳細(xì)代碼請看newEthLeveldb.go

通過LevelDB讀取的數(shù)據(jù)

本人1 年前用了 1 個月時間追到了 500w 高度。現(xiàn)在看起來真香丘侠,所以做人要淡定啊徒欣, 只要你熬得夠久,你就是藝術(shù)家了啊蜗字。


參考:

http://qyuan.top/2019/10/12/mpt-2/
https://golangnote.com/topic/81.html
https://johng.cn/leveldb-intro/
https://catkang.github.io/2017/01/07/leveldb-summary.html
https://juejin.im/post/5c22e049e51d45206d12568e
https://draveness.me/bigtable-leveldb.html
https://blog.ethereum.org/2019/07/10/geth-v1-9-0/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末打肝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子挪捕,更是在濱河造成了極大的恐慌粗梭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件级零,死亡現(xiàn)場離奇詭異楼吃,居然都是意外死亡,警方通過查閱死者的電腦和手機妄讯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酷宵,“玉大人亥贸,你說我怎么就攤上這事〗娇眩” “怎么了炕置?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長男韧。 經(jīng)常有香客問我朴摊,道長,這世上最難降的妖魔是什么此虑? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任甚纲,我火速辦了婚禮,結(jié)果婚禮上朦前,老公的妹妹穿的比我還像新娘介杆。我一直安慰自己鹃操,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布春哨。 她就那樣靜靜地躺著荆隘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赴背。 梳的紋絲不亂的頭發(fā)上椰拒,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音凰荚,去河邊找鬼燃观。 笑死,一個胖子當(dāng)著我的面吹牛浇揩,可吹牛的內(nèi)容都是我干的仪壮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼胳徽,長吁一口氣:“原來是場噩夢啊……” “哼积锅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起养盗,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缚陷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后往核,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箫爷,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年聂儒,在試婚紗的時候發(fā)現(xiàn)自己被綠了虎锚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡衩婚,死狀恐怖窜护,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情非春,我是刑警寧澤柱徙,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奇昙,受9級特大地震影響护侮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜储耐,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一羊初、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弧岳,春花似錦凳忙、人聲如沸业踏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勤家。三九已至,卻和暖如春柳恐,著一層夾襖步出監(jiān)牢的瞬間伐脖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工乐设, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讼庇,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓近尚,卻偏偏與公主長得像蠕啄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子戈锻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

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