先看下启动的日志:
y@ubuntu:~/blockchain/Ethereum/go-ethereum-1.8.13$ build/bin/geth --identity "TestNode1" --datadir "data0" --rpc --rpcapi "db,eth,net,web3" --port "30303" --networkid "29382" --ws --wsorigins="*" --rpccorsdomain="*" console
INFO [11-06|23:35:10.501] Maximum peer count ETH=25 LES=0 total=25
INFO [11-06|23:35:10.502] Starting peer-to-peer node instance=Geth/TestNode1/v1.8.13-stable/linux-amd64/go1.10.1
INFO [11-06|23:35:10.502] Allocated cache and file handles database=/home/y/blockchain/Ethereum/go-ethereum-1.8.13/data0/geth/chaindata cache=768 handles=512
INFO [11-06|23:35:10.522] Writing default main-net genesis block
INFO [11-06|23:35:10.919] Persisted trie from memory database nodes=12356 size=1.88mB time=80.670674ms gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [11-06|23:35:10.920] Initialised chain configuration config="{ChainID: 1 Homestead: 1150000 DAO: 1920000 DAOSupport: true EIP150: 2463000 EIP155: 2675000 EIP158: 2675000 Byzantium: 4370000 Constantinople: Engine: ethash}"
INFO [11-06|23:35:10.920] Disk storage enabled for ethash caches dir=/home/y/blockchain/Ethereum/go-ethereum-1.8.13/data0/geth/ethash count=3
INFO [11-06|23:35:10.920] Disk storage enabled for ethash DAGs dir=/home/y/.ethash count=2
INFO [11-06|23:35:10.920] Initialising Ethereum protocol versions="[63 62]" network=29382
INFO [11-06|23:35:10.920] Loaded most recent local header number=0 hash=d4e567…cb8fa3 td=17179869184
INFO [11-06|23:35:10.920] Loaded most recent local full block number=0 hash=d4e567…cb8fa3 td=17179869184
INFO [11-06|23:35:10.920] Loaded most recent local fast block number=0 hash=d4e567…cb8fa3 td=17179869184
INFO [11-06|23:35:10.920] Regenerated local transaction journal transactions=0 accounts=0
INFO [11-06|23:35:10.920] Starting P2P networking
INFO [11-06|23:35:13.046] UDP listener up self=enode://93348ffc72a60b28baabf268158749e187e393b5986a3fed9c1f2d6dfe76d3d232a984e74cf7034e6ea9a7607550bf58056c59a1fd812d4caa8ca7989f0ce0f0@[::]:30303
INFO [11-06|23:35:13.047] RLPx listener up self=enode://93348ffc72a60b28baabf268158749e187e393b5986a3fed9c1f2d6dfe76d3d232a984e74cf7034e6ea9a7607550bf58056c59a1fd812d4caa8ca7989f0ce0f0@[::]:30303
INFO [11-06|23:35:13.048] IPC endpoint opened url=/home/y/blockchain/Ethereum/go-ethereum-1.8.13/data0/geth.ipc
INFO [11-06|23:35:13.049] HTTP endpoint opened url=http://127.0.0.1:8545 cors=* vhosts=localhost
INFO [11-06|23:35:13.052] WebSocket endpoint opened url=ws://127.0.0.1:8546
Welcome to the Geth JavaScript console!
instance: Geth/TestNode1/v1.8.13-stable/linux-amd64/go1.10.1
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>
分析下源码
Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。
在cmd/geth/main.go中,首先定义app:
var (
// Git SHA1 commit hash of the release (set via linker flags)
gitCommit = ""
// The app that holds all commands and flags.
app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
// flags that configure the node
nodeFlags = []cli.Flag{
utils.IdentityFlag,
utils.UnlockedAccountFlag,
utils.PasswordFileFlag,
.....
然后通过init()函数来初始化app:
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.Flags = append(app.Flags, metricsFlags...)
....
其中app.Action表示如果用户没有输入其他的子命令的情况下,会调用这个字段指向的函数,即geth()。
geth的用法是
geth [options] command [command options] [arguments...]
app.Flags和app.Commands分别设置了对应的的[options]和command
通过上面的init()方法就把用户输入的命令解析完成了,保存在对象app中,下一步执行main()方法:
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
主要就是调用app.Run()方法:
func (a *App) Run(arguments []string) (err error) {
a.Setup() //初始化数据结构
// 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)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
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()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
if a.Action == nil {
a.Action = helpCommand.Action
}
// Run default Action
err = HandleAction(a.Action, context)
HandleExitCoder(err)
return err
}
最终会调用HandleAction()方法执行默认的action:
// HandleAction attempts to figure out which Action signature was used. If
// it's an ActionFunc or a func with the legacy signature for Action, the func
// is run!
func HandleAction(action interface{}, context *Context) (err error) {
if a, ok := action.(ActionFunc); ok {
return a(context)
} else if a, ok := action.(func(*Context) error); ok {
return a(context)
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
a(context)
return nil
} else {
return errInvalidActionType
}
}
而默认的action就是之前分析的init()方法中通过
app.Action = geth
设置的main.go中的geth()方法:
// 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 {
if args := ctx.Args(); len(args) > 0 {
return fmt.Errorf("invalid command: %q", args[0])
}
node := makeFullNode(ctx) //创建全节点对象
startNode(ctx, node) //启动全节点
node.Wait()
return nil
}
该方法会根据命令行参数创建并启动一个全节点,然后以阻塞模式运行,并等待被关闭。可以看到,该方法先通过makeFullNode()方法创建一个全节点对象,然后通过startNode()方法来启动该节点,下面详细分析下这两个方法。
func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx) //创建节点和配置信息,配置包括Eth,Shh,Node,Dashboard
utils.RegisterEthService(stack, &cfg.Eth) // 注册eth服务
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
}
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
shhEnabled := enableWhisper(ctx)
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
if shhEnabled || shhAutoEnabled {
if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
}
if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
}
utils.RegisterShhService(stack, &cfg.Shh)
}
// Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
}
return stack
}
makeConfigNode(),加载配置并创建节点
先调用 makeConfigNode() 方法来加载配置并创建节点,返回节点对象stack和配置对象cfg,程序里把节点叫做协议栈(protocol stack),所以可以看到,节点对象被命名为stack。
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
// Load defaults.
cfg := gethConfig{
Eth: eth.DefaultConfig, //配置eth的一些基本信息,里面设置了NetWorkId(默认值为1,这也是eth的主网)的信息
Shh: whisper.DefaultConfig, //里面配置了两个参数:1.MaxMessageSize 2.MinimumAcceptedPOW(POW:Proof of Work,工作量证明)
Node: defaultNodeConfig(), //这了初始化了节点的配置,主要有网络的一些配置,还有就是数据的存储路径
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) //这里设置了P2P,IPC,HTTP,WS,DataDir,KeyStoreDir的值
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
}
RegisterEthService(),注册eth服务
上面创建好节点和配置信息后,再调用cmd/utils/flags.go里的RegisterEthService()方法注册eth服务,
// 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)
})
} else {
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
fullNode, err := eth.New(ctx, cfg)
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)
}
}
这个函数里会判断同步的模式 ,如果是LightSync模式(该模式下只会下载区块头)则会使用les.New()创建轻节点,否则就使用eth.New()创建全节点,这里是建立全节点,即调用eth.New()方法:
// New creates a new Ethereum object (including the
// initialisation of the common Ethereum object)
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
if config.SyncMode == downloader.LightSync {
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
}
if !config.SyncMode.IsValid() {
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
}
//创建leveldb,leveldb是以太坊区块数据的存储数据库
chainDb, err := CreateDB(ctx, config, "chaindata")
if err != nil {
return nil, err
}
//装载创世区块。 根据节点条件判断是从数据库里面读取,还是从默认配置文件读取,还是从自定义配置文件读取,
//或者是从代码里面获取默认值。并返回区块链的config和创世区块的hash。
chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis)
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
return nil, genesisErr
}
log.Info("Initialised chain configuration", "config", chainConfig)
//初始化以太坊对象eth,装载Etherum struct的各个成员。eventMux和accountManager是Node启动eth service的时候传入的。
//eventMux可以认为是一个全局的事件多路复用器,accountManager认为是一个全局的账户管理器。engine创建共识引擎。
//etherbase配置此Etherum的主账号地址。初始化bloomRequests通道和bloom过滤器。
eth := &Ethereum{
config: config,
chainDb: chainDb,
chainConfig: chainConfig,
eventMux: ctx.EventMux,
accountManager: ctx.AccountManager,
engine: CreateConsensusEngine(ctx, &config.Ethash, chainConfig, chainDb),
shutdownChan: make(chan bool),
networkID: config.NetworkId,
gasPrice: config.GasPrice,
etherbase: config.Etherbase,
bloomRequests: make(chan chan *bloombits.Retrieval),
bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks),
}
log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId)
//判断数据库版本号和客户端版本号是否一致,如果不一致,提示更新客户端
if !config.SkipBcVersionCheck {
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
if bcVersion != core.BlockChainVersion && bcVersion != 0 {
return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d). Run geth upgradedb.\n", bcVersion, core.BlockChainVersion)
}
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
}
var (
vmConfig = vm.Config{EnablePreimageRecording: config.EnablePreimageRecording}
cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout}
)
//使用数据库里面的可用信息构造了一个初始化好的区块链,同时初始化了以太坊默认的验证器和处理器 (Validator and Processor)
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, eth.chainConfig, eth.engine, vmConfig)
if err != nil {
return nil, err
}
// 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)
eth.blockchain.SetHead(compat.RewindTo)
rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
}
//启动bloomIndexer.
eth.bloomIndexer.Start(eth.blockchain)
if config.TxPool.Journal != "" {
config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
}
//创建交易池,收集、排序以及过滤节点发送来的交易
eth.txPool = core.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain)
//创建以太坊协议管理器,管理对等节点的P2P网络通信功能
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb); err != nil {
return nil, err
}
//创建矿工,矿工用于创建区块和寻找工作量证明
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
eth.miner.SetExtra(makeExtraData(config.ExtraData))
eth.APIBackend = &EthAPIBackend{eth, nil}
gpoParams := config.GPO
if gpoParams.Default == nil {
gpoParams.Default = config.GasPrice
}
// 创建预言最新gasprice的预言机
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
return eth, nil
}
Ethereum作为一个service,注入进Node中,上面的方法实现的就是Ethereum service的初始化和启动过程,该过程配置了很多东西,基本上涉及到了以太坊区块链系统的所有内容。
总结下,这个函数主要做了以下工作:
1、创建leveldb数据库
2、加载创始区块
3、初始化以太坊对象eth
4、初始化区块链BlookChain
5、初始化交易池
6、初始化以太坊协议管理器
7、初始化矿工
8、初始化预言gasprice的预言机
回到cmd/geth/main.go中,在节点配置完成后执行startNode()方法来启动节点,
// startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner.
func startNode(ctx *cli.Context, stack *node.Node) {
debug.Memsize.Add("node", stack)
//启动节点
// Start up the node itself
utils.StartNode(stack)
//解锁账户
// Unlock any account specifically requested
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
passwords := utils.MakePasswordList(ctx)
unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
for i, account := range unlocks {
if trimmed := strings.TrimSpace(account); trimmed != "" {
unlockAccount(ctx, ks, trimmed, i, passwords)
}
}
// 开启钱包事件监听
// Register wallet event handlers to open and auto-derive wallets
events := make(chan accounts.WalletEvent, 16)
stack.AccountManager().Subscribe(events)
go func() {
// Create a chain state reader for self-derivation
rpcClient, err := stack.Attach()
if err != nil {
utils.Fatalf("Failed to attach to self: %v", err)
}
stateReader := ethclient.NewClient(rpcClient)
// Open any wallets already attached
for _, wallet := range stack.AccountManager().Wallets() {
if err := wallet.Open(""); err != nil {
log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
}
}
// Listen for wallet event till termination
for event := range events {
switch event.Kind {
case accounts.WalletArrived:
if err := event.Wallet.Open(""); err != nil {
log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
}
case accounts.WalletOpened:
status, _ := event.Wallet.Status()
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
derivationPath := accounts.DefaultBaseDerivationPath
if event.Wallet.URL().Scheme == "ledger" {
derivationPath = accounts.DefaultLedgerBaseDerivationPath
}
event.Wallet.SelfDerive(derivationPath, stateReader)
case accounts.WalletDropped:
log.Info("Old wallet dropped", "url", event.Wallet.URL())
event.Wallet.Close()
}
}
}()
// Start auxiliary services if enabled
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
// Mining only makes sense if a full Ethereum node is running
if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
utils.Fatalf("Light clients do not support mining")
}
var ethereum *eth.Ethereum
if err := stack.Service(ðereum); err != nil {
utils.Fatalf("Ethereum service not running: %v", err)
}
// Use a reduced number of threads if requested
if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 {
type threaded interface {
SetThreads(threads int)
}
if th, ok := ethereum.Engine().(threaded); ok {
th.SetThreads(threads)
}
}
// Set the gas price to the limits from the CLI and start mining
ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))
if err := ethereum.StartMining(true); err != nil {
utils.Fatalf("Failed to start mining: %v", err)
}
}
}
根据源码可以看到,startNode()的主要功能有:
1、启动node;
2、解锁账户;
3、开启钱包事件监听;
其中utils.StartNode(stack)方法会调用node/node.go中的Start()方法来创建一个 P2P 节点并启动它:
// Start create a live P2P node and starts running it.
func (n *Node) Start() error {
n.lock.Lock()
defer n.lock.Unlock()
//首先判断节点是否已经在运行了
// Short circuit if the node's already running
if n.server != nil {
return ErrNodeRunning
}
if err := n.openDataDir(); err != nil {
return err
}
// 初始化 p2p server
// Initialize the p2p server. This creates the node key and
// discovery databases.
n.serverConfig = n.config.P2P
n.serverConfig.PrivateKey = n.config.NodeKey()
n.serverConfig.Name = n.config.NodeName()
n.serverConfig.Logger = n.log
if n.serverConfig.StaticNodes == nil {
n.serverConfig.StaticNodes = n.config.StaticNodes()
}
if n.serverConfig.TrustedNodes == nil {
n.serverConfig.TrustedNodes = n.config.TrustedNodes()
}
if n.serverConfig.NodeDatabase == "" {
n.serverConfig.NodeDatabase = n.config.NodeDB()
}
//构建p2p.Server对象
running := &p2p.Server{Config: n.serverConfig}
n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name)
// Otherwise copy and specialize the P2P configuration
services := make(map[reflect.Type]Service)
for _, constructor := range n.serviceFuncs {
// Create a new context for the particular service
ctx := &ServiceContext{
config: n.config,
services: make(map[reflect.Type]Service),
EventMux: n.eventmux,
AccountManager: n.accman,
}
for kind, s := range services { // copy needed for threaded access
ctx.services[kind] = s
}
// Construct and save the service
service, err := constructor(ctx)
if err != nil {
return err
}
kind := reflect.TypeOf(service)
if _, exists := services[kind]; exists {
return &DuplicateServiceError{Kind: kind}
}
services[kind] = service
}
// Gather the protocols and start the freshly assembled P2P server
for _, service := range services {
running.Protocols = append(running.Protocols, service.Protocols()...)
}
//调用p2p.Server对象的Start()方法,启动p2p Server
if err := running.Start(); err != nil {
return convertFileLockError(err)
}
// 启动各个服务
// Start each of the services
started := []reflect.Type{}
for kind, service := range services {
// Start the next service, stopping all previous upon failure
if err := service.Start(running); err != nil {
for _, kind := range started {
services[kind].Stop()
}
running.Stop()
return err
}
// Mark the service started for potential cleanup
started = append(started, kind)
}
//最后启动RPC
// Lastly start the configured RPC interfaces
if err := n.startRPC(services); err != nil {
for _, service := range services {
service.Stop()
}
running.Stop()
return err
}
// Finish initializing the startup
n.services = services
n.server = running
n.stop = make(chan struct{})
return nil
}
在这个方法中,首先判断节点是否已经在运行,然后对p2p Server进行初始化,再构建p2p.Server对象running,然后执行该对象的Start()方法来启动p2p Server,最后启动各个services服务,启动RPC。
至此,以太坊节点的启动过程就完成了。
参考:
以太坊源码解读(3)以太坊启动流程简析
以太坊源码深入分析(5)-- Ethereum服务和以太坊P2P协议发送广播源码分析
以太坊源码深入分析(2)-- go-ethereum 客户端入口代码和Node分析
以太坊网络架构解析