以太坊創世區塊與鏈配置載入分析

本文首發于深入淺出區塊鏈社區
原文鏈接:以太坊創世區塊與鏈配置載入分析,原文已更新,請讀者前往原文閱讀。

創世區塊作為第零個區塊,其他區塊直接或間接引用到創世區塊。因此節點啟動之初必須載入正確的創世區塊信息,且不得任意修改。

以太坊允許通過創世配置文件來初始化創世區塊,也可使用選擇使用內置的多個網絡環境的創世配置。默認使用以太坊主網創世配置。

創世配置文件

如果你需要搭建以太坊私有鏈,那么了解創世配置是必須的,否則你大可不關心創世配置。下面是一份 JSON 格式的創世配置示例:

{
    "config": {
        "chainId": 1,
        "homesteadBlock": 1150000,
        "daoForkBlock": 1920000,
        "daoForkSupport": true,
        "eip150Block": 2463000,
        "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
        "eip155Block": 2675000,
        "eip158Block": 2675000,
        "byzantiumBlock": 4370000,
        "constantinopleBlock": 7280000,
        "petersburgBlock": 7280000,
        "ethash": {}
    },
    "nonce": "0x42",
    "timestamp": "0x0",
    "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
    "gasLimit": "0x1388",
    "difficulty": "0x400000000",
    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "coinbase": "0x0000000000000000000000000000000000000000",
    "number": "0x0",
    "gasUsed": "0x0",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "alloc": {
        "000d836201318ec6899a67540690382780743280": {
            "balance": "0xad78ebc5ac6200000"
        },
        "001762430ea9c3a26e5749afdb70da5f78ddbb8c": {
            "balance": "0xad78ebc5ac6200000"
        }
    }
}

根據配置用途可分為三大類:

  1. 鏈配置
    config項是定義鏈配置,會影響共識協議,雖然鏈配置對創世影響不大,但新區塊的出塊規則均依賴鏈配置。
  2. 創世區塊頭信息配置
    • nonce:隨機數,對應創世區塊 Nonce 字段。
    • timestamp:UTC時間戳,對應創世區塊 Time字段。
    • extraData:額外數據,對應創世區塊 Extra 字段。
    • gasLimit必填,燃料上限,對應創世區塊 GasLimit 字段。
    • difficulty必填,難度系數,對應創世區塊 Difficulty 字段。搭建私有鏈時,需要根據情況選擇合適的難度值,以便調整出塊。
    • minHash:一個哈希值,對應創世區塊的MixDigest字段。和 nonce 值一起證明在區塊上已經進行了足夠的計算。
    • coinbase:一個地址,對應創世區塊的Coinbase字段。
  3. 初始賬戶資產配置
    alloc 項是創世中初始賬戶資產配置。在生成創世區塊時,將此數據集中的賬戶資產寫入區塊中,相當于預挖礦。這對開發測試和私有鏈非常好用,不需要挖礦就可以直接為任意多個賬戶分配資產。

自定義創世

如果你計劃部署以太坊私有網絡或者一個獨立的測試環境,那么需要自定義創世,并初始化它。為了統一溝通,推薦先在用戶根目錄創建一個文件夾 deepeth,以做為《以太坊設計與實現》電子書學習工作目錄。

mkdir $HOME/deepeth && cd $HOME/deepeth

再準備兩個以太坊賬戶,以便在創世時存入資產。

geth --datadir $HOME/deepeth account new

因為是學習使用,推薦使用統一密碼 foobar,執行兩次命令,創建好兩個賬戶。這里使用 --datadir 參數指定以太坊運行時數據存放目錄,是讓大家將數據統一存放在一個本課程學習文件夾中。

再將下面配置內容保存到 $HOME/deepeth/genesis.json 文件,其中 alloc 項替換成剛剛創建的兩個以太坊賬戶地址。

{
    "config": {
        "chainId": 8888,
        "homesteadBlock": 0,
        "daoForkBlock": 0,
        "daoForkSupport": true,
        "eip150Block": 0,
        "eip155Block": 0,
        "eip158Block": 0,
        "byzantiumBlock": 0,
        "constantinopleBlock": 0,
        "petersburgBlock": 0,
        "ethash": {}
    },
    "nonce": "0x42",
    "timestamp": "0x0",
    "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
    "gasLimit": "0x1388",
    "difficulty": "0x1",
    "alloc": {
        "093f59f1d91017d30d8c2caa78feb5beb0d2cfaf": {
            "balance": "0xffffffffffffffff"
        },
        "ddf7202cbe0aaed1c2d5c4ef05e386501a054406": {
            "balance": "0xffffffffffffffff"
        }
    }
}

