啟動參數
以太坊是如何啟動一個網絡節點的呢?
./geth --datadir "../data0" --nodekeyhex "27aa615f5fa5430845e4e97229def5f23e9525a20640cc49304f40f3b43824dc" --bootnodes $enodeid --mine --debug --metrics --syncmode="full" --gcmode=archive --gasprice 0 --port 30303 --rpc --rpcaddr "0.0.0.0" --rpcport 8545 --rpcapi "db,eth,net,web3,personal" --nat any --allow-insecure-unlock 2>>log 1>>log 0>>log >>log &
參數說明:
- geth : 編譯好的geth程序,可以起別名
- datadir:數據庫和keystore密鑰的數據目錄
- nodekeyhex: 十六進制的P2P節點密鑰
- bootnodes:用於P2P發現引導的enode urls
- mine:打開挖礦
- debug:突出顯示調用位置日志(文件名及行號)
- metrics: 啟用metrics收集和報告
- syncmode:同步模式 ("fast", "full", or "light")
- gcmode:表示即時將內存中的數據寫入到文件中,否則重啟節點可能會導致區塊高度歸零而丟失數據
- gasprice:挖礦接受交易的最低gas價格
- port:網卡監聽端口(默認值:30303)
- rpc:啟用HTTP-RPC服務器
- rpcaddr:HTTP-RPC服務器接口地址(默認值:“localhost”)
- rpcport:HTTP-RPC服務器監聽端口(默認值:8545)
- rpcapi:基於HTTP-RPC接口提供的API
- nat: NAT端口映射機制 (any|none|upnp|pmp|extip:
) (默認: “any”) - allow-insecure-unlock:用於解鎖賬戶
詳細的以太坊啟動參數可以參考我的以太坊理論系列,里面有對參數的詳細解釋。
源碼分析
geth
位於cmd/geth/main.go
文件中,入口如下:
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
我們通過這張圖可以看出來:main()並不是真正意義上的入口,在初始化完常量和變量以后,會先調用模塊的init()函數,然后才是main()函數。所以初始化的工作是在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-2019 The go-ethereum Authors"
app.Commands = []cli.Command{
....
....
...
}
從這我們找到了入口函數geth:
func geth(ctx *cli.Context) error {
if args := ctx.Args(); len(args) > 0 {
return fmt.Errorf("invalid command: %q", args[0])
}
prepare(ctx)
node := makeFullNode(ctx)
defer node.Close()
startNode(ctx, node)
node.Wait()
return nil
}
主要做了以下幾件事:
- 准備操作內存緩存配額並設置度量系統
- 加載配置和注冊服務
- 啟動節點
- 守護當前線程
加載配置和注冊服務
makeFullNode
1.加載配置
makeConfigNode
首先加載默認配置(作為主網節點啟動):
cfg := gethConfig{
Eth: eth.DefaultConfig,
Shh: whisper.DefaultConfig,
Node: defaultNodeConfig(),
}
- eth.DefaultConfig : 以太坊節點的主要參數配置。主要包括: 同步模式(fast)、chainid、交易池配置、gasprice、挖礦配置等;
- whisper.DefaultConfig : 主要用於配置網絡間通訊;
- defaultNodeConfig() : 主要用於配置對外提供的RPC節點服務;
- dashboard.DefaultConfig : 主要用於對外提供看板數據訪問服務。
接着加載自定義配置(適用私有鏈):
if file := ctx.GlobalString(configFileFlag.Name); file != "" {
if err := loadConfig(file, &cfg); err != nil {
utils.Fatalf("%v", err)
}
}
最后加載命令窗口參數(開發階段):
utils.SetNodeConfig(ctx, &cfg.Node) // 本地節點配置
utils.SetEthConfig(ctx, stack, &cfg.Eth)// 以太坊配置
utils.SetShhConfig(ctx, stack, &cfg.Shh)// whisper配置
2.RegisterEthService
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)
}
}
出現了兩個新類型:ServiceContext和Service。
先看一下ServiceContext的定義:
type ServiceContext struct {
config *Config
services map[reflect.Type]Service // Index of the already constructed services
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
AccountManager *accounts.Manager // Account manager created by the node.
}
ServiceContext主要是存儲了一些從結點(或者叫協議棧)那里繼承過來的、和具體Service無關的一些信息,比如結點config、account manager等。其中有一個services字段保存了當前正在運行的所有Service.
接下來看一下Service的定義:
type Service interface {
// Protocols retrieves the P2P protocols the service wishes to start.
// 協議檢索服務希望啟動的P2P協議
Protocols() []p2p.Protocol
// APIs retrieves the list of RPC descriptors the service provides
// API檢索服務提供的RPC描述符列表
APIs() []rpc.API
// Start is called after all services have been constructed and the networking
// layer was also initialized to spawn any goroutines required by the service.
//在所有服務都已構建完畢並且網絡層也已初始化以生成服務所需的所有goroutine之后,將調用start。
Start(server *p2p.Server) error
// Stop terminates all goroutines belonging to the service, blocking until they
// are all terminated.
//Stop終止屬於該服務的所有goroutine,直到它們全部終止為止一直阻塞。
Stop() error
}
在服務注冊過程中,主要注冊四個服務:EthService、DashboardService、ShhService、EthStatsService,這四種服務類均擴展自Service接口。其中,EthService根據同步模式的不同,分為兩種實現:
- LightEthereum,支持LightSync模式
- Ethereum,支持FullSync、FastSync模式
LightEthereum作為輕客戶端,與Ethereum區別在於,它只需要更新區塊頭。當需要查詢區塊體數據時,需要通過調用其他全節點的les服務進行查詢;另外,輕客戶端本身是不能進行挖礦的。
回到RegisterEthService代碼,分兩個來講:
LightSync同步:
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return les.New(ctx, cfg)
})
func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
1.ctx.OpenDatabase // 創建leveldb數據庫
2.core.SetupGenesisBlockWithOverride// 根據創世配置初始化鏈數據目錄
3.實例化本地鏈id、共識引擎、注冊peer節點、帳戶管理器以及布隆過濾器的初始化
4.light.NewLightChain// 使用數據庫中可用的信息返回完全初始化的輕鏈。它初始化默認的以太坊頭
5.light.NewTxPool // 實例化交易池NewTxPool
6.leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil}
}
FullSync/Fast同步:
-
參數校驗
if config.SyncMode == downloader.LightSync { .... if !config.SyncMode.IsValid() { .... if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 { .... if config.NoPruning && config.TrieDirtyCache > 0 {
-
打開數據庫
ctx.OpenDatabaseWithFreezer
-
根據創世配置初始化鏈數據目錄
core.SetupGenesisBlockWithOverride
-
實例化Ethereum對象
-
創建BlockChain實例對象
core.NewBlockChain
-
實例化交易池
core.NewTxPool
-
實例化協議管理器
NewProtocolManager(...)
-
實例化對外API服務
&EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil}
3.RegisterShhService
注冊Whisper服務,用於p2p網絡間加密通信。
whisper.New(cfg), nil
4.RegisterEthStatsService
注冊狀態推送服務,將當前以太坊網絡狀態推送至指定URL地址.
ethstats.New(url, ethServ, lesServ)
啟動節點
啟動本地節點以及啟動所有注冊的服務。
1.啟動節點
startNode
1.1 stack.Start()
-
實例化p2p.Server對象。
running := &p2p.Server{Config: n.serverConfig}
-
為注冊的服務創建上下文
for _, constructor := range n.serviceFuncs { ctx := &ServiceContext{ .... } }
-
收集協議並啟動新組裝的p2p server
for kind, service := range services { if err := service.Start(running); err != nil { ... } }
-
最后啟動配置的RPC接口
n.startRPC(services)
- startInProc (啟動進程內通訊服務)
- startIPC (啟動IPC RPC端點)
- startHTTP(啟動HTTP RPC端點)
- startWS (啟動websocket RPC端點)
2.解鎖賬戶
unlockAccounts
在datadir/keystore目錄主要用於記錄在當前節點創建的帳戶keystore文件。如果你的keystore文件不在本地是無法進行解鎖的。
//解鎖datadir/keystore目錄中帳戶
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
passwords := utils.MakePasswordList(ctx)
for i, account := range unlocks {
unlockAccount(ks, account, i, passwords)
}
3.注冊錢包事件
events := make(chan accounts.WalletEvent, 16)
stack.AccountManager().Subscribe(events)
4.監聽錢包事件
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)
var derivationPaths []accounts.DerivationPath
if event.Wallet.URL().Scheme == "ledger" {
derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath)
}
derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)
event.Wallet.SelfDerive(derivationPaths, ethClient)
case accounts.WalletDropped:
log.Info("Old wallet dropped", "url", event.Wallet.URL())
event.Wallet.Close()
}
}
}()
5.啟動挖礦
ethereum.StartMining(threads)
啟動守護線程
stop通道阻塞當前線程,直到節點被停止。
node.Wait()
總結
以太坊啟動主要就做了3件事,包括加載配置注冊服務、啟動節點相關服務以及啟動守護線程。
參考:github地址