go-ethereum/command/server/config.go
2021-10-20 11:45:44 +02:00

616 lines
14 KiB
Go

package server
import (
"fmt"
"io/ioutil"
"math"
"math/big"
"os"
"path/filepath"
"runtime"
"strconv"
"time"
godebug "runtime/debug"
"github.com/ethereum/go-ethereum/command/server/chains"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/fdlimit"
"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/log"
"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/nat"
"github.com/ethereum/go-ethereum/params"
"github.com/imdario/mergo"
"github.com/mitchellh/go-homedir"
gopsutil "github.com/shirou/gopsutil/mem"
)
type Config struct {
chain *chains.Chain
Chain string
Debug bool
Whitelist map[string]string
LogLevel string
DataDir string
P2P *P2PConfig
SyncMode string
GcMode string
Snapshot bool
EthStats string
Heimdall *HeimdallConfig
TxPool *TxPoolConfig
Sealer *SealerConfig
JsonRPC *JsonRPCConfig
Gpo *GpoConfig
Ethstats string
Metrics *MetricsConfig
Cache *CacheConfig
Accounts *AccountsConfig
}
type P2PConfig struct {
MaxPeers uint64
MaxPendPeers uint64
Bind string
Port uint64
NoDiscover bool
NAT string
Discovery *P2PDiscovery
}
type P2PDiscovery struct {
V5Enabled bool
Bootnodes []string
BootnodesV4 []string
BootnodesV5 []string
StaticNodes []string
TrustedNodes []string
DNS []string
}
type HeimdallConfig struct {
URL string
Without bool
}
type TxPoolConfig struct {
Locals []string
NoLocals bool
Journal string
Rejournal time.Duration
PriceLimit uint64
PriceBump uint64
AccountSlots uint64
GlobalSlots uint64
AccountQueue uint64
GlobalQueue uint64
LifeTime time.Duration
}
type SealerConfig struct {
Enabled bool
Etherbase string
ExtraData string
GasCeil uint64
GasPrice *big.Int
}
type JsonRPCConfig struct {
IPCDisable bool
IPCPath string
Modules []string
VHost []string
Cors []string
GasCap uint64
TxFeeCap float64
Http *APIConfig
Ws *APIConfig
Graphql *APIConfig
}
type APIConfig struct {
Enabled bool
Port uint64
Prefix string
Host string
}
type GpoConfig struct {
Blocks uint64
Percentile uint64
MaxPrice *big.Int
IgnorePrice *big.Int
}
type MetricsConfig struct {
Enabled bool
Expensive bool
InfluxDB *InfluxDBConfig
}
type InfluxDBConfig struct {
V1Enabled bool
Endpoint string
Database string
Username string
Password string
Tags map[string]string
V2Enabled bool
Token string
Bucket string
Organization string
}
type CacheConfig struct {
Cache uint64
PercGc uint64
PercSnapshot uint64
PercDatabase uint64
PercTrie uint64
Journal string
Rejournal time.Duration
NoPrefetch bool
Preimages bool
TxLookupLimit uint64
}
type AccountsConfig struct {
Unlock []string
PasswordFile string
AllowInsecureUnlock bool
UseLightweightKDF bool
}
func DefaultConfig() *Config {
return &Config{
Chain: "mainnet",
Debug: false,
Whitelist: map[string]string{},
LogLevel: "INFO",
DataDir: defaultDataDir(),
P2P: &P2PConfig{
MaxPeers: 30,
MaxPendPeers: 50,
Bind: "0.0.0.0",
Port: 30303,
NoDiscover: false,
NAT: "any",
Discovery: &P2PDiscovery{
V5Enabled: false,
Bootnodes: []string{},
BootnodesV4: []string{},
BootnodesV5: []string{},
StaticNodes: []string{},
TrustedNodes: []string{},
DNS: []string{},
},
},
Heimdall: &HeimdallConfig{
URL: "http://localhost:1317",
Without: false,
},
SyncMode: "fast",
GcMode: "full",
Snapshot: true,
EthStats: "",
TxPool: &TxPoolConfig{
Locals: []string{},
NoLocals: false,
Journal: "",
Rejournal: time.Duration(1 * time.Hour),
PriceLimit: 1,
PriceBump: 10,
AccountSlots: 16,
GlobalSlots: 4096,
AccountQueue: 64,
GlobalQueue: 1024,
LifeTime: time.Duration(3 * time.Hour),
},
Sealer: &SealerConfig{
Enabled: false,
Etherbase: "",
GasCeil: 8000000,
GasPrice: big.NewInt(params.GWei),
ExtraData: "",
},
Gpo: &GpoConfig{
Blocks: 20,
Percentile: 60,
MaxPrice: gasprice.DefaultMaxPrice,
IgnorePrice: gasprice.DefaultIgnorePrice,
},
JsonRPC: &JsonRPCConfig{
IPCDisable: false,
IPCPath: "",
Modules: []string{"web3", "net"},
Cors: []string{"*"},
VHost: []string{"*"},
GasCap: ethconfig.Defaults.RPCGasCap,
TxFeeCap: ethconfig.Defaults.RPCTxFeeCap,
Http: &APIConfig{
Enabled: false,
Port: 8545,
Prefix: "",
Host: "localhost",
},
Ws: &APIConfig{
Enabled: false,
Port: 8546,
Prefix: "",
Host: "localhost",
},
Graphql: &APIConfig{
Enabled: false,
},
},
Ethstats: "",
Metrics: &MetricsConfig{
Enabled: false,
Expensive: false,
InfluxDB: &InfluxDBConfig{
V1Enabled: false,
Endpoint: "",
Database: "",
Username: "",
Password: "",
Tags: map[string]string{},
V2Enabled: false,
Token: "",
Bucket: "",
Organization: "",
},
},
Cache: &CacheConfig{
Cache: 1024,
PercDatabase: 50,
PercTrie: 15,
PercGc: 25,
PercSnapshot: 10,
Journal: "triecache",
Rejournal: 60 * time.Minute,
NoPrefetch: false,
Preimages: false,
TxLookupLimit: 2350000,
},
Accounts: &AccountsConfig{
Unlock: []string{},
PasswordFile: "",
AllowInsecureUnlock: false,
UseLightweightKDF: false,
},
}
}
func readConfigFile(path string) (*Config, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
// TODO: Use hcl as config format
ext := filepath.Ext(path)
switch ext {
case ".toml":
return readLegacyConfig(data)
default:
return nil, fmt.Errorf("file path extension '%s' not found", ext)
}
}
func (c *Config) loadChain() error {
chain, ok := chains.GetChain(c.Chain)
if !ok {
return fmt.Errorf("chain '%s' not found", c.Chain)
}
c.chain = chain
// preload some default values that depend on the chain file
if c.P2P.Discovery.DNS == nil {
c.P2P.Discovery.DNS = c.chain.DNS
}
// depending on the chain we have different cache values
if c.Chain != "mainnet" {
c.Cache.Cache = 4096
} else {
c.Cache.Cache = 1024
}
return nil
}
func (c *Config) buildEth() (*ethconfig.Config, error) {
dbHandles, err := makeDatabaseHandles()
if err != nil {
return nil, err
}
n := ethconfig.Defaults
n.NetworkId = c.chain.NetworkId
n.Genesis = c.chain.Genesis
n.HeimdallURL = c.Heimdall.URL
n.WithoutHeimdall = c.Heimdall.Without
// txpool options
{
n.TxPool.NoLocals = c.TxPool.NoLocals
n.TxPool.Journal = c.TxPool.Journal
n.TxPool.Rejournal = c.TxPool.Rejournal
n.TxPool.PriceLimit = c.TxPool.PriceLimit
n.TxPool.PriceBump = c.TxPool.PriceBump
n.TxPool.AccountSlots = c.TxPool.AccountSlots
n.TxPool.GlobalSlots = c.TxPool.GlobalSlots
n.TxPool.AccountQueue = c.TxPool.AccountQueue
n.TxPool.GlobalQueue = c.TxPool.GlobalQueue
n.TxPool.Lifetime = c.TxPool.LifeTime
}
// miner options
{
n.Miner.GasPrice = c.Sealer.GasPrice
n.Miner.GasCeil = c.Sealer.GasCeil
n.Miner.ExtraData = []byte(c.Sealer.ExtraData)
if etherbase := c.Sealer.Etherbase; etherbase != "" {
if !common.IsHexAddress(etherbase) {
return nil, fmt.Errorf("etherbase is not an address: %s", etherbase)
}
n.Miner.Etherbase = common.HexToAddress(etherbase)
}
}
// discovery (this params should be in node.Config)
{
n.EthDiscoveryURLs = c.P2P.Discovery.DNS
n.SnapDiscoveryURLs = c.P2P.Discovery.DNS
}
// whitelist
{
n.Whitelist = map[uint64]common.Hash{}
for k, v := range c.Whitelist {
number, err := strconv.ParseUint(k, 0, 64)
if err != nil {
return nil, fmt.Errorf("invalid whitelist block number %s: %v", k, err)
}
var hash common.Hash
if err = hash.UnmarshalText([]byte(v)); err != nil {
return nil, fmt.Errorf("invalid whitelist hash %s: %v", v, err)
}
n.Whitelist[number] = hash
}
}
// cache
{
cache := c.Cache.Cache
calcPerc := func(val uint64) int {
return int(cache * (val) / 100)
}
// Cap the cache allowance
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 := uint64(mem.Total / 1024 / 1024 / 3)
if cache > allowance {
log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
cache = allowance
}
}
// Tune the garbage collector
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))
n.TrieCleanCacheJournal = c.Cache.Journal
n.TrieCleanCacheRejournal = c.Cache.Rejournal
n.DatabaseCache = calcPerc(c.Cache.PercDatabase)
n.SnapshotCache = calcPerc(c.Cache.PercSnapshot)
n.TrieCleanCache = calcPerc(c.Cache.PercTrie)
n.TrieDirtyCache = calcPerc(c.Cache.PercGc)
n.NoPrefetch = c.Cache.NoPrefetch
n.Preimages = c.Cache.Preimages
n.TxLookupLimit = c.Cache.TxLookupLimit
}
n.RPCGasCap = c.JsonRPC.GasCap
if n.RPCGasCap != 0 {
log.Info("Set global gas cap", "cap", n.RPCGasCap)
} else {
log.Info("Global gas cap disabled")
}
n.RPCTxFeeCap = c.JsonRPC.TxFeeCap
// sync mode. It can either be "fast", "full" or "snap". We disable
// for now the "light" mode.
switch c.SyncMode {
case "fast":
n.SyncMode = downloader.FastSync
case "full":
n.SyncMode = downloader.FullSync
case "snap":
n.SyncMode = downloader.SnapSync
default:
return nil, fmt.Errorf("sync mode '%s' not found", c.SyncMode)
}
// archive mode. It can either be "archive" or "full".
switch c.GcMode {
case "full":
n.NoPruning = false
case "archive":
n.NoPruning = true
if !n.Preimages {
n.Preimages = true
log.Info("Enabling recording of key preimages since archive mode is used")
}
default:
return nil, fmt.Errorf("gcmode '%s' not found", c.GcMode)
}
// snapshot disable check
if c.Snapshot {
if n.SyncMode == downloader.SnapSync {
log.Info("Snap sync requested, enabling --snapshot")
} else {
// disable snapshot
n.TrieCleanCache += n.SnapshotCache
n.SnapshotCache = 0
}
}
n.DatabaseHandles = dbHandles
return &n, nil
}
var (
clientIdentifier = "bor"
gitCommit = "" // Git SHA1 commit hash of the release (set via linker flags)
gitDate = "" // Git commit date YYYYMMDD of the release (set via linker flags)
)
func (c *Config) buildNode() (*node.Config, error) {
ipcPath := ""
if !c.JsonRPC.IPCDisable {
ipcPath = clientIdentifier + ".ipc"
if c.JsonRPC.IPCPath != "" {
ipcPath = c.JsonRPC.IPCPath
}
}
cfg := &node.Config{
Name: clientIdentifier,
DataDir: c.DataDir,
UseLightweightKDF: c.Accounts.UseLightweightKDF,
InsecureUnlockAllowed: c.Accounts.AllowInsecureUnlock,
Version: params.VersionWithCommit(gitCommit, gitDate),
IPCPath: ipcPath,
P2P: p2p.Config{
MaxPeers: int(c.P2P.MaxPeers),
MaxPendingPeers: int(c.P2P.MaxPendPeers),
ListenAddr: c.P2P.Bind + ":" + strconv.Itoa(int(c.P2P.Port)),
DiscoveryV5: c.P2P.Discovery.V5Enabled,
},
HTTPModules: c.JsonRPC.Modules,
HTTPCors: c.JsonRPC.Cors,
HTTPVirtualHosts: c.JsonRPC.VHost,
HTTPPathPrefix: c.JsonRPC.Http.Prefix,
WSModules: c.JsonRPC.Modules,
WSOrigins: c.JsonRPC.Cors,
WSPathPrefix: c.JsonRPC.Ws.Prefix,
GraphQLCors: c.JsonRPC.Cors,
GraphQLVirtualHosts: c.JsonRPC.VHost,
}
// enable jsonrpc endpoints
{
if c.JsonRPC.Http.Enabled {
cfg.HTTPHost = c.JsonRPC.Http.Host
cfg.HTTPPort = int(c.JsonRPC.Http.Port)
}
if c.JsonRPC.Ws.Enabled {
cfg.WSHost = c.JsonRPC.Ws.Host
cfg.WSPort = int(c.JsonRPC.Ws.Port)
}
}
natif, err := nat.Parse(c.P2P.NAT)
if err != nil {
return nil, fmt.Errorf("wrong 'nat' flag: %v", err)
}
cfg.P2P.NAT = natif
// Discovery
// if no bootnodes are defined, use the ones from the chain file.
bootnodes := c.P2P.Discovery.Bootnodes
if len(bootnodes) == 0 {
bootnodes = c.chain.Bootnodes
}
if cfg.P2P.BootstrapNodes, err = parseBootnodes(bootnodes); err != nil {
return nil, err
}
if cfg.P2P.BootstrapNodesV5, err = parseBootnodes(c.P2P.Discovery.BootnodesV5); err != nil {
return nil, err
}
if cfg.P2P.StaticNodes, err = parseBootnodes(c.P2P.Discovery.StaticNodes); err != nil {
return nil, err
}
if cfg.P2P.TrustedNodes, err = parseBootnodes(c.P2P.Discovery.TrustedNodes); err != nil {
return nil, err
}
if c.P2P.NoDiscover {
// Disable networking, for now, we will not even allow incomming connections
cfg.P2P.MaxPeers = 0
cfg.P2P.NoDiscovery = true
}
return cfg, nil
}
func (c *Config) Merge(cc ...*Config) error {
for _, elem := range cc {
if err := mergo.Merge(c, elem, mergo.WithOverride, mergo.WithAppendSlice); err != nil {
return fmt.Errorf("failed to merge configurations: %v", err)
}
}
return nil
}
func makeDatabaseHandles() (int, error) {
limit, err := fdlimit.Maximum()
if err != nil {
return -1, err
}
raised, err := fdlimit.Raise(uint64(limit))
if err != nil {
return -1, err
}
return int(raised / 2), nil
}
func parseBootnodes(urls []string) ([]*enode.Node, error) {
dst := []*enode.Node{}
for _, url := range urls {
if url != "" {
node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return nil, fmt.Errorf("invalid bootstrap url '%s': %v", url, err)
}
dst = append(dst, node)
}
}
return dst, nil
}
func defaultDataDir() string {
// Try to place the data folder in the user's home dir
home, _ := homedir.Dir()
if home == "" {
// we cannot guess a stable location
return ""
}
switch runtime.GOOS {
case "darwin":
return filepath.Join(home, "Library", "Bor")
case "windows":
appdata := os.Getenv("LOCALAPPDATA")
if appdata == "" {
// Windows XP and below don't have LocalAppData.
panic("environment variable LocalAppData is undefined")
}
return filepath.Join(appdata, "Bor")
default:
return filepath.Join(home, ".bor")
}
}