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

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

創世區塊做爲第零個區塊,其餘區塊直接或間接引用到創世區塊。所以節點啓動之初必須載入正確的創世區塊信息,且不得任意修改。json

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

創世配置文件

若是你須要搭建以太坊私有鏈,那麼瞭解創世配置是必須的,不然你大可不關心創世配置。下面是一份 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 參數指定以太坊運行時數據存放目錄,是讓你們將數據統一存放在一個本課程學習文件夾中。ui

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

{
    "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 初始化創世區塊。.net

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 中。 這是多餘的一步,由於提交到數據庫是由外部進行,這裏只須要負責生成區塊。

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

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

相關文章
相關標籤/搜索