From 38d1b0cba277db17129a4870158115c5a428cffc Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 13 Feb 2020 14:38:30 +0100 Subject: [PATCH] cmd/geth: enable DNS discovery by default (#20660) * node: expose config in service context * eth: integrate p2p/dnsdisc * cmd/geth: add some DNS flags * eth: remove DNS URLs * cmd/utils: configure DNS names for testnets * params: update DNS URLs * cmd/geth: configure mainnet DNS * cmd/utils: rename DNS flag and fix flag processing * cmd/utils: remove debug print * node: fix test --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 36 ++++++++++++++++++++++++++---- eth/backend.go | 8 +++++++ eth/config.go | 8 +++++-- eth/{enr_entry.go => discovery.go} | 12 ++++++++++ eth/gen_config.go | 18 +++++++++++++++ node/node.go | 2 +- node/service.go | 20 ++++++++--------- node/service_test.go | 4 ++-- params/bootnodes.go | 13 +++++++++++ 11 files changed, 104 insertions(+), 19 deletions(-) rename eth/{enr_entry.go => discovery.go} (79%) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0369e527b5..99ef78238f 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -131,6 +131,7 @@ var ( utils.NetrestrictFlag, utils.NodeKeyFileFlag, utils.NodeKeyHexFlag, + utils.DNSDiscoveryFlag, utils.DeveloperFlag, utils.DeveloperPeriodFlag, utils.TestnetFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index b3b6b5f93d..6f3197b9c6 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -182,6 +182,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.BootnodesFlag, utils.BootnodesV4Flag, utils.BootnodesV5Flag, + utils.DNSDiscoveryFlag, utils.ListenPortFlag, utils.MaxPeersFlag, utils.MaxPendingPeersFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index d019c1fdcc..bdadebd852 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -658,6 +658,10 @@ var ( Name: "netrestrict", Usage: "Restricts network communication to the given IP networks (CIDR masks)", } + DNSDiscoveryFlag = cli.StringFlag{ + Name: "discovery.dns", + Usage: "Sets DNS discovery entry points (use \"\" to disable DNS)", + } // ATM the url is left to the user and deployment to JSpathFlag = cli.StringFlag{ @@ -811,9 +815,9 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { switch { case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(BootnodesV4Flag.Name): if ctx.GlobalIsSet(BootnodesV4Flag.Name) { - urls = strings.Split(ctx.GlobalString(BootnodesV4Flag.Name), ",") + urls = splitAndTrim(ctx.GlobalString(BootnodesV4Flag.Name)) } else { - urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",") + urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) } case ctx.GlobalBool(TestnetFlag.Name): urls = params.TestnetBootnodes @@ -845,9 +849,9 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { switch { case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(BootnodesV5Flag.Name): if ctx.GlobalIsSet(BootnodesV5Flag.Name) { - urls = strings.Split(ctx.GlobalString(BootnodesV5Flag.Name), ",") + urls = splitAndTrim(ctx.GlobalString(BootnodesV5Flag.Name)) } else { - urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",") + urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) } case ctx.GlobalBool(RinkebyFlag.Name): urls = params.RinkebyBootnodes @@ -1477,6 +1481,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(RPCGlobalGasCap.Name) { cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name)) } + if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { + urls := ctx.GlobalString(DNSDiscoveryFlag.Name) + if urls == "" { + cfg.DiscoveryURLs = []string{} + } else { + cfg.DiscoveryURLs = splitAndTrim(urls) + } + } // Override any default configs for hard coded networks. switch { @@ -1485,16 +1497,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.NetworkId = 3 } cfg.Genesis = core.DefaultTestnetGenesisBlock() + setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.TestnetGenesisHash]) case ctx.GlobalBool(RinkebyFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 4 } cfg.Genesis = core.DefaultRinkebyGenesisBlock() + setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.RinkebyGenesisHash]) case ctx.GlobalBool(GoerliFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 5 } cfg.Genesis = core.DefaultGoerliGenesisBlock() + setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.GoerliGenesisHash]) case ctx.GlobalBool(DeveloperFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337 @@ -1521,7 +1536,20 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) { cfg.Miner.GasPrice = big.NewInt(1) } + default: + if cfg.NetworkId == 1 { + setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.MainnetGenesisHash]) + } + } +} + +// setDNSDiscoveryDefaults configures DNS discovery with the given URL if +// no URLs are set. +func setDNSDiscoveryDefaults(cfg *eth.Config, url string) { + if cfg.DiscoveryURLs != nil { + return } + cfg.DiscoveryURLs = []string{url} } // RegisterEthService adds an Ethereum client to the stack. diff --git a/eth/backend.go b/eth/backend.go index adde609de0..bda307d95b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -47,6 +47,7 @@ import ( "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -74,6 +75,7 @@ type Ethereum struct { blockchain *core.BlockChain protocolManager *ProtocolManager lesServer LesServer + dialCandiates enode.Iterator // DB interfaces chainDb ethdb.Database // Block chain database @@ -220,6 +222,11 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) + eth.dialCandiates, err = eth.setupDiscovery(&ctx.Config.P2P) + if err != nil { + return nil, err + } + return eth, nil } @@ -510,6 +517,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol { for i, vsn := range ProtocolVersions { protos[i] = s.protocolManager.makeProtocol(vsn) protos[i].Attributes = []enr.Entry{s.currentEthEntry()} + protos[i].DialCandidates = s.dialCandiates } if s.lesServer != nil { protos = append(protos, s.lesServer.Protocols()...) diff --git a/eth/config.go b/eth/config.go index 8240391114..2eaf21fbc3 100644 --- a/eth/config.go +++ b/eth/config.go @@ -95,6 +95,10 @@ type Config struct { NetworkId uint64 // Network ID to use for selecting peers to connect to SyncMode downloader.SyncMode + // This can be set to list of enrtree:// URLs which will be queried for + // for nodes to connect to. + DiscoveryURLs []string + NoPruning bool // Whether to disable pruning and flush everything to disk NoPrefetch bool // Whether to disable prefetching and only load state on demand @@ -156,8 +160,8 @@ type Config struct { CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` // Istanbul block override (TODO: remove after the fork) - OverrideIstanbul *big.Int + OverrideIstanbul *big.Int `toml:",omitempty"` // MuirGlacier block override (TODO: remove after the fork) - OverrideMuirGlacier *big.Int + OverrideMuirGlacier *big.Int `toml:",omitempty"` } diff --git a/eth/enr_entry.go b/eth/discovery.go similarity index 79% rename from eth/enr_entry.go rename to eth/discovery.go index d9e7b95784..97d6322ca1 100644 --- a/eth/enr_entry.go +++ b/eth/discovery.go @@ -19,6 +19,8 @@ package eth import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" ) @@ -37,6 +39,7 @@ func (e ethEntry) ENRKey() string { return "eth" } +// startEthEntryUpdate starts the ENR updater loop. func (eth *Ethereum) startEthEntryUpdate(ln *enode.LocalNode) { var newHead = make(chan core.ChainHeadEvent, 10) sub := eth.blockchain.SubscribeChainHeadEvent(newHead) @@ -59,3 +62,12 @@ func (eth *Ethereum) startEthEntryUpdate(ln *enode.LocalNode) { func (eth *Ethereum) currentEthEntry() *ethEntry { return ðEntry{ForkID: forkid.NewID(eth.blockchain)} } + +// setupDiscovery creates the node discovery source for the eth protocol. +func (eth *Ethereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { + if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 { + return nil, nil + } + client := dnsdisc.NewClient(dnsdisc.Config{}) + return client.NewIterator(eth.config.DiscoveryURLs...) +} diff --git a/eth/gen_config.go b/eth/gen_config.go index bc4b55b120..1c659c393c 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -21,6 +21,7 @@ func (c Config) MarshalTOML() (interface{}, error) { Genesis *core.Genesis `toml:",omitempty"` NetworkId uint64 SyncMode downloader.SyncMode + DiscoveryURLs []string NoPruning bool NoPrefetch bool Whitelist map[uint64]common.Hash `toml:"-"` @@ -49,11 +50,14 @@ func (c Config) MarshalTOML() (interface{}, error) { RPCGasCap *big.Int `toml:",omitempty"` Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` + OverrideIstanbul *big.Int `toml:",omitempty"` + OverrideMuirGlacier *big.Int `toml:",omitempty"` } var enc Config enc.Genesis = c.Genesis enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode + enc.DiscoveryURLs = c.DiscoveryURLs enc.NoPruning = c.NoPruning enc.NoPrefetch = c.NoPrefetch enc.Whitelist = c.Whitelist @@ -82,6 +86,8 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.RPCGasCap = c.RPCGasCap enc.Checkpoint = c.Checkpoint enc.CheckpointOracle = c.CheckpointOracle + enc.OverrideIstanbul = c.OverrideIstanbul + enc.OverrideMuirGlacier = c.OverrideMuirGlacier return &enc, nil } @@ -91,6 +97,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { Genesis *core.Genesis `toml:",omitempty"` NetworkId *uint64 SyncMode *downloader.SyncMode + DiscoveryURLs []string NoPruning *bool NoPrefetch *bool Whitelist map[uint64]common.Hash `toml:"-"` @@ -119,6 +126,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { RPCGasCap *big.Int `toml:",omitempty"` Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` + OverrideIstanbul *big.Int `toml:",omitempty"` + OverrideMuirGlacier *big.Int `toml:",omitempty"` } var dec Config if err := unmarshal(&dec); err != nil { @@ -133,6 +142,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.SyncMode != nil { c.SyncMode = *dec.SyncMode } + if dec.DiscoveryURLs != nil { + c.DiscoveryURLs = dec.DiscoveryURLs + } if dec.NoPruning != nil { c.NoPruning = *dec.NoPruning } @@ -217,5 +229,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.CheckpointOracle != nil { c.CheckpointOracle = dec.CheckpointOracle } + if dec.OverrideIstanbul != nil { + c.OverrideIstanbul = dec.OverrideIstanbul + } + if dec.OverrideMuirGlacier != nil { + c.OverrideMuirGlacier = dec.OverrideMuirGlacier + } return nil } diff --git a/node/node.go b/node/node.go index c9c27d8262..1bdd5af7d2 100644 --- a/node/node.go +++ b/node/node.go @@ -194,7 +194,7 @@ func (n *Node) Start() error { for _, constructor := range n.serviceFuncs { // Create a new context for the particular service ctx := &ServiceContext{ - config: n.config, + Config: *n.config, services: make(map[reflect.Type]Service), EventMux: n.eventmux, AccountManager: n.accman, diff --git a/node/service.go b/node/service.go index 4dea00995c..ef5b995e4b 100644 --- a/node/service.go +++ b/node/service.go @@ -32,20 +32,20 @@ import ( // the protocol stack, that is passed to all constructors to be optionally used; // as well as utility methods to operate on the service environment. 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. + Config Config + EventMux *event.TypeMux // Event multiplexer used for decoupled notifications + AccountManager *accounts.Manager // Account manager created by the node. } // OpenDatabase opens an existing database with the given name (or creates one // if no previous can be found) from within the node's data directory. If the // node is an ephemeral one, a memory database is returned. func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, namespace string) (ethdb.Database, error) { - if ctx.config.DataDir == "" { + if ctx.Config.DataDir == "" { return rawdb.NewMemoryDatabase(), nil } - return rawdb.NewLevelDBDatabase(ctx.config.ResolvePath(name), cache, handles, namespace) + return rawdb.NewLevelDBDatabase(ctx.Config.ResolvePath(name), cache, handles, namespace) } // OpenDatabaseWithFreezer opens an existing database with the given name (or @@ -54,16 +54,16 @@ func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, nam // database to immutable append-only files. If the node is an ephemeral one, a // memory database is returned. func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) { - if ctx.config.DataDir == "" { + if ctx.Config.DataDir == "" { return rawdb.NewMemoryDatabase(), nil } - root := ctx.config.ResolvePath(name) + root := ctx.Config.ResolvePath(name) switch { case freezer == "": freezer = filepath.Join(root, "ancient") case !filepath.IsAbs(freezer): - freezer = ctx.config.ResolvePath(freezer) + freezer = ctx.Config.ResolvePath(freezer) } return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) } @@ -72,7 +72,7 @@ func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handl // and if the user actually uses persistent storage. It will return an empty string // for emphemeral storage and the user's own input for absolute paths. func (ctx *ServiceContext) ResolvePath(path string) string { - return ctx.config.ResolvePath(path) + return ctx.Config.ResolvePath(path) } // Service retrieves a currently running service registered of a specific type. @@ -88,7 +88,7 @@ func (ctx *ServiceContext) Service(service interface{}) error { // ExtRPCEnabled returns the indicator whether node enables the external // RPC(http, ws or graphql). func (ctx *ServiceContext) ExtRPCEnabled() bool { - return ctx.config.ExtRPCEnabled() + return ctx.Config.ExtRPCEnabled() } // ServiceConstructor is the function signature of the constructors needed to be diff --git a/node/service_test.go b/node/service_test.go index 63004a51ab..5da8e9e434 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -38,7 +38,7 @@ func TestContextDatabases(t *testing.T) { t.Fatalf("non-created database already exists") } // Request the opening/creation of a database and ensure it persists to disk - ctx := &ServiceContext{config: &Config{Name: "unit-test", DataDir: dir}} + ctx := &ServiceContext{Config: Config{Name: "unit-test", DataDir: dir}} db, err := ctx.OpenDatabase("persistent", 0, 0, "") if err != nil { t.Fatalf("failed to open persistent database: %v", err) @@ -49,7 +49,7 @@ func TestContextDatabases(t *testing.T) { t.Fatalf("persistent database doesn't exists: %v", err) } // Request th opening/creation of an ephemeral database and ensure it's not persisted - ctx = &ServiceContext{config: &Config{DataDir: ""}} + ctx = &ServiceContext{Config: Config{DataDir: ""}} db, err = ctx.OpenDatabase("ephemeral", 0, 0, "") if err != nil { t.Fatalf("failed to open ephemeral database: %v", err) diff --git a/params/bootnodes.go b/params/bootnodes.go index f92b487ff5..f27e5b7d18 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -16,6 +16,8 @@ package params +import "github.com/ethereum/go-ethereum/common" + // MainnetBootnodes are the enode URLs of the P2P bootstrap nodes running on // the main Ethereum network. var MainnetBootnodes = []string{ @@ -69,3 +71,14 @@ var DiscoveryV5Bootnodes = []string{ "enode://1c7a64d76c0334b0418c004af2f67c50e36a3be60b5e4790bdac0439d21603469a85fad36f2473c9a80eb043ae60936df905fa28f1ff614c3e5dc34f15dcd2dc@40.118.3.223:30306", "enode://85c85d7143ae8bb96924f2b54f1b3e70d8c4d367af305325d30a61385a432f247d2c75c45c6b4a60335060d072d7f5b35dd1d4c45f76941f62a4f83b6e75daaf@40.118.3.223:30307", } + +const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@" + +// These DNS names provide bootstrap connectivity for public testnets and the mainnet. +// See https://github.com/ethereum/discv4-dns-lists for more information. +var KnownDNSNetworks = map[common.Hash]string{ + MainnetGenesisHash: dnsPrefix + "all.mainnet.ethdisco.net", + TestnetGenesisHash: dnsPrefix + "all.ropsten.ethdisco.net", + RinkebyGenesisHash: dnsPrefix + "all.rinkeby.ethdisco.net", + GoerliGenesisHash: dnsPrefix + "all.goerli.ethdisco.net", +}