mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-28 07:36:44 +00:00
616 lines
14 KiB
Go
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")
|
|
}
|
|
}
|