日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[以太坊源代码分析] II. 数据的呈现和组织,缓存和更新

發(fā)布時(shí)間:2025/3/15 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [以太坊源代码分析] II. 数据的呈现和组织,缓存和更新 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在Ethereum的世界里,數(shù)據(jù)的最終存儲(chǔ)形式是[k,v]鍵值對(duì),目前使用的[k,v]型底層數(shù)據(jù)庫是LevelDB;所有與交易,操作相關(guān)的數(shù)據(jù),其呈現(xiàn)的集合形式是Block(Header);如果以Block為單位鏈接起來,則構(gòu)成更大粒度的BlockChain(HeaderChain);若以Block作切割,那么Transaction和Contract就是更小的粒度;所有交易或操作的結(jié)果,將以各個(gè)個(gè)體賬戶的狀態(tài)(state)存在,賬戶的呈現(xiàn)形式是stateObject,所有賬戶的集合受StateDB管理。下圖描繪了上述各數(shù)據(jù)單元的層次關(guān)系:


另一方面,上述數(shù)據(jù)單元如Block,stateObject,StateDB等,均大量使用Merkle-PatriciaTrie(MPT)數(shù)據(jù)結(jié)構(gòu)以組織和管理[k,v]型數(shù)據(jù)。利用MPT高效的分段哈希驗(yàn)證機(jī)制和靈活的節(jié)點(diǎn)(Node)插入/載入設(shè)計(jì),調(diào)用方均可快速且高效的實(shí)現(xiàn)對(duì)數(shù)據(jù)的插入、刪除、更新、壓縮和加密。以下各章節(jié)會(huì)對(duì)以上內(nèi)容分別展開詳細(xì)介紹。

1. Block和Header

Block(區(qū)塊)是Ethereum的核心數(shù)據(jù)結(jié)構(gòu)之一。所有賬戶的相關(guān)活動(dòng),以交易(Transaction)的格式存儲(chǔ),每個(gè)Block有一個(gè)交易對(duì)象的列表;每個(gè)交易的執(zhí)行結(jié)果,由一個(gè)Receipt對(duì)象與其包含的一組Log對(duì)象記錄;所有交易執(zhí)行完后生成的Receipt列表,存儲(chǔ)在Block中(經(jīng)過壓縮加密)。不同Block之間,通過前向指針ParentHash一個(gè)一個(gè)串聯(lián)起來成為一個(gè)單向鏈表,BlockChain 結(jié)構(gòu)體管理著這個(gè)鏈表。

Block結(jié)構(gòu)體基本可分為Header和Body兩個(gè)部分,其UML關(guān)系族如下圖所示:


Header部分

Header是Block的核心,注意到它的成員變量全都是公共的,這使得它可以很方便的向調(diào)用者提供關(guān)于Block屬性的操作。Header的成員變量全都很重要,值得細(xì)細(xì)理解:

  • ParentHash:指向父區(qū)塊(parentBlock)的指針。除了創(chuàng)世塊(Genesis Block)外,每個(gè)區(qū)塊有且只有一個(gè)父區(qū)塊。
  • Coinbase:挖掘出這個(gè)區(qū)塊的作者地址。在每次執(zhí)行交易時(shí)系統(tǒng)會(huì)給與一定補(bǔ)償?shù)腅ther,這筆金額就是發(fā)給這個(gè)地址的。
  • UncleHash:Block結(jié)構(gòu)體的成員uncles的RLP哈希值。uncles是一個(gè)Header數(shù)組,它的存在,頗具匠心。
  • Root:StateDB中的“state Trie”的根節(jié)點(diǎn)的RLP哈希值。Block中,每個(gè)賬戶以stateObject對(duì)象表示,賬戶以Address為唯一標(biāo)示,其信息在相關(guān)交易(Transaction)的執(zhí)行中被修改。所有賬戶對(duì)象可以逐個(gè)插入一個(gè)Merkle-PatricaTrie(MPT)結(jié)構(gòu)里,形成“state Trie”。
  • TxHash: Block中 “tx Trie”的根節(jié)點(diǎn)的RLP哈希值。Block的成員變量transactions中所有的tx對(duì)象,被逐個(gè)插入一個(gè)MPT結(jié)構(gòu),形成“tx Trie”。
  • ReceiptHash:Block中的 "Receipt Trie”的根節(jié)點(diǎn)的RLP哈希值。Block的所有Transaction執(zhí)行完后會(huì)生成一個(gè)Receipt數(shù)組,這個(gè)數(shù)組中的所有Receipt被逐個(gè)插入一個(gè)MPT結(jié)構(gòu)中,形成"Receipt Trie"。
  • Bloom:Bloom過濾器(Filter),用來快速判斷一個(gè)參數(shù)Log對(duì)象是否存在于一組已知的Log集合中。
  • Difficulty:區(qū)塊的難度。Block的Difficulty由共識(shí)算法基于parentBlock的Time和Difficulty計(jì)算得出,它會(huì)應(yīng)用在區(qū)塊的‘挖掘’階段。
  • Number:區(qū)塊的序號(hào)。Block的Number等于其父區(qū)塊Number +1。
  • Time:區(qū)塊“應(yīng)該”被創(chuàng)建的時(shí)間。由共識(shí)算法確定,一般來說,要么等于parentBlock.Time + 10s,要么等于當(dāng)前系統(tǒng)時(shí)間。
  • GasLimit:區(qū)塊內(nèi)所有Gas消耗的理論上限。該數(shù)值在區(qū)塊創(chuàng)建時(shí)設(shè)置,與父區(qū)塊有關(guān)。具體來說,根據(jù)父區(qū)塊的GasUsed同GasLimit * 2/3的大小關(guān)系來計(jì)算得出。
  • GasUsed:區(qū)塊內(nèi)所有Transaction執(zhí)行時(shí)所實(shí)際消耗的Gas總和。
  • Nonce:一個(gè)64bit的哈希數(shù),它被應(yīng)用在區(qū)塊的"挖掘"階段,并且在使用中會(huì)被修改。

Merkle-PatriciaTrie(MPT)是Ethereum用來存儲(chǔ)區(qū)塊數(shù)據(jù)的核心數(shù)據(jù)結(jié)構(gòu)。最簡單理解是一個(gè)倒置的樹形結(jié)構(gòu),每個(gè)節(jié)點(diǎn)可能有若干個(gè)子節(jié)點(diǎn),關(guān)于MPT在Ethereum中的實(shí)現(xiàn)細(xì)節(jié)在下文有專門介紹。

