Skip to content

Commit

Permalink
kwild,jsonrpc: add app.rpc-req-limit / cfg.AppCfg.RPCMaxReqSize (#936)
Browse files Browse the repository at this point in the history
This adds a configurable request size limit to kwild.

In toml: app.rpc_req_limit
With cli flag: --app.rpc-req-limit

The default is somewhat arbitrarily 4,200,000 bytes.

If the network's genesis config sets max transaction size larger
than this limit, kwild warns on startup.
  • Loading branch information
jchappelow authored Aug 27, 2024
1 parent 7ff2536 commit a0ac88c
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 10 deletions.
3 changes: 3 additions & 0 deletions cmd/kwil-admin/nodecfg/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ rpc_timeout = "{{ .AppCfg.RPCTimeout }}"
# Timeout on database reads initiated by the user RPC service
db_read_timeout = "{{ .AppCfg.ReadTxTimeout }}"
# RPC request size limit in bytes
rpc_req_limit = {{ .AppCfg.RPCMaxReqSize }}
# List of Extension endpoints to be enabled ex: ["localhost:50052", "169.198.102.34:50053"]
extension_endpoints = {{arrayFormatter .AppCfg.ExtensionEndpoints}}
Expand Down
2 changes: 2 additions & 0 deletions cmd/kwild/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type AppConfig struct {
DBName string `mapstructure:"pg_db_name"`

RPCTimeout Duration `mapstructure:"rpc_timeout"`
RPCMaxReqSize int `mapstructure:"rpc_req_limit"`
ReadTxTimeout Duration `mapstructure:"db_read_timeout"`
ExtensionEndpoints []string `mapstructure:"extension_endpoints"`
AdminRPCPass string `mapstructure:"admin_pass"`
Expand Down Expand Up @@ -580,6 +581,7 @@ func DefaultConfig() *KwildConfig {
DBUser: "kwild",
DBName: "kwild",
RPCTimeout: Duration(45 * time.Second),
RPCMaxReqSize: 4_200_000,
ReadTxTimeout: Duration(5 * time.Second),
Extensions: make(map[string]map[string]string),
Snapshots: SnapshotConfig{
Expand Down
3 changes: 3 additions & 0 deletions cmd/kwild/config/default_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ rpc_timeout = "45s"
# Timeout on database reads initiated by the user RPC service
db_read_timeout = "5s"

# RPC request size limit in bytes
rpc_req_limit = 4200000

# List of Extension endpoints to be enabled ex: ["localhost:50052", "169.198.102.34:50053"]
extension_endpoints = []

Expand Down
1 change: 1 addition & 0 deletions cmd/kwild/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func AddConfigFlags(flagSet *pflag.FlagSet, cfg *KwildConfig) {
flagSet.StringVar(&cfg.AppCfg.ProfileFile, "app.profile-file", cfg.AppCfg.ProfileFile, "kwild profile output file path (e.g. cpu.pprof)")

flagSet.Var(&cfg.AppCfg.RPCTimeout, "app.rpc-timeout", "timeout for RPC requests (through reading the request, handling the request, and sending the response)")
flagSet.IntVar(&cfg.AppCfg.RPCMaxReqSize, "app.rpc-req-limit", cfg.AppCfg.RPCMaxReqSize, "RPC request size limit")
flagSet.Var(&cfg.AppCfg.ReadTxTimeout, "app.db-read-timeout", "timeout for database reads initiated by RPC requests")

// Extension endpoints flags
Expand Down
3 changes: 3 additions & 0 deletions cmd/kwild/config/test_data/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ rpc_timeout = "45s"
# Timeout on database reads initiated by the user RPC service
db_read_timeout = "5s"

# RPC request size limit in bytes
rpc_req_limit = 4200000

# List of Extension endpoints to be enabled ex: ["localhost:50052", "169.198.102.34:50053"]
extension_endpoints = ["localhost:50052", "localhost:50053", "localhost:50054"]

Expand Down
6 changes: 6 additions & 0 deletions cmd/kwild/server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,16 @@ func buildServer(d *coreDependencies, closers *closeFuncs) *Server {
rpcSvcLogger := increaseLogLevel("user-json-svc", &d.log, d.cfg.Logging.RPCLevel)
rpcServerLogger := increaseLogLevel("user-jsonrpc-server", &d.log, d.cfg.Logging.RPCLevel)

if d.cfg.AppCfg.RPCMaxReqSize < d.cfg.ChainCfg.Mempool.MaxTxBytes {
d.log.Warnf("RPC request size limit (%d) is less than maximium transaction size (%d)",
d.cfg.AppCfg.RPCMaxReqSize, d.cfg.ChainCfg.Mempool.MaxTxBytes)
}

jsonRPCTxSvc := usersvc.NewService(db, e, wrappedCmtClient, txApp, abciApp, migrator,
*rpcSvcLogger, usersvc.WithReadTxTimeout(time.Duration(d.cfg.AppCfg.ReadTxTimeout)))
jsonRPCServer, err := rpcserver.NewServer(d.cfg.AppCfg.JSONRPCListenAddress,
*rpcServerLogger, rpcserver.WithTimeout(time.Duration(d.cfg.AppCfg.RPCTimeout)),
rpcserver.WithReqSizeLimit(d.cfg.AppCfg.RPCMaxReqSize),
rpcserver.WithCORS(), rpcserver.WithServerInfo(&usersvc.SpecInfo))
if err != nil {
failBuild(err, "unable to create json-rpc server")
Expand Down
29 changes: 19 additions & 10 deletions internal/services/jsonrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type serverConfig struct {
timeout time.Duration
enableCORS bool
specInfo *openrpc.Info
reqSzLimit int
}

type Opt func(*serverConfig)
Expand Down Expand Up @@ -83,6 +84,13 @@ func WithServerInfo(specInfo *openrpc.Info) Opt {
}
}

// WithReqSizeLimit sets the request size limit in bytes.
func WithReqSizeLimit(sz int) Opt {
return func(c *serverConfig) {
c.reqSzLimit = sz
}
}

// WithTimeout specifies a timeout on all RPC requests that when exceeded will
// cancel the request.
func WithTimeout(timeout time.Duration) Opt {
Expand Down Expand Up @@ -124,8 +132,12 @@ func checkAddr(addr string) (string, bool, error) {
return net.JoinHostPort(host, port), false, nil
}

// defaultWriteTimeout is the default WriteTimeout for the http.Server.
const defaultWriteTimeout = 45 * time.Second
const (
// defaultWriteTimeout is the default WriteTimeout for the http.Server.
defaultWriteTimeout = 45 * time.Second
// 4 MiB + overhead request size limit
defaultSzLimit = 1<<22 + 1<<14
)

var (
defaultSpecInfo = &openrpc.Info{
Expand Down Expand Up @@ -160,8 +172,9 @@ func NewServer(addr string, log log.Logger, opts ...Opt) (*Server, error) {
}

cfg := &serverConfig{
timeout: defaultWriteTimeout,
specInfo: defaultSpecInfo,
timeout: defaultWriteTimeout,
specInfo: defaultSpecInfo,
reqSzLimit: defaultSzLimit,
}
for _, opt := range opts {
opt(cfg)
Expand Down Expand Up @@ -202,7 +215,7 @@ func NewServer(addr string, log log.Logger, opts ...Opt) (*Server, error) {

var h http.Handler
h = http.HandlerFunc(s.handlerV1)
h = http.MaxBytesHandler(h, 1<<22)
h = http.MaxBytesHandler(h, int64(cfg.reqSzLimit))
// amazingly, exceeding the server's write timeout does not cancel request
// contexts: https://github.com/golang/go/issues/59602
// So, we add a timeout to the Request's context.
Expand Down Expand Up @@ -385,9 +398,6 @@ func (s *Server) ServeOn(ctx context.Context, ln net.Listener) error {
return err
}

// 4 MiB request size limit
const szLimit = 1 << 22

// handlerV1 handles all https json requests. It is the http.Handler for the
// JSON-RPC service mounted on the "/rpc/v1" endpoint. The endpoint is the same
// for all methods since this is JSON-RPC with a "method" field of the JSON
Expand All @@ -413,8 +423,7 @@ func (s *Server) handlerV1(w http.ResponseWriter, r *http.Request) {
}
}

bodyReader := http.MaxBytesReader(w, r.Body, szLimit)
body, err := io.ReadAll(bodyReader)
body, err := io.ReadAll(r.Body)
r.Body.Close()
if err != nil {
http.Error(w, "error reading request body", http.StatusBadRequest)
Expand Down

0 comments on commit a0ac88c

Please sign in to comment.