Added checks to RPC requests and introduced new flags to customise the parameters (#657)

* added a check to reject rpc requests with batch size > the one set using a newly added flag (rpcbatchlimit)

* added a check to reject rpc requests whose result size > the one set using a newly added flag (rpcreturndatalimit)

* updated the config files and docs
This commit is contained in:
Pratik Patil 2023-01-19 14:40:24 +05:30 committed by GitHub
parent 66c23d1eeb
commit e4dd2ee1ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 121 additions and 24 deletions

View file

@ -8,6 +8,8 @@ chain = "mainnet"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "/var/lib/bor/keystore" # keystore = "/var/lib/bor/keystore"
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
# gcmode = "full" # gcmode = "full"
# snapshot = true # snapshot = true

View file

@ -8,6 +8,8 @@ log-level = "INFO" # Set log level for the server
datadir = "var/lib/bor" # Path of the data directory to store information datadir = "var/lib/bor" # Path of the data directory to store information
ancient = "" # Data directory for ancient chain segments (default = inside chaindata) ancient = "" # Data directory for ancient chain segments (default = inside chaindata)
keystore = "" # Path of the directory where keystores are located keystore = "" # Path of the directory where keystores are located
"rpc.batchlimit" = 100 # Maximum number of messages in a batch (default=100, use 0 for no limits)
"rpc.returndatalimit" = 100000 # Maximum size (in bytes) a result of an rpc request could have (default=100000, use 0 for no limits)
syncmode = "full" # Blockchain sync mode (only "full" sync supported) syncmode = "full" # Blockchain sync mode (only "full" sync supported)
gcmode = "full" # Blockchain garbage collection mode ("full", "archive") gcmode = "full" # Blockchain garbage collection mode ("full", "archive")
snapshot = true # Enables the snapshot-database mode snapshot = true # Enables the snapshot-database mode

View file

@ -16,6 +16,10 @@ The ```bor server``` command runs the Bor client.
- ```keystore```: Path of the directory where keystores are located - ```keystore```: Path of the directory where keystores are located
- ```rpc.batchlimit```: Maximum number of messages in a batch (default=100, use 0 for no limits) (default: 100)
- ```rpc.returndatalimit```: Maximum size (in bytes) a result of an rpc request could have (default=100000, use 0 for no limits) (default: 100000)
- ```config```: File for the config file - ```config```: File for the config file
- ```syncmode```: Blockchain sync mode (only "full" sync supported) (default: full) - ```syncmode```: Blockchain sync mode (only "full" sync supported) (default: full)

View file

@ -317,6 +317,10 @@ func (b *EthAPIBackend) RPCGasCap() uint64 {
return b.eth.config.RPCGasCap return b.eth.config.RPCGasCap
} }
func (b *EthAPIBackend) RPCRpcReturnDataLimit() uint64 {
return b.eth.config.RPCReturnDataLimit
}
func (b *EthAPIBackend) RPCEVMTimeout() time.Duration { func (b *EthAPIBackend) RPCEVMTimeout() time.Duration {
return b.eth.config.RPCEVMTimeout return b.eth.config.RPCEVMTimeout
} }

View file