Root,TxHash和ReceiptHash,分別取自三個(gè)MPT類型對(duì)象:stateTrie, txTrie, 和receiptTrie的根節(jié)點(diǎn)哈希值。用一個(gè)32byte的哈希值,來代表一個(gè)有若干節(jié)點(diǎn)的樹形結(jié)構(gòu)(或若干元素的數(shù)組),這是為了加密。比如在Block的同步過程中,通過比對(duì)收到的TxHash,可以確認(rèn)數(shù)組成員transactions是否同步完整。

三者當(dāng)中,TxHash和ReceiptHash的生成稍微特殊一點(diǎn),因?yàn)檫@兩的數(shù)據(jù)來源是數(shù)組,而不像stateTrie原本就存在。如何將數(shù)組轉(zhuǎn)化成MPT結(jié)構(gòu)?考慮到MPT專門存儲(chǔ)[k,v]類型數(shù)據(jù),代碼里利用了點(diǎn)小技巧:將數(shù)組中每個(gè)元素的索引作為k,該元素的RLP編碼值作為v,組成一個(gè)[k,v]鍵值對(duì)作為一個(gè)節(jié)點(diǎn),這樣所有數(shù)組元素作為節(jié)點(diǎn)逐個(gè)插入一個(gè)初始化為空的MPT,形成MPT結(jié)構(gòu)。

在stateTrie,txTrie,receiptTrie這三個(gè)MPT結(jié)構(gòu)的產(chǎn)生時(shí)間上,receiptTrie 必須在Block的所有交易執(zhí)行完成才能生成;txTrie 理論上只需tx數(shù)組transactions即可,不過依然被限制在所有交易執(zhí)行完后才生成;最有趣的是stateTrie,由于它存儲(chǔ)了所有賬戶的信息,比如余額,發(fā)起交易次數(shù),虛擬機(jī)指令數(shù)組等等,所以隨著每次交易的執(zhí)行,stateTrie 其實(shí)一直在變化,這就使得Root值也在變化中。于是StateDB 定義了一個(gè)函數(shù)IntermediateRoot(),用來生成那一時(shí)刻的Root值:

