package server import ( "fmt" "log" "os" "strconv" "strings" "github.com/akamensky/argparse" pb "github.com/lbryio/herald.go/protobuf/go" "github.com/lbryio/lbcd/chaincfg" ) const ( ServeCmd = iota SearchCmd = iota DBCmd = iota ) // Args struct contains the arguments to the hub server. type Args struct { CmdType int Host string Port int DBPath string Chain *string EsHost string EsPort int PrometheusPort int NotifierPort int JSONRPCPort int JSONRPCHTTPPort int MaxSessions int SessionTimeout int EsIndex string RefreshDelta int CacheTTL int PeerFile string Banner *string Country string BlockingChannelIds []string FilteringChannelIds []string GenesisHash string ServerVersion string ProtocolMin string ProtocolMax string ServerDescription string PaymentAddress string DonationAddress string DailyFee string Debug bool DisableEs bool DisableLoadPeers bool DisableStartPrometheus bool DisableStartUDP bool DisableWritePeers bool DisableFederation bool DisableRocksDBRefresh bool DisableResolve bool DisableBlockingAndFiltering bool DisableStartNotifier bool DisableStartJSONRPC bool } const ( DefaultHost = "0.0.0.0" DefaultPort = 50051 DefaultDBPath = "/mnt/d/data/snapshot_1072108/lbry-rocksdb/" // FIXME DefaultEsHost = "http://localhost" DefaultEsIndex = "claims" DefaultEsPort = 9200 DefaultPrometheusPort = 2112 DefaultNotifierPort = 18080 DefaultJSONRPCPort = 50001 DefaultJSONRPCHTTPPort = 50002 DefaultMaxSessions = 10000 DefaultSessionTimeout = 300 DefaultRefreshDelta = 5 DefaultCacheTTL = 5 DefaultPeerFile = "peers.txt" DefaultBannerFile = "" DefaultCountry = "US" HUB_PROTOCOL_VERSION = "0.107.0" PROTOCOL_MIN = "0.54.0" PROTOCOL_MAX = "0.199.0" DefaultServerDescription = "Herald" DefaultPaymentAddress = "" DefaultDonationAddress = "" DefaultDailyFee = "1.0" DefaultDisableLoadPeers = false DefaultDisableStartPrometheus = false DefaultDisableStartUDP = false DefaultDisableWritePeers = false DefaultDisableFederation = false DefaultDisableRockDBRefresh = false DefaultDisableResolve = false DefaultDisableBlockingAndFiltering = false DisableStartNotifier = false DisableStartJSONRPC = false ) var ( DefaultBlockingChannelIds = []string{} DefaultFilteringChannelIds = []string{} ) func loadBanner(bannerFile *string, serverVersion string) *string { var banner string data, err := os.ReadFile(*bannerFile) if err != nil { banner = fmt.Sprintf("You are connected to an %s server.", serverVersion) } else { banner = string(data) } /* banner := os.Getenv("BANNER") if banner == "" { return nil } */ return &banner } // MakeDefaultArgs creates a default set of arguments for testing the server. func MakeDefaultTestArgs() *Args { args := &Args{ CmdType: ServeCmd, Host: DefaultHost, Port: DefaultPort, DBPath: DefaultDBPath, EsHost: DefaultEsHost, EsPort: DefaultEsPort, PrometheusPort: DefaultPrometheusPort, NotifierPort: DefaultNotifierPort, JSONRPCPort: DefaultJSONRPCPort, JSONRPCHTTPPort: DefaultJSONRPCHTTPPort, MaxSessions: DefaultMaxSessions, SessionTimeout: DefaultSessionTimeout, EsIndex: DefaultEsIndex, RefreshDelta: DefaultRefreshDelta, CacheTTL: DefaultCacheTTL, PeerFile: DefaultPeerFile, Banner: nil, Country: DefaultCountry, GenesisHash: chaincfg.TestNet3Params.GenesisHash.String(), ServerVersion: HUB_PROTOCOL_VERSION, ProtocolMin: PROTOCOL_MIN, ProtocolMax: PROTOCOL_MAX, ServerDescription: DefaultServerDescription, PaymentAddress: DefaultPaymentAddress, DonationAddress: DefaultDonationAddress, DailyFee: DefaultDailyFee, DisableEs: true, Debug: true, DisableLoadPeers: true, DisableStartPrometheus: true, DisableStartUDP: true, DisableWritePeers: true, DisableRocksDBRefresh: true, DisableResolve: true, DisableBlockingAndFiltering: true, DisableStartNotifier: true, DisableStartJSONRPC: true, } return args } // GetEnvironment takes the environment variables as an array of strings // and a getkeyval function to turn it into a map. func GetEnvironment(data []string, getkeyval func(item string) (key, val string)) map[string]string { items := make(map[string]string) for _, item := range data { key, val := getkeyval(item) items[key] = val } return items } // GetEnvironmentStandard gets the environment variables as a map. func GetEnvironmentStandard() map[string]string { return GetEnvironment(os.Environ(), func(item string) (key, val string) { splits := strings.Split(item, "=") key = splits[0] val = splits[1] return }) } // ParseArgs parses the command line arguments when started the hub server. func ParseArgs(searchRequest *pb.SearchRequest) *Args { environment := GetEnvironmentStandard() parser := argparse.NewParser("herald", "herald server and client") serveCmd := parser.NewCommand("serve", "start the hub server") searchCmd := parser.NewCommand("search", "claim search") dbCmd := parser.NewCommand("db", "db testing") validatePort := func(arg []string) error { _, err := strconv.ParseUint(arg[0], 10, 16) return err } // main server config arguments host := parser.String("", "rpchost", &argparse.Options{Required: false, Help: "RPC host", Default: DefaultHost}) port := parser.Int("", "rpcport", &argparse.Options{Required: false, Help: "RPC port", Validate: validatePort, Default: DefaultPort}) dbPath := parser.String("", "db-path", &argparse.Options{Required: false, Help: "RocksDB path", Default: DefaultDBPath}) chain := parser.Selector("", "chain", []string{chaincfg.MainNetParams.Name, chaincfg.TestNet3Params.Name, chaincfg.RegressionNetParams.Name, "testnet"}, &argparse.Options{Required: false, Help: "Which chain to use, default is 'mainnet'. Values 'regtest' and 'testnet' are for testing", Default: chaincfg.MainNetParams.Name}) esHost := parser.String("", "eshost", &argparse.Options{Required: false, Help: "elasticsearch host", Default: DefaultEsHost}) esPort := parser.Int("", "esport", &argparse.Options{Required: false, Help: "elasticsearch port", Default: DefaultEsPort}) prometheusPort := parser.Int("", "prometheus-port", &argparse.Options{Required: false, Help: "prometheus port", Default: DefaultPrometheusPort}) notifierPort := parser.Int("", "notifier-port", &argparse.Options{Required: false, Help: "notifier port", Default: DefaultNotifierPort}) jsonRPCPort := parser.Int("", "json-rpc-port", &argparse.Options{Required: false, Help: "JSON RPC port", Validate: validatePort, Default: DefaultJSONRPCPort}) jsonRPCHTTPPort := parser.Int("", "json-rpc-http-port", &argparse.Options{Required: false, Help: "JSON RPC over HTTP port", Validate: validatePort, Default: DefaultJSONRPCHTTPPort}) maxSessions := parser.Int("", "max-sessions", &argparse.Options{Required: false, Help: "Maximum number of electrum clients that can be connected", Default: DefaultMaxSessions}) sessionTimeout := parser.Int("", "session-timeout", &argparse.Options{Required: false, Help: "Session inactivity timeout (seconds)", Default: DefaultSessionTimeout}) esIndex := parser.String("", "esindex", &argparse.Options{Required: false, Help: "elasticsearch index name", Default: DefaultEsIndex}) refreshDelta := parser.Int("", "refresh-delta", &argparse.Options{Required: false, Help: "elasticsearch index refresh delta in seconds", Default: DefaultRefreshDelta}) cacheTTL := parser.Int("", "cachettl", &argparse.Options{Required: false, Help: "Cache TTL in minutes", Default: DefaultCacheTTL}) peerFile := parser.String("", "peerfile", &argparse.Options{Required: false, Help: "Initial peer file for federation", Default: DefaultPeerFile}) bannerFile := parser.String("", "bannerfile", &argparse.Options{Required: false, Help: "Banner file server.banner", Default: DefaultBannerFile}) country := parser.String("", "country", &argparse.Options{Required: false, Help: "Country this node is running in. Default US.", Default: DefaultCountry}) blockingChannelIds := parser.StringList("", "blocking-channel-ids", &argparse.Options{Required: false, Help: "Blocking channel ids", Default: DefaultBlockingChannelIds}) filteringChannelIds := parser.StringList("", "filtering-channel-ids", &argparse.Options{Required: false, Help: "Filtering channel ids", Default: DefaultFilteringChannelIds}) // arguments for server features serverDescription := parser.String("", "server-description", &argparse.Options{Required: false, Help: "Server description", Default: DefaultServerDescription}) paymentAddress := parser.String("", "payment-address", &argparse.Options{Required: false, Help: "Payment address", Default: DefaultPaymentAddress}) donationAddress := parser.String("", "donation-address", &argparse.Options{Required: false, Help: "Donation address", Default: DefaultDonationAddress}) dailyFee := parser.String("", "daily-fee", &argparse.Options{Required: false, Help: "Daily fee", Default: DefaultDailyFee}) // flags for disabling features debug := parser.Flag("", "debug", &argparse.Options{Required: false, Help: "enable debug logging", Default: false}) disableEs := parser.Flag("", "disable-es", &argparse.Options{Required: false, Help: "Disable elastic search, for running/testing independently", Default: false}) disableLoadPeers := parser.Flag("", "disable-load-peers", &argparse.Options{Required: false, Help: "Disable load peers from disk at startup", Default: DefaultDisableLoadPeers}) disableStartPrometheus := parser.Flag("", "disable-start-prometheus", &argparse.Options{Required: false, Help: "Disable start prometheus server", Default: DefaultDisableStartPrometheus}) disableStartUdp := parser.Flag("", "disable-start-udp", &argparse.Options{Required: false, Help: "Disable start UDP ping server", Default: DefaultDisableStartUDP}) disableWritePeers := parser.Flag("", "disable-write-peers", &argparse.Options{Required: false, Help: "Disable write peer to disk as we learn about them", Default: DefaultDisableWritePeers}) disableFederation := parser.Flag("", "disable-federation", &argparse.Options{Required: false, Help: "Disable server federation", Default: DefaultDisableFederation}) disableRocksDBRefresh := parser.Flag("", "disable-rocksdb-refresh", &argparse.Options{Required: false, Help: "Disable rocksdb refreshing", Default: DefaultDisableRockDBRefresh}) disableResolve := parser.Flag("", "disable-resolve", &argparse.Options{Required: false, Help: "Disable resolve endpoint (and rocksdb loading)", Default: DefaultDisableRockDBRefresh}) disableBlockingAndFiltering := parser.Flag("", "disable-blocking-and-filtering", &argparse.Options{Required: false, Help: "Disable blocking and filtering of channels and streams", Default: DefaultDisableBlockingAndFiltering}) disableStartNotifier := parser.Flag("", "disable-start-notifier", &argparse.Options{Required: false, Help: "Disable start notifier", Default: DisableStartNotifier}) disableStartJSONRPC := parser.Flag("", "disable-start-jsonrpc", &argparse.Options{Required: false, Help: "Disable start jsonrpc endpoint", Default: DisableStartJSONRPC}) // search command arguments text := parser.String("", "text", &argparse.Options{Required: false, Help: "text query"}) name := parser.String("", "name", &argparse.Options{Required: false, Help: "name"}) claimType := parser.String("", "claim_type", &argparse.Options{Required: false, Help: "claim_type"}) id := parser.String("", "id", &argparse.Options{Required: false, Help: "id"}) author := parser.String("", "author", &argparse.Options{Required: false, Help: "author"}) title := parser.String("", "title", &argparse.Options{Required: false, Help: "title"}) description := parser.String("", "description", &argparse.Options{Required: false, Help: "description"}) channelId := parser.String("", "channel_id", &argparse.Options{Required: false, Help: "channel id"}) channelIds := parser.StringList("", "channel_ids", &argparse.Options{Required: false, Help: "channel ids"}) // Now parse the arguments err := parser.Parse(os.Args) if err != nil { log.Fatalln(parser.Usage(err)) } // Use default JSON RPC port only if *neither* JSON RPC arg is specified. if *jsonRPCPort == 0 && *jsonRPCHTTPPort == 0 { *jsonRPCPort = DefaultJSONRPCPort } banner := loadBanner(bannerFile, HUB_PROTOCOL_VERSION) args := &Args{ CmdType: SearchCmd, Host: *host, Port: *port, DBPath: *dbPath, Chain: chain, EsHost: *esHost, EsPort: *esPort, PrometheusPort: *prometheusPort, NotifierPort: *notifierPort, JSONRPCPort: *jsonRPCPort, JSONRPCHTTPPort: *jsonRPCHTTPPort, MaxSessions: *maxSessions, SessionTimeout: *sessionTimeout, EsIndex: *esIndex, RefreshDelta: *refreshDelta, CacheTTL: *cacheTTL, PeerFile: *peerFile, Banner: banner, Country: *country, BlockingChannelIds: *blockingChannelIds, FilteringChannelIds: *filteringChannelIds, GenesisHash: "", ServerVersion: HUB_PROTOCOL_VERSION, ProtocolMin: PROTOCOL_MIN, ProtocolMax: PROTOCOL_MAX, ServerDescription: *serverDescription, PaymentAddress: *paymentAddress, DonationAddress: *donationAddress, DailyFee: *dailyFee, Debug: *debug, DisableEs: *disableEs, DisableLoadPeers: *disableLoadPeers, DisableStartPrometheus: *disableStartPrometheus, DisableStartUDP: *disableStartUdp, DisableWritePeers: *disableWritePeers, DisableFederation: *disableFederation, DisableRocksDBRefresh: *disableRocksDBRefresh, DisableResolve: *disableResolve, DisableBlockingAndFiltering: *disableBlockingAndFiltering, DisableStartNotifier: *disableStartNotifier, DisableStartJSONRPC: *disableStartJSONRPC, } if esHost, ok := environment["ELASTIC_HOST"]; ok { args.EsHost = esHost } if !strings.HasPrefix(args.EsHost, "http") { args.EsHost = "http://" + args.EsHost } if esPort, ok := environment["ELASTIC_PORT"]; ok { args.EsPort, err = strconv.Atoi(esPort) if err != nil { log.Fatal(err) } } if prometheusPort, ok := environment["GOHUB_PROMETHEUS_PORT"]; ok { args.PrometheusPort, err = strconv.Atoi(prometheusPort) if err != nil { log.Fatal(err) } } /* Verify no invalid argument combinations */ if len(*channelIds) > 0 && *channelId != "" { log.Fatal("Cannot specify both channel_id and channel_ids") } if serveCmd.Happened() { args.CmdType = ServeCmd } else if searchCmd.Happened() { args.CmdType = SearchCmd } else if dbCmd.Happened() { args.CmdType = DBCmd } if *text != "" { searchRequest.Text = *text } if *name != "" { searchRequest.ClaimName = *name } if *claimType != "" { searchRequest.ClaimType = []string{*claimType} } if *id != "" { searchRequest.ClaimId = &pb.InvertibleField{Invert: false, Value: []string{*id}} } if *author != "" { searchRequest.Author = *author } if *title != "" { searchRequest.Title = *title } if *description != "" { searchRequest.Description = *description } if *channelId != "" { searchRequest.ChannelId = &pb.InvertibleField{Invert: false, Value: []string{*channelId}} } if len(*channelIds) > 0 { searchRequest.ChannelId = &pb.InvertibleField{Invert: false, Value: *channelIds} } return args }