在上一章介紹了關於區塊鏈的一些基礎知識,這一章會分析指令 geth --datadir dev/data/02 init private-geth/genesis.json 的源碼,若你的ethereum的debug環境尚未搭建,那麼須要先搭建ethereum的dabug環境。javascript
{ "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
接下來就讓咱們跟如下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{} }
到這裏這一章就介紹結束了。有什麼理解錯誤的地方還望指正。