[ethereum源碼分析](3) ethereum初始化指令

前言

  在上一章介紹了關於區塊鏈的一些基礎知識,這一章會分析指令 geth --datadir dev/data/02 init private-geth/genesis.json 的源碼,若你的ethereum的debug環境尚未搭建,那麼須要先搭建ethereum的dabug環境javascript

 

準備工做

  • 建立文件 genesis.json ,內容以下:
{
  "config": {
    "chainId": 666,  //可用於網絡標識,在eip155裏有用到,目前來看是作重放保護的,目前eth的公網的網絡id爲1
    "homesteadBlock": 0,  //以太坊版本
    "eip155Block": 0,  //(Ethereum Improvement Proposals)簡單重訪攻擊保護,因爲是私有鏈,無硬分叉,此處咱們設置爲0
    "eip158Block": 0  //同上
  },
  "coinbase" : "0x0000000000000000000000000000000000000000",  //礦工帳號
  "difficulty" : "0x40000",  //設置當前區塊的難度,值越大挖礦難度越大
  "extraData" : "",  //附加信息,能夠填寫任意信息
  "gasLimit" : "0xffffffff",  //該值設置對GAS的消耗總量限制,用來限制區塊能包含的交易信息總和
  "nonce" : "0x0000000000000042",  //是一個64位的隨機數,用於挖礦,注意他和mixhash的設置須要知足以太坊的Yellow paper, 4.3.4. Block Header Validity, (44)章節所描述的條件。
  "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",  //與nonce配合用於挖礦
  "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",  //上一個區塊的hash值,創世區塊沒有上一個區塊,所以設置爲0
  "timestamp" : "0x00",  //設置創世塊的時間戳
  "alloc": { 
  "1fd4027fe390abaa49e5afde7896ff1e5ecacabf": { "balance": "20000000000000000000" }
 }  //用來預置帳號以及帳號的以太幣數量
}

 

 

 

指令分析

指令: geth --datadir dev/data/02 init private-geth/genesis.json html

介紹:上面的指令主要的工做爲:java

  • 生成創世區塊
  • 生成帳號的一些信息

分析:node

  •  dev/data/02 :eth數據保存的地址,主要保存了區塊信息和帳號信息,日誌信息
  •  private-geth/genesis.json :eth初始化的一些配置參數

 

代碼分析

接下來就讓咱們跟如下debug,來一探ethereum的真面目。shell

  • 進入入口程序

因爲咱們使用的是 geth 指令,因此咱們代開下面的代碼:數據庫

  • 找到如下函數

main.go:json

func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

 

上面的函數是geth命令的入口函數,這段代碼首先調用了 app.Run(os.Args) 這個函數, os.Args 爲系統參數(例: --datadir dev/data/02 init private-geth/genesis.jso )。那麼讓咱們來看看 app 是什麼。緩存

App:bash

// App is the main structure of a cli application. It is recommended that
// an app be created with the cli.NewApp() function
type App struct {
    // The name of the program. Defaults to path.Base(os.Args[0])
    Name string
    // Full name of command for help, defaults to Name
    HelpName string
    // Description of the program.
    Usage string
    // Text to override the USAGE section of help
    UsageText string
    // Description of the program argument format.
    ArgsUsage string
    // Version of the program
    Version string
    // Description of the program
    Description string
    // List of commands to execute
    Commands []Command
    // List of flags to parse
    Flags []Flag
    // Boolean to enable bash completion commands
    EnableBashCompletion bool
    // Boolean to hide built-in help command
    HideHelp bool
    // Boolean to hide built-in version flag and the VERSION section of help
    HideVersion bool
    // Populate on app startup, only gettable through method Categories()
    categories CommandCategories
    // An action to execute when the bash-completion flag is set
    BashComplete BashCompleteFunc
    // An action to execute before any subcommands are run, but after the context is ready
    // If a non-nil error is returned, no subcommands are run
    Before BeforeFunc
    // An action to execute after any subcommands are run, but after the subcommand has finished
    // It is run even if Action() panics
    After AfterFunc

    // The action to execute when no subcommands are specified
    // Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
    // *Note*: support for the deprecated `Action` signature will be removed in a future version
    Action interface{}

    // Execute this function if the proper command cannot be found
    CommandNotFound CommandNotFoundFunc
    // Execute this function if an usage error occurs
    OnUsageError OnUsageErrorFunc
    // Compilation date
    Compiled time.Time
    // List of all authors who contributed
    Authors []Author
    // Copyright of the binary if any
    Copyright string
    // Name of Author (Note: Use App.Authors, this is deprecated)
    Author string
    // Email of Author (Note: Use App.Authors, this is deprecated)
    Email string
    // Writer writer to write output to
    Writer io.Writer
    // ErrWriter writes error output
    ErrWriter io.Writer
    // Other custom info
    Metadata map[string]interface{}
    // Carries a function which returns app specific info.
    ExtraInfo func() map[string]string
    // CustomAppHelpTemplate the text template for app help topic.
    // cli.go uses text/template to render templates. You can
    // render custom help text by setting this variable.
    CustomAppHelpTemplate string

    didSetup bool
}

 

