源码下载
以太坊官方提供了Go、C++、Python各个版本的实现,具体可以在官方GitHub(https://github.com/ethereum)中进行查找:
本系列源码分析文章主要基于当前最新版本v 1.10.2版本进行分析:
https://github.com/ethereum/go-ethereum/releases
目录结构
以太坊源码目录结构如下:
├─accounts 账号相关
├─build 编译生成的程序
├─cmd geth程序主体
├─common 工具函数库
├─consensus 共识算法
├─console 交互式命令
├─contracts 合约相关
├─core 以太坊核心部分
├─crypto 加密函数库
├─docs 说明文档
├─eth 以太坊协议
├─ethclient 以太坊RPC客户端
├─ethdb 底层存储
├─ethstats 统计报告
├─event 事件处理
├─graphql
├─internal RPC调用
├─les 轻量级子协议
├─light 轻客户端部分功能
├─log 日志模块
├─metrics 服务监控相关
├─miner 挖矿相关
├─mobile Geth的移动端API
├─node 接口节点
├─p2p p2p网络协议
├─params 一些预设参数值
├─rlp RLP系列化格式
├─rpc RPC接口
├─signer 签名相关
├─swarm 分布式存储
├─tests 以太坊JSON测试
└─trie Merkle Patricia实现
启动分析
以太坊启动的入口位于go-ethereum-1.10.2\\\\cmd\\\\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])
}
prepare(ctx)
stack, backend := makeFullNode(ctx)
defer stack.Close()
startNode(ctx, stack, backend)
stack.Wait()
return nil
}
在这里主要做了一下几件事情:
1、准备操作内存缓存余量和度量设置
2、加载配置
3、启动节点
4、启动守护进程
准备工作
在这里我们一步一步来看,在prepare函数中首先会根据传入的参数来匹配一些已知的全局启动参数并打印其log,之后根据轻节点和全节点来设置分配给内部缓存的大小,然后进行度量设置:
文件位置:go-ethereum-1.10.2\\\\cmd\\\\geth\\\\main.go L263
// prepare manipulates memory cache allowance and setups metric system.
// This function should be called before launching devp2p stack.
func prepare(ctx *cli.Context) {
// If we\\\'re running a known preset, log it for convenience.
switch {
case ctx.GlobalIsSet(utils.RopstenFlag.Name):
log.Info(\\\"Starting Geth on Ropsten testnet...\\\")
case ctx.GlobalIsSet(utils.RinkebyFlag.Name):
log.Info(\\\"Starting Geth on Rinkeby testnet...\\\")
case ctx.GlobalIsSet(utils.GoerliFlag.Name):
log.Info(\\\"Starting Geth on G?rli testnet...\\\")
case ctx.GlobalIsSet(utils.YoloV3Flag.Name):
log.Info(\\\"Starting Geth on YOLOv3 testnet...\\\")
case ctx.GlobalIsSet(utils.DeveloperFlag.Name):
log.Info(\\\"Starting Geth in ephemeral dev mode...\\\")
case !ctx.GlobalIsSet(utils.NetworkIdFlag.Name):
log.Info(\\\"Starting Geth on Ethereum mainnet...\\\")
}
// If we\\\'re a full node on mainnet without --cache specified, bump default cache allowance
if ctx.GlobalString(utils.SyncModeFlag.Name) != \\\"light\\\" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) {
// Make sure we\\\'re not on any supported preconfigured testnet either
if !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) {
// Nope, we\\\'re really on mainnet. Bump that cache up!
log.Info(\\\"Bumping default cache on mainnet\\\", \\\"provided\\\", ctx.GlobalInt(utils.CacheFlag.Name), \\\"updated\\\", 4096)
ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096))
}
}
// If we\\\'re running a light client on any network, drop the cache to some meaningfully low amount
if ctx.GlobalString(utils.SyncModeFlag.Name) == \\\"light\\\" && !ctx.GlobalIsSet(utils.CacheFlag.Name) {
log.Info(\\\"Dropping default light client cache\\\", \\\"provided\\\", ctx.GlobalInt(utils.CacheFlag.Name), \\\"updated\\\", 128)
ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(128))
}
// Start metrics export if enabled
utils.SetupMetrics(ctx)
// Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second)
}
最后调用CollectProcessMetrics定期收集有关运行过程的各种度量
文件位置:go-ethereum-1.10.2\\\\metrics\\\\metrics.go L57
// CollectProcessMetrics periodically collects various metrics about the running
// process.
func CollectProcessMetrics(refresh time.Duration) {
// Short circuit if the metrics system is disabled
if !Enabled {
return
}
refreshFreq := int64(refresh / time.Second)
// Create the various data collectors
cpuStats := make([]*CPUStats, 2)
memstats := make([]*runtime.MemStats, 2)
diskstats := make([]*DiskStats, 2)
for i := 0; i < len(memstats); i++ {
cpuStats[i] = new(CPUStats)
memstats[i] = new(runtime.MemStats)
diskstats[i] = new(DiskStats)
}
// Define the various metrics to collect
var (
cpuSysLoad = GetOrRegisterGauge(\\\"system/cpu/sysload\\\", DefaultRegistry)
cpuSysWait = GetOrRegisterGauge(\\\"system/cpu/syswait\\\", DefaultRegistry)
cpuProcLoad = GetOrRegisterGauge(\\\"system/cpu/procload\\\", DefaultRegistry)
cpuThreads = GetOrRegisterGauge(\\\"system/cpu/threads\\\", DefaultRegistry)
cpuGoroutines = GetOrRegisterGauge(\\\"system/cpu/goroutines\\\", DefaultRegistry)
memPauses = GetOrRegisterMeter(\\\"system/memory/pauses\\\", DefaultRegistry)
memAllocs = GetOrRegisterMeter(\\\"system/memory/allocs\\\", DefaultRegistry)
memFrees = GetOrRegisterMeter(\\\"system/memory/frees\\\", DefaultRegistry)
memHeld = GetOrRegisterGauge(\\\"system/memory/held\\\", DefaultRegistry)
memUsed = GetOrRegisterGauge(\\\"system/memory/used\\\", DefaultRegistry)
diskReads = GetOrRegisterMeter(\\\"system/disk/readcount\\\", DefaultRegistry)
diskReadBytes = GetOrRegisterMeter(\\\"system/disk/readdata\\\", DefaultRegistry)
diskReadBytesCounter = GetOrRegisterCounter(\\\"system/disk/readbytes\\\", DefaultRegistry)
diskWrites = GetOrRegisterMeter(\\\"system/disk/writecount\\\", DefaultRegistry)
diskWriteBytes = GetOrRegisterMeter(\\\"system/disk/writedata\\\", DefaultRegistry)
diskWriteBytesCounter = GetOrRegisterCounter(\\\"system/disk/writebytes\\\", DefaultRegistry)
)
// Iterate loading the different stats and updating the meters
for i := 1; ; i++ {
location1 := i % 2
location2 := (i - 1) % 2
ReadCPUStats(cpuStats[location1])
cpuSysLoad.Update((cpuStats[location1].GlobalTime - cpuStats[location2].GlobalTime) / refreshFreq)
cpuSysWait.Update((cpuStats[location1].GlobalWait - cpuStats[location2].GlobalWait) / refreshFreq)
cpuProcLoad.Update((cpuStats[location1].LocalTime - cpuStats[location2].LocalTime) / refreshFreq)
cpuThreads.Update(int64(threadCreateProfile.Count()))
cpuGoroutines.Update(int64(runtime.NumGoroutine()))
runtime.ReadMemStats(memstats[location1])
memPauses.Mark(int64(memstats[location1].PauseTotalNs - memstats[location2].PauseTotalNs))
memAllocs.Mark(int64(memstats[location1].Mallocs - memstats[location2].Mallocs))
memFrees.Mark(int64(memstats[location1].Frees - memstats[location2].Frees))
memHeld.Update(int64(memstats[location1].HeapSys - memstats[location1].HeapReleased))
memUsed.Update(int64(memstats[location1].Alloc))
if ReadDiskStats(diskstats[location1]) == nil {
diskReads.Mark(diskstats[location1].ReadCount - diskstats[location2].ReadCount)
diskReadBytes.Mark(diskstats[location1].ReadBytes - diskstats[location2].ReadBytes)
diskWrites.Mark(diskstats[location1].WriteCount - diskstats[location2].WriteCount)
diskWriteBytes.Mark(diskstats[location1].WriteBytes - diskstats[location2].WriteBytes)
diskReadBytesCounter.Inc(diskstats[location1].ReadBytes - diskstats[location2].ReadBytes)
diskWriteBytesCounter.Inc(diskstats[location1].WriteBytes - diskstats[location2].WriteBytes)
}
time.Sleep(refresh)
}
}
加载配置
之后调用makeFullNode加载配置文件:
文件位置:go-ethereum-1.10.2\\\\cmd\\\\geth\\\\config.go L141
// makeFullNode loads geth configuration and creates the Ethereum backend.
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
stack, cfg := makeConfigNode(ctx)
if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) {
cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name))
}
backend := utils.RegisterEthService(stack, &cfg.Eth)
// Configure GraphQL if requested
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, backend, cfg.Node)
}
// Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != \\\"\\\" {
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
}
return stack, backend
}
在这里会调用makeConfigNode来加载配置文件:
文件位置:go-ethereum-1.10.2\\\\cmd\\\\geth\\\\config.go L109
// makeConfigNode loads geth configuration and creates a blank node instance.
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
// Load defaults.
cfg := gethConfig{
Eth: ethconfig.Defaults,
Node: defaultNodeConfig(),
Metrics: metrics.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)
}
applyMetricConfig(ctx, &cfg)
return stack, cfg
}
从上面的代码中我们可以看到这里会加载默认设置,主要有以下几个:
-
ethconfig.Defaults:以太坊主网上使用的默认设置(缓存配置、数据库配置、网络ID配置、交易查询限制、Gas设置等)
// Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{
SyncMode: downloader.FastSync,
Ethash: ethash.Config{
CacheDir: \\\"ethash\\\",
CachesInMem: 2,
CachesOnDisk: 3,
CachesLockMmap: false,
DatasetsInMem: 1,
DatasetsOnDisk: 2,
DatasetsLockMmap: false,
},
NetworkId: 1,
TxLookupLimit: 2350000,
LightPeers: 100,
UltraLightFraction: 75,
DatabaseCache: 512,
TrieCleanCache: 154,
TrieCleanCacheJournal: \\\"triecache\\\",
TrieCleanCacheRejournal: 60 * time.Minute,
TrieDirtyCache: 256,
TrieTimeout: 60 * time.Minute,
SnapshotCache: 102,
Miner: miner.Config{
GasFloor: 8000000,
GasCeil: 8000000,
GasPrice: big.NewInt(params.GWei),
Recommit: 3 * time.Second,
},
TxPool: core.DefaultTxPoolConfig,
RPCGasCap: 25000000,
GPO: FullNodeGPO,
RPCTxFeeCap: 1, // 1 ether
}
defaultNodeConfig:默认节点配置
// fileDir:go-ethereum-1.10.2\\\\cmd\\\\geth\\\\config.go L99
func defaultNodeConfig() node.Config {
cfg := node.DefaultConfig
cfg.Name = clientIdentifier
cfg.Version = params.VersionWithCommit(gitCommit, gitDate)
cfg.HTTPModules = append(cfg.HTTPModules, \\\"eth\\\")
cfg.WSModules = append(cfg.WSModules, \\\"eth\\\")
cfg.IPCPath = \\\"geth.ipc\\\"
return cfg
}
// filedir:go-ethereum-1.10.2\\\\node\\\\defaults.go L39
// DefaultConfig contains reasonable default settings.
var DefaultConfig = Config{
DataDir: DefaultDataDir(),
HTTPPort: DefaultHTTPPort,
HTTPModules: []string{\\\"net\\\", \\\"web3\\\"},
HTTPVirtualHosts: []string{\\\"localhost\\\"},
HTTPTimeouts: rpc.DefaultHTTPTimeouts,
WSPort: DefaultWSPort,
WSModules: []string{\\\"net\\\", \\\"web3\\\"},
GraphQLVirtualHosts: []string{\\\"localhost\\\"},
P2P: p2p.Config{
ListenAddr: \\\":30303\\\",
MaxPeers: 50,
NAT: nat.Any(),
},
}
metrics.DefaultConfig:度量的默认配置
// filedir:go-ethereum-1.10.2\\\\metrics\\\\config.go
// DefaultConfig is the default config for metrics used in go-ethereum.
var DefaultConfig = Config{
Enabled: false,
EnabledExpensive: false,
HTTP: \\\"127.0.0.1\\\",
Port: 6060,
EnableInfluxDB: false,
InfluxDBEndpoint: \\\"http://localhost:8086\\\",
InfluxDBDatabase: \\\"geth\\\",
InfluxDBUsername: \\\"test\\\",
InfluxDBPassword: \\\"test\\\",
InfluxDBTags: \\\"host=localhost\\\",
}
之后加载用户自定义的配置:
// filedir:go-ethereum-1.10.2\\\\cmd\\\\geth\\\\config.go L118
// Load config file.
if file := ctx.GlobalString(configFileFlag.Name); file != \\\"\\\" {
if err := loadConfig(file, &cfg); err != nil {
utils.Fatalf(\\\"%v\\\", err)
}
}
之后调用SetNodeConfig应用对应的配置参数:
// filedir:go-ethereum-1.10.2\\\\cmd\\\\geth\\\\config.go L126
// 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)
}
applyMetricConfig(ctx, &cfg)
return stack, cfg
然后调用node.New方法来创建一个新的P2P节点:
// filedir:go-ethereum-1.10.2\\\\node\\\\node.go
// New creates a new P2P node, ready for protocol registration.
func New(conf *Config) (*Node, error) {
// Copy config and resolve the datadir so future changes to the current
// working directory don\\\'t affect the node.
confCopy := *conf
conf = &confCopy
if conf.DataDir != \\\"\\\" {
absdatadir, err := filepath.Abs(conf.DataDir)
if err != nil {
return nil, err
}
conf.DataDir = absdatadir
}
if conf.Logger == nil {
conf.Logger = log.New()
}
// Ensure that the instance name doesn\\\'t cause weird conflicts with
// other files in the data directory.
if strings.ContainsAny(conf.Name, `/\\\\`) {
return nil, errors.New(`Config.Name must not contain \\\'/\\\' or \\\'\\\\\\\'`)
}
if conf.Name == datadirDefaultKeyStore {
return nil, errors.New(`Config.Name cannot be \\\"` + datadirDefaultKeyStore + `\\\"`)
}
if strings.HasSuffix(conf.Name, \\\".ipc\\\") {
return nil, errors.New(`Config.Name cannot end in \\\".ipc\\\"`)
}
node := &Node{
config: conf,
inprocHandler: rpc.NewServer(),
eventmux: new(event.TypeMux),
log: conf.Logger,
stop: make(chan struct{}),
server: &p2p.Server{Config: conf.P2P},
databases: make(map[*closeTrackingDB]struct{}),
}
// Register built-in APIs.
node.rpcAPIs = append(node.rpcAPIs, node.apis()...)
// Acquire the instance directory lock.
if err := node.openDataDir(); err != nil {
return nil, err
}
// Ensure that the AccountManager method works before the node has started. We rely on
// this in cmd/geth.
am, ephemeralKeystore, err := makeAccountManager(conf)
if err != nil {
return nil, err
}
node.accman = am
node.ephemKeystore = ephemeralKeystore
// Initialize the p2p server. This creates the node key and discovery databases.
node.server.Config.PrivateKey = node.config.NodeKey()
node.server.Config.Name = node.config.NodeName()
node.server.Config.Logger = node.log
if node.server.Config.StaticNodes == nil {
node.server.Config.StaticNodes = node.config.StaticNodes()
}
if node.server.Config.TrustedNodes == nil {
node.server.Config.TrustedNodes = node.config.TrustedNodes()
}
if node.server.Config.NodeDatabase == \\\"\\\" {
node.server.Config.NodeDatabase = node.config.NodeDB()
}
// Check HTTP/WS prefixes are valid.
if err := validatePrefix(\\\"HTTP\\\", conf.HTTPPathPrefix); err != nil {
return nil, err
}
if err := validatePrefix(\\\"WebSocket\\\", conf.WSPathPrefix); err != nil {
return nil, err
}
// Configure RPC servers.
node.http = newHTTPServer(node.log, conf.HTTPTimeouts)
node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
node.ipc = newIPCServer(node.log, conf.IPCEndpoint())
return node, nil
}
然后调用SetEthConfig将eth相关命令行标志应用于配置
// filedir:go-ethereum-1.10.2\\\\cmd\\\\utils\\\\flags.go
// SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Avoid conflicting network flags
CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag)
CheckExclusive(ctx, LightServeFlag, SyncModeFlag, \\\"light\\\")
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can\\\'t use both ephemeral unlocked and external signer
if ctx.GlobalString(GCModeFlag.Name) == \\\"archive\\\" && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 {
ctx.GlobalSet(TxLookupLimitFlag.Name, \\\"0\\\")
log.Warn(\\\"Disable transaction unindexing for archive node\\\")
}
if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 {
log.Warn(\\\"LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited\\\")
}
var ks *keystore.KeyStore
if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 {
ks = keystores[0].(*keystore.KeyStore)
}
setEtherbase(ctx, ks, cfg)
setGPO(ctx, &cfg.GPO, ctx.GlobalString(SyncModeFlag.Name) == \\\"light\\\")
setTxPool(ctx, &cfg.TxPool)
setEthash(ctx, cfg)
setMiner(ctx, &cfg.Miner)
setWhitelist(ctx, cfg)
setLes(ctx, cfg)
// Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory()
if err == nil {
if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 {
log.Warn(\\\"Lowering memory allowance on 32bit arch\\\", \\\"available\\\", mem.Total/1024/1024, \\\"addressable\\\", 2*1024)
mem.Total = 2 * 1024 * 1024 * 1024
}
allowance := int(mem.Total / 1024 / 1024 / 3)
if cache := ctx.GlobalInt(CacheFlag.Name); cache > allowance {
log.Warn(\\\"Sanitizing cache to Go\\\'s GC limits\\\", \\\"provided\\\", cache, \\\"updated\\\", allowance)
ctx.GlobalSet(CacheFlag.Name, strconv.Itoa(allowance))
}
}
// Ensure Go\\\'s GC ignores the database cache for trigger percentage
cache := ctx.GlobalInt(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))
if ctx.GlobalIsSet(SyncModeFlag.Name) {
cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)
}
if ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = ctx.GlobalUint64(NetworkIdFlag.Name)
}
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheDatabaseFlag.Name) {
cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
}
cfg.DatabaseHandles = MakeDatabaseHandles()
if ctx.GlobalIsSet(AncientFlag.Name) {
cfg.DatabaseFreezer = ctx.GlobalString(AncientFlag.Name)
}
if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != \\\"full\\\" && gcmode != \\\"archive\\\" {
Fatalf(\\\"--%s must be either \\\'full\\\' or \\\'archive\\\'\\\", GCModeFlag.Name)
}
if ctx.GlobalIsSet(GCModeFlag.Name) {
cfg.NoPruning = ctx.GlobalString(GCModeFlag.Name) == \\\"archive\\\"
}
if ctx.GlobalIsSet(CacheNoPrefetchFlag.Name) {
cfg.NoPrefetch = ctx.GlobalBool(CacheNoPrefetchFlag.Name)
}
// Read the value from the flag no matter if it\\\'s set or not.
cfg.Preimages = ctx.GlobalBool(CachePreimagesFlag.Name)
if cfg.NoPruning && !cfg.Preimages {
cfg.Preimages = true
log.Info(\\\"Enabling recording of key preimages since archive mode is used\\\")
}
if ctx.GlobalIsSet(TxLookupLimitFlag.Name) {
cfg.TxLookupLimit = ctx.GlobalUint64(TxLookupLimitFlag.Name)
}
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
cfg.TrieCleanCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100
}
if ctx.GlobalIsSet(CacheTrieJournalFlag.Name) {
cfg.TrieCleanCacheJournal = ctx.GlobalString(CacheTrieJournalFlag.Name)
}
if ctx.GlobalIsSet(CacheTrieRejournalFlag.Name) {
cfg.TrieCleanCacheRejournal = ctx.GlobalDuration(CacheTrieRejournalFlag.Name)
}
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
}
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) {
cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100
}
if !ctx.GlobalBool(SnapshotFlag.Name) {
// If snap-sync is requested, this flag is also required
if cfg.SyncMode == downloader.SnapSync {
log.Info(\\\"Snap sync requested, enabling --snapshot\\\")
} else {
cfg.TrieCleanCache += cfg.SnapshotCache
cfg.SnapshotCache = 0 // Disabled
}
}
if ctx.GlobalIsSet(DocRootFlag.Name) {
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
}
if ctx.GlobalIsSet(VMEnableDebugFlag.Name) {
// TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name)
}
if ctx.GlobalIsSet(EWASMInterpreterFlag.Name) {
cfg.EWASMInterpreter = ctx.GlobalString(EWASMInterpreterFlag.Name)
}
if ctx.GlobalIsSet(EVMInterpreterFlag.Name) {
cfg.EVMInterpreter = ctx.GlobalString(EVMInterpreterFlag.Name)
}
if ctx.GlobalIsSet(RPCGlobalGasCapFlag.Name) {
cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCapFlag.Name)
}
if cfg.RPCGasCap != 0 {
log.Info(\\\"Set global gas cap\\\", \\\"cap\\\", cfg.RPCGasCap)
} else {
log.Info(\\\"Global gas cap disabled\\\")
}
if ctx.GlobalIsSet(RPCGlobalTxFeeCapFlag.Name) {
cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name)
}
if ctx.GlobalIsSet(NoDiscoverFlag.Name) {
cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{}
} else if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) {
urls := ctx.GlobalString(DNSDiscoveryFlag.Name)
if urls == \\\"\\\" {
cfg.EthDiscoveryURLs = []string{}
} else {
cfg.EthDiscoveryURLs = SplitAndTrim(urls)
}
}
// Override any default configs for hard coded networks.
switch {
case ctx.GlobalBool(MainnetFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 1
}
cfg.Genesis = core.DefaultGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
case ctx.GlobalBool(RopstenFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 3
}
cfg.Genesis = core.DefaultRopstenGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.RopstenGenesisHash)
case ctx.GlobalBool(RinkebyFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 4
}
cfg.Genesis = core.DefaultRinkebyGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash)
case ctx.GlobalBool(GoerliFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 5
}
cfg.Genesis = core.DefaultGoerliGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash)
case ctx.GlobalBool(YoloV3Flag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = new(big.Int).SetBytes([]byte(\\\"yolov3x\\\")).Uint64() // \\\"yolov3x\\\"
}
cfg.Genesis = core.DefaultYoloV3GenesisBlock()
case ctx.GlobalBool(DeveloperFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 1337
}
// Create new developer account or reuse existing one
var (
developer accounts.Account
passphrase string
err error
)
if list := MakePasswordList(ctx); len(list) > 0 {
// Just take the first value. Although the function returns a possible multiple values and
// some usages iterate through them as attempts, that doesn\\\'t make sense in this setting,
// when we\\\'re definitely concerned with only one account.
passphrase = list[0]
}
// setEtherbase has been called above, configuring the miner address from command line flags.
if cfg.Miner.Etherbase != (common.Address{}) {
developer = accounts.Account{Address: cfg.Miner.Etherbase}
} else if accs := ks.Accounts(); len(accs) > 0 {
developer = ks.Accounts()[0]
} else {
developer, err = ks.NewAccount(passphrase)
if err != nil {
Fatalf(\\\"Failed to create developer account: %v\\\", err)
}
}
if err := ks.Unlock(developer, passphrase); err != nil {
Fatalf(\\\"Failed to unlock developer account: %v\\\", err)
}
log.Info(\\\"Using developer account\\\", \\\"address\\\", developer.Address)
// Create a new developer genesis block or reuse existing one
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
if ctx.GlobalIsSet(DataDirFlag.Name) {
// Check if we have an already initialized chain and fall back to
// that if so. Otherwise we need to generate a new genesis spec.
chaindb := MakeChainDatabase(ctx, stack, true)
if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) {
cfg.Genesis = nil // fallback to db content
}
chaindb.Close()
}
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) {
cfg.Miner.GasPrice = big.NewInt(1)
}
default:
if cfg.NetworkId == 1 {
SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
}
}
}
最后调用applyMetricConfig进行全局设置
// filedir:go-ethereum-1.10.2\\\\cmd\\\\geth\\\\config.go
func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) {
if ctx.GlobalIsSet(utils.MetricsEnabledFlag.Name) {
cfg.Metrics.Enabled = ctx.GlobalBool(utils.MetricsEnabledFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsEnabledExpensiveFlag.Name) {
cfg.Metrics.EnabledExpensive = ctx.GlobalBool(utils.MetricsEnabledExpensiveFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsHTTPFlag.Name) {
cfg.Metrics.HTTP = ctx.GlobalString(utils.MetricsHTTPFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsPortFlag.Name) {
cfg.Metrics.Port = ctx.GlobalInt(utils.MetricsPortFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsEnableInfluxDBFlag.Name) {
cfg.Metrics.EnableInfluxDB = ctx.GlobalBool(utils.MetricsEnableInfluxDBFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsInfluxDBEndpointFlag.Name) {
cfg.Metrics.InfluxDBEndpoint = ctx.GlobalString(utils.MetricsInfluxDBEndpointFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsInfluxDBDatabaseFlag.Name) {
cfg.Metrics.InfluxDBDatabase = ctx.GlobalString(utils.MetricsInfluxDBDatabaseFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsInfluxDBUsernameFlag.Name) {
cfg.Metrics.InfluxDBUsername = ctx.GlobalString(utils.MetricsInfluxDBUsernameFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsInfluxDBPasswordFlag.Name) {
cfg.Metrics.InfluxDBPassword = ctx.GlobalString(utils.MetricsInfluxDBPasswordFlag.Name)
}
if ctx.GlobalIsSet(utils.MetricsInfluxDBTagsFlag.Name) {
cfg.Metrics.InfluxDBTags = ctx.GlobalString(utils.MetricsInfluxDBTagsFlag.Name)
}
启动节点
之后调用startNode启动节点:
// filedir: go-ethereum-1.10.2\\\\cmd\\\\geth\\\\main.go L328
// 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, backend ethapi.Backend) {
debug.Memsize.Add(\\\"node\\\", stack)
// Start up the node itself
utils.StartNode(ctx, stack)
// Unlock any account specifically requested
unlockAccounts(ctx, stack)
// Register wallet event handlers to open and auto-derive wallets
events := make(chan accounts.WalletEvent, 16)
stack.AccountManager().Subscribe(events)
// Create a client to interact with local geth node.
rpcClient, err := stack.Attach()
if err != nil {
utils.Fatalf(\\\"Failed to attach to self: %v\\\", err)
}
ethClient := ethclient.NewClient(rpcClient)
go func() {
// 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)
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()
}
}
}()
// Spawn a standalone goroutine for status synchronization monitoring,
// close the node when synchronization is complete if user required.
if ctx.GlobalBool(utils.ExitWhenSyncedFlag.Name) {
go func() {
sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
defer sub.Unsubscribe()
for {
event := <-sub.Chan()
if event == nil {
continue
}
done, ok := event.Data.(downloader.DoneEvent)
if !ok {
continue
}
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
log.Info(\\\"Synchronisation completed\\\", \\\"latestnum\\\", done.Latest.Number, \\\"latesthash\\\", done.Latest.Hash(),
\\\"age\\\", common.PrettyAge(timestamp))
stack.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.GlobalString(utils.SyncModeFlag.Name) == \\\"light\\\" {
utils.Fatalf(\\\"Light clients do not support mining\\\")
}
ethBackend, ok := backend.(*eth.EthAPIBackend)
if !ok {
utils.Fatalf(\\\"Ethereum service not running: %v\\\", err)
}
// Set the gas price to the limits from the CLI and start mining
gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
ethBackend.TxPool().SetGasPrice(gasprice)
// start mining
threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name)
if err := ethBackend.StartMining(threads); err != nil {
utils.Fatalf(\\\"Failed to start mining: %v\\\", err)
}
}
}
startNode的具体实现如下:
// filedir:go-ethereum-1.10.2\\\\cmd\\\\utils\\\\cmd.go
func StartNode(ctx *cli.Context, stack *node.Node) {
if err := stack.Start(); err != nil {
Fatalf(\\\"Error starting protocol stack: %v\\\", err)
}
go func() {
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigc)
minFreeDiskSpace := ethconfig.Defaults.TrieDirtyCache
if ctx.GlobalIsSet(MinFreeDiskSpaceFlag.Name) {
minFreeDiskSpace = ctx.GlobalInt(MinFreeDiskSpaceFlag.Name)
} else if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
minFreeDiskSpace = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
}
if minFreeDiskSpace > 0 {
go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024)
}
<-sigc
log.Info(\\\"Got interrupt, shutting down...\\\")
go stack.Close()
for i := 10; i > 0; i-- {
<-sigc
if i > 1 {
log.Warn(\\\"Already shutting down, interrupt more to panic.\\\", \\\"times\\\", i-1)
}
}
debug.Exit() // ensure trace and CPU profile data is flushed.
debug.LoudPanic(\\\"boom\\\")
}()
}
在这里会调用node的start方法启动所有注册的生命周期、RPC服务和P2P网络:
// filedir:go-ethereum-1.10.2\\\\node\\\\node.go
// Start starts all registered lifecycles, RPC services and p2p networking.
// Node can only be started once.
func (n *Node) Start() error {
n.startStopLock.Lock()
defer n.startStopLock.Unlock()
n.lock.Lock()
switch n.state {
case runningState:
n.lock.Unlock()
return ErrNodeRunning
case closedState:
n.lock.Unlock()
return ErrNodeStopped
}
n.state = runningState
// open networking and RPC endpoints
err := n.openEndpoints()
lifecycles := make([]Lifecycle, len(n.lifecycles))
copy(lifecycles, n.lifecycles)
n.lock.Unlock()
// Check if endpoint startup failed.
if err != nil {
n.doClose(nil)
return err
}
// Start all registered lifecycles.
var started []Lifecycle
for _, lifecycle := range lifecycles {
if err = lifecycle.Start(); err != nil {
break
}
started = append(started, lifecycle)
}
// Check if any lifecycle failed to start.
if err != nil {
n.stopServices(started)
n.doClose(nil)
}
return err
}
之后调用unlockAccounts来解锁账户:
// filedir:go-ethereum-1.10.2\\\\cmd\\\\geth\\\\main.go L426
// unlockAccounts unlocks any account specifically requested.
func unlockAccounts(ctx *cli.Context, stack *node.Node) {
var unlocks []string
inputs := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), \\\",\\\")
for _, input := range inputs {
if trimmed := strings.TrimSpace(input); trimmed != \\\"\\\" {
unlocks = append(unlocks, trimmed)
}
}
// Short circuit if there is no account to unlock.
if len(unlocks) == 0 {
return
}
// If insecure account unlocking is not allowed if node\\\'s APIs are exposed to external.
// Print warning log to user and skip unlocking.
if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() {
utils.Fatalf(\\\"Account unlock with HTTP access is forbidden!\\\")
}
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
passwords := utils.MakePasswordList(ctx)
for i, account := range unlocks {
unlockAccount(ks, account, i, passwords)
}
}
之后注册钱包事件:
// Register wallet event handlers to open and auto-derive wallets
events := make(chan accounts.WalletEvent, 16)
stack.AccountManager().Subscribe(events)
之后监听钱包事件:
// 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)
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()
}
}
}
生成用于状态同步监视的独立goroutine
// Spawn a standalone goroutine for status synchronization monitoring,
// close the node when synchronization is complete if user required.
if ctx.GlobalBool(utils.ExitWhenSyncedFlag.Name) {
go func() {
sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
defer sub.Unsubscribe()
for {
event := <-sub.Chan()
if event == nil {
continue
}
done, ok := event.Data.(downloader.DoneEvent)
if !ok {
continue
}
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
log.Info(\\\"Synchronisation completed\\\", \\\"latestnum\\\", done.Latest.Number, \\\"latesthash\\\", done.Latest.Hash(),
\\\"age\\\", common.PrettyAge(timestamp))
stack.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.GlobalString(utils.SyncModeFlag.Name) == \\\"light\\\" {
utils.Fatalf(\\\"Light clients do not support mining\\\")
}
ethBackend, ok := backend.(*eth.EthAPIBackend)
if !ok {
utils.Fatalf(\\\"Ethereum service not running: %v\\\", err)
}
// Set the gas price to the limits from the CLI and start mining
gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
ethBackend.TxPool().SetGasPrice(gasprice)
// start mining
threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name)
if err := ethBackend.StartMining(threads); err != nil {
utils.Fatalf(\\\"Failed to start mining: %v\\\", err)
}
}
挖矿函数的具体逻辑代码如下,这里首先会检查矿工是否运行、配置本地挖矿奖励地址、最后执行挖矿:
// filedir:go-ethereum-1.10.2\\\\eth\\\\backend.go
// StartMining starts the miner with the given number of CPU threads. If mining
// is already running, this method adjust the number of threads allowed to use
// and updates the minimum price required by the transaction pool.
func (s *Ethereum) StartMining(threads int) error {
// Update the thread count within the consensus engine
type threaded interface {
SetThreads(threads int)
}
if th, ok := s.engine.(threaded); ok {
log.Info(\\\"Updated mining threads\\\", \\\"threads\\\", threads)
if threads == 0 {
threads = -1 // Disable the miner from within
}
th.SetThreads(threads)
}
// If the miner was not running, initialize it
if !s.IsMining() {
// Propagate the initial price point to the transaction pool
s.lock.RLock()
price := s.gasPrice
s.lock.RUnlock()
s.txPool.SetGasPrice(price)
// Configure the local mining address
eb, err := s.Etherbase()
if err != nil {
log.Error(\\\"Cannot start mining without etherbase\\\", \\\"err\\\", err)
return fmt.Errorf(\\\"etherbase missing: %v\\\", err)
}
if clique, ok := s.engine.(*clique.Clique); ok {
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
if wallet == nil || err != nil {
log.Error(\\\"Etherbase account unavailable locally\\\", \\\"err\\\", err)
return fmt.Errorf(\\\"signer missing: %v\\\", err)
}
clique.Authorize(eb, wallet.SignData)
}
// If mining is started, we can disable the transaction rejection mechanism
// introduced to speed sync times.
atomic.StoreUint32(&s.handler.acceptTxs, 1)
go s.miner.Start(eb)
}
return nil
}
之后等待节点关闭:
// Wait blocks until the node is closed.
func (n *Node) Wait() {
<-n.stop
}
流程图示
根据上面的启动流程分析,我们可以简单的绘制一个启动流程图(这里主要以一些关键操作为主,忽略了一些if…else..条件判断语句,较为简化)
启动公链
以太坊官方提供了编译打包好的二进制文件,我们可以直接下载对应的二进制文件来启动公链(你有可以使用docker来搭建):
https://geth.ethereum.org/downloads/
下面我们使用Geth来启动一个节点:
./geth --ropsten --rpc --rpcaddr 192.168.174.212 --rpcport 8989
原创文章,作者:七芒星实验室,如若转载,请注明出处:https://www.sudun.com/ask/34268.html