然后,執行 geth 子命令 init 初始化創世區塊。

geth  --datadir $HOME/deepeth init genesis.json

執行成功后,便可啟動該私有鏈:

geth --maxpeers 0 --datadir $HOME/deepeth  console

執行如下命令,可以查看到前面創建的兩個賬戶,均已有資產:

eth.getBalance(eth.accounts[0])
// 18446744073709551615
eth.getBalance(eth.accounts[1])
// 18446744073709551615

至此,我們已完成創世定制版。

內置的創世配置

上面我已完成自定義創世,但以太坊作為去中心平臺,需要許多節點一起參與。僅僅為了測試,多個節點來搭建私有鏈比較麻煩。如果希望和別人一起聯調,或者需要在測試網絡中測試DAPP時,該怎么辦呢?那么,可使用以太坊測試網絡。以太坊公開的測試網絡有 5 個,目前仍在運行的有 4 個,具體見下表格。

|測試網|共識機制|出塊間隔|提供方|上線時間|備注|狀態|
|---|---|---|---|---|---|---|---|
| Morden | PoW || 以太坊官方 |2015.7|因難度炸彈被迫退役 |stopped|
|Ropsten |PoW |30秒|以太坊官方|2016.11|接替Morden| running|
|Kovan | PoA | 4秒|以太坊錢包Parity開發團隊| 2017.3 |不支持geth| running |
|Rinkeby | PoA |15秒| 以太坊官方| 2017.4|最常用,只支持geth | running|
|Sokol | PoA |5秒| 以太坊官方POA.network團隊| 2017.12|不支持geth | running|
|G?rli | PoA | 15秒| 以太坊柏林社區 | 2018.9| 首個以太坊2.0實驗場| running|

支持 geth 的3個測試網絡的創世配置已內置在以太坊代碼中,具體見 core/genesis.go 文件:

// DefaultTestnetGenesisBlock returns the Ropsten network genesis block.
func DefaultTestnetGenesisBlock() *Genesis{}
// DefaultRinkebyGenesisBlock returns the Rinkeby network genesis block.
func DefaultRinkebyGenesisBlock() *Genesis
// DefaultGoerliGenesisBlock returns the G?rli network genesis block.
func DefaultGoerliGenesisBlock() *Genesis{}

當然不會缺以太坊主網創世配置,也是 geth 運行的默認配置。

// DefaultGenesisBlock returns the Ethereum main net genesis block.
func DefaultGenesisBlock() *Genesis{}

如果你不想自定義創世配置文件用于開發測試,那么以太坊也提供一份專用于本地開發的配置。


// DeveloperGenesisBlock returns the 'geth --dev' genesis block. Note, this must
// be seeded with the
func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis

運行 geth --dev console 可臨時運行使用。但如果需要長期使用此模式,則需要指定 datadir

geth --dev --datadir $HOME/deepeth/dev console

首次運行 dev 模式會自動創建一個空密碼的賬戶,并開啟挖礦。當有新交易時,將立刻打包出塊。

geth 創世區塊加載流程

在運行 geth 時需根據配置文件加載創世配置以及創世區塊,并校驗其合法性。如果配置信息隨意變更,易引起共識校驗不通過等問題。只有在加載并檢查通過時,才能繼續運行程序。

<img src="https://img.learnblockchain.cn/2019/04/07_20190407101509.png" width="400px" alt="創世加載流程">

上圖是一個簡要流程,下面分別講解“加載創世配置”和“安裝創世區塊”兩個子流程。

加載創世配置

應使用哪種創世配置,由用戶在啟動 geth 時決定。下圖是創世配置選擇流程圖:

以太坊創世配置選擇流程圖

通過 geth 命令參數可選擇不同網絡配置,可以通過 networkid 選擇,也可使用網絡名稱啟用。

  1. 使用 networkid:
    不同網絡使用不同ID標識。

    • 1=Frontier,主網環境,是默認選項。
    • 2=Morden 測試網絡,但已禁用。
    • 3=Ropsten 測試網絡。
    • 4=Rinkeby 測試網絡。
  2. 直接使用網絡名稱:

    • testnet: Ropsten 測試網絡。
    • rinkeby: Rinkeby 測試網絡。
    • goerli: G?rli 測試網絡。
    • dev: 本地開發環境。

geth 啟動時根據不同參數選擇加載不同網絡配置,并對應不同網絡環境。如果不做任何選擇,雖然在此不會做出選擇,但在后面流程中會默認使用主網配置。

