mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
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:
parent
66c23d1eeb
commit
e4dd2ee1ac
24 changed files with 121 additions and 24 deletions
|
|
@ -8,6 +8,8 @@ chain = "mainnet"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = "/var/lib/bor/keystore"
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
# gcmode = "full"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -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
|
||||
ancient = "" # Data directory for ancient chain segments (default = inside chaindata)
|
||||
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)
|
||||
gcmode = "full" # Blockchain garbage collection mode ("full", "archive")
|
||||
snapshot = true # Enables the snapshot-database mode
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ The ```bor server``` command runs the Bor client.
|
|||
|
||||
- ```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
|
||||
|
||||
- ```syncmode```: Blockchain sync mode (only "full" sync supported) (default: full)
|
||||
|
|
|
|||
|
|
@ -317,6 +317,10 @@ func (b *EthAPIBackend) RPCGasCap() uint64 {
|
|||
return b.eth.config.RPCGasCap
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) RPCRpcReturnDataLimit() uint64 {
|
||||
return b.eth.config.RPCReturnDataLimit
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) RPCEVMTimeout() time.Duration {
|
||||
return b.eth.config.RPCEVMTimeout
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,11 +94,12 @@ var Defaults = Config{
|
|||
GasPrice: big.NewInt(params.GWei),
|
||||
Recommit: 125 * time.Second,
|
||||
},
|
||||
TxPool: core.DefaultTxPoolConfig,
|
||||
RPCGasCap: 50000000,
|
||||
RPCEVMTimeout: 5 * time.Second,
|
||||
GPO: FullNodeGPO,
|
||||
RPCTxFeeCap: 5, // 5 matic
|
||||
TxPool: core.DefaultTxPoolConfig,
|
||||
RPCGasCap: 50000000,
|
||||
RPCReturnDataLimit: 100000,
|
||||
RPCEVMTimeout: 5 * time.Second,
|
||||
GPO: FullNodeGPO,
|
||||
RPCTxFeeCap: 5, // 5 matic
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -199,6 +200,9 @@ type Config struct {
|
|||
// RPCGasCap is the global gas cap for eth-call variants.
|
||||
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 time.Duration
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ func (b *testBackend) RPCGasCap() uint64 {
|
|||
return 25000000
|
||||
}
|
||||
|
||||
func (b *testBackend) RPCRpcReturnDataLimit() uint64 {
|
||||
return 100000
|
||||
}
|
||||
|
||||
func (b *testBackend) ChainConfig() *params.ChainConfig {
|
||||
return b.chainConfig
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,12 @@ type Config struct {
|
|||
// KeyStoreDir is the directory to store keystores
|
||||
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 string `hcl:"syncmode,optional" toml:"syncmode,optional"`
|
||||
|
||||
|
|
@ -435,12 +441,14 @@ type DeveloperConfig struct {
|
|||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Chain: "mainnet",
|
||||
Identity: Hostname(),
|
||||
RequiredBlocks: map[string]string{},
|
||||
LogLevel: "INFO",
|
||||
DataDir: DefaultDataDir(),
|
||||
Ancient: "",
|
||||
Chain: "mainnet",
|
||||
Identity: Hostname(),
|
||||
RequiredBlocks: map[string]string{},
|
||||
LogLevel: "INFO",
|
||||
DataDir: DefaultDataDir(),
|
||||
Ancient: "",
|
||||
RPCBatchLimit: 100,
|
||||
RPCReturnDataLimit: 100000,
|
||||
P2P: &P2PConfig{
|
||||
MaxPeers: 50,
|
||||
MaxPendPeers: 50,
|
||||
|
|
@ -936,6 +944,8 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (*
|
|||
n.BorLogs = c.BorLogs
|
||||
n.DatabaseHandles = dbHandles
|
||||
|
||||
n.RPCReturnDataLimit = c.RPCReturnDataLimit
|
||||
|
||||
if c.Ancient != "" {
|
||||
n.DatabaseFreezer = c.Ancient
|
||||
}
|
||||
|
|
@ -986,6 +996,7 @@ func (c *Config) buildNode() (*node.Config, error) {
|
|||
WriteTimeout: c.JsonRPC.HttpTimeout.WriteTimeout,
|
||||
IdleTimeout: c.JsonRPC.HttpTimeout.IdleTimeout,
|
||||
},
|
||||
RPCBatchLimit: c.RPCBatchLimit,
|
||||
}
|
||||
|
||||
// dev mode
|
||||
|
|
|
|||
|
|
@ -46,6 +46,18 @@ func (c *Command) Flags() *flagset.Flagset {
|
|||
Usage: "Path of the directory where keystores are located",
|
||||
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{
|
||||
Name: "config",
|
||||
Usage: "File for the config file",
|
||||
|
|
|
|||
|
|
@ -1078,6 +1078,11 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, bl
|
|||
if err != nil {
|
||||
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 len(result.Revert()) > 0 {
|
||||
return nil, newRevertError(result)
|
||||
|
|
|
|||
|
|
@ -48,10 +48,11 @@ type Backend interface {
|
|||
ChainDb() ethdb.Database
|
||||
AccountManager() *accounts.Manager
|
||||
ExtRPCEnabled() bool
|
||||
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
|
||||
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
|
||||
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
|
||||
UnprotectedAllowed() bool // allows only for EIP155 transactions.
|
||||
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
|
||||
RPCRpcReturnDataLimit() uint64 // Maximum size (in bytes) a result of an rpc request could have
|
||||
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
|
||||
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
|
||||
UnprotectedAllowed() bool // allows only for EIP155 transactions.
|
||||
|
||||
// Blockchain API
|
||||
SetHead(number uint64)
|
||||
|
|
|
|||
|
|
@ -294,6 +294,10 @@ func (b *LesApiBackend) RPCGasCap() uint64 {
|
|||
return b.eth.config.RPCGasCap
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) RPCRpcReturnDataLimit() uint64 {
|
||||
return b.eth.config.RPCReturnDataLimit
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) RPCEVMTimeout() time.Duration {
|
||||
return b.eth.config.RPCEVMTimeout
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,6 +204,9 @@ type Config struct {
|
|||
|
||||
// JWTSecret is the hex-encoded jwt secret.
|
||||
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
|
||||
|
|
|
|||
11
node/node.go
11
node/node.go
|
|
@ -113,6 +113,9 @@ func New(conf *Config) (*Node, error) {
|
|||
databases: make(map[*closeTrackingDB]struct{}),
|
||||
}
|
||||
|
||||
// set RPC batch limit
|
||||
node.inprocHandler.SetRPCBatchLimit(conf.RPCBatchLimit)
|
||||
|
||||
// Register built-in APIs.
|
||||
node.rpcAPIs = append(node.rpcAPIs, node.apis()...)
|
||||
|
||||
|
|
@ -153,10 +156,10 @@ func New(conf *Config) (*Node, error) {
|
|||
}
|
||||
|
||||
// Configure RPC servers.
|
||||
node.http = newHTTPServer(node.log, conf.HTTPTimeouts)
|
||||
node.httpAuth = newHTTPServer(node.log, conf.HTTPTimeouts)
|
||||
node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
|
||||
node.wsAuth = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
|
||||
node.http = newHTTPServer(node.log, conf.HTTPTimeouts, conf.RPCBatchLimit)
|
||||
node.httpAuth = newHTTPServer(node.log, conf.HTTPTimeouts, conf.RPCBatchLimit)
|
||||
node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts, conf.RPCBatchLimit)
|
||||
node.wsAuth = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts, conf.RPCBatchLimit)
|
||||
node.ipc = newIPCServer(node.log, conf.IPCEndpoint())
|
||||
|
||||
return node, nil
|
||||
|
|
|
|||
|
|
@ -81,10 +81,12 @@ type httpServer struct {
|
|||
port int
|
||||
|
||||
handlerNames map[string]string
|
||||
|
||||
RPCBatchLimit uint64
|
||||
}
|
||||
|
||||
func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer {
|
||||
h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)}
|
||||
func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts, rpcBatchLimit uint64) *httpServer {
|
||||
h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string), RPCBatchLimit: rpcBatchLimit}
|
||||
|
||||
h.httpHandler.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.
|
||||
srv := rpc.NewServer()
|
||||
srv.SetRPCBatchLimit(h.RPCBatchLimit)
|
||||
if err := RegisterApis(apis, config.Modules, srv, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -314,6 +317,7 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
|
|||
}
|
||||
// Create RPC server and handler.
|
||||
srv := rpc.NewServer()
|
||||
srv.SetRPCBatchLimit(h.RPCBatchLimit)
|
||||
if err := RegisterApis(apis, config.Modules, srv, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ func Test_checkPath(t *testing.T) {
|
|||
func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer {
|
||||
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))
|
||||
if ws {
|
||||
assert.NoError(t, srv.enableWS(nil, *wsConf))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ chain = "mainnet"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = ""
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
gcmode = "archive"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ chain = "mainnet"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = ""
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
# gcmode = "full"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ chain = "mainnet"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = "$BOR_DIR/keystore"
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
# gcmode = "full"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ chain = "mainnet"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = "$BOR_DIR/keystore"
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
# gcmode = "full"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ chain = "mumbai"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = ""
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
gcmode = "archive"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ chain = "mumbai"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = ""
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
# gcmode = "full"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ chain = "mumbai"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = "$BOR_DIR/keystore"
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
# gcmode = "full"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ chain = "mumbai"
|
|||
datadir = "/var/lib/bor/data"
|
||||
# ancient = ""
|
||||
# keystore = "$BOR_DIR/keystore"
|
||||
# "rpc.batchlimit" = 100
|
||||
# "rpc.returndatalimit" = 100000
|
||||
syncmode = "full"
|
||||
# gcmode = "full"
|
||||
# snapshot = true
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package rpc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
|
||||
|
|
@ -47,6 +48,8 @@ type Server struct {
|
|||
idgen func() ID
|
||||
run int32
|
||||
codecs mapset.Set
|
||||
|
||||
BatchLimit uint64
|
||||
}
|
||||
|
||||
// NewServer creates a new server instance with no registered handlers.
|
||||
|
|
@ -59,6 +62,10 @@ func NewServer() *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
|
||||
// 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
|
||||
|
|
@ -105,12 +112,23 @@ func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) {
|
|||
reqs, batch, err := codec.readBatch()
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
h.handleMsg(reqs[0])
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue