Fantom-foundation/go-lachesis

View on GitHub
cmd/lachesis/config.go

Summary

Maintainability
A
0 mins
Test Coverage
package main

import (
    "bufio"
    "errors"
    "fmt"
    "os"
    "path"
    "path/filepath"
    "reflect"
    "strings"

    "github.com/Fantom-foundation/go-opera/cmd/opera/launcher"
    "github.com/ethereum/go-ethereum/cmd/utils"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/log"
    "github.com/ethereum/go-ethereum/metrics"
    "github.com/ethereum/go-ethereum/node"
    "github.com/ethereum/go-ethereum/p2p/discv5"
    "github.com/ethereum/go-ethereum/params"
    "github.com/naoina/toml"
    "gopkg.in/urfave/cli.v1"

    "github.com/Fantom-foundation/go-lachesis/evmcore"
    "github.com/Fantom-foundation/go-lachesis/gossip"
    "github.com/Fantom-foundation/go-lachesis/gossip/gasprice"
    "github.com/Fantom-foundation/go-lachesis/lachesis"
)

var (
    dumpConfigCommand = cli.Command{
        Action:      utils.MigrateFlags(dumpConfig),
        Name:        "dumpconfig",
        Usage:       "Show configuration values",
        ArgsUsage:   "",
        Flags:       append(nodeFlags, testFlags...),
        Category:    "MISCELLANEOUS COMMANDS",
        Description: `The dumpconfig command shows configuration values.`,
    }

    configFileFlag = cli.StringFlag{
        Name:  "legacy_config",
        Usage: "Lachesis TOML configuration file",
    }

    operaConfigFileFlag = cli.StringFlag{
        Name:  "config",
        Usage: "Opera TOML configuration file",
    }

    // DataDirFlag defines directory to store Lachesis state and user's wallets
    DataDirFlag = utils.DirectoryFlag{
        Name:  "datadir",
        Usage: "Data directory for the databases and keystore",
        Value: utils.DirectoryString(DefaultDataDir()),
    }
)

// 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 {
        return fmt.Errorf("field '%s' is not defined in %s", field, rt.String())
    },
}

type config struct {
    Node     node.Config
    Lachesis gossip.Config
}

func loadAllConfigs(file string, cfg *config) 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())
    }
    if err != nil {
        return errors.New(fmt.Sprintf("TOML config file error: %v.\n"+
            "Use 'dumpconfig' command to get an example config file.\n"+
            "If node was recently upgraded and a previous network config file is used, then check updates for the config file.", err))
    }
    return err
}

func defaultLachesisConfig(ctx *cli.Context) lachesis.Config {
    var cfg lachesis.Config

    switch {
    case ctx.GlobalIsSet(FakeNetFlag.Name):
        _, accs, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name))
        if err != nil {
            log.Crit("Invalid flag", "flag", FakeNetFlag.Name, "err", err)
        }
        cfg = lachesis.FakeNetConfig(accs)
    case ctx.GlobalBool(utils.LegacyTestnetFlag.Name):
        cfg = lachesis.TestNetConfig()
    default:
        cfg = lachesis.MainNetConfig()
    }

    return cfg
}

func setBootnodes(ctx *cli.Context, urls []string, cfg *node.Config) {
    for _, url := range urls {
        if url != "" {
            node, err := discv5.ParseNode(url)
            if err != nil {
                log.Error("Bootstrap URL invalid", "enode", url, "err", err)
                continue
            }
            cfg.P2P.BootstrapNodesV5 = append(cfg.P2P.BootstrapNodesV5, node)
        }
    }
}

func setDataDir(ctx *cli.Context, cfg *node.Config) {
    defaultDataDir := DefaultDataDir()

    switch {
    case ctx.GlobalIsSet(utils.DataDirFlag.Name):
        cfg.DataDir = ctx.GlobalString(utils.DataDirFlag.Name)
    case ctx.GlobalIsSet(FakeNetFlag.Name):
        _, accs, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name))
        if err != nil {
            log.Crit("Invalid flag", "flag", FakeNetFlag.Name, "err", err)
        }
        cfg.DataDir = filepath.Join(defaultDataDir, fmt.Sprintf("fakenet-%d", len(accs.Accounts)))
    case ctx.GlobalBool(utils.LegacyTestnetFlag.Name):
        cfg.DataDir = filepath.Join(defaultDataDir, "testnet")
    default:
        cfg.DataDir = defaultDataDir
    }
}