那麼 app 是在 main.go 的 init 函數中初始化的,下面讓咱們來看看 init 函數。網絡

func init() {
    // Initialize the CLI app and start Geth
    app.Action = geth
    app.HideVersion = true // we have a command to print the version
    app.Copyright = "Copyright 2013-2018 The go-ethereum Authors"
    app.Commands = []cli.Command{
        // See chaincmd.go:
        initCommand,
        importCommand,
        exportCommand,
        importPreimagesCommand,
        exportPreimagesCommand,
        copydbCommand,
        removedbCommand,
        dumpCommand,
        // See monitorcmd.go:
        monitorCommand,
        // See accountcmd.go:
        accountCommand,
        walletCommand,
        // See consolecmd.go:
        consoleCommand,
        attachCommand,
        javascriptCommand,
        // See misccmd.go:
        makecacheCommand,
        makedagCommand,
        versionCommand,
        bugCommand,
        licenseCommand,
        // See config.go
        dumpConfigCommand,
    }
    sort.Sort(cli.CommandsByName(app.Commands))

    app.Flags = append(app.Flags, nodeFlags...)
    app.Flags = append(app.Flags, rpcFlags...)
    app.Flags = append(app.Flags, consoleFlags...)
    app.Flags = append(app.Flags, debug.Flags...)
    app.Flags = append(app.Flags, whisperFlags...)

    app.Before = func(ctx *cli.Context) error {
        runtime.GOMAXPROCS(runtime.NumCPU())
        if err := debug.Setup(ctx); err != nil {
            return err
        }
        // Cap the cache allowance and tune the garbage colelctor
        var mem gosigar.Mem
        if err := mem.Get(); err == nil {
            allowance := int(mem.Total / 1024 / 1024 / 3)
            if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance {
                log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
                ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(allowance))
            }
        }
        // Ensure Go's GC ignores the database cache for trigger percentage
        cache := ctx.GlobalInt(utils.CacheFlag.Name)
        gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024)))

        log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
        godebug.SetGCPercent(int(gogc))

        // Start system runtime metrics collection
        go metrics.CollectProcessMetrics(3 * time.Second)

        utils.SetupNetwork(ctx)
        return nil
    }

    app.After = func(ctx *cli.Context) error {
        debug.Exit()
        console.Stdin.Close() // Resets terminal mode.
        return nil
    }
}

 

從上面的代碼,能夠看到,它將全部的指令放到了 app 中緩存了起來。經過這個緩存的指令集,咱們能夠找到須要執行的代碼。下面就讓咱們來看一下 app.Run(os.Args) 裏面的代碼。

func (a *App) Run(arguments []string) (err error) {
    a.Setup()//在這個裏面主要作了三件事:1.初始化App中的commands(主要初始化Command.HelpName)2.增長helpCommand指令到App.Commands,一共21個指令 3.初始化App.categories,主要是給指令分類

    // handle the completion flag separately from the flagset since
    // completion could be attempted after a flag, but before its value was put
    // on the command line. this causes the flagset to interpret the completion
    // flag name as the value of the flag before it which is undesirable
    // note that we can only do this because the shell autocomplete function
    // always appends the completion flag at the end of the command
    shellComplete, arguments := checkShellCompleteFlag(a, arguments)

    // parse flags
    set, err := flagSet(a.Name, a.Flags)//這裏初始化了一些FlagSet,FlagSet裏面存儲了一些eth的默認配置,好比networkId=1
    if err != nil {
        return err
    }

    set.SetOutput(ioutil.Discard)
    err = set.Parse(arguments[1:])//將命令行參數設置到set中,能夠經過看裏面的代碼知道,命令行輸入的參數有兩種:1.環境配置參數以--爲開頭 2.命令參數,須要執行代碼
    nerr := normalizeFlags(a.Flags, set)
    context := NewContext(a, set, nil)
    if nerr != nil {
        fmt.Fprintln(a.Writer, nerr)
        ShowAppHelp(context)
        return nerr
    }
    context.shellComplete = shellComplete

    if checkCompletions(context) {
        return nil
    }

    if err != nil {
        if a.OnUsageError != nil {
            err := a.OnUsageError(context, err, false)
            HandleExitCoder(err)
            return err
        }
        fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
        ShowAppHelp(context)
        return err
    }

    if !a.HideHelp && checkHelp(context) {
        ShowAppHelp(context)
        return nil
    }

    if !a.HideVersion && checkVersion(context) {
        ShowVersion(context)
        return nil
    }

    if a.After != nil {
        defer func() {
            if afterErr := a.After(context); afterErr != nil {
                if err != nil {
                    err = NewMultiError(err, afterErr)
                } else {
                    err = afterErr
                }
            }
        }()
    }

    if a.Before != nil {
        beforeErr := a.Before(context)
        if beforeErr != nil {
            ShowAppHelp(context)
            HandleExitCoder(beforeErr)
            err = beforeErr
            return err
        }
    }

    args := context.Args()//獲取須要執行的命令,當前爲init
    if args.Present() {
        name := args.First()
        c := a.Command(name)//查找是否有init命令
        if c != nil {
            return c.Run(context)//執行init命令
        }
    }

    if a.Action == nil {
        a.Action = helpCommand.Action
    }

    // Run default Action
    err = HandleAction(a.Action, context)

    HandleExitCoder(err)
    return err
}

 從上面的註釋咱們能夠知道,命令行輸入的參數 init 在 c.Run(context) 這行代碼被執行。那麼下面就讓咱們來 c.Run(context) 的代碼。

func (c Command) Run(ctx *Context) (err error) {
    if len(c.Subcommands) > 0 {
        return c.startApp(ctx)
    }

    if !c.HideHelp && (HelpFlag != BoolFlag{}) {
        // append help to flags
        c.Flags = append(
            c.Flags,
            HelpFlag,
        )
    }

    set, err := flagSet(c.Name, c.Flags)
    if err != nil {
        return err
    }
    set.SetOutput(ioutil.Discard)

    if c.SkipFlagParsing {
        err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
    } else if !c.SkipArgReorder {
        firstFlagIndex := -1
        terminatorIndex := -1
        for index, arg := range ctx.Args() {
            if arg == "--" {
                terminatorIndex = index
                break
            } else if arg == "-" {
                // Do nothing. A dash alone is not really a flag.
                continue
            } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
                firstFlagIndex = index
            }
        }

        if firstFlagIndex > -1 {
            args := ctx.Args()
            regularArgs := make([]string, len(args[1:firstFlagIndex]))
            copy(regularArgs, args[1:firstFlagIndex])

            var flagArgs []string
            if terminatorIndex > -1 {
                flagArgs = args[firstFlagIndex:terminatorIndex]
                regularArgs = append(regularArgs, args[terminatorIndex:]...)
            } else {
                flagArgs = args[firstFlagIndex:]
            }

            err = set.Parse(append(flagArgs, regularArgs...))
        } else {
            err = set.Parse(ctx.Args().Tail())//初始化init命令的參數,該處爲private-geth/genesis.json
        }
    } else {
        err = set.Parse(ctx.Args().Tail())
    }

    nerr := normalizeFlags(c.Flags, set)
    if nerr != nil {
        fmt.Fprintln(ctx.App.Writer, nerr)
        fmt.Fprintln(ctx.App.Writer)
        ShowCommandHelp(ctx, c.Name)
        return nerr
    }

    context := NewContext(ctx.App, set, ctx)
    context.Command = c
    if checkCommandCompletions(context, c.Name) {
        return nil
    }

    if err != nil {
        if c.OnUsageError != nil {
            err := c.OnUsageError(context, err, false)
            HandleExitCoder(err)
            return err
        }
        fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
        fmt.Fprintln(context.App.Writer)
        ShowCommandHelp(context, c.Name)
        return err
    }

    if checkCommandHelp(context, c.Name) {
        return nil
    }

    if c.After != nil {
        defer func() {
            afterErr := c.After(context)
            if afterErr != nil {
                HandleExitCoder(err)
                if err != nil {
                    err = NewMultiError(err, afterErr)
                } else {
                    err = afterErr
                }
            }
        }()
    }

    if c.Before != nil {
        err = c.Before(context)
        if err != nil {
            ShowCommandHelp(context, c.Name)
            HandleExitCoder(err)
            return err
        }
    }

    if c.Action == nil {
        c.Action = helpSubcommand.Action
    }

    err = HandleAction(c.Action, context)//這一行是用來執行init指令的,指令須要執行的代碼連接到了c.Action

    if err != nil {
        HandleExitCoder(err)
    }
    return err
}

 

