From 57dfc1ed6d44525516ce7608dab161328e65b9f9 Mon Sep 17 00:00:00 2001 From: Jamie Holdstock Date: Thu, 14 May 2020 19:02:45 +0100 Subject: [PATCH] Add decred logging and config (#8) --- config.go | 264 +++++++++++++++++++++++++++++++++++++++++++ database/database.go | 41 ++++--- database/log.go | 26 +++++ go.mod | 7 +- go.sum | 19 +--- log.go | 99 ++++++++++++++++ main.go | 67 +---------- methods.go | 4 +- params.go | 29 +++++ run_tests.sh | 1 - 10 files changed, 457 insertions(+), 100 deletions(-) create mode 100644 config.go create mode 100644 database/log.go create mode 100644 log.go create mode 100644 params.go diff --git a/config.go b/config.go new file mode 100644 index 0000000..ca84415 --- /dev/null +++ b/config.go @@ -0,0 +1,264 @@ +package main + +import ( + "crypto/ed25519" + "crypto/rand" + "errors" + "fmt" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" + + "github.com/decred/dcrd/dcrutil/v3" + flags "github.com/jessevdk/go-flags" +) + +var ( + defaultListen = ":3000" + defaultLogLevel = "debug" + defaultVSPFee = 0.01 + defaultNetwork = "testnet" + defaultHomeDir = dcrutil.AppDataDir("dcrvsp", false) + defaultConfigFilename = "dcrvsp.conf" + defaultConfigFile = filepath.Join(defaultHomeDir, defaultConfigFilename) +) + +// config defines the configuration options for the VSP. +type config struct { + Listen string `long:"listen" ini-name:"listen" description:"The ip:port to listen for API requests."` + LogLevel string `long:"loglevel" ini-name:"loglevel" description:"Logging level." choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"critical"` + Network string `long:"network" ini-name:"network" description:"Decred network to use." choice:"testnet" choice:"mainnet" choice:"simnet"` + VSPFee float64 `long:"vspfee" ini-name:"vspfee" description:"The fee percentage charged for VSP use. eg. 0.01 (1%), 0.05 (5%)."` + HomeDir string `long:"homedir" ini-name:"homedir" no-ini:"true" description:"Path to application home directory. Used for storing VSP database and logs."` + ConfigFile string `long:"configfile" ini-name:"configfile" no-ini:"true" description:"Path to configuration file."` + + signKey ed25519.PrivateKey + pubKey ed25519.PublicKey + dbPath string + netParams *netParams +} + +// fileExists reports whether the named file or directory exists. +func fileExists(name string) bool { + if _, err := os.Stat(name); os.IsNotExist(err) { + return false + } + return true +} + +// cleanAndExpandPath expands environment variables and leading ~ in the +// passed path, cleans the result, and returns it. +func cleanAndExpandPath(path string) string { + // Nothing to do when no path is given. + if path == "" { + return path + } + + // NOTE: The os.ExpandEnv doesn't work with Windows cmd.exe-style + // %VARIABLE%, but the variables can still be expanded via POSIX-style + // $VARIABLE. + path = os.ExpandEnv(path) + + if !strings.HasPrefix(path, "~") { + return filepath.Clean(path) + } + + // Expand initial ~ to the current user's home directory, or ~otheruser + // to otheruser's home directory. On Windows, both forward and backward + // slashes can be used. + path = path[1:] + + var pathSeparators string + if runtime.GOOS == "windows" { + pathSeparators = string(os.PathSeparator) + "/" + } else { + pathSeparators = string(os.PathSeparator) + } + + userName := "" + if i := strings.IndexAny(path, pathSeparators); i != -1 { + userName = path[:i] + path = path[i:] + } + + homeDir := "" + var u *user.User + var err error + if userName == "" { + u, err = user.Current() + } else { + u, err = user.Lookup(userName) + } + if err == nil { + homeDir = u.HomeDir + } + // Fallback to CWD if user lookup fails or user has no home directory. + if homeDir == "" { + homeDir = "." + } + + return filepath.Join(homeDir, path) +} + +// 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 dcrvsp 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) { + + // Default config. + cfg := config{ + Listen: defaultListen, + LogLevel: defaultLogLevel, + Network: defaultNetwork, + VSPFee: defaultVSPFee, + HomeDir: defaultHomeDir, + ConfigFile: defaultConfigFile, + } + + // 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 := cfg + + preParser := flags.NewParser(&preCfg, flags.HelpFlag) + + _, err := preParser.Parse() + if err != nil { + if e, ok := err.(*flags.Error); ok && e.Type != flags.ErrHelp { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } else if ok && e.Type == flags.ErrHelp { + fmt.Fprintln(os.Stdout, err) + os.Exit(0) + } + } + + appName := filepath.Base(os.Args[0]) + appName = strings.TrimSuffix(appName, filepath.Ext(appName)) + usageMessage := fmt.Sprintf("Use %s -h to show usage", appName) + + // Update the home directory if specified on CLI. Since the home + // directory is updated, other variables need to be updated to + // reflect the new changes. + if preCfg.HomeDir != "" { + cfg.HomeDir, _ = filepath.Abs(preCfg.HomeDir) + + if preCfg.ConfigFile == defaultConfigFile { + defaultConfigFile = filepath.Join(cfg.HomeDir, defaultConfigFilename) + preCfg.ConfigFile = defaultConfigFile + cfg.ConfigFile = defaultConfigFile + } else { + cfg.ConfigFile = preCfg.ConfigFile + } + } + + // Create the home directory if it doesn't already exist. + funcName := "loadConfig" + err = os.MkdirAll(cfg.HomeDir, 0700) + if err != nil { + str := "%s: failed to create home directory: %v" + err := fmt.Errorf(str, funcName, err) + fmt.Fprintln(os.Stderr, err) + return nil, err + } + + // Create a default config file when one does not exist and the user did + // not specify an override. + if preCfg.ConfigFile == defaultConfigFile && !fileExists(preCfg.ConfigFile) { + preIni := flags.NewIniParser(preParser) + err = preIni.WriteFile(preCfg.ConfigFile, + flags.IniIncludeComments|flags.IniIncludeDefaults) + if err != nil { + return nil, fmt.Errorf("error creating a default "+ + "config file: %v", err) + } + } + + // Load additional config from file. + parser := flags.NewParser(&preCfg, flags.Default) + + err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile) + if err != nil { + fmt.Fprintf(os.Stderr, "error parsing config file: %v\n", err) + os.Exit(1) + } + + // Parse command line options again to ensure they take precedence. + _, err = parser.Parse() + if err != nil { + if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { + fmt.Fprintln(os.Stderr, usageMessage) + } + return nil, err + } + + // Set the active network. + switch cfg.Network { + case "testnet": + cfg.netParams = &testNet3Params + case "mainnet": + cfg.netParams = &mainNetParams + case "simnet": + cfg.netParams = &simNetParams + } + + // Create the data directory. + dataDir := filepath.Join(cfg.HomeDir, "data", cfg.netParams.Name) + err = os.MkdirAll(dataDir, 0700) + if err != nil { + str := "%s: failed to create data directory: %v" + err := fmt.Errorf(str, funcName, err) + return nil, err + } + + // Initialize loggers and log rotation. + logDir := filepath.Join(cfg.HomeDir, "logs", cfg.netParams.Name) + initLogRotator(filepath.Join(logDir, "dcrvsp.log")) + setLogLevels(cfg.LogLevel) + + // Set the database path + cfg.dbPath = filepath.Join(dataDir, "vsp.db") + + // Set pubKey/signKey. Read from seed file if it exists, otherwise generate + // one. + seedPath := filepath.Join(cfg.HomeDir, "sign.seed") + seed, err := ioutil.ReadFile(seedPath) + if err != nil { + if !os.IsNotExist(err) { + return nil, errors.New("seedPath does not exist") + } + + _, cfg.signKey, err = ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate signing key: %v", err) + } + err = ioutil.WriteFile(seedPath, cfg.signKey.Seed(), 0400) + if err != nil { + return nil, fmt.Errorf("failed to save signing key: %v", err) + } + } else { + cfg.signKey = ed25519.NewKeyFromSeed(seed) + } + + // Derive pubKey from signKey + pubKey, ok := cfg.signKey.Public().(ed25519.PublicKey) + if !ok { + return nil, fmt.Errorf("failed to cast signing key: %T", pubKey) + } + cfg.pubKey = pubKey + + return &cfg, nil +} diff --git a/database/database.go b/database/database.go index 81680e4..268ad68 100644 --- a/database/database.go +++ b/database/database.go @@ -34,25 +34,11 @@ func New(dbFile string) (*VspDatabase, error) { return nil, fmt.Errorf("unable to open db file: %v", err) } - err = createBuckets(db) - if err != nil { - return nil, err - } - - return &VspDatabase{db: db}, nil -} - -// Close releases all database resources. It will block waiting for any open -// transactions to finish before closing the database and returning. -func (vdb *VspDatabase) Close() error { - return vdb.db.Close() -} - -// createBuckets creates all storage buckets of the VSP if they don't already -// exist. -func createBuckets(db *bolt.DB) error { - return db.Update(func(tx *bolt.Tx) error { + // Create all storage buckets of the VSP if they don't already exist. + var newDB bool + err = db.Update(func(tx *bolt.Tx) error { if tx.Bucket(vspBktK) == nil { + newDB = true // Create parent bucket. vspBkt, err := tx.CreateBucket(vspBktK) if err != nil { @@ -76,4 +62,23 @@ func createBuckets(db *bolt.DB) error { return nil }) + + if err != nil { + return nil, err + } + + if newDB { + log.Debugf("Created new database %s", dbFile) + } else { + log.Debugf("Using existing database %s", dbFile) + } + + return &VspDatabase{db: db}, nil +} + +// Close releases all database resources. It will block waiting for any open +// transactions to finish before closing the database and returning. +func (vdb *VspDatabase) Close() error { + log.Debug("Closing database") + return vdb.db.Close() } diff --git a/database/log.go b/database/log.go new file mode 100644 index 0000000..af64caf --- /dev/null +++ b/database/log.go @@ -0,0 +1,26 @@ +package database + +import ( + "github.com/decred/slog" +) + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log slog.Logger + +// The default amount of logging is none. +func init() { + DisableLog() +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + log = slog.Disabled +} + +// UseLogger uses a specified Logger to output package logging info. +func UseLogger(logger slog.Logger) { + log = logger +} diff --git a/go.mod b/go.mod index f11b016..82450c8 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( decred.org/dcrwallet v1.2.3-0.20200507155221-397dd551e317 - github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200311044114-143c1884e4c8 + github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200511175520-d08cb3f72b3b github.com/decred/dcrd/chaincfg/chainhash v1.0.2 github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200511175520-d08cb3f72b3b github.com/decred/dcrd/dcrec v1.0.0 @@ -12,9 +12,10 @@ require ( github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.0.0 github.com/decred/dcrd/txscript/v3 v3.0.0-20200511175520-d08cb3f72b3b github.com/decred/dcrd/wire v1.3.0 + github.com/decred/slog v1.0.0 github.com/gin-gonic/gin v1.6.3 + github.com/jessevdk/go-flags v1.4.0 + github.com/jrick/logrotate v1.0.0 github.com/jrick/wsrpc/v2 v2.3.3 - github.com/kr/pretty v0.2.0 // indirect go.etcd.io/bbolt v1.3.4 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index 58fdfe5..c88a817 100644 --- a/go.sum +++ b/go.sum @@ -10,7 +10,6 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/decred/base58 v1.0.1/go.mod h1:H2ENcsJjye1G7CbRa67kV9OFaui0LGr56ntKKoY5g9c= @@ -19,8 +18,9 @@ github.com/decred/base58 v1.0.2/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbi github.com/decred/dcrd/addrmgr v1.1.0/go.mod h1:exghL+0+QeVvO4MXezWJ1C2tcpBn3ngfuP6S1R+adB8= github.com/decred/dcrd/blockchain/stake/v2 v2.0.2/go.mod h1:o2TT/l/YFdrt15waUdlZ3g90zfSwlA0WgQqHV9UGJF4= github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:aDL94kcVJfaaJP+acWUJrlK7g7xEOqTSiFe6bSN3yRQ= -github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200311044114-143c1884e4c8 h1:6oEo1yQYyfnT9qCERrLWMi9BlDzVBeyl011ssIAVQ3w= github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:4zE60yDWlfCDtmqnyP5o1k1K0oyhNn3Tvqo6F93/+RU= +github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200511175520-d08cb3f72b3b h1:8ChbBKdGbsfAUVWwqUzZIbGHg1z0YpFrVokpNETpal0= +github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200511175520-d08cb3f72b3b/go.mod h1:4zE60yDWlfCDtmqnyP5o1k1K0oyhNn3Tvqo6F93/+RU= github.com/decred/dcrd/blockchain/standalone v1.1.0 h1:yclvVGEY09Gf8A4GSAo+NCtL1dW2TYJ4OKp4+g0ICI0= github.com/decred/dcrd/blockchain/standalone v1.1.0/go.mod h1:6K8ZgzlWM1Kz2TwXbrtiAvfvIwfAmlzrtpA7CVPCUPE= github.com/decred/dcrd/blockchain/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:R9rIXU8kEJVC9Z4LAlh9bo9hiT3a+ihys3mCrz4PVao= @@ -85,7 +85,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -104,35 +103,27 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/bitset v1.0.0/go.mod h1:ZOYB5Uvkla7wIEY4FEssPVi3IQXa02arznRaYaAEPe4= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/wsrpc/v2 v2.3.2/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU= github.com/jrick/wsrpc/v2 v2.3.3 h1:cGM2YUPrG8crjXFWw3b6IMcwqYHJMkteLqEb/WlDSP4= github.com/jrick/wsrpc/v2 v2.3.3/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -183,8 +174,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/log.go b/log.go new file mode 100644 index 0000000..41ace19 --- /dev/null +++ b/log.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/decred/slog" + "github.com/jrick/logrotate/rotator" + + "github.com/jholdstock/dcrvsp/database" +) + +// logWriter implements an io.Writer that outputs to both standard output and +// the write-end pipe of an initialized log rotator. +type logWriter struct{} + +func (logWriter) Write(p []byte) (n int, err error) { + os.Stdout.Write(p) + return logRotator.Write(p) +} + +// Loggers per subsystem. A single backend logger is created and all subsytem +// loggers created from it will write to the backend. When adding new +// subsystems, add the subsystem logger variable here and to the +// subsystemLoggers map. +// +// Loggers can not be used before the log rotator has been initialized with a +// log file. This must be performed early during application startup by calling +// initLogRotator. +var ( + // backendLog is the logging backend used to create all subsystem loggers. + // The backend must not be used before the log rotator has been initialized, + // or data races and/or nil pointer dereferences will occur. + backendLog = slog.NewBackend(logWriter{}) + + // logRotator is one of the logging outputs. It should be closed on + // application shutdown. + logRotator *rotator.Rotator + + vspLog = backendLog.Logger("VSP") + dbLog = backendLog.Logger("DB") +) + +// Initialize package-global logger variables. +func init() { + database.UseLogger(dbLog) +} + +// subsystemLoggers maps each subsystem identifier to its associated logger. +var subsystemLoggers = map[string]slog.Logger{ + "VSP": vspLog, + "DB": dbLog, +} + +// initLogRotator initializes the logging rotater to write logs to logFile and +// create roll files in the same directory. It must be called before the +// package-global log rotater variables are used. +func initLogRotator(logFile string) { + logDir, _ := filepath.Split(logFile) + err := os.MkdirAll(logDir, 0700) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to create log directory: %v\n", err) + os.Exit(1) + } + r, err := rotator.New(logFile, 10*1024, false, 3) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to create file rotator: %v\n", err) + os.Exit(1) + } + + logRotator = r +} + +// setLogLevel sets the logging level for provided subsystem. Invalid +// subsystems are ignored. Uninitialized subsystems are dynamically created as +// needed. +func setLogLevel(subsystemID string, logLevel string) { + // Ignore invalid subsystems. + logger, ok := subsystemLoggers[subsystemID] + if !ok { + return + } + + // Defaults to info if the log level is invalid. + level, _ := slog.LevelFromString(logLevel) + logger.SetLevel(level) +} + +// setLogLevels sets the log level for all subsystem loggers to the passed +// level. It also dynamically creates the subsystem loggers as needed, so it +// can be used to initialize the logging system. +func setLogLevels(logLevel string) { + // Configure all sub-systems with the new logging level. Dynamically + // create loggers as needed. + for subsystemID := range subsystemLoggers { + setLogLevel(subsystemID, logLevel) + } +} diff --git a/main.go b/main.go index eec830a..1fca91b 100644 --- a/main.go +++ b/main.go @@ -1,81 +1,26 @@ package main import ( - "crypto/ed25519" - "crypto/rand" - "errors" - "fmt" - "io/ioutil" "log" - "os" - "path/filepath" - "github.com/decred/dcrd/chaincfg/v3" "github.com/jholdstock/dcrvsp/database" "github.com/jrick/wsrpc/v2" ) -const listen = ":3000" +var cfg *config -type Config struct { - signKey ed25519.PrivateKey - pubKey ed25519.PublicKey - poolFees float64 - netParams *chaincfg.Params - dbFile string -} - -var cfg Config - -// Database with stubbed methods var db *database.VspDatabase var nodeConnection *wsrpc.Client -func initConfig() (*Config, error) { - homePath := "~/.dcrvsp" - - seedPath := filepath.Join(homePath, "sign.seed") - seed, err := ioutil.ReadFile(seedPath) - var signKey ed25519.PrivateKey - if err != nil { - if !os.IsNotExist(err) { - return nil, errors.New("seedPath does not exist") - } - - _, signKey, err = ed25519.GenerateKey(rand.Reader) - if err != nil { - return nil, fmt.Errorf("failed to generate signing key: %v", err) - } - err = ioutil.WriteFile(seedPath, signKey.Seed(), 0400) - if err != nil { - return nil, fmt.Errorf("failed to save signing key: %v", err) - } - } else { - signKey = ed25519.NewKeyFromSeed(seed) - } - - pubKey, ok := signKey.Public().(ed25519.PublicKey) - if !ok { - return nil, fmt.Errorf("failed to cast signing key: %T", pubKey) - } - - return &Config{ - netParams: chaincfg.TestNet3Params(), - dbFile: filepath.Join(homePath, "database.db"), - pubKey: pubKey, - poolFees: 0.1, - signKey: signKey, - }, nil -} - func main() { - cfg, err := initConfig() + var err error + cfg, err := loadConfig() if err != nil { log.Fatalf("config error: %v", err) } - db, err = database.New(cfg.dbFile) + db, err = database.New(cfg.dbPath) if err != nil { log.Fatalf("database error: %v", err) } @@ -83,6 +28,6 @@ func main() { defer db.Close() // Start HTTP server - log.Printf("Listening on %s", listen) - log.Print(newRouter().Run(listen)) + log.Printf("Listening on %s", cfg.Listen) + log.Print(newRouter().Run(cfg.Listen)) } diff --git a/methods.go b/methods.go index 74dbb44..b8abfe8 100644 --- a/methods.go +++ b/methods.go @@ -49,7 +49,7 @@ func pubKey(c *gin.Context) { func fee(c *gin.Context) { sendJSONResponse(feeResponse{ Timestamp: time.Now().Unix(), - Fee: cfg.poolFees, + Fee: cfg.VSPFee, }, http.StatusOK, c) } @@ -273,7 +273,7 @@ findAddress: return } - minFee := txrules.StakePoolTicketFee(sDiff, relayFee, int32(feeEntry.BlockHeight), cfg.poolFees, cfg.netParams) + minFee := txrules.StakePoolTicketFee(sDiff, relayFee, int32(feeEntry.BlockHeight), cfg.VSPFee, cfg.netParams.Params) if feeAmount < minFee { fmt.Printf("too cheap: %v %v", feeAmount, minFee) c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("dont get cheap on me, dodgson (sent:%v required:%v)", feeAmount, minFee)) diff --git a/params.go b/params.go new file mode 100644 index 0000000..34273cf --- /dev/null +++ b/params.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/decred/dcrd/chaincfg/v3" +) + +type netParams struct { + *chaincfg.Params + DcrdRPCServerPort string + WalletRPCServerPort string +} + +var mainNetParams = netParams{ + Params: chaincfg.MainNetParams(), + DcrdRPCServerPort: "9109", + WalletRPCServerPort: "9111", +} + +var testNet3Params = netParams{ + Params: chaincfg.TestNet3Params(), + DcrdRPCServerPort: "19109", + WalletRPCServerPort: "19111", +} + +var simNetParams = netParams{ + Params: chaincfg.SimNetParams(), + DcrdRPCServerPort: "19556", + WalletRPCServerPort: "19558", +} diff --git a/run_tests.sh b/run_tests.sh index 89bc4a2..a9c092a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -24,7 +24,6 @@ golangci-lint run --disable-all --deadline=10m \ --enable=gosimple \ --enable=unconvert \ --enable=ineffassign \ - --enable=staticcheck \ --enable=structcheck \ --enable=goimports \ --enable=misspell \