func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
    if ctx.GlobalIsSet(utils.LegacyGpoBlocksFlag.Name) {
        cfg.Blocks = ctx.GlobalInt(utils.LegacyGpoBlocksFlag.Name)
    }
    if ctx.GlobalIsSet(utils.GpoBlocksFlag.Name) {
        cfg.Blocks = ctx.GlobalInt(utils.GpoBlocksFlag.Name)
    }
    if ctx.GlobalIsSet(utils.LegacyGpoPercentileFlag.Name) {
        cfg.Percentile = ctx.GlobalInt(utils.LegacyGpoPercentileFlag.Name)
    }
    if ctx.GlobalIsSet(utils.GpoPercentileFlag.Name) {
        cfg.Percentile = ctx.GlobalInt(utils.GpoPercentileFlag.Name)
    }
}

func setTxPool(ctx *cli.Context, cfg *evmcore.TxPoolConfig) {
    if ctx.GlobalIsSet(utils.TxPoolLocalsFlag.Name) {
        locals := strings.Split(ctx.GlobalString(utils.TxPoolLocalsFlag.Name), ",")
        for _, account := range locals {
            if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
                utils.Fatalf("Invalid account in --txpool.locals: %s", trimmed)
            } else {
                cfg.Locals = append(cfg.Locals, common.HexToAddress(account))
            }
        }
    }
    if ctx.GlobalIsSet(utils.TxPoolNoLocalsFlag.Name) {
        cfg.NoLocals = ctx.GlobalBool(utils.TxPoolNoLocalsFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolJournalFlag.Name) {
        cfg.Journal = ctx.GlobalString(utils.TxPoolJournalFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolRejournalFlag.Name) {
        cfg.Rejournal = ctx.GlobalDuration(utils.TxPoolRejournalFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolPriceLimitFlag.Name) {
        cfg.PriceLimit = ctx.GlobalUint64(utils.TxPoolPriceLimitFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolPriceBumpFlag.Name) {
        cfg.PriceBump = ctx.GlobalUint64(utils.TxPoolPriceBumpFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolAccountSlotsFlag.Name) {
        cfg.AccountSlots = ctx.GlobalUint64(utils.TxPoolAccountSlotsFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolGlobalSlotsFlag.Name) {
        cfg.GlobalSlots = ctx.GlobalUint64(utils.TxPoolGlobalSlotsFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolAccountQueueFlag.Name) {
        cfg.AccountQueue = ctx.GlobalUint64(utils.TxPoolAccountQueueFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolGlobalQueueFlag.Name) {
        cfg.GlobalQueue = ctx.GlobalUint64(utils.TxPoolGlobalQueueFlag.Name)
    }
    if ctx.GlobalIsSet(utils.TxPoolLifetimeFlag.Name) {
        cfg.Lifetime = ctx.GlobalDuration(utils.TxPoolLifetimeFlag.Name)
    }
}

func gossipConfigWithFlags(ctx *cli.Context, src gossip.Config) gossip.Config {
    cfg := src

    // Avoid conflicting network flags
    utils.CheckExclusive(ctx, FakeNetFlag, utils.DeveloperFlag, utils.LegacyTestnetFlag)
    utils.CheckExclusive(ctx, FakeNetFlag, utils.DeveloperFlag, utils.ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer

    setGPO(ctx, &cfg.GPO)
    setTxPool(ctx, &cfg.TxPool)

    if ctx.GlobalIsSet(utils.NetworkIdFlag.Name) {
        cfg.Net.NetworkID = ctx.GlobalUint64(utils.NetworkIdFlag.Name)
    }
    // TODO cache config
    //if ctx.GlobalIsSet(utils.CacheFlag.Name) || ctx.GlobalIsSet(utils.CacheDatabaseFlag.Name) {
    //    cfg.DatabaseCache = ctx.GlobalInt(utils.CacheFlag.Name) * ctx.GlobalInt(utils.CacheDatabaseFlag.Name) / 100
    //}
    //if ctx.GlobalIsSet(utils.CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
    //    cfg.TrieCleanCache = ctx.GlobalInt(utils.CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100
    //}
    //if ctx.GlobalIsSet(utils.CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
    //    cfg.TrieDirtyCache = ctx.GlobalInt(utils.CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
    //}

    if ctx.GlobalIsSet(utils.VMEnableDebugFlag.Name) {
        cfg.EnablePreimageRecording = ctx.GlobalBool(utils.VMEnableDebugFlag.Name)
    }

    if ctx.GlobalIsSet(utils.EWASMInterpreterFlag.Name) {
        cfg.EWASMInterpreter = ctx.GlobalString(utils.EWASMInterpreterFlag.Name)
    }

    if ctx.GlobalIsSet(utils.EVMInterpreterFlag.Name) {
        cfg.EVMInterpreter = ctx.GlobalString(utils.EVMInterpreterFlag.Name)
    }
    if ctx.GlobalIsSet(utils.RPCGlobalGasCap.Name) {
        cfg.RPCGasCap = ctx.GlobalUint64(utils.RPCGlobalGasCap.Name)
    }
    if ctx.GlobalIsSet(utils.RPCGlobalTxFeeCap.Name) {
        cfg.RPCTxFeeCap = ctx.GlobalFloat64(utils.RPCGlobalTxFeeCap.Name)
    }

    return cfg
}

func nodeConfigWithFlags(ctx *cli.Context, cfg node.Config) node.Config {
    utils.SetNodeConfig(ctx, &cfg)

    if !ctx.GlobalIsSet(FakeNetFlag.Name) {
        setBootnodes(ctx, Bootnodes, &cfg)
    }
    setDataDir(ctx, &cfg)
    return cfg
}

func makeAllConfigs(ctx *cli.Context) *config {
    // Defaults (low priority)
    net := defaultLachesisConfig(ctx)
    cfg := config{Lachesis: gossip.DefaultConfig(net), Node: defaultNodeConfig()}

    // Load config file (medium priority)
    if file := ctx.GlobalString(configFileFlag.Name); file != "" {
        if err := loadAllConfigs(file, &cfg); err != nil {
            utils.Fatalf("%v", err)
        }
    }

    // Apply flags (high priority)
    cfg.Lachesis = gossipConfigWithFlags(ctx, cfg.Lachesis)
    cfg.Node = nodeConfigWithFlags(ctx, cfg.Node)

    return &cfg
}

func defaultNodeConfig() node.Config {
    cfg := NodeDefaultConfig
    cfg.Name = clientIdentifier
    cfg.Version = params.VersionWithCommit(gitCommit, gitDate)
    cfg.HTTPModules = append(cfg.HTTPModules, "eth", "ftm", "sfc", "web3")
    cfg.WSModules = append(cfg.WSModules, "eth", "ftm", "sfc", "web3")
    cfg.IPCPath = "lachesis.ipc"
    cfg.DataDir = DefaultDataDir()
    return cfg
}

// dumpConfig is the dumpconfig command.
func dumpConfig(ctx *cli.Context) error {
    cfg := makeAllConfigs(ctx)
    comment := ""

    out, err := tomlSettings.Marshal(&cfg)
    if err != nil {
        return err
    }

    log.Info("Dumping legacy Lachesis config file")

    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)

    log.Info("Dumping Opera config file")

    operaDatadir := path.Join(cfg.Node.DataDir, "opera")
    operaArgs := excludeArg(os.Args, "--"+validatorFlag.Name, true)
    operaArgs = excludeArg(operaArgs, "--"+configFileFlag.Name, true)
    operaArgs = excludeArg(operaArgs, "--"+DataDirFlag.Name, true)
    operaArgs = addFrontArgs(operaArgs, []string{"--" + DataDirFlag.Name, operaDatadir})
    operaArgs = excludeArg(operaArgs, "--"+utils.LegacyTestnetFlag.Name, false)

    metrics.Enabled = false
    return launcher.Launch(operaArgs)
}