那麼最終 init 指令須要執行的代碼是 MigrateFlags ,能夠在 main.go  initCommand 中看到須要執行的代碼。

    initCommand = cli.Command{
        Action:    utils.MigrateFlags(initGenesis),
        Name:      "init",
        Usage:     "Bootstrap and initialize a new genesis block",
        ArgsUsage: "<genesisPath>",
        Flags: []cli.Flag{
            utils.DataDirFlag,
            utils.LightModeFlag,
        },
        Category: "BLOCKCHAIN COMMANDS",
        Description: `
The init command initializes a new genesis block and definition for the network.
This is a destructive action and changes the network in which you will be
participating.

It expects the genesis file as argument.`,
    }

 

從上面能夠看到,執行 MigrateFlags 會先執行 initGenesis ,下面就來看看 initGenesis 的代碼。

func initGenesis(ctx *cli.Context) error {
    // Make sure we have a valid genesis JSON
    genesisPath := ctx.Args().First()//獲取命令行參數,此處爲private-geth/genesis.json
    if len(genesisPath) == 0 {
        utils.Fatalf("Must supply path to genesis JSON file")
    }
    file, err := os.Open(genesisPath)//打開private-geth/genesis.json文件
    if err != nil {
        utils.Fatalf("Failed to read genesis file: %v", err)
    }
    defer file.Close()

    genesis := new(core.Genesis)//構造一個Genesis
    if err := json.NewDecoder(file).Decode(genesis); err != nil {//讀取配置文件genesis.json,構造genesis結構體
        utils.Fatalf("invalid genesis file: %v", err)
    }
    // Open an initialise both full and light databases
    stack := makeFullNode(ctx)//這裏面初始化了一些配置信息,網絡的一些設置等
    for _, name := range []string{"chaindata", "lightchaindata"} {
        chaindb, err := stack.OpenDatabase(name, 0, 0)
        if err != nil {
            utils.Fatalf("Failed to open database: %v", err)
        }
        _, hash, err := core.SetupGenesisBlock(chaindb, genesis)//這裏將創世區塊寫入leveldb
        if err != nil {
            utils.Fatalf("Failed to write genesis block: %v", err)
        }
        log.Info("Successfully wrote genesis state", "database", name, "hash", hash)
    }
    return nil
}

 下面就讓咱們看看這裏是如何構建創世區塊的,構建創世區塊的過程在 core.SetupGenesisBlock(chaindb, genesis) 裏面。

