Initialization for Committing a Block
Types
Walkthrough
geth
sets up aNode
; seecmd/geth/main.go#L236
:// geth is the main entry point into the system if no special subcommand is ran. // It creates a default node based on the command line arguments and runs it in // blocking mode, waiting for it to be shut down. func geth(ctx *cli.Context) error { node := makeFullNode(ctx) // <<=== #1 startNode(ctx, node) node.Wait() return nil }
The
makeFullNode
method is shown below; seecmd/geth/config.go#L153-L179
. It invokesmakeConfigNode
, which returns a tuple consisting of the newNode
and the correspondingeth.Config
data); see#2a
below. On the next line (see#2b
below)RegisterEthService
(shown later) uses the tuple to add the new Ethereum client to the stack (TODO describe theNode
stack); seecmd/geth/config.go#L156
:func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) // <<=== #2a utils.RegisterEthService(stack, &cfg.Eth) // <<=== #2b if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) { utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit) }
The private
makeConfigNode
method added to thecli.Context
type incmd/geth/config.go
uses the user-supplied command line to return a tuple containing a reference to a newNode
and the new instance ofgethConfig
used to construct theNode
; seecmd/geth/config.go#L110-L141
. It starts by creating an instance ofgethConfig
, described above; the two properties of interest areEth
andNode
. The former property is assigned a default value (eth.DefaultConfig
, see#3a
, and shown in detail next), while the latter property's value is computed by thedefaultNodeConfig
method, see#3b
and shown in detail following.func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ Eth: eth.DefaultConfig, // <<=== #3a Shh: whisper.DefaultConfig, Node: defaultNodeConfig(), // <<=== #3b Dashboard: dashboard.DefaultConfig, } // Load config file. if file := ctx.GlobalString(configFileFlag.Name); file != "" { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } } // Apply flags. utils.SetNodeConfig(ctx, &cfg.Node) stack, err := node.New(&cfg.Node) if err != nil { utils.Fatalf("Failed to create the protocol stack: %v", err) } utils.SetEthConfig(ctx, stack, &cfg.Eth) if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } utils.SetShhConfig(ctx, stack, &cfg.Shh) utils.SetDashboardConfig(ctx, &cfg.Dashboard) return stack, cfg }
a.
eth.DefaultConfig
looks like this; seeeth/config.go#L36-L58
:// DefaultConfig contains default settings for use on the Ethereum main net. var DefaultConfig = Config{ SyncMode: downloader.FastSync, Ethash: ethash.Config{ CacheDir: "ethash", CachesInMem: 2, CachesOnDisk: 3, DatasetsInMem: 1, DatasetsOnDisk: 2, }, NetworkId: 1, LightPeers: 100, DatabaseCache: 768, TrieCache: 256, TrieTimeout: 60 * time.Minute, GasPrice: big.NewInt(18 * params.Shannon), TxPool: core.DefaultTxPoolConfig, GPO: gasprice.Config{ Blocks: 20, Percentile: 60, }, }
b. The
defaultNodeConfig
method returns anode.Config
instance based onnode.DefaultConfig
, seecmd/geth/config.go#L100-L108
:func defaultNodeConfig() node.Config { cfg := node.DefaultConfig // <<=== #3c cfg.Name = clientIdentifier cfg.Version = params.VersionWithCommit(gitCommit) cfg.HTTPModules = append(cfg.HTTPModules, "eth", "shh") cfg.WSModules = append(cfg.WSModules, "eth", "shh") cfg.IPCPath = "geth.ipc" return cfg }
c.
node.DefaultConfig
looks like this; seenode/defaults.go#L36-L49
:// DefaultConfig contains reasonable default settings. var DefaultConfig = Config{ DataDir: DefaultDataDir(), HTTPPort: DefaultHTTPPort, HTTPModules: []string{"net", "web3"}, HTTPVirtualHosts: []string{"localhost"}, WSPort: DefaultWSPort, WSModules: []string{"net", "web3"}, P2P: p2p.Config{ ListenAddr: ":30303", MaxPeers: 25, NAT: nat.Any(), }, }
RegisterEthService
is a publicly visible function; seecmd/utils/flags.go#L1126-L1146
. As previously mentioned,RegisterEthService
uses a reference to a newNode
(which is actually a new Ethereum client) and the correspondingeth.Config
data to add the new Ethereum client. Theeth.New
method, shown in#4a
and#4b
, returns the registered Ethereum client.TODO
the terminology is confusing; the terms "Ethereum client", "stack" and "Ethereum service" all seem to refer to the same object.// RegisterEthService adds an Ethereum client to the stack. func RegisterEthService(stack *node.Node, cfg *eth.Config) { var err error if cfg.SyncMode == downloader.LightSync { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return les.New(ctx, cfg) // <<=== #4a }) } else { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { fullNode, err := eth.New(ctx, cfg) // <<=== #4b if fullNode != nil && cfg.LightServ > 0 { ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) } return fullNode, err }) } if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } }
The
eth.New
method accepts anode.ServiceContext
and a reference to aneth.Config
instance and returns a reference to a newLightEthereum
instance; seeles/backend.go#L83-L140
. The newLightEthereum
instance contains aProtocolManager
, which includes onep2p.Protocol
for every supported protocol version; see #5.func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { chainDb, err := eth.CreateDB(ctx, config, "lightchaindata") if err != nil { return nil, err } chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { return nil, genesisErr } log.Info("Initialised chain configuration", "config", chainConfig) peers := newPeerSet() quitSync := make(chan struct{}) leth := &LightEthereum{ config: config, chainConfig: chainConfig, chainDb: chainDb, eventMux: ctx.EventMux, peers: peers, reqDist: newRequestDistributor(peers, quitSync), accountManager: ctx.AccountManager, engine: eth.CreateConsensusEngine(ctx, &config.Ethash, chainConfig, chainDb), shutdownChan: make(chan bool), networkId: config.NetworkId, bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: eth.NewBloomIndexer(chainDb, light.BloomTrieFrequency), chtIndexer: light.NewChtIndexer(chainDb, true), bloomTrieIndexer: light.NewBloomTrieIndexer(chainDb, true), } leth.relay = NewLesTxRelay(peers, leth.reqDist) leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool) leth.odr = NewLesOdr(chainDb, leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer, leth.retriever) if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil { return nil, err } leth.bloomIndexer.Start(leth.blockchain) // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) leth.blockchain.SetHead(compat.RewindTo) rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) // <<=== #6 on next line if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, true, ClientProtocolVersions, config.NetworkId, leth.eventMux, leth.engine, leth.peers, leth.blockchain, nil, chainDb, leth.odr, leth.relay, leth.serverPool, quitSync, &leth.wg); err != nil { return nil, err } leth.ApiBackend = &LesApiBackend{leth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.GasPrice } leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) return leth, nil }
The Ethereum sub-protocol manages peers within the Ethereum network. The
NewProtocolManager
method returns a new Ethereum sub-protocol manager.NewProtocolManager
is a long method, so only a portion of it is shown here; seeeth/handler.go#L99-L182
to see all of it.Suggestion
: refactorNewProtocolManager
into smaller methods.ProtocolManager.SubProtocols
is assigned ap2p.Protocol
for every supported protocol; seeeth/handler.go#L132
:// Initiate a sub-protocol for every implemented version we can handle manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions)) for i, version := range ProtocolVersions { // Skip protocol version if incompatible with the mode of operation if mode == downloader.FastSync && version < eth63 { continue } // Compatible; initialise the sub-protocol version := version // Closure for the run manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{ // <<=== #6a Name: ProtocolName, Version: version, Length: ProtocolLengths[i], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { // <<=== #7b start peer := manager.newPeer(int(version), p, rw) select { case manager.newPeerCh <- peer: manager.wg.Add(1) defer manager.wg.Done() return manager.handle(peer)</b> case <-manager.quitSync: return p2p.DiscQuitting } }, // <<=== #7b end NodeInfo: func() interface{} { return manager.NodeInfo() }, PeerInfo: func(id discover.NodeID) interface{} { if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { return p.Info() } return nil }, }) }
Each of the
SubProtocols
defines aRun
method that calls theProtocolManager
'shandle()
method; seeeth/handler.go#L142
, contained in the preceding code snippet.The
Run
method of eachSubProtocol
is called whengeth
starts theNode
.a. First the
Node.Start()
method is invoked; seenode/node.go#L138-L228
// Start create a live P2P node and starts running it. func (n *Node) Start() error {
b. Within
Node.Start()
, this line defines a variable calledrunning
that will point to a newServer
instance:running := &p2p.Server{Config: n.serverConfig}
d. The supported protocols are appended and the p2p server is started; see
node/node.go#L196-L198
// Gather the protocols and start the freshly assembled P2P server for _, service := range services { running.Protocols = append(running.Protocols, service.Protocols()...) } if err := running.Start(); err != nil { return convertFileLockError(err) }
e. Near the end of
Node.Start()
, we seep2p/server.go#L504
go srv.run(dialer)
f. TODO write explanation for this step; see
p2p/server.go#L894
remoteRequested, err := p.run()
g. TODO write explanation for this step;see
p2p/peer.go#L197
p.startProtocols(writeStart, writeErr)
h. TODO write explanation for this step;see
p2p/peer.go#L348
err := proto.Run(p, rw)
An infinite loop handles incoming messages from the connected peer. See eth/handler.go#L307-L313:
// main loop. handle incoming messages. for { if err := pm.handleMsg(p); err != nil { p.Log().Debug("Ethereum message handling failed", "err", err) return err } }