Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api, config: modular server #8210

Closed
wants to merge 11 commits into from
109 changes: 65 additions & 44 deletions server/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@ package api

import (
"fmt"
"net"
"net/http"
"strings"
"time"

"github.com/gogo/gateway"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/tendermint/tendermint/libs/log"
tmrpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server/config"
grpcservice "github.com/cosmos/cosmos-sdk/server/services/grpc"
tmservice "github.com/cosmos/cosmos-sdk/server/services/tendermint"
"github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/telemetry"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/cosmos/cosmos-sdk/types/rest"
Expand All @@ -24,15 +23,17 @@ import (
_ "github.com/cosmos/cosmos-sdk/client/docs/statik"
)

// Server defines the server's API interface.
type Server struct {
var _ types.Server = &BaseServer{}

// BaseServer defines the SDK server's.
type BaseServer struct {
services map[string]types.Service
config config.ServerConfig
metrics *telemetry.Metrics

Router *mux.Router
GRPCGatewayRouter *runtime.ServeMux
ClientCtx client.Context

logger log.Logger
metrics *telemetry.Metrics
listener net.Listener
}

// CustomGRPCHeaderMatcher for mapping request headers to
Expand All @@ -49,7 +50,8 @@ func CustomGRPCHeaderMatcher(key string) (string, bool) {
}
}

func New(clientCtx client.Context, logger log.Logger) *Server {
// New creates the default SDK server instance.
func New(clientCtx client.Context, logger log.Logger, cfg config.ServerConfig) *BaseServer {
// The default JSON marshaller used by the gRPC-Gateway is unable to marshal non-nullable non-scalar fields.
// Using the gogo/gateway package with the gRPC-Gateway WithMarshaler option fixes the scalar field marshalling issue.
marshalerOption := &gateway.JSONPb{
Expand All @@ -59,10 +61,19 @@ func New(clientCtx client.Context, logger log.Logger) *Server {
AnyResolver: clientCtx.InterfaceRegistry,
}

return &Server{
Router: mux.NewRouter(),
router := mux.NewRouter()
tmsrv := tmservice.NewService(logger, router)
grpcsrv := grpcservice.NewService("")

services := make(map[string]types.Service)
services[tmsrv.Name()] = tmsrv
services[grpcsrv.Name()] = grpcsrv

return &BaseServer{
services: services,
config: cfg,
Router: router,
ClientCtx: clientCtx,
logger: logger,
GRPCGatewayRouter: runtime.NewServeMux(
// Custom marshaler option is required for gogo proto
runtime.WithMarshalerOption(runtime.MIMEWildcard, marshalerOption),
Expand All @@ -78,13 +89,23 @@ func New(clientCtx client.Context, logger log.Logger) *Server {
}
}

// Start starts the API server. Internally, the API server leverages Tendermint's
// JSON RPC server. Configuration options are provided via config.APIConfig
// and are delegated to the Tendermint JSON RPC server. The process is
// non-blocking, so an external signal handler must be used.
func (s *Server) Start(cfg config.Config) error {
if cfg.Telemetry.Enabled {
m, err := telemetry.New(cfg.Telemetry)
// GetService implements the Server interface.
func (s *BaseServer) GetService(name string) types.Service {
service, _ := s.services[name]
return service
}

// RegisterServices implements the Server interface.
func (s *BaseServer) RegisterServices() error {
for name, service := range s.services {
if !service.RegisterRoutes() {
return fmt.Errorf("failed to register routes for service %s", name)
}
}

sdkCfg := s.config.GetSDKConfig()
if sdkCfg.Telemetry.Enabled {
m, err := telemetry.New(sdkCfg.Telemetry)
if err != nil {
return err
}
Expand All @@ -93,41 +114,41 @@ func (s *Server) Start(cfg config.Config) error {
s.registerMetrics()
}

tmCfg := tmrpcserver.DefaultConfig()
tmCfg.MaxOpenConnections = int(cfg.API.MaxOpenConnections)
tmCfg.ReadTimeout = time.Duration(cfg.API.RPCReadTimeout) * time.Second
tmCfg.WriteTimeout = time.Duration(cfg.API.RPCWriteTimeout) * time.Second
tmCfg.MaxBodyBytes = int64(cfg.API.RPCMaxBodyBytes)

listener, err := tmrpcserver.Listen(cfg.API.Address, tmCfg)
if err != nil {
return err
}

s.registerGRPCGatewayRoutes()

s.listener = listener
var h http.Handler = s.Router
return nil
}

if cfg.API.EnableUnsafeCORS {
allowAllCORS := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))
return tmrpcserver.Serve(s.listener, allowAllCORS(h), s.logger, tmCfg)
// Start starts the API server. Internally, the API server leverages Tendermint's
// JSON RPC server. Configuration options are provided via config.APIConfig
// and are delegated to the Tendermint JSON RPC server. The process is
// non-blocking, so an external signal handler must be used.
func (s *BaseServer) Start() error {
for name, service := range s.services {
if err := service.Start(s.config); err != nil {
return fmt.Errorf("service %s start failed: %w", name, err)
}
}

s.logger.Info("starting API server...")
return tmrpcserver.Serve(s.listener, s.Router, s.logger, tmCfg)
return nil
}

// Close closes the API server.
func (s *Server) Close() error {
return s.listener.Close()
// Stop implements the Server interface.
func (s *BaseServer) Stop() error {
for name, service := range s.services {
if err := service.Stop(); err != nil {
return fmt.Errorf("service %s stop failed: %w", name, err)
}
}

return nil
}

func (s *Server) registerGRPCGatewayRoutes() {
func (s *BaseServer) registerGRPCGatewayRoutes() {
s.Router.PathPrefix("/").Handler(s.GRPCGatewayRouter)
}

func (s *Server) registerMetrics() {
func (s *BaseServer) registerMetrics() {
metricsHandler := func(w http.ResponseWriter, r *http.Request) {
format := strings.TrimSpace(r.FormValue("format"))

Expand Down
30 changes: 26 additions & 4 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const (
DefaultGRPCAddress = "0.0.0.0:9090"
)

type ServerConfig interface {
GetSDKConfig() *Config
}

// BaseConfig defines the server's basic configuration
type BaseConfig struct {
// The minimum gas prices a validator is willing to accept for processing a
Expand Down Expand Up @@ -118,6 +122,8 @@ type StateSyncConfig struct {
SnapshotKeepRecent uint32 `mapstructure:"snapshot-keep-recent"`
}

var _ ServerConfig = &Config{}

// Config defines the server's top level configuration
type Config struct {
BaseConfig `mapstructure:",squash"`
Expand All @@ -129,6 +135,11 @@ type Config struct {
StateSync StateSyncConfig `mapstructure:"state-sync"`
}

// GetSDKConfig implements the ServerConfig interface.
func (c *Config) GetSDKConfig() *Config {
return c
}

// SetMinGasPrices sets the validator's minimum gas prices.
func (c *Config) SetMinGasPrices(gasPrices sdk.DecCoins) {
c.MinGasPrices = gasPrices.String()
Expand Down Expand Up @@ -157,7 +168,7 @@ func (c *Config) GetMinGasPrices() sdk.DecCoins {
}

// DefaultConfig returns server's default configuration.
func DefaultConfig() *Config {
func DefaultConfig() ServerConfig {
return &Config{
BaseConfig: BaseConfig{
MinGasPrices: defaultMinGasPrices,
Expand Down Expand Up @@ -192,8 +203,17 @@ func DefaultConfig() *Config {
}
}

// GetConfig returns a fully parsed Config object.
func GetConfig(v *viper.Viper) Config {
// Generator creates a general purpose server configuration
type Generator func(cfg *Config) ServerConfig

// DefaultGenerator is the default generator for a configuration file that
// is passed to the served during its start.
func DefaultGenerator(cfg *Config) ServerConfig {
return cfg
}

// GetConfig returns a fully parsed ServerConfig object.
func GetConfig(v *viper.Viper, cfgGen Generator) ServerConfig {
globalLabelsRaw := v.Get("telemetry.global-labels").([]interface{})
globalLabels := make([][]string, 0, len(globalLabelsRaw))
for _, glr := range globalLabelsRaw {
Expand All @@ -203,7 +223,7 @@ func GetConfig(v *viper.Viper) Config {
}
}

return Config{
cfg := &Config{
BaseConfig: BaseConfig{
MinGasPrices: v.GetString("minimum-gas-prices"),
InterBlockCache: v.GetBool("inter-block-cache"),
Expand Down Expand Up @@ -244,4 +264,6 @@ func GetConfig(v *viper.Viper) Config {
SnapshotKeepRecent: v.GetUint32("state-sync.snapshot-keep-recent"),
},
}

return cfgGen(cfg)
}
4 changes: 2 additions & 2 deletions server/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import (
)

func TestDefaultConfig(t *testing.T) {
cfg := DefaultConfig()
cfg := DefaultConfig().GetSDKConfig()
require.True(t, cfg.GetMinGasPrices().IsZero())
}

func TestSetMinimumFees(t *testing.T) {
cfg := DefaultConfig()
cfg := DefaultConfig().GetSDKConfig()
cfg.SetMinGasPrices(sdk.DecCoins{sdk.NewInt64DecCoin("foo", 5)})
require.Equal(t, "5.000000000000000000foo", cfg.MinGasPrices)
}
15 changes: 9 additions & 6 deletions server/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
tmos "github.com/tendermint/tendermint/libs/os"
)

const defaultConfigTemplate = `# This is a TOML config file.
// DefaultConfigTemplate defines the standard config template for the application.
// It can be extended to allow for custom configurations.
var DefaultConfigTemplate = `# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml

###############################################################################
Expand Down Expand Up @@ -163,21 +165,22 @@ snapshot-interval = {{ .StateSync.SnapshotInterval }}
snapshot-keep-recent = {{ .StateSync.SnapshotKeepRecent }}
`

var configTemplate *template.Template
// ConfigTemplate is the template variable for the configuration toml file.
var ConfigTemplate *template.Template

func init() {
var err error

tmpl := template.New("appConfigFileTemplate")

if configTemplate, err = tmpl.Parse(defaultConfigTemplate); err != nil {
if ConfigTemplate, err = tmpl.Parse(DefaultConfigTemplate); err != nil {
panic(err)
}
}

// ParseConfig retrieves the default environment configuration for the
// application.
func ParseConfig(v *viper.Viper) (*Config, error) {
func ParseConfig(v *viper.Viper) (ServerConfig, error) {
conf := DefaultConfig()
err := v.Unmarshal(conf)

Expand All @@ -186,10 +189,10 @@ func ParseConfig(v *viper.Viper) (*Config, error) {

// WriteConfigFile renders config using the template and writes it to
// configFilePath.
func WriteConfigFile(configFilePath string, config *Config) {
func WriteConfigFile(configFilePath string, config ServerConfig) {
var buffer bytes.Buffer

if err := configTemplate.Execute(&buffer, config); err != nil {
if err := ConfigTemplate.Execute(&buffer, config); err != nil {
panic(err)
}

Expand Down
42 changes: 0 additions & 42 deletions server/grpc/server.go

This file was deleted.

File renamed without changes.
Loading