func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
    if genesis != nil && genesis.Config == nil {
        return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
    }

    // Just commit the new block if there is no stored genesis block.
    stored := rawdb.ReadCanonicalHash(db, 0)
    if (stored == common.Hash{}) {
        if genesis == nil {
            log.Info("Writing default main-net genesis block")
            genesis = DefaultGenesisBlock()
        } else {
            log.Info("Writing custom genesis block")
        }
        block, err := genesis.Commit(db)//最終咱們的代碼會走到這裏,這裏將genesis寫入數據庫
        return genesis.Config, block.Hash(), err
    }

    // Check whether the genesis block is already written.
    if genesis != nil {
        hash := genesis.ToBlock(nil).Hash()
        if hash != stored {
            return genesis.Config, hash, &GenesisMismatchError{stored, hash}
        }
    }

    // Get the existing chain configuration.
    newcfg := genesis.configOrDefault(stored)
    storedcfg := rawdb.ReadChainConfig(db, stored)
    if storedcfg == nil {
        log.Warn("Found genesis block without chain config")
        rawdb.WriteChainConfig(db, stored, newcfg)
        return newcfg, stored, nil
    }
    // Special case: don't change the existing config of a non-mainnet chain if no new
    // config is supplied. These chains would get AllProtocolChanges (and a compat error)
    // if we just continued here.
    if genesis == nil && stored != params.MainnetGenesisHash {
        return storedcfg, stored, nil
    }

    // Check config compatibility and write the config. Compatibility errors
    // are returned to the caller unless we're already at block zero.
    height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db))
    if height == nil {
        return newcfg, stored, fmt.Errorf("missing block number for head header hash")
    }
    compatErr := storedcfg.CheckCompatible(newcfg, *height)
    if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 {
        return newcfg, stored, compatErr
    }
    rawdb.WriteChainConfig(db, stored, newcfg)
    return newcfg, stored, nil
}

 接下來就讓咱們跟一下 genesis.Commit(db) 的代碼。

// Commit writes the block and state of a genesis specification to the database.
// The block is committed as the canonical head block.
func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
    block := g.ToBlock(db)//這裏面作了兩件事:1.寫入狀態樹 2.構造創世區塊
    if block.Number().Sign() != 0 {
        return nil, fmt.Errorf("can't commit genesis block with number > 0")
    }
    rawdb.WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty)
    rawdb.WriteBlock(db, block)
    rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil)
    rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
    rawdb.WriteHeadBlockHash(db, block.Hash())
    rawdb.WriteHeadHeaderHash(db, block.Hash())

    config := g.Config
    if config == nil {
        config = params.AllEthashProtocolChanges
    }
    rawdb.WriteChainConfig(db, block.Hash(), config)//寫入鏈的配置信息
    return block, nil
}

 

那麼讓咱們來看看 g.ToBlock(db) 的代碼。

// ToBlock creates the genesis block and writes state of a genesis specification
// to the given database (or discards it if nil).
func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
    if db == nil {
        db = ethdb.NewMemDatabase()
    }
    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:       new(big.Int).SetUint64(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)//這裏構造了一個創世區塊
}

 

下面讓咱們看下 types.Header 的結構體。

type Header struct {
    ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`//父區塊頭的Hash值
    UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`//當前區塊ommers列表的Hash值
    Coinbase    common.Address `json:"miner"            gencodec:"required"`//接收挖此區塊費用的礦工錢包地址
    Root        common.Hash    `json:"stateRoot"        gencodec:"required"`//狀態樹根節點的Hash值
    TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`//包含此區塊所列的全部交易的樹的根節點Hash值
    ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`//包含此區塊所列的全部交易收據的樹的根節點Hash值
    Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`//由日誌信息組成的一個Bloom過濾器 (數據結構)
    Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`//挖此區塊的難度
    Number      *big.Int       `json:"number"           gencodec:"required"`//區塊編號,也就是區塊高度
    GasLimit    uint64         `json:"gasLimit"         gencodec:"required"`//每一個區塊當前的gasLimit
    GasUsed     uint64         `json:"gasUsed"          gencodec:"required"`//此區塊中交易所用的總gas量
    Time        *big.Int       `json:"timestamp"        gencodec:"required"`//此區塊建立時間戳
    Extra       []byte         `json:"extraData"        gencodec:"required"`//與此區塊相關的附加數據
    MixDigest   common.Hash    `json:"mixHash"          gencodec:"required"`//一個Hash值,當與nonce組合時,證實此區塊已經執行了足夠的計算
    Nonce       BlockNonce     `json:"nonce"            gencodec:"required"`//一個Hash值,當與mixHash組合時,證實此區塊已經執行了足夠的計算
}

 

最後咱們看下 types.Block 的結構體。

type Block struct {
    header       *Header//區塊頭信息
    uncles       []*Header
    transactions Transactions//區塊交易信息

    // caches
    hash atomic.Value
    size atomic.Value

    // Td is used by package core to store the total difficulty
    // of the chain up to and including the block.
    td *big.Int

    // These fields are used by package eth to track
    // inter-peer block relay.
    ReceivedAt   time.Time
    ReceivedFrom interface{}
}

 

到這裏這一章就介紹結束了。有什麼理解錯誤的地方還望指正。

相關文章
相關標籤/搜索