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

原文鏈接
請大家前往深入淺出區塊鏈主站, 獲取最新內容。

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

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

創世配置文件

如果你需要搭建以太坊私有鏈,那麼瞭解創世配置是必須的,否則你大可不關心創世配置。下面是一份 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 時決定。下圖是創世配置選擇流程圖:br/>![以太坊創世配置選擇流程圖](https://img.learnblockchain.cn/2019/04/[email protected])
通過 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 中。
    這是多餘的一步,因爲提交到數據庫是由外部進行,這裏只需要負責生成區塊。
  • ❽ 利用區塊頭創建區塊,且區塊中無交易記錄。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章