diff --git a/cmd/serve.go b/cmd/serve.go index e824d10..6435362 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -1,4 +1,4 @@ -// Copyright © 2020 Vulcanize, Inc +// Copyright © 2019 Vulcanize, Inc // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by @@ -17,35 +17,130 @@ package cmd import ( "fmt" + "os" + "os/signal" + "sync" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/statediff" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/viper" + + sd "github.com/vulcanize/eth-statediff-service/pkg" ) // serveCmd represents the serve command var serveCmd = &cobra.Command{ Use: "serve", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: + Short: "Standup a standalone statediffing RPC service on top of leveldb", + Long: `Usage -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, +./eth-statediff-service serve --config={path to toml config file}`, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("serve called") + subCommand = cmd.CalledAs() + logWithCommand = *logrus.WithField("SubCommand", subCommand) + serve() }, } +func serve() { + logWithCommand.Info("starting statediff RPC service") + + // load params + path := viper.GetString("leveldb.path") + ancientPath := viper.GetString("leveldb.ancient") + if path == "" || ancientPath == "" { + logWithCommand.Fatal("require a valid eth leveldb primary datastore path and ancient datastore path") + } + chainID := viper.GetUint64("eth.chainID") + config, err := chainConfig(chainID) + if err != nil { + logWithCommand.Fatal(err) + } + + // create leveldb reader + logWithCommand.Info("creating leveldb reader") + lvlDBReader, err := sd.NewLvlDBReader(path, ancientPath, config) + if err != nil { + logWithCommand.Fatal(err) + } + + // create statediff service + logWithCommand.Info("creating statediff service") + statediffService, err := sd.NewStateDiffService(lvlDBReader) + if err != nil { + logWithCommand.Fatal(err) + } + + // start service and servers + logWithCommand.Info("starting statediff service") + wg := new(sync.WaitGroup) + go statediffService.Loop(wg) + logWithCommand.Info("starting rpc servers") + if err := startServers(statediffService); err != nil { + logWithCommand.Fatal(err) + } + + // clean shutdown + shutdown := make(chan os.Signal) + signal.Notify(shutdown, os.Interrupt) + <-shutdown + logWithCommand.Info("received interrupt signal, shutting down") + statediffService.Stop() + wg.Wait() +} + func init() { rootCmd.AddCommand(serveCmd) - // Here you will define your flags and configuration settings. + serveCmd.PersistentFlags().String("leveldb-path", "", "path to primary datastore") + serveCmd.PersistentFlags().String("ancient-path", "", "path to ancient datastore") + serveCmd.PersistentFlags().Uint("chain-id", 1, "ethereum chain id (mainnet = 1)") + serveCmd.PersistentFlags().String("http-path", "", "vdb server http path") + serveCmd.PersistentFlags().String("ipc-path", "", "vdb server ipc path") - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // serveCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + viper.BindPFlag("leveldb.path", serveCmd.PersistentFlags().Lookup("leveldb-path")) + viper.BindPFlag("leveldb.ancient", serveCmd.PersistentFlags().Lookup("ancient-path")) + viper.BindPFlag("eth.chainID", serveCmd.PersistentFlags().Lookup("chain-id")) + viper.BindPFlag("server.httpPath", serveCmd.PersistentFlags().Lookup("http-path")) + viper.BindPFlag("server.ipcPath", serveCmd.PersistentFlags().Lookup("ipc-path")) +} + +func startServers(serv sd.IService) error { + ipcPath := viper.GetString("server.ipcPath") + httpPath := viper.GetString("server.httpPath") + if ipcPath == "" && httpPath == "" { + logWithCommand.Fatal("need an ipc path and/or an http path") + } + if ipcPath != "" { + logWithCommand.Debug("starting up IPC server") + _, _, err := rpc.StartIPCEndpoint(ipcPath, serv.APIs()) + if err != nil { + return err + } + } + if httpPath != "" { + logWithCommand.Debug("starting up HTTP server") + if _, _, err := rpc.StartHTTPEndpoint(httpPath, serv.APIs(), []string{statediff.APIName}, nil, nil, rpc.HTTPTimeouts{}); err != nil { + return err + } + } + return nil +} + +func chainConfig(chainID uint64) (*params.ChainConfig, error) { + switch chainID { + case 1: + return params.MainnetChainConfig, nil + case 3: + return params.TestnetChainConfig, nil // Ropsten + case 4: + return params.RinkebyChainConfig, nil + case 5: + return params.GoerliChainConfig, nil + default: + return nil, fmt.Errorf("chain config for chainid %d not available", chainID) + } }