[plain]?view plain?copy
  • //?core/state/statedb.go??
  • func?(s?*StateDB)?IntermediateRoot(deleteEmptyObjects?bool)?common.Hash??
  • 這個(gè)函數(shù)的返回值,代表了所有賬戶信息的一個(gè)即時(shí)狀態(tài)

    關(guān)于Header.Root的生成時(shí)間,在上篇帖子提到的交易執(zhí)行過程中,交易執(zhí)行的入口函數(shù)StateProcessor.Process()在返回前調(diào)用了Engine.Finalize()。正是這個(gè)Finalize(),在內(nèi)部調(diào)用上述IntermediateRoot()函數(shù)并賦值給header.Root。所以Root值就是在該區(qū)塊所有交易完成后,所有賬戶信息的即時(shí)狀態(tài)。

    Body結(jié)構(gòu)體

    Block的成員變量td?表示的是整個(gè)區(qū)塊鏈表從源頭創(chuàng)世塊開始,到當(dāng)前區(qū)塊截止,累積的所有區(qū)塊Difficulty之和,td 取名totalDifficulty。從概念上可知,某個(gè)區(qū)塊與父區(qū)塊的td之差,就等于該區(qū)塊Header帶有的Difficulty值。

    Body可以理解為Block里的數(shù)組成員集合,它相對(duì)于Header需要更多的內(nèi)存空間,所以在數(shù)據(jù)傳輸和驗(yàn)證時(shí),往往與Header是分開進(jìn)行的。

    Uncles是Body非常特別的一個(gè)成員,從業(yè)務(wù)功能上說,它并不是Block結(jié)構(gòu)體必須的,它的出現(xiàn)當(dāng)然會(huì)占用整個(gè)Block計(jì)算哈希值時(shí)更長的時(shí)間,目的是為了抵消整個(gè)Ethereum網(wǎng)絡(luò)中那些計(jì)算能力特別強(qiáng)大的節(jié)點(diǎn)會(huì)對(duì)區(qū)塊的產(chǎn)生有過大的影響力,防止這些節(jié)點(diǎn)破壞“去中心化”這個(gè)根本宗旨。官方描述可見ethereum-wiki

    Block的唯一標(biāo)識(shí)符

    同Ethereum世界里的其他對(duì)象類似,Block對(duì)象的唯一標(biāo)識(shí)符,就是它的(RLP)哈希值。需要注意的是,Block的哈希值,等于其Header成員的(RLP)哈希值

    [plain]?view plain?copy
  • //?core/types/Block.go??
  • func?(b?*Block)?Hash()?common.Hash?{??
  • ????if?hash?:=?b.hash.Load();?hash?!=?nil?{??
  • ????????return?hash.(common.Hash)??
  • ????}??
  • ????v?:=?b.header.Hash()??
  • ????b.hash.Store(v)??
  • ????return?v??
  • }??
  • func?(h?*Header)?Hash()?common.Hash?{??
  • ????return?rlpHash(h)??
  • }??
  • 小技巧:Block的成員hash會(huì)緩存上一次Header計(jì)算出的哈希值,以避免不必要的計(jì)算。

    Block的哈希值等于其Header的(RLP)哈希值,這就從根本上明確了Block(結(jié)構(gòu)體)和Header表示的是同一個(gè)區(qū)塊對(duì)象。考慮到這兩種結(jié)構(gòu)體所占內(nèi)存空間的差異,這種設(shè)計(jì)可以帶來很多便利。比如在數(shù)據(jù)傳輸時(shí),完全可以先傳輸Header對(duì)象,驗(yàn)證通過后再傳輸Block對(duì)象,收到后還可以利用二者的成員哈希值做相互驗(yàn)證。

    成員分散存儲(chǔ)在底層數(shù)據(jù)庫

    Header和Block的主要成員變量,最終還是要存儲(chǔ)在底層數(shù)據(jù)庫中。Ethereum 選用的是LevelDB, 屬于非關(guān)系型數(shù)據(jù)庫,存儲(chǔ)單元是[k,v]鍵值對(duì)。我們來看看具體的存儲(chǔ)方式(core/database_util.go)

    keyvalue
    'h' + num + hashheader's RLP raw data
    'h' + num + hash + 't'td
    'h' + num + 'n'hash
    'H' + hashnum
    'b' + num + hashbody's RLP raw data
    'r' + num + hashreceipts RLP
    'l' + hashtx/receipt lookup metadata

    這里的hash就是該Block(或Header)對(duì)象的RLP哈希值,在代碼中也被稱為canonical hash;num是Number的uint64類型,大端(big endian)整型數(shù)。可以發(fā)現(xiàn),num 和 hash是key中出現(xiàn)最多的成分;同時(shí)num和hash還分別作為value被單獨(dú)存儲(chǔ),而每當(dāng)此時(shí)則另一方必組成key。這些信息都在強(qiáng)烈的暗示,num(Number)和hash是Block最為重要的兩個(gè)屬性:num用來確定Block在整個(gè)區(qū)塊鏈中所處的位置,hash用來辨識(shí)惟一的Block/Header對(duì)象

    通過以上的設(shè)計(jì),Block結(jié)構(gòu)體的所有重要成員,都被存儲(chǔ)進(jìn)了底層數(shù)據(jù)庫。當(dāng)所有Block對(duì)象的信息都已經(jīng)寫進(jìn)數(shù)據(jù)庫后,我們就可以使用BlockChain結(jié)構(gòu)體來處理整個(gè)塊鏈。

    2. HeaderChain和BlockChain

    BlockChain結(jié)構(gòu)體被用來管理整個(gè)區(qū)塊單向鏈表,在一個(gè)Ethereum客戶端軟件(比如錢包)中,只會(huì)有一個(gè)BlockChain對(duì)象存在。同Block/Header的關(guān)系類似,BlockChain還有一個(gè)成員變量類型是HeaderChain, 用來管理所有Header組成的單向鏈表。當(dāng)然,HeaderChain在全局范圍內(nèi)也僅有一個(gè)對(duì)象,并被BlockChain持有(準(zhǔn)確說是HeaderChain只會(huì)被BlockChain和LightChain持有,LightChain類似于BlockChain,但默認(rèn)只處理Headers,不過依然可以下載bodies和receipts)。它們的UML關(guān)系圖如下所示:


    在結(jié)構(gòu)體的設(shè)計(jì)上,BlockChain 同HeadeChain有諸多類似之處。比如二者都有相同的ChainConfig對(duì)象,有相同的Database接口行為變量以提供[k,v]數(shù)據(jù)的讀取和寫入;BlockChain 有成員genesisBlock和currentBlock,分別對(duì)應(yīng)創(chuàng)世塊和當(dāng)前塊,而HeaderChain則有g(shù)enesisHeader和currentHeader;BlockChain 有bodyCache,blockCache 等成員用以緩存高頻調(diào)用對(duì)象,而HeaderChain則有headerCache, tdCache, numberCache等緩存成員變量。除此之外,BlockChain 相對(duì)于HeaderChain主要增多了Processor和Validator兩個(gè)接口行為變量,前者用以執(zhí)行所有交易對(duì)象,后者可以驗(yàn)證諸如Body等數(shù)據(jù)成員的有效性。

    Engine是共識(shí)算法定義的行為接口。共識(shí)算法是整個(gè)數(shù)字貨幣體系最重要的概念之一,它在理論上的完整性,有力的支撐了“去中心化”這個(gè)偉大設(shè)想的實(shí)現(xiàn)。落實(shí)在代碼層面,consensus.Engine就是Ethereum系統(tǒng)里共識(shí)算法的一個(gè)主要行為接口,它基于符合某種共識(shí)算法規(guī)范的算法庫,提供包括VerifyHeaders(),VerifyUncles()等一系列涉及到數(shù)據(jù)合法性的關(guān)鍵函數(shù)。不僅僅BlockChain和HeaderChain結(jié)構(gòu)體,在Ethereum系統(tǒng)里,所有跟驗(yàn)證區(qū)塊對(duì)象相關(guān)的操作都會(huì)調(diào)用Engine行為接口來完成。目前存在兩種共識(shí)算法規(guī)范,一種是基于運(yùn)算能力(proof-of-work),叫Ethash;另一種基于某個(gè)投票機(jī)制(proof-of-authority),叫Clique。具體內(nèi)容在之后的文章中會(huì)有更多展開。

    區(qū)塊鏈的操作

    從邏輯上講,既然BlockChain和HeaderChain都管理著一個(gè)類似單向鏈表的結(jié)構(gòu),那么它們提供的操作方法肯定包括插入,刪除,和查找

    查找比較簡單,以BlockChain為例,它有一個(gè)成員currentBlock,指向當(dāng)前最新的Block,而HeaderChain也有一個(gè)類似的成員currentHeader。除此之外,底層數(shù)據(jù)庫里還分別存有當(dāng)前最新Block和Header的canonical hash:

    keyvalue
    "LastHeader"hash
    "LastBlock"hash
    "LastFast"hash
    這里“LastFast”所存儲(chǔ)的是在一種特別的同步方式FastSync下,最新Block的canonical hash。FastSync相比于FullSync,可以僅僅同步Header而不考慮Body,故此得名Fast。

    以BlockChain為例,通過"LastBlock"為key從數(shù)據(jù)庫中獲取最新的Block之后,用num逐一遍歷,得到目標(biāo)Block的num后,用'h'+num+'n'作key,就可以從數(shù)據(jù)庫中獲取目標(biāo)canonical hash。

    插入和刪除。區(qū)塊鏈跟普通單向鏈表有一點(diǎn)非常明顯的不同,在于Header的前向指針ParentHash是不能修改的,即當(dāng)前區(qū)塊的父區(qū)塊是不能修改的。所以在插入的實(shí)現(xiàn)中,當(dāng)決定寫入一個(gè)新的Header進(jìn)底層數(shù)據(jù)庫時(shí),從這個(gè)Header開始回溯,要保證它的parent,以及parent的parent等等,都已經(jīng)寫入數(shù)據(jù)庫了。只有這樣,才能確保從創(chuàng)世塊(num為0)起始,直到當(dāng)前新寫入的區(qū)塊,整個(gè)鏈?zhǔn)浇Y(jié)構(gòu)是完整的,沒有中斷或分叉。刪除的情形也類似,要從num最大的區(qū)塊開始,逐步回溯。在BlockChain的操作里,刪除一般是伴隨著插入出現(xiàn)的,即當(dāng)需要插入新區(qū)塊時(shí),才可能有舊的區(qū)塊需要被刪除,這種情形在代碼里被稱為reorg。

    3. 精巧的Merkle-PatriciaTrie

    Ethereum 使用的Merkle-PatriciaTrie(MPT)結(jié)構(gòu),源自于Trie結(jié)構(gòu),又分別繼承了PatriciaTrie和MerkleTree的優(yōu)點(diǎn),并基于內(nèi)部數(shù)據(jù)的特性,設(shè)計(jì)了全新的節(jié)點(diǎn)體系和插入/載入機(jī)制。

    Trie,又稱為字典樹或者前綴樹(prefix tree),屬于查找樹的一種。它與平衡二叉樹的主要不同點(diǎn)包括:每個(gè)節(jié)點(diǎn)數(shù)據(jù)所攜帶的key不會(huì)存儲(chǔ)在Trie的節(jié)點(diǎn)中,而是通過該節(jié)點(diǎn)在整個(gè)樹形結(jié)構(gòu)里位置來體現(xiàn);同一個(gè)父節(jié)點(diǎn)的子節(jié)點(diǎn),共享該父節(jié)點(diǎn)的key作為它們各自key的前綴,因此根節(jié)點(diǎn)key為空;待存儲(chǔ)的數(shù)據(jù)只存于葉子節(jié)點(diǎn)中,非葉子節(jié)點(diǎn)幫助形成葉子節(jié)點(diǎn)key的前綴。下圖來自wiki-Trie,展示了一個(gè)簡單的Trie結(jié)構(gòu)。


    PatriciaTrie,又被稱為RadixTree或緊湊前綴樹(compact prefix tree),是一種空間使用率經(jīng)過優(yōu)化的Trie。與Trie不同的是,PatriciaTrie里如果存在一個(gè)父節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn),那么這個(gè)父節(jié)點(diǎn)將與其子節(jié)點(diǎn)合并。這樣可以縮短Trie中不必要的深度,大大加快搜索節(jié)點(diǎn)速度。

    MerkleTree,也叫哈希樹(hash tree),是密碼學(xué)的一個(gè)概念,注意理論上它不一定是Trie。在哈希樹中,葉子節(jié)點(diǎn)的標(biāo)簽是它所關(guān)聯(lián)數(shù)據(jù)塊的哈希值,而非葉子節(jié)點(diǎn)的標(biāo)簽是它的所有子節(jié)點(diǎn)的標(biāo)簽拼接而成字符串的哈希值。哈希樹的優(yōu)勢在于,它能夠?qū)Υ罅康臄?shù)據(jù)內(nèi)容迅速作出高效且安全的驗(yàn)證。假設(shè)一個(gè)hash tree中有n個(gè)葉子節(jié)點(diǎn),如果想要驗(yàn)證其中一個(gè)葉子節(jié)點(diǎn)是否正確-即該節(jié)點(diǎn)數(shù)據(jù)屬于源數(shù)據(jù)集合并且數(shù)據(jù)本身完整,所需哈希計(jì)算的時(shí)間復(fù)雜度是是O(log(n)),相比之下hash list大約需要時(shí)間復(fù)雜度O(n)的哈希計(jì)算,hash tree的表現(xiàn)無疑是優(yōu)秀的。


    上圖來自wiki-MerkleTree,展示了一個(gè)簡單的二叉哈希樹。四個(gè)有效數(shù)據(jù)塊L1-L4,分別被關(guān)聯(lián)到一個(gè)葉子節(jié)點(diǎn)上。Hash0-0和Hash0-1分別等于數(shù)據(jù)塊L1和L2的哈希值,而Hash0則等于Hash0-0和Hash0-1二者拼接成的新字符串的哈希值,依次類推,根節(jié)點(diǎn)的標(biāo)簽topHash等于Hash0和Hash1二者拼接成的新字符串的哈希值。

    哈希樹最主要的應(yīng)用場景是p2p網(wǎng)絡(luò)中的數(shù)據(jù)傳輸。因?yàn)閜2p網(wǎng)絡(luò)中可能存在未知數(shù)目的不可信數(shù)據(jù)源,所以確保下載到的數(shù)據(jù)正確可信并且無損壞無改動(dòng),就顯得非常重要。哈希樹可用來解決這個(gè)問題:每個(gè)待下載文件按照某種方式分割成若干小塊后,組成類似上圖的哈希樹。首先從一個(gè)絕對(duì)可信的數(shù)據(jù)源獲取該文件對(duì)應(yīng)哈希樹的根節(jié)點(diǎn)哈希值(top hash),有了這個(gè)可靠的top hash后,就可以開始從整個(gè)p2p網(wǎng)絡(luò)下載文件。不同的數(shù)據(jù)部分可以從不同的源下載,由于哈希樹中任意的分支樹都可以單獨(dú)驗(yàn)證哈希值,所以一旦發(fā)現(xiàn)任何數(shù)據(jù)部分無法通過驗(yàn)證,都可以切換到其他數(shù)據(jù)源進(jìn)行下載那部分?jǐn)?shù)據(jù)。最終,完整下載文件所對(duì)應(yīng)哈希樹的top hash值,一定要與我們的可靠top hash相等。

    Merkle-Patricia Trie(MPT)的實(shí)現(xiàn)

    MPT是Ethereum自定義的Trie型數(shù)據(jù)結(jié)構(gòu)。在代碼中,trie.Trie結(jié)構(gòu)體用來管理一個(gè)MPT結(jié)構(gòu),其中每個(gè)節(jié)點(diǎn)都是行為接口Node的實(shí)現(xiàn)類。下圖是Trie結(jié)構(gòu)體和node接口族的UML關(guān)系圖:


    在Trie結(jié)構(gòu)體中,成員root始終作為整個(gè)MPT的根節(jié)點(diǎn);originalRoot的作用是在創(chuàng)建Trie對(duì)象時(shí)承接入?yún)ashNode;cacheGen是cache次數(shù)的計(jì)數(shù)器,每次Trie的變動(dòng)提交后(寫入的對(duì)象可由外部參數(shù)傳入),cacheGen自增1。Trie結(jié)構(gòu)體提供包括對(duì)節(jié)點(diǎn)的插入、刪除、更新,所有節(jié)點(diǎn)改動(dòng)的提交(寫入到傳入?yún)?shù)),以及返回整個(gè)MPT的哈希值。

    node接口族擔(dān)當(dāng)整個(gè)MPT中的各種節(jié)點(diǎn),node接口分四種實(shí)現(xiàn): fullNode,shortNode,valueNode,hashNode,其中只有fullNode和shortNode可以帶有子節(jié)點(diǎn)。

    fullNode?是一個(gè)可以攜帶多個(gè)子節(jié)點(diǎn)的父(枝)節(jié)點(diǎn)。它有一個(gè)容量為17的node數(shù)組成員變量Children,數(shù)組中前16個(gè)空位分別對(duì)應(yīng)16進(jìn)制(hex)下的0-9a-f,這樣對(duì)于每個(gè)子節(jié)點(diǎn),根據(jù)其key值16進(jìn)制形式下的第一位的值,就可掛載到Children數(shù)組的某個(gè)位置,fullNode本身不再需要額外key變量;Children數(shù)組的第17位,留給該fullNode的數(shù)據(jù)部分。fullNode明顯繼承了原生trie的特點(diǎn),而每個(gè)父節(jié)點(diǎn)最多擁有16個(gè)分支也包含了基于總體效率的考量。

    shortNode?是一個(gè)僅有一個(gè)子節(jié)點(diǎn)的父(枝)節(jié)點(diǎn)。它的成員變量Val指向一個(gè)子節(jié)點(diǎn),而成員Key是一個(gè)任意長度的字符串(字節(jié)數(shù)組[]byte)。顯然shortNode的設(shè)計(jì)體現(xiàn)了PatriciaTrie的特點(diǎn),通過合并只有一個(gè)子節(jié)點(diǎn)的父節(jié)點(diǎn)和其子節(jié)點(diǎn)來縮短trie的深度,結(jié)果就是有些節(jié)點(diǎn)會(huì)有長度更長的key。

    valueNode?充當(dāng)MPT的葉子節(jié)點(diǎn)。它其實(shí)是字節(jié)數(shù)組[]byte的一個(gè)別名,不帶子節(jié)點(diǎn)。在使用中,valueNode就是所攜帶數(shù)據(jù)部分的RLP哈希值,長度32byte,數(shù)據(jù)的RLP編碼值作為valueNode的匹配項(xiàng)存儲(chǔ)在數(shù)據(jù)庫里。

    這三種類型覆蓋了一個(gè)普通Trie(也許是PatriciaTrie)的所有節(jié)點(diǎn)需求。任何一個(gè)[k,v]類型數(shù)據(jù)被插入一個(gè)MPT時(shí),會(huì)以k字符串為路徑沿著root向下延伸,在此次插入結(jié)束時(shí)首先成為一個(gè)valueNode,k會(huì)以自頂點(diǎn)root起到到該節(jié)點(diǎn)止的key path形式存在。但之后隨著其他節(jié)點(diǎn)的不斷插入和刪除,根據(jù)MPT結(jié)構(gòu)的要求,原有節(jié)點(diǎn)可能會(huì)變化成其他node實(shí)現(xiàn)類型,同時(shí)MPT中也會(huì)不斷裂變或者合并出新的(父)節(jié)點(diǎn)。比如:

    • 假設(shè)一個(gè)shortNode S已經(jīng)有一個(gè)子節(jié)點(diǎn)A,現(xiàn)在要新插入一個(gè)子節(jié)點(diǎn)B,那么會(huì)有兩種可能,要么新節(jié)點(diǎn)B沿著A的路徑繼續(xù)向下,這樣S的子節(jié)點(diǎn)會(huì)被更新;要么S的Key分裂成兩段,前一段分配給S作為新的Key,同時(shí)裂變出一個(gè)新的fullNode作為S的子節(jié)點(diǎn),以同時(shí)容納B,以及需要更新的A。
    • 如果一個(gè)fullNode原本只有兩個(gè)子節(jié)點(diǎn),現(xiàn)在要?jiǎng)h除其中一個(gè)子節(jié)點(diǎn),那么這個(gè)fullNode就會(huì)退化為shortNode,同時(shí)保留的子節(jié)點(diǎn)如果是shortNode,還可以跟它再合并。
    • 如果一個(gè)shortNode的子節(jié)點(diǎn)是葉子節(jié)點(diǎn)同時(shí)又被刪除了,那么這個(gè)shortNode就會(huì)退化成一個(gè)valueNode,成為一個(gè)葉子節(jié)點(diǎn)。

    諸如此類的情形還有很多,提前設(shè)想過這些案例,才能正確實(shí)現(xiàn)MPT的插入/刪除/查找等操作。當(dāng)然,所有查找樹(search tree)結(jié)構(gòu)的操作,免不了用到遞歸。

    特殊的那個(gè) - hashNode

    hashNode 跟valueNode一樣,也是字符數(shù)組[]byte的一個(gè)別名,同樣存放32byte的哈希值,也沒有子節(jié)點(diǎn)。不同的是,hashNode是fullNode或者shortNode對(duì)象的RLP哈希值,所以它跟valueNode在使用上有著莫大的不同。

    在MPT中,hashNode幾乎不會(huì)單獨(dú)存在(有時(shí)遍歷遇到一個(gè)hashNode往往因?yàn)樵镜膎ode被折疊了),而是以nodeFlag結(jié)構(gòu)體的成員(nodeFlag.hash)的形式,被fullNode和shortNode間接持有。一旦fullNode或shortNode的成員變量(包括子結(jié)構(gòu))發(fā)生任何變化,它們的hashNode就一定需要更新。所以在trie.Trie結(jié)構(gòu)體的insert(),delete()等函數(shù)實(shí)現(xiàn)中,可以看到除了新創(chuàng)建的fullNode、shortNode,那些子結(jié)構(gòu)有所改變的fullNode、shortNode的nodeFlag成員也會(huì)被重設(shè),hashNode會(huì)被清空。在下次trie.Hash()調(diào)用時(shí),整個(gè)MPT自底向上的遍歷過程中,所有清空的hashNode會(huì)被重新賦值。這樣trie.Hash()結(jié)束后,我們可以得到一個(gè)根節(jié)點(diǎn)root的hashNode,它就是此時(shí)此刻這個(gè)MPT結(jié)構(gòu)的哈希值。上文中提到的,Block的成員變量Root、TxHash、ReceiptHash的生成,正是源出于此。

    明顯的,hashNode體現(xiàn)了MerkleTree的特點(diǎn):每個(gè)父節(jié)點(diǎn)的哈希值來源于所有子節(jié)點(diǎn)哈希值的組合,一個(gè)頂點(diǎn)的哈希值能夠代表一整個(gè)樹形結(jié)構(gòu)。valueNode加上之前的fullNode,shortNode,valueNode,構(gòu)成了一個(gè)完整的Merkle-PatriciaTrie結(jié)構(gòu),很好的融合了各種原型結(jié)構(gòu)的優(yōu)點(diǎn),又根據(jù)Ethereum系統(tǒng)的實(shí)際情況,作了實(shí)際的優(yōu)化和平衡。MPT這個(gè)數(shù)據(jù)結(jié)構(gòu)在設(shè)計(jì)中的種種細(xì)節(jié),的確值得好好品味。

    函數(shù)實(shí)現(xiàn)

    代碼方面,創(chuàng)建新nodeFlag對(duì)象的函數(shù)叫newFlags()。在nodeFlag初始化過程中,bool成員dirty置為true,表明了所代表的父節(jié)點(diǎn)有改動(dòng)需要提交,同時(shí)hashNode成員hash,直接設(shè)空。

    [plain]?view plain?copy
  • //?trie/trie.go??
  • func?(t?*Trie)?newFlag()?nodeFlag?{??
  • ????return?nodeFlag{dirty:?true,?gen:?t.cacheGen}??
  • }??
  • 每個(gè)hashNode被賦值的過程,就是它所代表的fullNode或shortNode被折疊(collapse)的過程。基于效率和數(shù)據(jù)安全考慮,trie.Trie僅提供整個(gè)MPT結(jié)構(gòu)的折疊操作Hash()函數(shù),它默認(rèn)從頂點(diǎn)root開始遍歷。

    [plain]?view plain?copy
  • func?(t?*Trie)?Hash()?common.Hash?{??
  • ????hash,?cached,?_?:=?t.hashRoot(db:nil)??
  • ????t.root?=?hash??
  • ????return?...??
  • }??
  • func?(t?*Trie)?hashRoot(db?DatabaseWriter)?(node,?node,?error)?{??
  • ????if?(t.root?==?nil)?{...}??
  • ????h?:=?newHasher(t.cachegen,?t.cachelimit)??
  • ????return?h.hash(t.root,?db,?force:true)??
  • }??
  • hashRoot()函數(shù)內(nèi)部調(diào)用Hasher結(jié)構(gòu)體進(jìn)行折疊操作:

    [plain]?view plain?copy
  • //?trie/hasher.go??
  • func?(h?*hasher)?hash(n?node,?db?DatabaseWriter,?force?bool)?(hash?node,?cached?node,?error)??
  • func?(h?*hasher)?hashChildren(original?node,?db?DatabaseWriter)?(hash?node,?cached?node,?error)??
  • func?(h?*hasher)?store(n?node,?db?DatabaseWriter,?force?bool)?(node,?error)??
  • 折疊node的入口是hasher.hash(),在執(zhí)行中,hash()和hashChiildren()相互調(diào)用以遍歷整個(gè)MPT結(jié)構(gòu),store()對(duì)節(jié)點(diǎn)作RLP哈希計(jì)算。折疊node的基本邏輯是:如果node沒有子節(jié)點(diǎn),那么直接返回;如果這個(gè)node帶有子節(jié)點(diǎn),那么首先將子節(jié)點(diǎn)折疊成hashNode。當(dāng)這個(gè)node的子節(jié)點(diǎn)全都變成哈希值hashNode之后,再對(duì)這個(gè)node作RLP+哈希計(jì)算,得到它的哈希值,亦即hashNode。
    注意到hash()和hashChildren()返回兩個(gè)node類型對(duì)象,第一個(gè)@hash是入?yún)經(jīng)過折疊的hashNode哈希值,第二個(gè)@cached是沒有經(jīng)過折疊的n,并且n的hashNode還被賦值了。

    由于Hasher.hash()有一個(gè)數(shù)據(jù)庫接口類型的參數(shù),這樣在折疊MPT過程中,如果db不為空,就把每次計(jì)算hashNode時(shí)的哈希值和它對(duì)應(yīng)的節(jié)點(diǎn)RLP編碼值一起存進(jìn)數(shù)據(jù)庫里,這也正是Commit()的邏輯。

    [plain]?view plain?copy
  • func?(t?*Trie)?Commit()?(root?hash,?error)?{??
  • ????if?t.db?==?nil?{...}??
  • ????return?t.CommitTo(t.db)??
  • }??
  • func?(t?*Trie)?CommitTo(db?DatabaseWriter)?(root?common.Hash,?error)?{??
  • ????hash,?cached,?error?:=?t.hashRoot(db)??
  • ????t.root?=?cached??
  • ????...??
  • }??
  • 回看一下Trie.Hash(),它在調(diào)用hashRoot()時(shí),傳入的是空值db。只有顯式調(diào)用Commit()或者CommitTo()才可以提交數(shù)據(jù),所以Hash()多次調(diào)用也是安全的。

    在MPT的查找,插入,刪除中,如果遍歷過程中遇到一個(gè)hashNode,首先需要從數(shù)據(jù)庫里以這個(gè)哈希值為k,讀取出相匹配的v,然后再將v解碼恢復(fù)成fullNode或shortNode。在代碼中這個(gè)過程叫resolve。

    [plain]?view plain?copy
  • //?trie/trie.go??
  • func?(t?*trie)?resolve(n,?prefix)?(node,error)?{??
  • ????if?n,?ok?:=?n.(hashNode);?ok?{??
  • ????????return?resolveHash(n,?prefix)??
  • ????}??
  • ????return?n,?nil??
  • }??
  • func?(t?*Trie)?resolveHash(n?hashNode,?prefix?[]byte)?(node,error)?{??
  • ????enc,?err?:=?t.db.Get(n)??
  • ????...??
  • ????dec?:=?mustDecodeNode(n,?enc,?t.cachegen)??
  • ????return?dec,?nil??
  • }??
  • 這樣,涉及到hashNode的所有操作就基本完整了。

    MPT中對(duì)key的編碼

    當(dāng)[k,v]數(shù)據(jù)插入MPT時(shí),它們的k(key)都必須經(jīng)過編碼。這時(shí)對(duì)key的編碼,要保證原本是[]byte類型的key能夠以16進(jìn)制形式按位進(jìn)入fullNode.Children[],因?yàn)镃hildren[]數(shù)組最多只能容納16個(gè)子節(jié)點(diǎn)。相應(yīng)的,Ethereum代碼中在這里定義了一種編碼方式叫Hex,將1byte的字符大小限制在4bit(16進(jìn)制)以內(nèi)。

    先來看Hex編碼的實(shí)現(xiàn),這里將原本[]byte形式稱之為keybytes,Hex編碼的基本邏輯如下圖:


    很簡單,就是將keybytes中的1byte信息,將高4bit和低4bit分別放到兩個(gè)byte里,最后在尾部加1byte標(biāo)記當(dāng)前屬于Hex格式。這樣新產(chǎn)生的key雖然形式還是[]byte,但是每個(gè)byte大小已經(jīng)被限制在4bit以內(nèi),代碼中把這種新數(shù)據(jù)的每一位稱為nibble。這樣經(jīng)過編碼之后,帶有[]nibble格式的key的數(shù)據(jù)就可以順利的進(jìn)入fullNode.Children[]數(shù)組了。

    Hex編碼雖然解決了key是keybytes形式的數(shù)據(jù)插入MPT的問題,但代價(jià)也很大,就是數(shù)據(jù)冗余。典型的如shortNode,目前Hex格式下的Key,長度會(huì)變成是原來keybytes格式下的兩倍。這一點(diǎn)對(duì)于節(jié)點(diǎn)的哈希計(jì)算,比如計(jì)算hashNode,影響很大。所以Ethereum又定義了另一種編碼格式叫Compact,用來對(duì)Hex格式進(jìn)行優(yōu)化。

    Compact編碼又叫hex prefix編碼,它的主要意圖是將Hex格式的字符串恢復(fù)到keybytes的格式,同時(shí)要加入當(dāng)前Compact格式的標(biāo)記位,還要考慮在奇偶不同長度Hex格式字符串下,避免引入多余的byte。


    如上圖所示,Compact編碼首先將Hex尾部標(biāo)記byte去掉,然后將原本每2 nibble的數(shù)據(jù)合并到1byte;增添1byte在輸出數(shù)據(jù)頭部以放置Compact格式標(biāo)記位;如果輸入Hex格式字符串有效長度為奇數(shù),還可以將Hex字符串的第一個(gè)nibble放置在標(biāo)記位byte里的低4bit。

    Key編碼的設(shè)計(jì)細(xì)節(jié),也體現(xiàn)出MPT整個(gè)數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)的思路很完整。

    4. 數(shù)據(jù)庫體系

    到目前為止,Ethereum系統(tǒng)中區(qū)塊數(shù)據(jù)的呈現(xiàn),組織管理已經(jīng)介紹了不少,我們可以開始探討存儲(chǔ)部分了。先來看看數(shù)據(jù)存儲(chǔ)部分的UML關(guān)系圖。


    屬于Ethereum代碼范圍內(nèi)的最底層數(shù)據(jù)庫是ethdb.LDBDatabase,它通過持有一個(gè)levelDB的對(duì)象,最終為Ethereum世界里所有需要存儲(chǔ)/讀取[k,b]的需求提供服務(wù)。

    留意到圖中多次出現(xiàn)一種類似的設(shè)計(jì)模式,比如trie.Trie持有一個(gè)本地接口trie.<<Database>>,而后者的具體實(shí)現(xiàn)是ethdb.LDBDatabase。這種設(shè)計(jì)模式其實(shí)是golang的語法帶來的。在golang中,一個(gè)結(jié)構(gòu)體(類)要實(shí)現(xiàn)另一個(gè)接口的所有方法,不必在結(jié)構(gòu)體聲明時(shí)顯式繼承那個(gè)接口,只要完全實(shí)現(xiàn)那些方法。這樣,當(dāng)一個(gè)結(jié)構(gòu)體想調(diào)用另一個(gè)包路徑下結(jié)構(gòu)體的多個(gè)方法時(shí),可以聲明一個(gè)本地接口,帶有幾個(gè)同想要調(diào)用方法完全一樣的方法,就可以了,這種方式的優(yōu)點(diǎn)是不同包之間的代碼更充分的解耦合。所以在上圖中,這些輔助性的本地接口全都被標(biāo)為灰色,只需要關(guān)注實(shí)際調(diào)用的實(shí)現(xiàn)類就好了。

    系統(tǒng)設(shè)計(jì)中,在底層數(shù)據(jù)庫模塊和業(yè)務(wù)模型之間,往往需要設(shè)置本地存儲(chǔ)模塊,它面向業(yè)務(wù)模型,可以根據(jù)業(yè)務(wù)需求靈活的設(shè)計(jì)各種存儲(chǔ)格式和單元,同時(shí)又連接底層數(shù)據(jù)庫,如果底層數(shù)據(jù)庫(或者第三方API)有變動(dòng),可以大大減少對(duì)業(yè)務(wù)模塊的影響。在Ethereum世界里,StateDB就擔(dān)任這個(gè)角色,它通過大量的stateObject對(duì)象集合,管理所有“賬戶”信息。

    面向業(yè)務(wù)的存儲(chǔ)模塊 - StateDB

    StateDB有一個(gè)trie.Trie類型成員trie,它又被稱為storage trie或stte trie,這個(gè)MPT結(jié)構(gòu)中存儲(chǔ)的都是stateObject對(duì)象,每個(gè)stateObject對(duì)象以其地址(20 bytes)作為插入節(jié)點(diǎn)的Key;每次在一個(gè)區(qū)塊的交易開始執(zhí)行前,trie由一個(gè)哈希值(hashNode)恢復(fù)出來。另外還有一個(gè)map結(jié)構(gòu),也是存放stateObject,每個(gè)stateObject的地址作為map的key。那么問題來了,這些數(shù)據(jù)結(jié)構(gòu)之間是怎樣的關(guān)系呢?


    如上圖所示,每當(dāng)一個(gè)stateObject有改動(dòng),亦即“賬戶”信息有變動(dòng)時(shí),這個(gè)stateObject對(duì)象會(huì)更新,并且這個(gè)stateObject會(huì)標(biāo)為dirty,此時(shí)所有的數(shù)據(jù)改動(dòng)還僅僅存儲(chǔ)在map里。當(dāng)IntermediateRoot()調(diào)用時(shí),所有標(biāo)為dirty的stateObject才會(huì)被一起寫入trie。而整個(gè)trie中的內(nèi)容只有在CommitTo()調(diào)用時(shí)被一起提交到底層數(shù)據(jù)庫。可見,這個(gè)map被用作本地的一級(jí)緩存,trie是二級(jí)緩存,底層數(shù)據(jù)庫是第三級(jí),各級(jí)數(shù)據(jù)結(jié)構(gòu)的界限非常清晰,這樣逐級(jí)緩存數(shù)據(jù),每一級(jí)數(shù)據(jù)向上一級(jí)提交的時(shí)機(jī)也根據(jù)業(yè)務(wù)需求做了合理的選擇。

    StateDB中賬戶狀態(tài)的版本管理

    StateDB還可以管理賬戶狀態(tài)的版本。這個(gè)功能用到了幾個(gè)結(jié)構(gòu)體:journal,revision,先來看看UML關(guān)系圖:


    其中journal對(duì)象是journalEntry的散列,長度不固定,可任意添加元素。接口journalEntry存在若干種實(shí)現(xiàn)體,描述了從單個(gè)賬戶操作(賬戶余額,發(fā)起合約次數(shù)等),到account trie變化(創(chuàng)建新賬戶對(duì)象,賬戶消亡)等各種最小事件。revision結(jié)構(gòu)體,用來描述一個(gè)‘版本’,它的兩個(gè)整型成員jd和journalIndex,都是基于journal散列進(jìn)行操作的。


    上圖簡述了StateDB中賬戶狀態(tài)的版本是如何管理的。首先journal散列會(huì)隨著系統(tǒng)運(yùn)行不斷的增長,記錄所有發(fā)生過的單位事件;當(dāng)某個(gè)時(shí)刻需要產(chǎn)生一個(gè)賬戶狀態(tài)版本時(shí),代碼中相應(yīng)的是Snapshop()調(diào)用,會(huì)產(chǎn)生一個(gè)新revision對(duì)象,記錄下當(dāng)前journal散列的長度,和一個(gè)自增1的版本號(hào)。

    基于以上的設(shè)計(jì),當(dāng)發(fā)生回退要求時(shí),只要根據(jù)相應(yīng)的revision中的journalIndex,在journal散列上,根據(jù)所記錄的所有journalEntry,即可使所有賬戶回退到那個(gè)狀態(tài)。

    Ethereum里的賬戶 - stateObject

    每個(gè)stateObject對(duì)象管理著Ethereum世界里的一個(gè)“賬戶”。stateObject有一個(gè)成員變量data,類型是Accunt結(jié)構(gòu)體,里面存有賬戶Ether余額,合約發(fā)起次數(shù),最新發(fā)起合約指令集的哈希值,以及一個(gè)MPT結(jié)構(gòu)的頂點(diǎn)哈希值。

    stateObject內(nèi)部也有一個(gè)Trie類型的成員trie,被稱為storage trie,它里面存放的是一種被稱為State的數(shù)據(jù)。State跟每個(gè)賬戶相關(guān),格式是[Hash, Hash]鍵值對(duì)。有意思的是,stateObject內(nèi)部也有類似StateDB一樣的二級(jí)數(shù)據(jù)緩存機(jī)制,用來緩存和更新這些State。


    stateObject定義了一種類型名為storage的map結(jié)構(gòu),用來存放[]Hash,Hash]類型的數(shù)據(jù)對(duì),也就是State數(shù)據(jù)。當(dāng)SetState()調(diào)用發(fā)生時(shí),storage內(nèi)部State數(shù)據(jù)被更新,相應(yīng)標(biāo)示為"dirty"。之后,待有需要時(shí)(比如updateRoot()調(diào)用),那些標(biāo)為"dirty"的State數(shù)據(jù)被一起寫入storage trie,而storage trie中的所有內(nèi)容在CommitTo()調(diào)用時(shí)再一起提交到底層數(shù)據(jù)庫。

    State數(shù)據(jù)略顯神秘,目前筆者尚未完全理解它的含義,在代碼里,僅僅查到某些合約指令中會(huì)調(diào)用SetState(),來更新某個(gè)stateObject中的State數(shù)據(jù)。

    小結(jié)

    任何一個(gè)系統(tǒng)中,數(shù)據(jù)部分的占用空間,運(yùn)行效率當(dāng)然會(huì)影響到整體性能。如何簡潔完整的呈現(xiàn)數(shù)據(jù),并涵蓋業(yè)務(wù)模型下的大大小小各種需求;如何高效的管理數(shù)據(jù),使得插入、刪除、查找數(shù)據(jù)更快速;如何在業(yè)務(wù)模塊和底層數(shù)據(jù)庫之間安排面向業(yè)務(wù)的、接口友好的本地存儲(chǔ)模塊,使得內(nèi)存占用更緊湊,提交和回退數(shù)據(jù)更加安全等等,都是值得全面思考的。從本文中,可以看到整個(gè)Ethereum系統(tǒng)的架構(gòu)設(shè)計(jì)、代碼實(shí)現(xiàn)上,對(duì)于以上各個(gè)話題都進(jìn)行了諸多考量,值得同業(yè)者學(xué)習(xí)參考。

    • Block結(jié)構(gòu)體主要分為Header和Body,Header相對(duì)輕量,涵蓋了Block的所有屬性,包括特征標(biāo)示,前向指針,和內(nèi)部數(shù)據(jù)集的驗(yàn)證哈希值等;Body相對(duì)重量,持有內(nèi)部數(shù)據(jù)集。每個(gè)Block的Header部分,Body部分,以及一些特征屬性,都以[k,v]形式單獨(dú)存儲(chǔ)在底層數(shù)據(jù)庫中。
    • BlockChain管理Block組成的一個(gè)單向鏈表,HeaderChain管理Header組成的單向鏈表,并且BlockChain持有HeaderChain。在做插入/刪除/查找時(shí),要注意回溯,以及數(shù)據(jù)庫中相應(yīng)的增刪。
    • Merkle-PatriciaTrie(MPT)數(shù)據(jù)結(jié)構(gòu)用來組織管理[k,v]型數(shù)據(jù),它設(shè)計(jì)了靈活多變的節(jié)點(diǎn)體系和編碼格式,既融合了多種原型結(jié)構(gòu)的優(yōu)點(diǎn),又兼顧了業(yè)務(wù)需求和運(yùn)行效率。
    • StateDB作為本地存儲(chǔ)模塊,它面向業(yè)務(wù)模型,又連接底層數(shù)據(jù)庫,內(nèi)部利用兩極緩存機(jī)制來存儲(chǔ)和更新所有代表“賬戶”的stateObject對(duì)象。

    • stateObject除了管理著賬戶余額等信息之外,也用了類似的兩級(jí)緩存機(jī)制來存儲(chǔ)和更新所有的State數(shù)據(jù)。

    原文:?http://blog.csdn.net/teaspring/article/details/75390210

    總結(jié)

    以上是生活随笔為你收集整理的[以太坊源代码分析] II. 数据的呈现和组织,缓存和更新的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。