forked from forks/go-ethereum
I ran into this while trying to debug a discv5 thing. I tried to disable DNS discovery using `--discovery.dns=false`, which doesn't work. Annoyingly, geth started anyway and discarded the error silently. I eventually found my mistake, but it took way longer than it should have. Also including a small change to the error message for invalid DNS URLs here. The user actually needs to see the URL to make sense of the error.
465 lines
16 KiB
Go
465 lines
16 KiB
Go
// Copyright 2014 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
// Package eth implements the Ethereum protocol.
|
|
package eth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/consensus"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/core/state/pruner"
|
|
"github.com/ethereum/go-ethereum/core/txpool"
|
|
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
|
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
|
"github.com/ethereum/go-ethereum/core/txpool/locals"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
|
"github.com/ethereum/go-ethereum/eth/gasprice"
|
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
|
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/event"
|
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
|
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
|
|
"github.com/ethereum/go-ethereum/internal/version"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/miner"
|
|
"github.com/ethereum/go-ethereum/node"
|
|
"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/params"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
gethversion "github.com/ethereum/go-ethereum/version"
|
|
)
|
|
|
|
// Config contains the configuration options of the ETH protocol.
|
|
// Deprecated: use ethconfig.Config instead.
|
|
type Config = ethconfig.Config
|
|
|
|
// Ethereum implements the Ethereum full node service.
|
|
type Ethereum struct {
|
|
// core protocol objects
|
|
config *ethconfig.Config
|
|
txPool *txpool.TxPool
|
|
localTxTracker *locals.TxTracker
|
|
blockchain *core.BlockChain
|
|
|
|
handler *handler
|
|
discmix *enode.FairMix
|
|
|
|
// DB interfaces
|
|
chainDb ethdb.Database // Block chain database
|
|
|
|
eventMux *event.TypeMux
|
|
engine consensus.Engine
|
|
accountManager *accounts.Manager
|
|
|
|
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
|
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
|
|
closeBloomHandler chan struct{}
|
|
|
|
APIBackend *EthAPIBackend
|
|
|
|
miner *miner.Miner
|
|
gasPrice *big.Int
|
|
|
|
networkID uint64
|
|
netRPCService *ethapi.NetAPI
|
|
|
|
p2pServer *p2p.Server
|
|
|
|
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
|
|
|
|
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
|
|
}
|
|
|
|
// New creates a new Ethereum object (including the initialisation of the common Ethereum object),
|
|
// whose lifecycle will be managed by the provided node.
|
|
func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|
// Ensure configuration values are compatible and sane
|
|
if !config.SyncMode.IsValid() {
|
|
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
|
|
}
|
|
if config.Miner.GasPrice == nil || config.Miner.GasPrice.Sign() <= 0 {
|
|
log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice)
|
|
config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice)
|
|
}
|
|
if config.NoPruning && config.TrieDirtyCache > 0 {
|
|
if config.SnapshotCache > 0 {
|
|
config.TrieCleanCache += config.TrieDirtyCache * 3 / 5
|
|
config.SnapshotCache += config.TrieDirtyCache * 2 / 5
|
|
} else {
|
|
config.TrieCleanCache += config.TrieDirtyCache
|
|
}
|
|
config.TrieDirtyCache = 0
|
|
}
|
|
log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
|
|
|
|
// Assemble the Ethereum object
|
|
chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scheme, err := rawdb.ParseStateScheme(config.StateScheme, chainDb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Try to recover offline state pruning only in hash-based.
|
|
if scheme == rawdb.HashScheme {
|
|
if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb); err != nil {
|
|
log.Error("Failed to recover state", "error", err)
|
|
}
|
|
}
|
|
// Transfer mining-related config to the ethash config.
|
|
chainConfig, err := core.LoadChainConfig(chainDb, config.Genesis)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
engine, err := ethconfig.CreateConsensusEngine(chainConfig, chainDb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
networkID := config.NetworkId
|
|
if networkID == 0 {
|
|
networkID = chainConfig.ChainID.Uint64()
|
|
}
|
|
eth := &Ethereum{
|
|
config: config,
|
|
chainDb: chainDb,
|
|
eventMux: stack.EventMux(),
|
|
accountManager: stack.AccountManager(),
|
|
engine: engine,
|
|
closeBloomHandler: make(chan struct{}),
|
|
networkID: networkID,
|
|
gasPrice: config.Miner.GasPrice,
|
|
bloomRequests: make(chan chan *bloombits.Retrieval),
|
|
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
|
|
p2pServer: stack.Server(),
|
|
discmix: enode.NewFairMix(0),
|
|
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
|
|
}
|
|
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
|
|
var dbVer = "<nil>"
|
|
if bcVersion != nil {
|
|
dbVer = fmt.Sprintf("%d", *bcVersion)
|
|
}
|
|
log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer)
|
|
|
|
if !config.SkipBcVersionCheck {
|
|
if bcVersion != nil && *bcVersion > core.BlockChainVersion {
|
|
return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, version.WithMeta, core.BlockChainVersion)
|
|
} else if bcVersion == nil || *bcVersion < core.BlockChainVersion {
|
|
if bcVersion != nil { // only print warning on upgrade, not on init
|
|
log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion)
|
|
}
|
|
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
|
|
}
|
|
}
|
|
var (
|
|
vmConfig = vm.Config{
|
|
EnablePreimageRecording: config.EnablePreimageRecording,
|
|
}
|
|
cacheConfig = &core.CacheConfig{
|
|
TrieCleanLimit: config.TrieCleanCache,
|
|
TrieCleanNoPrefetch: config.NoPrefetch,
|
|
TrieDirtyLimit: config.TrieDirtyCache,
|
|
TrieDirtyDisabled: config.NoPruning,
|
|
TrieTimeLimit: config.TrieTimeout,
|
|
SnapshotLimit: config.SnapshotCache,
|
|
Preimages: config.Preimages,
|
|
StateHistory: config.StateHistory,
|
|
StateScheme: scheme,
|
|
}
|
|
)
|
|
if config.VMTrace != "" {
|
|
traceConfig := json.RawMessage("{}")
|
|
if config.VMTraceJsonConfig != "" {
|
|
traceConfig = json.RawMessage(config.VMTraceJsonConfig)
|
|
}
|
|
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create tracer %s: %v", config.VMTrace, err)
|
|
}
|
|
vmConfig.Tracer = t
|
|
}
|
|
// Override the chain config with provided settings.
|
|
var overrides core.ChainOverrides
|
|
if config.OverrideCancun != nil {
|
|
overrides.OverrideCancun = config.OverrideCancun
|
|
}
|
|
if config.OverrideVerkle != nil {
|
|
overrides.OverrideVerkle = config.OverrideVerkle
|
|
}
|
|
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, &config.TransactionHistory)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
eth.bloomIndexer.Start(eth.blockchain)
|
|
|
|
if config.BlobPool.Datadir != "" {
|
|
config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir)
|
|
}
|
|
blobPool := blobpool.New(config.BlobPool, eth.blockchain)
|
|
|
|
if config.TxPool.Journal != "" {
|
|
config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
|
|
}
|
|
legacyPool := legacypool.New(config.TxPool, eth.blockchain)
|
|
|
|
eth.txPool, err = txpool.New(config.TxPool.PriceLimit, eth.blockchain, []txpool.SubPool{legacyPool, blobPool})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !config.TxPool.NoLocals {
|
|
rejournal := config.TxPool.Rejournal
|
|
if rejournal < time.Second {
|
|
log.Warn("Sanitizing invalid txpool journal time", "provided", rejournal, "updated", time.Second)
|
|
rejournal = time.Second
|
|
}
|
|
eth.localTxTracker = locals.New(config.TxPool.Journal, rejournal, eth.blockchain.Config(), eth.txPool)
|
|
stack.RegisterLifecycle(eth.localTxTracker)
|
|
}
|
|
// Permit the downloader to use the trie cache allowance during fast sync
|
|
cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
|
|
if eth.handler, err = newHandler(&handlerConfig{
|
|
NodeID: eth.p2pServer.Self().ID(),
|
|
Database: chainDb,
|
|
Chain: eth.blockchain,
|
|
TxPool: eth.txPool,
|
|
Network: networkID,
|
|
Sync: config.SyncMode,
|
|
BloomCache: uint64(cacheLimit),
|
|
EventMux: eth.eventMux,
|
|
RequiredBlocks: config.RequiredBlocks,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
eth.miner = miner.New(eth, config.Miner, eth.engine)
|
|
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
|
eth.miner.SetPrioAddresses(config.TxPool.Locals)
|
|
|
|
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
|
|
if eth.APIBackend.allowUnprotectedTxs {
|
|
log.Info("Unprotected transactions allowed")
|
|
}
|
|
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, config.GPO, config.Miner.GasPrice)
|
|
|
|
// Start the RPC service
|
|
eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, networkID)
|
|
|
|
// Register the backend on the node
|
|
stack.RegisterAPIs(eth.APIs())
|
|
stack.RegisterProtocols(eth.Protocols())
|
|
stack.RegisterLifecycle(eth)
|
|
|
|
// Successful startup; push a marker and check previous unclean shutdowns.
|
|
eth.shutdownTracker.MarkStartup()
|
|
|
|
return eth, nil
|
|
}
|
|
|
|
func makeExtraData(extra []byte) []byte {
|
|
if len(extra) == 0 {
|
|
// create default extradata
|
|
extra, _ = rlp.EncodeToBytes([]interface{}{
|
|
uint(gethversion.Major<<16 | gethversion.Minor<<8 | gethversion.Patch),
|
|
"geth",
|
|
runtime.Version(),
|
|
runtime.GOOS,
|
|
})
|
|
}
|
|
if uint64(len(extra)) > params.MaximumExtraDataSize {
|
|
log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize)
|
|
extra = nil
|
|
}
|
|
return extra
|
|
}
|
|
|
|
// APIs return the collection of RPC services the ethereum package offers.
|
|
// NOTE, some of these services probably need to be moved to somewhere else.
|
|
func (s *Ethereum) APIs() []rpc.API {
|
|
apis := ethapi.GetAPIs(s.APIBackend)
|
|
|
|
// Append any APIs exposed explicitly by the consensus engine
|
|
apis = append(apis, s.engine.APIs(s.BlockChain())...)
|
|
|
|
// Append all the local APIs and return
|
|
return append(apis, []rpc.API{
|
|
{
|
|
Namespace: "miner",
|
|
Service: NewMinerAPI(s),
|
|
}, {
|
|
Namespace: "eth",
|
|
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain, s.eventMux),
|
|
}, {
|
|
Namespace: "admin",
|
|
Service: NewAdminAPI(s),
|
|
}, {
|
|
Namespace: "debug",
|
|
Service: NewDebugAPI(s),
|
|
}, {
|
|
Namespace: "net",
|
|
Service: s.netRPCService,
|
|
},
|
|
}...)
|
|
}
|
|
|
|
func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
|
|
s.blockchain.ResetWithGenesisBlock(gb)
|
|
}
|
|
|
|
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
|
|
|
func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
|
|
func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
|
|
func (s *Ethereum) TxPool() *txpool.TxPool { return s.txPool }
|
|
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
|
|
func (s *Ethereum) Engine() consensus.Engine { return s.engine }
|
|
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
|
|
func (s *Ethereum) IsListening() bool { return true } // Always listening
|
|
func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader }
|
|
func (s *Ethereum) Synced() bool { return s.handler.synced.Load() }
|
|
func (s *Ethereum) SetSynced() { s.handler.enableSyncedFeatures() }
|
|
func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning }
|
|
func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
|
|
|
|
// Protocols returns all the currently configured
|
|
// network protocols to start.
|
|
func (s *Ethereum) Protocols() []p2p.Protocol {
|
|
protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.discmix)
|
|
if s.config.SnapshotCache > 0 {
|
|
protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler))...)
|
|
}
|
|
return protos
|
|
}
|
|
|
|
// Start implements node.Lifecycle, starting all internal goroutines needed by the
|
|
// Ethereum protocol implementation.
|
|
func (s *Ethereum) Start() error {
|
|
if err := s.setupDiscovery(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Start the bloom bits servicing goroutines
|
|
s.startBloomHandlers(params.BloomBitsBlocks)
|
|
|
|
// Regularly update shutdown marker
|
|
s.shutdownTracker.Start()
|
|
|
|
// Start the networking layer
|
|
s.handler.Start(s.p2pServer.MaxPeers)
|
|
return nil
|
|
}
|
|
|
|
func (s *Ethereum) setupDiscovery() error {
|
|
eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode())
|
|
|
|
// Add eth nodes from DNS.
|
|
dnsclient := dnsdisc.NewClient(dnsdisc.Config{})
|
|
if len(s.config.EthDiscoveryURLs) > 0 {
|
|
iter, err := dnsclient.NewIterator(s.config.EthDiscoveryURLs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.discmix.AddSource(iter)
|
|
}
|
|
|
|
// Add snap nodes from DNS.
|
|
if len(s.config.SnapDiscoveryURLs) > 0 {
|
|
iter, err := dnsclient.NewIterator(s.config.SnapDiscoveryURLs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.discmix.AddSource(iter)
|
|
}
|
|
|
|
// Add DHT nodes from discv5.
|
|
if s.p2pServer.DiscoveryV5() != nil {
|
|
filter := eth.NewNodeFilter(s.blockchain)
|
|
iter := enode.Filter(s.p2pServer.DiscoveryV5().RandomNodes(), filter)
|
|
s.discmix.AddSource(iter)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop implements node.Lifecycle, terminating all internal goroutines used by the
|
|
// Ethereum protocol.
|
|
func (s *Ethereum) Stop() error {
|
|
// Stop all the peer-related stuff first.
|
|
s.discmix.Close()
|
|
s.handler.Stop()
|
|
|
|
// Then stop everything else.
|
|
s.bloomIndexer.Close()
|
|
close(s.closeBloomHandler)
|
|
s.txPool.Close()
|
|
s.blockchain.Stop()
|
|
s.engine.Close()
|
|
|
|
// Clean shutdown marker as the last thing before closing db
|
|
s.shutdownTracker.Stop()
|
|
|
|
s.chainDb.Close()
|
|
s.eventMux.Stop()
|
|
|
|
return nil
|
|
}
|
|
|
|
// SyncMode retrieves the current sync mode, either explicitly set, or derived
|
|
// from the chain status.
|
|
func (s *Ethereum) SyncMode() ethconfig.SyncMode {
|
|
// If we're in snap sync mode, return that directly
|
|
if s.handler.snapSync.Load() {
|
|
return ethconfig.SnapSync
|
|
}
|
|
// We are probably in full sync, but we might have rewound to before the
|
|
// snap sync pivot, check if we should re-enable snap sync.
|
|
head := s.blockchain.CurrentBlock()
|
|
if pivot := rawdb.ReadLastPivotNumber(s.chainDb); pivot != nil {
|
|
if head.Number.Uint64() < *pivot {
|
|
return ethconfig.SnapSync
|
|
}
|
|
}
|
|
// We are in a full sync, but the associated head state is missing. To complete
|
|
// the head state, forcefully rerun the snap sync. Note it doesn't mean the
|
|
// persistent state is corrupted, just mismatch with the head block.
|
|
if !s.blockchain.HasState(head.Root) {
|
|
log.Info("Reenabled snap sync as chain is stateless")
|
|
return ethconfig.SnapSync
|
|
}
|
|
// Nope, we're really full syncing
|
|
return ethconfig.FullSync
|
|
}
|