Ori Newman 765dd170e4
Optimizations and header size reduce hardfork (#1853)
* Modify DefaultTimeout to 120 seconds

A temporary workaround for nodes having trouble to sync (currently the download of pruning point related data during IBD takes more than 30 seconds)

* Cache existence in reachability store

* Cache block level in the header

* Fix IBD indication on submit block

* Add hardForkOmitGenesisFromParentsDAAScore logic

* Fix NumThreads bug in the wallet

* Get rid of ParentsAtLevel header method

* Fix a bug in BuildPruningPointProof

* Increase race detector timeout

* Add cache to BuildPruningPointProof

* Add comments and temp comment out go vet

* Fix ParentsAtLevel

* Dont fill empty parents

* Change HardForkOmitGenesisFromParentsDAAScore in fast netsync test

* Add --allow-submit-block-when-not-synced in stability tests

* Fix TestPruning

* Return fast tests

* Fix off by one error on kaspawallet

* Fetch only one block with trusted data at a time

* Update fork DAA score

* Don't ban for unexpected message type

* Fix tests

Co-authored-by: Michael Sutton <mikisiton2@gmail.com>
Co-authored-by: Ori Newman <>
2021-11-22 09:00:39 +02:00

594 lines
24 KiB
Go

// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package config
import (
// _ "embed" is necessary for the go:embed feature.
_ "embed"
"fmt"
"net"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"github.com/btcsuite/go-socks/socks"
"github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/network"
"github.com/kaspanet/kaspad/version"
"github.com/pkg/errors"
)
const (
defaultConfigFilename = "kaspad.conf"
defaultDataDirname = "data"
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "kaspad.log"
defaultErrLogFilename = "kaspad_err.log"
defaultTargetOutboundPeers = 8
defaultMaxInboundPeers = 117
defaultBanDuration = time.Hour * 24
defaultBanThreshold = 100
//DefaultConnectTimeout is the default connection timeout when dialing
DefaultConnectTimeout = time.Second * 30
defaultMaxRPCClients = 10
defaultMaxRPCWebsockets = 25
defaultMaxRPCConcurrentReqs = 20
defaultBlockMaxMass = 10000000
blockMaxMassMin = 1000
blockMaxMassMax = 10000000
defaultMinRelayTxFee = 1e-5 // 1 sompi per byte
defaultMaxOrphanTransactions = 100
//DefaultMaxOrphanTxSize is the default maximum size for an orphan transaction
DefaultMaxOrphanTxSize = 100000
defaultSigCacheMaxSize = 100000
sampleConfigFilename = "sample-kaspad.conf"
defaultMaxUTXOCacheSize = 5000000000
)
var (
// DefaultAppDir is the default home directory for kaspad.
DefaultAppDir = util.AppDir("kaspad", false)
defaultConfigFile = filepath.Join(DefaultAppDir, defaultConfigFilename)
defaultDataDir = filepath.Join(DefaultAppDir)
defaultRPCKeyFile = filepath.Join(DefaultAppDir, "rpc.key")
defaultRPCCertFile = filepath.Join(DefaultAppDir, "rpc.cert")
)
//go:embed sample-kaspad.conf
var sampleConfig string
// RunServiceCommand is only set to a real function on Windows. It is used
// to parse and execute service commands specified via the -s flag.
var RunServiceCommand func(string) error
// Flags defines the configuration options for kaspad.
//
// See loadConfig for details on the configuration load process.
type Flags struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
AppDir string `short:"b" long:"appdir" description:"Directory to store data"`
LogDir string `long:"logdir" description:"Directory to log output."`
AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
DisableListen bool `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect or --proxy options are used without also specifying listen interfaces via --listen"`
Listeners []string `long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 16111, testnet: 16211)"`
TargetOutboundPeers int `long:"outpeers" description:"Target number of outbound peers"`
MaxInboundPeers int `long:"maxinpeers" description:"Max number of inbound peers"`
DisableBanning bool `long:"nobanning" description:"Disable banning of misbehaving peers"`
BanDuration time.Duration `long:"banduration" description:"How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second"`
BanThreshold uint32 `long:"banthreshold" description:"Maximum allowed ban score before disconnecting and banning misbehaving peers."`
Whitelists []string `long:"whitelist" description:"Add an IP network or IP that will not be banned. (eg. 192.168.1.0/24 or ::1)"`
RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 16110, testnet: 16210)"`
RPCCert string `long:"rpccert" description:"File containing the certificate file"`
RPCKey string `long:"rpckey" description:"File containing the certificate key"`
RPCMaxClients int `long:"rpcmaxclients" description:"Max number of RPC clients for standard connections"`
RPCMaxWebsockets int `long:"rpcmaxwebsockets" description:"Max number of RPC websocket connections"`
RPCMaxConcurrentReqs int `long:"rpcmaxconcurrentreqs" description:"Max number of concurrent RPC requests that may be processed concurrently"`
DisableRPC bool `long:"norpc" description:"Disable built-in RPC server"`
DisableDNSSeed bool `long:"nodnsseed" description:"Disable DNS seeding for peers"`
DNSSeed string `long:"dnsseed" description:"Override DNS seeds with specified hostname (Only 1 hostname allowed)"`
GRPCSeed string `long:"grpcseed" description:"Hostname of gRPC server for seeding peers"`
ExternalIPs []string `long:"externalip" description:"Add an ip to the list of local addresses we claim to listen on to peers"`
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
DbType string `long:"dbtype" description:"Database backend to use for the Block DAG"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
LogLevel string `short:"d" long:"loglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
Upnp bool `long:"upnp" description:"Use UPnP to map our listening port outside of NAT"`
MinRelayTxFee float64 `long:"minrelaytxfee" description:"The minimum transaction fee in KAS/kB to be considered a non-zero fee."`
MaxOrphanTxs uint64 `long:"maxorphantx" description:"Max number of orphan transactions to keep in memory"`
BlockMaxMass uint64 `long:"blockmaxmass" description:"Maximum transaction mass to be used when creating a block"`
UserAgentComments []string `long:"uacomment" description:"Comment to add to the user agent -- See BIP 14 for more information."`
NoPeerBloomFilters bool `long:"nopeerbloomfilters" description:"Disable bloom filtering support"`
SigCacheMaxSize uint `long:"sigcachemaxsize" description:"The maximum number of entries in the signature verification cache"`
BlocksOnly bool `long:"blocksonly" description:"Do not accept transactions from remote peers."`
RelayNonStd bool `long:"relaynonstd" description:"Relay non-standard transactions regardless of the default settings for the active network."`
RejectNonStd bool `long:"rejectnonstd" description:"Reject non-standard transactions regardless of the default settings for the active network."`
ResetDatabase bool `long:"reset-db" description:"Reset database before starting node. It's needed when switching between subnetworks."`
MaxUTXOCacheSize uint64 `long:"maxutxocachesize" description:"Max size of loaded UTXO into ram from the disk in bytes"`
UTXOIndex bool `long:"utxoindex" description:"Enable the UTXO index"`
IsArchivalNode bool `long:"archival" description:"Run as an archival node: don't delete old block data when moving the pruning point (Warning: heavy disk usage)'"`
AllowSubmitBlockWhenNotSynced bool `long:"allow-submit-block-when-not-synced" hidden:"true" description:"Allow the node to accept blocks from RPC while not synced (this flag is mainly used for testing)"`
EnableSanityCheckPruningUTXOSet bool `long:"enable-sanity-check-pruning-utxo" hidden:"true" description:"When moving the pruning point - check that the utxo set matches the utxo commitment"`
NetworkFlags
ServiceOptions *ServiceOptions
}
// Config defines the configuration options for kaspad.
//
// See loadConfig for details on the configuration load process.
type Config struct {
*Flags
Lookup func(string) ([]net.IP, error)
Dial func(string, string, time.Duration) (net.Conn, error)
MiningAddrs []util.Address
MinRelayTxFee util.Amount
Whitelists []*net.IPNet
SubnetworkID *externalapi.DomainSubnetworkID // nil in full nodes
}
// ServiceOptions defines the configuration options for the daemon as a service on
// Windows.
type ServiceOptions struct {
ServiceCommand string `short:"s" long:"service" description:"Service command {install, remove, start, stop}"`
}
// cleanAndExpandPath expands environment variables and leading ~ in the
// passed path, cleans the result, and returns it.
func cleanAndExpandPath(path string) string {
// Expand initial ~ to OS specific home directory.
if strings.HasPrefix(path, "~") {
homeDir := filepath.Dir(DefaultAppDir)
path = strings.Replace(path, "~", homeDir, 1)
}
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
// but they variables can still be expanded via POSIX-style $VARIABLE.
return filepath.Clean(os.ExpandEnv(path))
}
// newConfigParser returns a new command line flags parser.
func newConfigParser(cfgFlags *Flags, options flags.Options) *flags.Parser {
parser := flags.NewParser(cfgFlags, options)
if runtime.GOOS == "windows" {
parser.AddGroup("Service Options", "Service Options", cfgFlags.ServiceOptions)
}
return parser
}
func defaultFlags() *Flags {
return &Flags{
ConfigFile: defaultConfigFile,
LogLevel: defaultLogLevel,
TargetOutboundPeers: defaultTargetOutboundPeers,
MaxInboundPeers: defaultMaxInboundPeers,
BanDuration: defaultBanDuration,
BanThreshold: defaultBanThreshold,
RPCMaxClients: defaultMaxRPCClients,
RPCMaxWebsockets: defaultMaxRPCWebsockets,
RPCMaxConcurrentReqs: defaultMaxRPCConcurrentReqs,
AppDir: defaultDataDir,
RPCKey: defaultRPCKeyFile,
RPCCert: defaultRPCCertFile,
BlockMaxMass: defaultBlockMaxMass,
MaxOrphanTxs: defaultMaxOrphanTransactions,
SigCacheMaxSize: defaultSigCacheMaxSize,
MinRelayTxFee: defaultMinRelayTxFee,
MaxUTXOCacheSize: defaultMaxUTXOCacheSize,
ServiceOptions: &ServiceOptions{},
}
}
// DefaultConfig returns the default kaspad configuration
func DefaultConfig() *Config {
config := &Config{Flags: defaultFlags()}
config.NetworkFlags.ActiveNetParams = &dagconfig.MainnetParams
return config
}
// LoadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 1) Start with a default config with sane settings
// 2) Pre-parse the command line to check for an alternative config file
// 3) Load configuration file overwriting defaults with any specified options
// 4) Parse CLI options and overwrite/add any specified options
//
// The above results in kaspad functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options. Command line options always take precedence.
func LoadConfig() (*Config, error) {
cfgFlags := defaultFlags()
// Pre-parse the command line options to see if an alternative config
// file or the version flag was specified. Any errors aside from the
// help message error can be ignored here since they will be caught by
// the final parse below.
preCfg := cfgFlags
preParser := newConfigParser(preCfg, flags.HelpFlag)
_, err := preParser.Parse()
if err != nil {
var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
return nil, err
}
}
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
// Show the version and exit if the version flag was specified.
if preCfg.ShowVersion {
fmt.Println(appName, "version", version.Version())
os.Exit(0)
}
// Load additional config from file.
var configFileError error
parser := newConfigParser(cfgFlags, flags.Default)
cfg := &Config{
Flags: cfgFlags,
}
if !preCfg.Simnet || preCfg.ConfigFile != defaultConfigFile {
if _, err := os.Stat(preCfg.ConfigFile); os.IsNotExist(err) {
err := createDefaultConfigFile(preCfg.ConfigFile)
if err != nil {
return nil, errors.Wrap(err, "Error creating a default config file")
}
}
err := flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
if err != nil {
if pErr := &(os.PathError{}); !errors.As(err, &pErr) {
return nil, errors.Wrapf(err, "Error parsing config file: %s\n\n%s", err, usageMessage)
}
configFileError = err
}
}
// Parse command line options again to ensure they take precedence.
_, err = parser.Parse()
if err != nil {
var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); !ok || flagsErr.Type != flags.ErrHelp {
return nil, errors.Wrapf(err, "Error parsing command line arguments: %s\n\n%s", err, usageMessage)
}
return nil, err
}
// Create the home directory if it doesn't already exist.
funcName := "loadConfig"
err = os.MkdirAll(DefaultAppDir, 0700)
if err != nil {
// Show a nicer error message if it's because a symlink is
// linked to a directory that does not exist (probably because
// it's not mounted).
var e *os.PathError
if ok := errors.As(err, &e); ok && os.IsExist(err) {
if link, lerr := os.Readlink(e.Path); lerr == nil {
str := "is symlink %s -> %s mounted?"
err = errors.Errorf(str, e.Path, link)
}
}
str := "%s: Failed to create home directory: %s"
err := errors.Errorf(str, funcName, err)
return nil, err
}
err = cfg.ResolveNetwork(parser)
if err != nil {
return nil, err
}
// Set the default policy for relaying non-standard transactions
// according to the default of the active network. The set
// configuration value takes precedence over the default value for the
// selected network.
relayNonStd := cfg.NetParams().RelayNonStdTxs
switch {
case cfg.RelayNonStd && cfg.RejectNonStd:
str := "%s: rejectnonstd and relaynonstd cannot be used " +
"together -- choose only one"
err := errors.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
case cfg.RejectNonStd:
relayNonStd = false
case cfg.RelayNonStd:
relayNonStd = true
}
cfg.RelayNonStd = relayNonStd
cfg.AppDir = cleanAndExpandPath(cfg.AppDir)
// Append the network type to the app directory so it is "namespaced"
// per network.
// All data is specific to a network, so namespacing the data directory
// means each individual piece of serialized data does not have to
// worry about changing names per network and such.
cfg.AppDir = filepath.Join(cfg.AppDir, cfg.NetParams().Name)
// Logs directory is usually under the home directory, unless otherwise specified
if cfg.LogDir == "" {
cfg.LogDir = filepath.Join(cfg.AppDir, defaultLogDirname)
}
cfg.LogDir = cleanAndExpandPath(cfg.LogDir)
// Special show command to list supported subsystems and exit.
if cfg.LogLevel == "show" {
fmt.Println("Supported subsystems", logger.SupportedSubsystems())
os.Exit(0)
}
// Initialize log rotation. After log rotation has been initialized, the
// logger variables may be used.
logger.InitLog(filepath.Join(cfg.LogDir, defaultLogFilename), filepath.Join(cfg.LogDir, defaultErrLogFilename))
// Parse, validate, and set debug log level(s).
if err := logger.ParseAndSetLogLevels(cfg.LogLevel); err != nil {
err := errors.Errorf("%s: %s", funcName, err.Error())
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
// Validate profile port number
if cfg.Profile != "" {
profilePort, err := strconv.Atoi(cfg.Profile)
if err != nil || profilePort < 1024 || profilePort > 65535 {
str := "%s: The profile port must be between 1024 and 65535"
err := errors.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
}
// Don't allow ban durations that are too short.
if cfg.BanDuration < time.Second {
str := "%s: The banduration option may not be less than 1s -- parsed [%s]"
err := errors.Errorf(str, funcName, cfg.BanDuration)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
// Validate any given whitelisted IP addresses and networks.
if len(cfg.Whitelists) > 0 {
var ip net.IP
cfg.Whitelists = make([]*net.IPNet, 0, len(cfg.Flags.Whitelists))
for _, addr := range cfg.Flags.Whitelists {
_, ipnet, err := net.ParseCIDR(addr)
if err != nil {
ip = net.ParseIP(addr)
if ip == nil {
str := "%s: The whitelist value of '%s' is invalid"
err = errors.Errorf(str, funcName, addr)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
var bits int
if ip.To4() == nil {
// IPv6
bits = 128
} else {
bits = 32
}
ipnet = &net.IPNet{
IP: ip,
Mask: net.CIDRMask(bits, bits),
}
}
cfg.Whitelists = append(cfg.Whitelists, ipnet)
}
}
// --addPeer and --connect do not mix.
if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
str := "%s: the --addpeer and --connect options can not be " +
"mixed"
err := errors.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
// --proxy or --connect without --listen disables listening.
if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) &&
len(cfg.Listeners) == 0 {
cfg.DisableListen = true
}
// ConnectPeers means no DNS seeding and no outbound peers
if len(cfg.ConnectPeers) > 0 {
cfg.DisableDNSSeed = true
cfg.TargetOutboundPeers = 0
}
// Add the default listener if none were specified. The default
// listener is all addresses on the listen port for the network
// we are to connect to.
if len(cfg.Listeners) == 0 {
cfg.Listeners = []string{
net.JoinHostPort("", cfg.NetParams().DefaultPort),
}
}
if cfg.DisableRPC {
log.Infof("RPC service is disabled")
}
// Add the default RPC listener if none were specified. The default
// RPC listener is all addresses on the RPC listen port for the
// network we are to connect to.
if !cfg.DisableRPC && len(cfg.RPCListeners) == 0 {
cfg.RPCListeners = []string{
net.JoinHostPort("", cfg.NetParams().RPCPort),
}
}
if cfg.RPCMaxConcurrentReqs < 0 {
str := "%s: The rpcmaxwebsocketconcurrentrequests option may " +
"not be less than 0 -- parsed [%d]"
err := errors.Errorf(str, funcName, cfg.RPCMaxConcurrentReqs)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
// Validate the the minrelaytxfee.
cfg.MinRelayTxFee, err = util.NewAmount(cfg.Flags.MinRelayTxFee)
if err != nil {
str := "%s: invalid minrelaytxfee: %s"
err := errors.Errorf(str, funcName, err)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
// Disallow 0 and negative min tx fees.
if cfg.MinRelayTxFee == 0 {
str := "%s: The minrelaytxfee option must be greater than 0 -- parsed [%d]"
err := errors.Errorf(str, funcName, cfg.MinRelayTxFee)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
// Limit the max block mass to a sane value.
if cfg.BlockMaxMass < blockMaxMassMin || cfg.BlockMaxMass >
blockMaxMassMax {
str := "%s: The blockmaxmass option must be in between %d " +
"and %d -- parsed [%d]"
err := errors.Errorf(str, funcName, blockMaxMassMin,
blockMaxMassMax, cfg.BlockMaxMass)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
// Look for illegal characters in the user agent comments.
for _, uaComment := range cfg.UserAgentComments {
if strings.ContainsAny(uaComment, "/:()") {
err := errors.Errorf("%s: The following characters must not "+
"appear in user agent comments: '/', ':', '(', ')'",
funcName)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
}
// Add default port to all listener addresses if needed and remove
// duplicate addresses.
cfg.Listeners, err = network.NormalizeAddresses(cfg.Listeners,
cfg.NetParams().DefaultPort)
if err != nil {
return nil, err
}
// Add default port to all rpc listener addresses if needed and remove
// duplicate addresses.
cfg.RPCListeners, err = network.NormalizeAddresses(cfg.RPCListeners,
cfg.NetParams().RPCPort)
if err != nil {
return nil, err
}
// Disallow --addpeer and --connect used together
if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
str := "%s: --addpeer and --connect can not be used together"
err := errors.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
// Add default port to all added peer addresses if needed and remove
// duplicate addresses.
cfg.AddPeers, err = network.NormalizeAddresses(cfg.AddPeers,
cfg.NetParams().DefaultPort)
if err != nil {
return nil, err
}
cfg.ConnectPeers, err = network.NormalizeAddresses(cfg.ConnectPeers,
cfg.NetParams().DefaultPort)
if err != nil {
return nil, err
}
// Setup dial and DNS resolution (lookup) functions depending on the
// specified options. The default is to use the standard
// net.DialTimeout function as well as the system DNS resolver. When a
// proxy is specified, the dial function is set to the proxy specific
// dial function.
cfg.Dial = net.DialTimeout
cfg.Lookup = net.LookupIP
if cfg.Proxy != "" {
_, _, err := net.SplitHostPort(cfg.Proxy)
if err != nil {
str := "%s: Proxy address '%s' is invalid: %s"
err := errors.Errorf(str, funcName, cfg.Proxy, err)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
proxy := &socks.Proxy{
Addr: cfg.Proxy,
Username: cfg.ProxyUser,
Password: cfg.ProxyPass,
}
cfg.Dial = proxy.DialTimeout
}
// Warn about missing config file only after all other configuration is
// done. This prevents the warning on help messages and invalid
// options. Note this should go directly before the return.
if configFileError != nil {
log.Warnf("%s", configFileError)
}
return cfg, nil
}
// createDefaultConfig copies the file sample-kaspad.conf to the given destination path,
// and populates it with some randomly generated RPC username and password.
func createDefaultConfigFile(destinationPath string) error {
// Create the destination directory if it does not exists
err := os.MkdirAll(filepath.Dir(destinationPath), 0700)
if err != nil {
return err
}
dest, err := os.OpenFile(destinationPath,
os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer dest.Close()
_, err = dest.WriteString(sampleConfig)
return err
}