@ -94,11 +94,12 @@ var Defaults = Config{
GasPrice: big.NewInt(params.GWei), GasPrice: big.NewInt(params.GWei),
Recommit: 125 * time.Second, Recommit: 125 * time.Second,
}, },
TxPool: core.DefaultTxPoolConfig, TxPool: core.DefaultTxPoolConfig,
RPCGasCap: 50000000, RPCGasCap: 50000000,
RPCEVMTimeout: 5 * time.Second, RPCReturnDataLimit: 100000,
GPO: FullNodeGPO, RPCEVMTimeout: 5 * time.Second,
RPCTxFeeCap: 5, // 5 matic GPO: FullNodeGPO,
RPCTxFeeCap: 5, // 5 matic
} }
func init() { func init() {
@ -199,6 +200,9 @@ type Config struct {
// RPCGasCap is the global gas cap for eth-call variants. // RPCGasCap is the global gas cap for eth-call variants.
RPCGasCap uint64 RPCGasCap uint64
// Maximum size (in bytes) a result of an rpc request could have
RPCReturnDataLimit uint64
// RPCEVMTimeout is the global timeout for eth-call. // RPCEVMTimeout is the global timeout for eth-call.
RPCEVMTimeout time.Duration RPCEVMTimeout time.Duration

View file

@ -126,6 +126,10 @@ func (b *testBackend) RPCGasCap() uint64 {
return 25000000 return 25000000
} }
func (b *testBackend) RPCRpcReturnDataLimit() uint64 {
return 100000
}
func (b *testBackend) ChainConfig() *params.ChainConfig { func (b *testBackend) ChainConfig() *params.ChainConfig {
return b.chainConfig return b.chainConfig
} }

View file

@ -60,6 +60,12 @@ type Config struct {
// KeyStoreDir is the directory to store keystores // KeyStoreDir is the directory to store keystores
KeyStoreDir string `hcl:"keystore,optional" toml:"keystore,optional"` KeyStoreDir string `hcl:"keystore,optional" toml:"keystore,optional"`
// Maximum number of messages in a batch (default=100, use 0 for no limits)
RPCBatchLimit uint64 `hcl:"rpc.batchlimit,optional" toml:"rpc.batchlimit,optional"`
// Maximum size (in bytes) a result of an rpc request could have (default=100000, use 0 for no limits)
RPCReturnDataLimit uint64 `hcl:"rpc.returndatalimit,optional" toml:"rpc.returndatalimit,optional"`
// SyncMode selects the sync protocol // SyncMode selects the sync protocol
SyncMode string `hcl:"syncmode,optional" toml:"syncmode,optional"` SyncMode string `hcl:"syncmode,optional" toml:"syncmode,optional"`
@ -435,12 +441,14 @@ type DeveloperConfig struct {
func DefaultConfig() *Config { func DefaultConfig() *Config {
return &Config{ return &Config{
Chain: "mainnet", Chain: "mainnet",
Identity: Hostname(), Identity: Hostname(),
RequiredBlocks: map[string]string{}, RequiredBlocks: map[string]string{},
LogLevel: "INFO", LogLevel: "INFO",
DataDir: DefaultDataDir(), DataDir: DefaultDataDir(),
Ancient: "", Ancient: "",
RPCBatchLimit: 100,
RPCReturnDataLimit: 100000,
P2P: &P2PConfig{ P2P: &P2PConfig{
MaxPeers: 50, MaxPeers: 50,
MaxPendPeers: 50, MaxPendPeers: 50,
@ -936,6 +944,8 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (*
n.BorLogs = c.BorLogs n.BorLogs = c.BorLogs
n.DatabaseHandles = dbHandles n.DatabaseHandles = dbHandles
n.RPCReturnDataLimit = c.RPCReturnDataLimit
if c.Ancient != "" { if c.Ancient != "" {
n.DatabaseFreezer = c.Ancient n.DatabaseFreezer = c.Ancient
} }
@ -986,6 +996,7 @@ func (c *Config) buildNode() (*node.Config, error) {
WriteTimeout: c.JsonRPC.HttpTimeout.WriteTimeout, WriteTimeout: c.JsonRPC.HttpTimeout.WriteTimeout,
IdleTimeout: c.JsonRPC.HttpTimeout.IdleTimeout, IdleTimeout: c.JsonRPC.HttpTimeout.IdleTimeout,
}, },
RPCBatchLimit: c.RPCBatchLimit,
} }
// dev mode // dev mode

View file

@ -46,6 +46,18 @@ func (c *Command) Flags() *flagset.Flagset {
Usage: "Path of the directory where keystores are located", Usage: "Path of the directory where keystores are located",
Value: &c.cliConfig.KeyStoreDir, Value: &c.cliConfig.KeyStoreDir,
}) })
f.Uint64Flag(&flagset.Uint64Flag{
Name: "rpc.batchlimit",
Usage: "Maximum number of messages in a batch (default=100, use 0 for no limits)",
Value: &c.cliConfig.RPCBatchLimit,
Default: c.cliConfig.RPCBatchLimit,
})
f.Uint64Flag(&flagset.Uint64Flag{
Name: "rpc.returndatalimit",
Usage: "Maximum size (in bytes) a result of an rpc request could have (default=100000, use 0 for no limits)",
Value: &c.cliConfig.RPCReturnDataLimit,
Default: c.cliConfig.RPCReturnDataLimit,
})
f.StringFlag(&flagset.StringFlag{ f.StringFlag(&flagset.StringFlag{
Name: "config", Name: "config",
Usage: "File for the config file", Usage: "File for the config file",

View file

@ -1078,6 +1078,11 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, bl
if err != nil { if err != nil {
return nil, err return nil, err
} }
if int(s.b.RPCRpcReturnDataLimit()) > 0 && len(result.ReturnData) > int(s.b.RPCRpcReturnDataLimit()) {
return nil, fmt.Errorf("call returned result of length %d exceeding limit %d", len(result.ReturnData), int(s.b.RPCRpcReturnDataLimit()))
}
// If the result contains a revert reason, try to unpack and return it. // If the result contains a revert reason, try to unpack and return it.
if len(result.Revert()) > 0 { if len(result.Revert()) > 0 {
return nil, newRevertError(result) return nil, newRevertError(result)

View file

@ -48,10 +48,11 @@ type Backend interface {
ChainDb() ethdb.Database ChainDb() ethdb.Database
AccountManager() *accounts.Manager AccountManager() *accounts.Manager
ExtRPCEnabled() bool ExtRPCEnabled() bool
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection RPCRpcReturnDataLimit() uint64 // Maximum size (in bytes) a result of an rpc request could have
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
UnprotectedAllowed() bool // allows only for EIP155 transactions. RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
UnprotectedAllowed() bool // allows only for EIP155 transactions.
// Blockchain API // Blockchain API
SetHead(number uint64) SetHead(number uint64)

View file

@ -294,6 +294,10 @@ func (b *LesApiBackend) RPCGasCap() uint64 {
return b.eth.config.RPCGasCap return b.eth.config.RPCGasCap
} }
func (b *LesApiBackend) RPCRpcReturnDataLimit() uint64 {
return b.eth.config.RPCReturnDataLimit
}
func (b *LesApiBackend) RPCEVMTimeout() time.Duration { func (b *LesApiBackend) RPCEVMTimeout() time.Duration {
return b.eth.config.RPCEVMTimeout return b.eth.config.RPCEVMTimeout
} }

View file

@ -204,6 +204,9 @@ type Config struct {
// JWTSecret is the hex-encoded jwt secret. // JWTSecret is the hex-encoded jwt secret.
JWTSecret string `toml:",omitempty"` JWTSecret string `toml:",omitempty"`
// Maximum number of messages in a batch
RPCBatchLimit uint64 `toml:",omitempty"`
} }
// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into

View file

@ -113,6 +113,9 @@ func New(conf *Config) (*Node, error) {
databases: make(map[*closeTrackingDB]struct{}), databases: make(map[*closeTrackingDB]struct{}),
} }
// set RPC batch limit
node.inprocHandler.SetRPCBatchLimit(conf.RPCBatchLimit)
// Register built-in APIs. // Register built-in APIs.
node.rpcAPIs = append(node.rpcAPIs, node.apis()...) node.rpcAPIs = append(node.rpcAPIs, node.apis()...)
@ -153,10 +156,10 @@ func New(conf *Config) (*Node, error) {
} }
// Configure RPC servers. // Configure RPC servers.
node.http = newHTTPServer(node.log, conf.HTTPTimeouts) node.http = newHTTPServer(node.log, conf.HTTPTimeouts, conf.RPCBatchLimit)
node.httpAuth = newHTTPServer(node.log, conf.HTTPTimeouts) node.httpAuth = newHTTPServer(node.log, conf.HTTPTimeouts, conf.RPCBatchLimit)
node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts, conf.RPCBatchLimit)
node.wsAuth = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) node.wsAuth = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts, conf.RPCBatchLimit)
node.ipc = newIPCServer(node.log, conf.IPCEndpoint()) node.ipc = newIPCServer(node.log, conf.IPCEndpoint())
return node, nil return node, nil

View file

@ -81,10 +81,12 @@ type httpServer struct {
port int port int
handlerNames map[string]string handlerNames map[string]string
RPCBatchLimit uint64
} }
func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer { func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts, rpcBatchLimit uint64) *httpServer {
h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)} h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string), RPCBatchLimit: rpcBatchLimit}
h.httpHandler.Store((*rpcHandler)(nil)) h.httpHandler.Store((*rpcHandler)(nil))
h.wsHandler.Store((*rpcHandler)(nil)) h.wsHandler.Store((*rpcHandler)(nil))
@ -283,6 +285,7 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
// Create RPC server and handler. // Create RPC server and handler.
srv := rpc.NewServer() srv := rpc.NewServer()
srv.SetRPCBatchLimit(h.RPCBatchLimit)
if err := RegisterApis(apis, config.Modules, srv, false); err != nil { if err := RegisterApis(apis, config.Modules, srv, false); err != nil {
return err return err
} }
@ -314,6 +317,7 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
} }
// Create RPC server and handler. // Create RPC server and handler.
srv := rpc.NewServer() srv := rpc.NewServer()
srv.SetRPCBatchLimit(h.RPCBatchLimit)
if err := RegisterApis(apis, config.Modules, srv, false); err != nil { if err := RegisterApis(apis, config.Modules, srv, false); err != nil {
return err return err
} }