安裝創世區塊

上面已初步選擇創世配置,而這一步則根據配置加載或者初始化創世單元。下圖是處理流程:

安裝創世區塊

首先,需要從數據庫中根據區塊高度 0 讀取創世區塊哈希。如果不存在則說明本地屬于第一次啟動,直接使用運行時創世配置來構建創世區塊。屬于首次,還需要存儲創世區塊和鏈配置。

如果存在,則需要使用運行時創世配置構建創世區塊并和本次已存儲的創世區塊哈希進行對比。一旦不一致,則返回錯誤,不得繼續。

隨后,還需要檢查鏈配置。先從數據庫獲取鏈配置,如果不存在,則無需校驗直接使用運行時鏈配置。否則,需要檢查運行時鏈配置是否正確,只有正確時才能替換更新。但有一個例外:主網配置不得隨意更改,由代碼控制而非人為指定。

總的來說,以太坊默認使用主網配置,只有在首次運行時才創建和存儲創世區塊,其他時候僅僅用于校驗。而鏈配置除主網外則在規則下可隨時變更。

構建創建區塊

上面我們已知曉總體流程,這里再細說下以太坊是如何根據創世配置生成創世區塊。核心代碼位于 core/genesis.go:229

func (g *Genesis) ToBlock(db ethdb.Database) *types.Block{
    if db == nil {
        db = rawdb.NewMemoryDatabase()
    }
    statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))//?
    for addr, account := range g.Alloc { //?
        statedb.AddBalance(addr, account.Balance)
        statedb.SetCode(addr, account.Code)
        statedb.SetNonce(addr, account.Nonce)
        for key, value := range account.Storage {
            statedb.SetState(addr, key, value)
        }
    }
    root := statedb.IntermediateRoot(false)//?
    head := &types.Header{//?
        Number:     new(big.Int).SetUint64(g.Number),
        Nonce:      types.EncodeNonce(g.Nonce),
        Time:       g.Timestamp,
        ParentHash: g.ParentHash,
        Extra:      g.ExtraData,
        GasLimit:   g.GasLimit,
        GasUsed:    g.GasUsed,
        Difficulty: g.Difficulty,
        MixDigest:  g.Mixhash,
        Coinbase:   g.Coinbase,
        Root:       root,
    }
    //?
    if g.GasLimit == 0 {
        head.GasLimit = params.GenesisGasLimit
    }
    if g.Difficulty == nil {
        head.Difficulty = params.GenesisDifficulty
    }

    statedb.Commit(false)//?
    statedb.Database().TrieDB().Commit(root, true)//?

    return types.NewBlock(head, nil, nil, nil)//?
}

上面代碼是根據創世配置生成創世區塊的代碼邏輯,細節如下:

  • ? 創世區塊無父塊,從零初始化全新的 state(后續文章會詳細講解 state對象)。

  • ? 遍歷配置中 Alloc 項賬戶集合數據,直接寫入 state 中。
    這里不單可以設置 balance,還可以設置 codenonce 以及任意多個 storage 數據。
    意味著創世時便可以直接部署智能合約。例如下面配置則在創世時部署了一個名為093f59f1d91017d30d8c2caa78feb5beb0d2cfaf 的智能合約。

    "alloc": {
            "093f59f1d91017d30d8c2caa78feb5beb0d2cfaf": {
                "balance": "0xffffffffffffffff",
                "nonce": "0x3",
                "code":"0x606060",
                "storage":{
                "11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa":"1234ff"
                }
            }
    }
    
  • ? 將賬戶數據寫入 state 后,便可以計算出 state 數據的默克爾樹的根值,稱之為 StateRoot
    此值記錄在區塊頭 Root 字段中。

  • ? 創世配置的一部分配置,則直接映射到區塊頭中,完成創世區塊頭的構建。

  • ? 因為 GasLimitDifficulty 直接影響到下一個區塊出塊處理。
    因此未設置時使用默認配置(Difficulty=131072,GasLimit=4712388)。

  • ? 提交 state,將 state 數據提交到底層的內存 trie 數據中。

  • ? 將內存 trie 數據更新到 db 中。
    這是多余的一步,因為提交到數據庫是由外部進行,這里只需要負責生成區塊。

  • ? 利用區塊頭創建區塊,且區塊中無交易記錄。

深入淺出區塊鏈 - 系統學習區塊鏈,學區塊鏈都在這里,打造最好的區塊鏈技術博客。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容