fix: limit total number of filters that can be created (#661)
* Problem: No way to limit total number of filters that can be created Solution: Add a config parameter to set the total number of filters that can be created * Add defer statement for releasing locks * Change default value for filter cap to 200 * Changed data type of filter cap to int32 * Add changelog entry * Update CHANGELOG.md * Fix struct alignment Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
parent
8e12d94359
commit
c7a2fb97c7
@ -51,7 +51,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
|||||||
* (deps) [tharsis#655](https://github.com/tharsis/ethermint/pull/665) Bump Cosmos SDK version to [`v0.44.2`](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.44.2).
|
* (deps) [tharsis#655](https://github.com/tharsis/ethermint/pull/665) Bump Cosmos SDK version to [`v0.44.2`](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.44.2).
|
||||||
* (evm) [tharsis#650](https://github.com/tharsis/ethermint/pull/650) Fix panic when flattening the cache context in case transaction is reverted.
|
* (evm) [tharsis#650](https://github.com/tharsis/ethermint/pull/650) Fix panic when flattening the cache context in case transaction is reverted.
|
||||||
* (rpc, test) [tharsis#608](https://github.com/tharsis/ethermint/pull/608) Fix rpc test.
|
* (rpc, test) [tharsis#608](https://github.com/tharsis/ethermint/pull/608) Fix rpc test.
|
||||||
* (evm) [tharsis#660](https://github.com/tharsis/ethermint/pull/660) Fix nil pointer panic in ApplyNativeMessage.
|
* (rpc) [tharsis#661](https://github.com/tharsis/ethermint/pull/661) Fix OOM bug when creating too many filters using JSON-RPC.
|
||||||
|
* (evm) [tharsis#660](https://github.com/tharsis/ethermint/pull/660) Fix `nil` pointer panic in `ApplyNativeMessage`.
|
||||||
|
|
||||||
## [v0.7.0] - 2021-10-07
|
## [v0.7.0] - 2021-10-07
|
||||||
|
|
||||||
|
@ -765,6 +765,11 @@ func (e *EVMBackend) RPCGasCap() uint64 {
|
|||||||
return e.cfg.JSONRPC.GasCap
|
return e.cfg.JSONRPC.GasCap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RPCFilterCap is the limit for total number of filters that can be created
|
||||||
|
func (e *EVMBackend) RPCFilterCap() int32 {
|
||||||
|
return e.cfg.JSONRPC.FilterCap
|
||||||
|
}
|
||||||
|
|
||||||
// RPCMinGasPrice returns the minimum gas price for a transaction obtained from
|
// RPCMinGasPrice returns the minimum gas price for a transaction obtained from
|
||||||
// the node config. If set value is 0, it will default to 20.
|
// the node config. If set value is 0, it will default to 20.
|
||||||
|
|
||||||
|
@ -36,6 +36,8 @@ type Backend interface {
|
|||||||
BloomStatus() (uint64, uint64)
|
BloomStatus() (uint64, uint64)
|
||||||
|
|
||||||
GetFilteredBlocks(from int64, to int64, bloomIndexes [][]BloomIV, filterAddresses bool) ([]int64, error)
|
GetFilteredBlocks(from int64, to int64, bloomIndexes [][]BloomIV, filterAddresses bool) ([]int64, error)
|
||||||
|
|
||||||
|
RPCFilterCap() int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// consider a filter inactive if it has not been polled for within deadline
|
// consider a filter inactive if it has not been polled for within deadline
|
||||||
@ -107,15 +109,20 @@ func (api *PublicFilterAPI) timeoutLoop() {
|
|||||||
//
|
//
|
||||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newPendingTransactionFilter
|
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newPendingTransactionFilter
|
||||||
func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
|
func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
defer api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
if len(api.filters) >= int(api.backend.RPCFilterCap()) {
|
||||||
|
return rpc.ID("error creating pending tx filter: max limit reached")
|
||||||
|
}
|
||||||
|
|
||||||
pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs()
|
pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// wrap error on the ID
|
// wrap error on the ID
|
||||||
return rpc.ID(fmt.Sprintf("error creating pending tx filter: %s", err.Error()))
|
return rpc.ID(fmt.Sprintf("error creating pending tx filter: %s", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub}
|
api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub}
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
|
go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
|
||||||
defer cancelSubs()
|
defer cancelSubs()
|
||||||
@ -219,6 +226,13 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su
|
|||||||
//
|
//
|
||||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter
|
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter
|
||||||
func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
|
func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
defer api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
if len(api.filters) >= int(api.backend.RPCFilterCap()) {
|
||||||
|
return rpc.ID("error creating block filter: max limit reached")
|
||||||
|
}
|
||||||
|
|
||||||
headerSub, cancelSubs, err := api.events.SubscribeNewHeads()
|
headerSub, cancelSubs, err := api.events.SubscribeNewHeads()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// wrap error on the ID
|
// wrap error on the ID
|
||||||
@ -228,9 +242,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
|
|||||||
// TODO: use events to get the base fee amount
|
// TODO: use events to get the base fee amount
|
||||||
baseFee := big.NewInt(params.InitialBaseFee)
|
baseFee := big.NewInt(params.InitialBaseFee)
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: headerSub}
|
api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: headerSub}
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
|
go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
|
||||||
defer cancelSubs()
|
defer cancelSubs()
|
||||||
@ -404,6 +416,13 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri
|
|||||||
//
|
//
|
||||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter
|
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter
|
||||||
func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) {
|
func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) {
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
defer api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
if len(api.filters) >= int(api.backend.RPCFilterCap()) {
|
||||||
|
return rpc.ID(""), fmt.Errorf("error creating filter: max limit reached")
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
filterID = rpc.ID("")
|
filterID = rpc.ID("")
|
||||||
err error
|
err error
|
||||||
@ -416,9 +435,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
|
|||||||
|
|
||||||
filterID = logsSub.ID()
|
filterID = logsSub.ID()
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[filterID] = &filter{typ: filters.LogsSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: logsSub}
|
api.filters[filterID] = &filter{typ: filters.LogsSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: logsSub}
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
go func(eventCh <-chan coretypes.ResultEvent) {
|
go func(eventCh <-chan coretypes.ResultEvent) {
|
||||||
defer cancelSubs()
|
defer cancelSubs()
|
||||||
|
@ -28,6 +28,8 @@ const (
|
|||||||
DefaultEVMTracer = "json"
|
DefaultEVMTracer = "json"
|
||||||
|
|
||||||
DefaultGasCap uint64 = 25000000
|
DefaultGasCap uint64 = 25000000
|
||||||
|
|
||||||
|
DefaultFilterCap int32 = 200
|
||||||
)
|
)
|
||||||
|
|
||||||
var evmTracers = []string{DefaultEVMTracer, "markdown", "struct", "access_list"}
|
var evmTracers = []string{DefaultEVMTracer, "markdown", "struct", "access_list"}
|
||||||
@ -51,16 +53,18 @@ type EVMConfig struct {
|
|||||||
|
|
||||||
// JSONRPCConfig defines configuration for the EVM RPC server.
|
// JSONRPCConfig defines configuration for the EVM RPC server.
|
||||||
type JSONRPCConfig struct {
|
type JSONRPCConfig struct {
|
||||||
|
// API defines a list of JSON-RPC namespaces that should be enabled
|
||||||
|
API []string `mapstructure:"api"`
|
||||||
// Address defines the HTTP server to listen on
|
// Address defines the HTTP server to listen on
|
||||||
Address string `mapstructure:"address"`
|
Address string `mapstructure:"address"`
|
||||||
// WsAddress defines the WebSocket server to listen on
|
// WsAddress defines the WebSocket server to listen on
|
||||||
WsAddress string `mapstructure:"ws-address"`
|
WsAddress string `mapstructure:"ws-address"`
|
||||||
// API defines a list of JSON-RPC namespaces that should be enabled
|
|
||||||
API []string `mapstructure:"api"`
|
|
||||||
// Enable defines if the EVM RPC server should be enabled.
|
|
||||||
Enable bool `mapstructure:"enable"`
|
|
||||||
// GasCap is the global gas cap for eth-call variants.
|
// GasCap is the global gas cap for eth-call variants.
|
||||||
GasCap uint64 `mapstructure:"gas-cap"`
|
GasCap uint64 `mapstructure:"gas-cap"`
|
||||||
|
// FilterCap is the global cap for total number of filters that can be created.
|
||||||
|
FilterCap int32 `mapstructure:"filter-cap"`
|
||||||
|
// Enable defines if the EVM RPC server should be enabled.
|
||||||
|
Enable bool `mapstructure:"enable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLSConfig defines the certificate and matching private key for the server.
|
// TLSConfig defines the certificate and matching private key for the server.
|
||||||
@ -145,6 +149,7 @@ func DefaultJSONRPCConfig() *JSONRPCConfig {
|
|||||||
Address: DefaultJSONRPCAddress,
|
Address: DefaultJSONRPCAddress,
|
||||||
WsAddress: DefaultJSONRPCWsAddress,
|
WsAddress: DefaultJSONRPCWsAddress,
|
||||||
GasCap: DefaultGasCap,
|
GasCap: DefaultGasCap,
|
||||||
|
FilterCap: DefaultFilterCap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +159,10 @@ func (c JSONRPCConfig) Validate() error {
|
|||||||
return errors.New("cannot enable JSON-RPC without defining any API namespace")
|
return errors.New("cannot enable JSON-RPC without defining any API namespace")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.FilterCap < 0 {
|
||||||
|
return errors.New("JSON-RPC filter-cap cannot be negative")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: validate APIs
|
// TODO: validate APIs
|
||||||
seenAPIs := make(map[string]bool)
|
seenAPIs := make(map[string]bool)
|
||||||
for _, api := range c.API {
|
for _, api := range c.API {
|
||||||
@ -207,6 +216,7 @@ func GetConfig(v *viper.Viper) Config {
|
|||||||
Address: v.GetString("json-rpc.address"),
|
Address: v.GetString("json-rpc.address"),
|
||||||
WsAddress: v.GetString("json-rpc.ws-address"),
|
WsAddress: v.GetString("json-rpc.ws-address"),
|
||||||
GasCap: v.GetUint64("json-rpc.gas-cap"),
|
GasCap: v.GetUint64("json-rpc.gas-cap"),
|
||||||
|
FilterCap: v.GetInt32("json-rpc.filter-cap"),
|
||||||
},
|
},
|
||||||
TLS: TLSConfig{
|
TLS: TLSConfig{
|
||||||
CertificatePath: v.GetString("tls.certificate-path"),
|
CertificatePath: v.GetString("tls.certificate-path"),
|
||||||
|
@ -35,6 +35,9 @@ api = "{{range $index, $elmt := .JSONRPC.API}}{{if $index}},{{$elmt}}{{else}}{{$
|
|||||||
# GasCap sets a cap on gas that can be used in eth_call/estimateGas (0=infinite). Default: 25,000,000.
|
# GasCap sets a cap on gas that can be used in eth_call/estimateGas (0=infinite). Default: 25,000,000.
|
||||||
gas-cap = {{ .JSONRPC.GasCap }}
|
gas-cap = {{ .JSONRPC.GasCap }}
|
||||||
|
|
||||||
|
# FilterCap sets the global cap for total number of filters that can be created
|
||||||
|
filter-cap = {{ .JSONRPC.FilterCap }}
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
### TLS Configuration ###
|
### TLS Configuration ###
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -26,11 +26,12 @@ const (
|
|||||||
|
|
||||||
// JSON-RPC flags
|
// JSON-RPC flags
|
||||||
const (
|
const (
|
||||||
JSONRPCEnable = "json-rpc.enable"
|
JSONRPCEnable = "json-rpc.enable"
|
||||||
JSONRPCAPI = "json-rpc.api"
|
JSONRPCAPI = "json-rpc.api"
|
||||||
JSONRPCAddress = "json-rpc.address"
|
JSONRPCAddress = "json-rpc.address"
|
||||||
JSONWsAddress = "json-rpc.ws-address"
|
JSONWsAddress = "json-rpc.ws-address"
|
||||||
JSONRPCGasCap = "json-rpc.gas-cap"
|
JSONRPCGasCap = "json-rpc.gas-cap"
|
||||||
|
JSONRPCFilterCap = "json-rpc.filter-cap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EVM flags
|
// EVM flags
|
||||||
|
@ -156,6 +156,7 @@ which accepts a path for the resulting pprof file.
|
|||||||
cmd.Flags().String(srvflags.JSONRPCAddress, config.DefaultJSONRPCAddress, "the JSON-RPC server address to listen on")
|
cmd.Flags().String(srvflags.JSONRPCAddress, config.DefaultJSONRPCAddress, "the JSON-RPC server address to listen on")
|
||||||
cmd.Flags().String(srvflags.JSONWsAddress, config.DefaultJSONRPCWsAddress, "the JSON-RPC WS server address to listen on")
|
cmd.Flags().String(srvflags.JSONWsAddress, config.DefaultJSONRPCWsAddress, "the JSON-RPC WS server address to listen on")
|
||||||
cmd.Flags().Uint64(srvflags.JSONRPCGasCap, config.DefaultGasCap, "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)")
|
cmd.Flags().Uint64(srvflags.JSONRPCGasCap, config.DefaultGasCap, "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)")
|
||||||
|
cmd.Flags().Int32(srvflags.JSONRPCFilterCap, config.DefaultFilterCap, "Sets the global cap for total number of filters that can be created")
|
||||||
|
|
||||||
cmd.Flags().String(srvflags.EVMTracer, config.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)")
|
cmd.Flags().String(srvflags.EVMTracer, config.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user