View file

@ -234,7 +234,7 @@ func Test_checkPath(t *testing.T) {
func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer { func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer {
t.Helper() t.Helper()
srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts) srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts, 100)
assert.NoError(t, srv.enableRPC(nil, *conf)) assert.NoError(t, srv.enableRPC(nil, *conf))
if ws { if ws {
assert.NoError(t, srv.enableWS(nil, *wsConf)) assert.NoError(t, srv.enableWS(nil, *wsConf))

View file

@ -4,6 +4,8 @@ chain = "mainnet"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "" # keystore = ""
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
gcmode = "archive" gcmode = "archive"
# snapshot = true # snapshot = true

View file

@ -4,6 +4,8 @@ chain = "mainnet"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "" # keystore = ""
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
# gcmode = "full" # gcmode = "full"
# snapshot = true # snapshot = true

View file

@ -6,6 +6,8 @@ chain = "mainnet"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "$BOR_DIR/keystore" # keystore = "$BOR_DIR/keystore"
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
# gcmode = "full" # gcmode = "full"
# snapshot = true # snapshot = true

View file

@ -6,6 +6,8 @@ chain = "mainnet"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "$BOR_DIR/keystore" # keystore = "$BOR_DIR/keystore"
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
# gcmode = "full" # gcmode = "full"
# snapshot = true # snapshot = true

View file

@ -4,6 +4,8 @@ chain = "mumbai"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "" # keystore = ""
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
gcmode = "archive" gcmode = "archive"
# snapshot = true # snapshot = true

View file

@ -4,6 +4,8 @@ chain = "mumbai"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "" # keystore = ""
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
# gcmode = "full" # gcmode = "full"
# snapshot = true # snapshot = true

View file

@ -6,6 +6,8 @@ chain = "mumbai"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "$BOR_DIR/keystore" # keystore = "$BOR_DIR/keystore"
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
# gcmode = "full" # gcmode = "full"
# snapshot = true # snapshot = true

View file

@ -6,6 +6,8 @@ chain = "mumbai"
datadir = "/var/lib/bor/data" datadir = "/var/lib/bor/data"
# ancient = "" # ancient = ""
# keystore = "$BOR_DIR/keystore" # keystore = "$BOR_DIR/keystore"
# "rpc.batchlimit" = 100
# "rpc.returndatalimit" = 100000
syncmode = "full" syncmode = "full"
# gcmode = "full" # gcmode = "full"
# snapshot = true # snapshot = true

View file

@ -18,6 +18,7 @@ package rpc
import ( import (
"context" "context"
"fmt"
"io" "io"
"sync/atomic" "sync/atomic"
@ -47,6 +48,8 @@ type Server struct {
idgen func() ID idgen func() ID
run int32 run int32
codecs mapset.Set codecs mapset.Set
BatchLimit uint64
} }
// NewServer creates a new server instance with no registered handlers. // NewServer creates a new server instance with no registered handlers.
@ -59,6 +62,10 @@ func NewServer() *Server {
return server return server
} }
func (s *Server) SetRPCBatchLimit(batchLimit uint64) {
s.BatchLimit = batchLimit
}
// RegisterName creates a service for the given receiver type under the given name. When no // RegisterName creates a service for the given receiver type under the given name. When no
// methods on the given receiver match the criteria to be either a RPC method or a // methods on the given receiver match the criteria to be either a RPC method or a
// subscription an error is returned. Otherwise a new service is created and added to the // subscription an error is returned. Otherwise a new service is created and added to the
@ -105,12 +112,23 @@ func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) {
reqs, batch, err := codec.readBatch() reqs, batch, err := codec.readBatch()
if err != nil { if err != nil {
if err != io.EOF { if err != io.EOF {
codec.writeJSON(ctx, errorMessage(&invalidMessageError{"parse error"})) if err1 := codec.writeJSON(ctx, err); err1 != nil {
log.Warn("WARNING - error in reading batch", "err", err1)
return
}
} }
return return
} }
if batch { if batch {
h.handleBatch(reqs) if s.BatchLimit > 0 && len(reqs) > int(s.BatchLimit) {
if err1 := codec.writeJSON(ctx, errorMessage(fmt.Errorf("batch limit %d exceeded: %d requests given", s.BatchLimit, len(reqs)))); err1 != nil {
log.Warn("WARNING - requests given exceeds the batch limit", "err", err1)
log.Debug("batch limit %d exceeded: %d requests given", s.BatchLimit, len(reqs))
}
} else {
h.handleBatch(reqs)
}
} else { } else {
h.handleMsg(reqs[0]) h.handleMsg(reqs[0])
} }