// Copyright 2017 The go-ethereum Authors // This file is part of go-ethereum. // // go-ethereum is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // go-ethereum is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with go-ethereum. If not, see . package main import ( "bufio" "errors" "fmt" "os" "reflect" "runtime" "strings" "unicode" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/beacon/blsync" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/naoina/toml" "github.com/urfave/cli/v2" ) var ( dumpConfigCommand = &cli.Command{ Action: dumpConfig, Name: "dumpconfig", Usage: "Export configuration values in a TOML format", ArgsUsage: "", Flags: flags.Merge(nodeFlags, rpcFlags), Description: `Export configuration values in TOML format (to stdout by default).`, } configFileFlag = &cli.StringFlag{ Name: "config", Usage: "TOML configuration file", Category: flags.EthCategory, } ) // These settings ensure that TOML keys use the same names as Go struct fields. var tomlSettings = toml.Config{ NormFieldName: func(rt reflect.Type, key string) string { return key }, FieldToKey: func(rt reflect.Type, field string) string { return field }, MissingField: func(rt reflect.Type, field string) error { id := fmt.Sprintf("%s.%s", rt.String(), field) if deprecatedConfigFields[id] { log.Warn(fmt.Sprintf("Config field '%s' is deprecated and won't have any effect.", id)) return nil } var link string if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" { link = fmt.Sprintf(", see https://godoc.org/%s#%s for available fields", rt.PkgPath(), rt.Name()) } return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link) }, } var deprecatedConfigFields = map[string]bool{ "ethconfig.Config.EVMInterpreter": true, "ethconfig.Config.EWASMInterpreter": true, "ethconfig.Config.TrieCleanCacheJournal": true, "ethconfig.Config.TrieCleanCacheRejournal": true, "ethconfig.Config.LightServ": true, "ethconfig.Config.LightIngress": true, "ethconfig.Config.LightEgress": true, "ethconfig.Config.LightPeers": true, "ethconfig.Config.LightNoPrune": true, "ethconfig.Config.LightNoSyncServe": true, } type ethstatsConfig struct { URL string `toml:",omitempty"` } type gethConfig struct { Eth ethconfig.Config Node node.Config Ethstats ethstatsConfig Metrics metrics.Config } func loadConfig(file string, cfg *gethConfig) error { f, err := os.Open(file) if err != nil { return err } defer f.Close() err = tomlSettings.NewDecoder(bufio.NewReader(f)).Decode(cfg) // Add file name to errors that have a line number. if _, ok := err.(*toml.LineError); ok { err = errors.New(file + ", " + err.Error()) } return err } func defaultNodeConfig() node.Config { git, _ := version.VCS() cfg := node.DefaultConfig cfg.Name = clientIdentifier cfg.Version = version.WithCommit(git.Commit, git.Date) cfg.HTTPModules = append(cfg.HTTPModules, "eth") cfg.WSModules = append(cfg.WSModules, "eth") cfg.IPCPath = clientIdentifier + ".ipc" return cfg } // loadBaseConfig loads the gethConfig based on the given command line // parameters and config file. func loadBaseConfig(ctx *cli.Context) gethConfig { // Load defaults. cfg := gethConfig{ Eth: ethconfig.Defaults, Node: defaultNodeConfig(), Metrics: metrics.DefaultConfig, } // Load config file. if file := ctx.String(configFileFlag.Name); file != "" { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } } // Apply flags. utils.SetNodeConfig(ctx, &cfg.Node) return cfg } // makeConfigNode loads geth configuration and creates a blank node instance. func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { cfg := loadBaseConfig(ctx) stack, err := node.New(&cfg.Node) if err != nil { utils.Fatalf("Failed to create the protocol stack: %v", err) } // Node doesn't by default populate account manager backends if err := setAccountManagerBackends(stack.Config(), stack.AccountManager(), stack.KeyStoreDir()); err != nil { utils.Fatalf("Failed to set account manager backends: %v", err) } utils.SetEthConfig(ctx, stack, &cfg.Eth) if ctx.IsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.String(utils.EthStatsURLFlag.Name) } applyMetricConfig(ctx, &cfg) return stack, cfg } // makeFullNode loads geth configuration and creates the Ethereum backend. func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) if ctx.IsSet(utils.OverrideCancun.Name) { v := ctx.Uint64(utils.OverrideCancun.Name) cfg.Eth.OverrideCancun = &v } if ctx.IsSet(utils.OverrideVerkle.Name) { v := ctx.Uint64(utils.OverrideVerkle.Name) cfg.Eth.OverrideVerkle = &v } backend, eth := utils.RegisterEthService(stack, &cfg.Eth) // Create gauge with geth system and build information if eth != nil { // The 'eth' backend may be nil in light mode var protos []string for _, p := range eth.Protocols() { protos = append(protos, fmt.Sprintf("%v/%d", p.Name, p.Version)) } metrics.NewRegisteredGaugeInfo("geth/info", nil).Update(metrics.GaugeInfoValue{ "arch": runtime.GOARCH, "os": runtime.GOOS, "version": cfg.Node.Version, "protocols": strings.Join(protos, ","), }) } // Configure log filter RPC API. filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth) // Configure GraphQL if requested. if ctx.IsSet(utils.GraphQLEnabledFlag.Name) { utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL) } // Configure full-sync tester service if requested if ctx.IsSet(utils.SyncTargetFlag.Name) { hex := hexutil.MustDecode(ctx.String(utils.SyncTargetFlag.Name)) if len(hex) != common.HashLength { utils.Fatalf("invalid sync target length: have %d, want %d", len(hex), common.HashLength) } utils.RegisterFullSyncTester(stack, eth, common.BytesToHash(hex)) } if ctx.IsSet(utils.DeveloperFlag.Name) { // Start dev mode. simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), eth) if err != nil { utils.Fatalf("failed to register dev mode catalyst service: %v", err) } catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon) stack.RegisterLifecycle(simBeacon) } else if ctx.IsSet(utils.BeaconApiFlag.Name) { // Start blsync mode. srv := rpc.NewServer() srv.RegisterName("engine", catalyst.NewConsensusAPI(eth)) blsyncer := blsync.NewClient(ctx) blsyncer.SetEngineRPC(rpc.DialInProc(srv)) stack.RegisterLifecycle(blsyncer) } else { // Launch the engine API for interacting with external consensus client. err := catalyst.Register(stack, eth) if err != nil { utils.Fatalf("failed to register catalyst service: %v", err) } } return stack } // dumpConfig is the dumpconfig command. func dumpConfig(ctx *cli.Context) error { _, cfg := makeConfigNode(ctx) comment := "" if cfg.Eth.Genesis != nil { cfg.Eth.Genesis = nil comment += "# Note: this config doesn't contain the genesis block.\n\n" } out, err := tomlSettings.Marshal(&cfg) if err != nil { return err } dump := os.Stdout if ctx.NArg() > 0 { dump, err = os.OpenFile(ctx.Args().Get(0), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } defer dump.Close() } dump.WriteString(comment) dump.Write(out) return nil } func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) { if ctx.IsSet(utils.MetricsEnabledFlag.Name) { cfg.Metrics.Enabled = ctx.Bool(utils.MetricsEnabledFlag.Name) } if ctx.IsSet(utils.MetricsEnabledExpensiveFlag.Name) { log.Warn("Expensive metrics are collected by default, please remove this flag", "flag", utils.MetricsEnabledExpensiveFlag.Name) } if ctx.IsSet(utils.MetricsHTTPFlag.Name) { cfg.Metrics.HTTP = ctx.String(utils.MetricsHTTPFlag.Name) } if ctx.IsSet(utils.MetricsPortFlag.Name) { cfg.Metrics.Port = ctx.Int(utils.MetricsPortFlag.Name) } if ctx.IsSet(utils.MetricsEnableInfluxDBFlag.Name) { cfg.Metrics.EnableInfluxDB = ctx.Bool(utils.MetricsEnableInfluxDBFlag.Name) } if ctx.IsSet(utils.MetricsInfluxDBEndpointFlag.Name) { cfg.Metrics.InfluxDBEndpoint = ctx.String(utils.MetricsInfluxDBEndpointFlag.Name) } if ctx.IsSet(utils.MetricsInfluxDBDatabaseFlag.Name) { cfg.Metrics.InfluxDBDatabase = ctx.String(utils.MetricsInfluxDBDatabaseFlag.Name) } if ctx.IsSet(utils.MetricsInfluxDBUsernameFlag.Name) { cfg.Metrics.InfluxDBUsername = ctx.String(utils.MetricsInfluxDBUsernameFlag.Name) } if ctx.IsSet(utils.MetricsInfluxDBPasswordFlag.Name) { cfg.Metrics.InfluxDBPassword = ctx.String(utils.MetricsInfluxDBPasswordFlag.Name) } if ctx.IsSet(utils.MetricsInfluxDBTagsFlag.Name) { cfg.Metrics.InfluxDBTags = ctx.String(utils.MetricsInfluxDBTagsFlag.Name) } if ctx.IsSet(utils.MetricsEnableInfluxDBV2Flag.Name) { cfg.Metrics.EnableInfluxDBV2 = ctx.Bool(utils.MetricsEnableInfluxDBV2Flag.Name) } if ctx.IsSet(utils.MetricsInfluxDBTokenFlag.Name) { cfg.Metrics.InfluxDBToken = ctx.String(utils.MetricsInfluxDBTokenFlag.Name) } if ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name) { cfg.Metrics.InfluxDBBucket = ctx.String(utils.MetricsInfluxDBBucketFlag.Name) } if ctx.IsSet(utils.MetricsInfluxDBOrganizationFlag.Name) { cfg.Metrics.InfluxDBOrganization = ctx.String(utils.MetricsInfluxDBOrganizationFlag.Name) } } func setAccountManagerBackends(conf *node.Config, am *accounts.Manager, keydir string) error { scryptN := keystore.StandardScryptN scryptP := keystore.StandardScryptP if conf.UseLightweightKDF { scryptN = keystore.LightScryptN scryptP = keystore.LightScryptP } // Assemble the supported backends if len(conf.ExternalSigner) > 0 { log.Info("Using external signer", "url", conf.ExternalSigner) if extBackend, err := external.NewExternalBackend(conf.ExternalSigner); err == nil { am.AddBackend(extBackend) return nil } else { return fmt.Errorf("error connecting to external signer: %v", err) } } // For now, we're using EITHER external signer OR local signers. // If/when we implement some form of lockfile for USB and keystore wallets, // we can have both, but it's very confusing for the user to see the same // accounts in both externally and locally, plus very racey. am.AddBackend(keystore.NewKeyStore(keydir, scryptN, scryptP)) if conf.USB { // Start a USB hub for Ledger hardware wallets if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) } else { am.AddBackend(ledgerhub) } // Start a USB hub for Trezor hardware wallets (HID version) if trezorhub, err := usbwallet.NewTrezorHubWithHID(); err != nil { log.Warn(fmt.Sprintf("Failed to start HID Trezor hub, disabling: %v", err)) } else { am.AddBackend(trezorhub) } // Start a USB hub for Trezor hardware wallets (WebUSB version) if trezorhub, err := usbwallet.NewTrezorHubWithWebUSB(); err != nil { log.Warn(fmt.Sprintf("Failed to start WebUSB Trezor hub, disabling: %v", err)) } else { am.AddBackend(trezorhub) } } if len(conf.SmartCardDaemonPath) > 0 { // Start a smart card hub if schub, err := scwallet.NewHub(conf.SmartCardDaemonPath, scwallet.Scheme, keydir); err != nil { log.Warn(fmt.Sprintf("Failed to start smart card hub, disabling: %v", err)) } else { am.AddBackend(schub) } } return nil }