rpc: configure gas cap (#457)

* rpc: configure gas cap

* c++

* rm old const

* docs
This commit is contained in:
Federico Kunze Küllmer 2021-08-18 10:11:51 -04:00 committed by GitHub
parent 1b95d06c92
commit 83c838330f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 7104 additions and 7086 deletions

View File

@ -71,6 +71,7 @@ the Tracer type used to collect execution traces from the EVM transaction execut
### Improvements ### Improvements
* (rpc) [tharsis#457](https://github.com/tharsis/ethermint/pull/457) Configure RPC gas cap through app config.
* (evm) [tharsis#434](https://github.com/tharsis/ethermint/pull/434) Support different `Tracer` types for the EVM. * (evm) [tharsis#434](https://github.com/tharsis/ethermint/pull/434) Support different `Tracer` types for the EVM.
* (deps) [tharsis#427](https://github.com/tharsis/ethermint/pull/427) Bump ibc-go to [`v1.0.0`](https://github.com/cosmos/ibc-go/releases/tag/v1.0.0) * (deps) [tharsis#427](https://github.com/tharsis/ethermint/pull/427) Bump ibc-go to [`v1.0.0`](https://github.com/cosmos/ibc-go/releases/tag/v1.0.0)
* (gRPC) [tharsis#239](https://github.com/tharsis/ethermint/pull/239) Query `ChainConfig` via gRPC. * (gRPC) [tharsis#239](https://github.com/tharsis/ethermint/pull/239) Query `ChainConfig` via gRPC.

View File

@ -22,7 +22,19 @@ ethermintd start --json-rpc.enable
ethermintd start --json-rpc.api eth,txpool,personal,net,debug,web3,miner ethermintd start --json-rpc.api eth,txpool,personal,net,debug,web3,miner
``` ```
### CORS ## Set a Gas Cap
`eth_call` and `eth_estimateGas` define a global gas cap over rpc for DoS protection. You can override the default gas cap value of 25,000,000 by passing a custom value when starting the node:
```bash
# set gas cap to 85M
ethermintd start --json-rpc.gas-cap 85000000000
# set gas cap to infinite (=0)
ethermintd start --json-rpc.gas-cap 0
```
## CORS
If accessing the RPC from a browser, CORS will need to be enabled with the appropriate domain set. Otherwise, JavaScript calls are limit by the same-origin policy and requests will fail: If accessing the RPC from a browser, CORS will need to be enabled with the appropriate domain set. Otherwise, JavaScript calls are limit by the same-origin policy and requests will fail:

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@ const (
// GetRPCAPIs returns the list of all APIs // GetRPCAPIs returns the list of all APIs
func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, selectedAPIs []string) []rpc.API { func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, selectedAPIs []string) []rpc.API {
nonceLock := new(types.AddrLocker) nonceLock := new(types.AddrLocker)
evmBackend := backend.NewEVMBackend(ctx.Logger, clientCtx) evmBackend := backend.NewEVMBackend(ctx, ctx.Logger, clientCtx)
var apis []rpc.API var apis []rpc.API
// remove duplicates // remove duplicates

View File

@ -10,6 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
codectypes "github.com/cosmos/cosmos-sdk/codec/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/server"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
@ -28,6 +29,7 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/tharsis/ethermint/ethereum/rpc/types" "github.com/tharsis/ethermint/ethereum/rpc/types"
"github.com/tharsis/ethermint/server/config"
ethermint "github.com/tharsis/ethermint/types" ethermint "github.com/tharsis/ethermint/types"
evmtypes "github.com/tharsis/ethermint/x/evm/types" evmtypes "github.com/tharsis/ethermint/x/evm/types"
) )
@ -48,6 +50,7 @@ type Backend interface {
BloomStatus() (uint64, uint64) BloomStatus() (uint64, uint64)
GetCoinbase() (sdk.AccAddress, error) GetCoinbase() (sdk.AccAddress, error)
EstimateGas(args evmtypes.CallArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error) EstimateGas(args evmtypes.CallArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error)
RPCGasCap() uint64
} }
var _ Backend = (*EVMBackend)(nil) var _ Backend = (*EVMBackend)(nil)
@ -59,20 +62,28 @@ type EVMBackend struct {
queryClient *types.QueryClient // gRPC query client queryClient *types.QueryClient // gRPC query client
logger log.Logger logger log.Logger
chainID *big.Int chainID *big.Int
cfg config.Config
} }
// NewEVMBackend creates a new EVMBackend instance // NewEVMBackend creates a new EVMBackend instance
func NewEVMBackend(logger log.Logger, clientCtx client.Context) *EVMBackend { func NewEVMBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context) *EVMBackend {
chainID, err := ethermint.ParseChainID(clientCtx.ChainID) chainID, err := ethermint.ParseChainID(clientCtx.ChainID)
if err != nil { if err != nil {
panic(err) panic(err)
} }
appConf, err := config.ParseConfig(ctx.Viper)
if err != nil {
panic(err)
}
return &EVMBackend{ return &EVMBackend{
ctx: context.Background(), ctx: context.Background(),
clientCtx: clientCtx, clientCtx: clientCtx,
queryClient: types.NewQueryClient(clientCtx), queryClient: types.NewQueryClient(clientCtx),
logger: logger.With("module", "evm-backend"), logger: logger.With("module", "evm-backend"),
chainID: chainID, chainID: chainID,
cfg: *appConf,
} }
} }
@ -547,7 +558,8 @@ func (e *EVMBackend) EstimateGas(args evmtypes.CallArgs, blockNrOptional *types.
if err != nil { if err != nil {
return 0, err return 0, err
} }
req := evmtypes.EthCallRequest{Args: bz, GasCap: ethermint.DefaultRPCGasLimit}
req := evmtypes.EthCallRequest{Args: bz, GasCap: e.RPCGasCap()}
// From ContextWithHeight: if the provided height is 0, // From ContextWithHeight: if the provided height is 0,
// it will return an empty context and the gRPC query will use // it will return an empty context and the gRPC query will use
@ -581,3 +593,8 @@ func (e *EVMBackend) GetTransactionCount(address common.Address, blockNum types.
n := hexutil.Uint64(nonce) n := hexutil.Uint64(nonce)
return &n, nil return &n, nil
} }
// RPCGasCap is the global gas cap for eth-call variants.
func (e *EVMBackend) RPCGasCap() uint64 {
return e.cfg.JSONRPC.GasCap
}

View File

@ -12,7 +12,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/tharsis/ethermint/ethereum/rpc/types" "github.com/tharsis/ethermint/ethereum/rpc/types"
ethermint "github.com/tharsis/ethermint/types"
evmtypes "github.com/tharsis/ethermint/x/evm/types" evmtypes "github.com/tharsis/ethermint/x/evm/types"
) )
@ -21,10 +20,8 @@ import (
func (e *EVMBackend) setTxDefaults(args types.SendTxArgs) (types.SendTxArgs, error) { func (e *EVMBackend) setTxDefaults(args types.SendTxArgs) (types.SendTxArgs, error) {
if args.GasPrice == nil { if args.GasPrice == nil {
// TODO: Change to either: // TODO: Suggest a gas price based on the previous included txs
// - min gas price from context once available through server/daemon, or args.GasPrice = (*hexutil.Big)(new(big.Int).SetUint64(e.RPCGasCap()))
// - suggest a gas price based on the previous included txs
args.GasPrice = (*hexutil.Big)(big.NewInt(ethermint.DefaultGasPrice))
} }
if args.Nonce == nil { if args.Nonce == nil {

View File

@ -167,8 +167,7 @@ func (e *PublicAPI) Hashrate() hexutil.Uint64 {
// GasPrice returns the current gas price based on Ethermint's gas price oracle. // GasPrice returns the current gas price based on Ethermint's gas price oracle.
func (e *PublicAPI) GasPrice() *hexutil.Big { func (e *PublicAPI) GasPrice() *hexutil.Big {
e.logger.Debug("eth_gasPrice") e.logger.Debug("eth_gasPrice")
// TODO: use minimum value defined in config instead of default or implement oracle out := new(big.Int).SetUint64(e.backend.RPCGasCap())
out := big.NewInt(ethermint.DefaultGasPrice)
return (*hexutil.Big)(out) return (*hexutil.Big)(out)
} }
@ -438,7 +437,7 @@ func (e *PublicAPI) doCall(
if err != nil { if err != nil {
return nil, err return nil, err
} }
req := evmtypes.EthCallRequest{Args: bz, GasCap: ethermint.DefaultRPCGasLimit} req := evmtypes.EthCallRequest{Args: bz, GasCap: e.backend.RPCGasCap()}
// From ContextWithHeight: if the provided height is 0, // From ContextWithHeight: if the provided height is 0,
// it will return an empty context and the gRPC query will use // it will return an empty context and the gRPC query will use

View File

@ -9,7 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/config" sdkconfig "github.com/cosmos/cosmos-sdk/server/config"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
@ -23,6 +23,7 @@ import (
"github.com/tharsis/ethermint/ethereum/rpc/backend" "github.com/tharsis/ethermint/ethereum/rpc/backend"
rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types" rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types"
"github.com/tharsis/ethermint/server/config"
) )
// API is the miner prefixed set of APIs in the Miner JSON-RPC spec. // API is the miner prefixed set of APIs in the Miner JSON-RPC spec.
@ -186,7 +187,7 @@ func (api *API) SetGasPrice(gasPrice hexutil.Big) bool {
c := sdk.NewDecCoin(unit, sdk.NewIntFromBigInt(gasPrice.ToInt())) c := sdk.NewDecCoin(unit, sdk.NewIntFromBigInt(gasPrice.ToInt()))
appConf.SetMinGasPrices(sdk.DecCoins{c}) appConf.SetMinGasPrices(sdk.DecCoins{c})
config.WriteConfigFile(api.ctx.Viper.ConfigFileUsed(), appConf) sdkconfig.WriteConfigFile(api.ctx.Viper.ConfigFileUsed(), appConf)
api.logger.Info("Your configuration file was modified. Please RESTART your node.", "gas-price", c.String()) api.logger.Info("Your configuration file was modified. Please RESTART your node.", "gas-price", c.String())
return true return true
} }

View File

@ -1,6 +1,7 @@
package config package config
import ( import (
"errors"
"fmt" "fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -24,6 +25,8 @@ const (
// DefaultEVMTracer is the default vm.Tracer type // DefaultEVMTracer is the default vm.Tracer type
DefaultEVMTracer = "json" DefaultEVMTracer = "json"
DefaultGasCap uint64 = 25000000
) )
var ( var (
@ -113,6 +116,27 @@ type JSONRPCConfig struct {
Enable bool `mapstructure:"enable"` Enable bool `mapstructure:"enable"`
// EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk) // EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk)
EnableUnsafeCORS bool `mapstructure:"enable-unsafe-cors"` EnableUnsafeCORS bool `mapstructure:"enable-unsafe-cors"`
// GasCap is the global gas cap for eth-call variants.
GasCap uint64 `mapstructure:"gas-cap"`
}
// Validate returns an error if the JSON-RPC configuration fields are invalid.
func (c JSONRPCConfig) Validate() error {
if c.Enable && len(c.API) == 0 {
return errors.New("cannot enable JSON-RPC without defining any API namespace")
}
// TODO: validate APIs
seenAPIs := make(map[string]bool)
for _, api := range c.API {
if seenAPIs[api] {
return fmt.Errorf("repeated API namespace '%s'", api)
}
seenAPIs[api] = true
}
return nil
} }
// DefaultJSONRPCConfig returns an EVM config with the JSON-RPC API enabled by default // DefaultJSONRPCConfig returns an EVM config with the JSON-RPC API enabled by default
@ -123,6 +147,7 @@ func DefaultJSONRPCConfig() *JSONRPCConfig {
Address: DefaultJSONRPCAddress, Address: DefaultJSONRPCAddress,
WsAddress: DefaultJSONRPCWsAddress, WsAddress: DefaultJSONRPCWsAddress,
EnableUnsafeCORS: false, EnableUnsafeCORS: false,
GasCap: DefaultGasCap,
} }
} }
@ -150,17 +175,29 @@ 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"),
EnableUnsafeCORS: v.GetBool("json-rpc.enable-unsafe-cors"), EnableUnsafeCORS: v.GetBool("json-rpc.enable-unsafe-cors"),
GasCap: v.GetUint64("json-rpc.gas-cap"),
}, },
} }
} }
// ParseConfig retrieves the default environment configuration for the
// application.
func ParseConfig(v *viper.Viper) (*Config, error) {
conf := DefaultConfig()
err := v.Unmarshal(conf)
return conf, err
}
// ValidateBasic returns an error any of the application configuration fields are invalid // ValidateBasic returns an error any of the application configuration fields are invalid
func (c Config) ValidateBasic() error { func (c Config) ValidateBasic() error {
if err := c.EVM.Validate(); err != nil { if err := c.EVM.Validate(); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrAppConfig, "invalid evm config value: %s", err.Error()) return sdkerrors.Wrapf(sdkerrors.ErrAppConfig, "invalid evm config value: %s", err.Error())
} }
// TODO: validate JSON-RPC APIs if err := c.JSONRPC.Validate(); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrAppConfig, "invalid json-rpc config value: %s", err.Error())
}
return c.Config.ValidateBasic() return c.Config.ValidateBasic()
} }

View File

@ -34,4 +34,7 @@ api = "{{range $index, $elmt := .JSONRPC.API}}{{if $index}},{{$elmt}}{{else}}{{$
# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk) # EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk)
enable-unsafe-cors = "{{ .JSONRPC.EnableUnsafeCORS }}" enable-unsafe-cors = "{{ .JSONRPC.EnableUnsafeCORS }}"
# GasCap sets a cap on gas that can be used in eth_call/estimateGas (0=infinite). Default: 25,000,000.
gas-cap = {{ .JSONRPC.GasCap }}
` `

View File

@ -31,6 +31,7 @@ const (
JSONRPCAddress = "json-rpc.address" JSONRPCAddress = "json-rpc.address"
JSONWsAddress = "json-rpc.ws-address" JSONWsAddress = "json-rpc.ws-address"
JSONEnableUnsafeCORS = "json-rpc.enable-unsafe-cors" JSONEnableUnsafeCORS = "json-rpc.enable-unsafe-cors"
JSONRPCGasCap = "json-rpc.gas-cap"
) )
// EVM flags // EVM flags

View File

@ -143,6 +143,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().Bool(srvflags.JSONEnableUnsafeCORS, false, "Define if the JSON-RPC server should enabled CORS (unsafe - use it at your own risk)") cmd.Flags().Bool(srvflags.JSONEnableUnsafeCORS, false, "Define if the JSON-RPC server should enabled CORS (unsafe - use it at your own risk)")
cmd.Flags().Uint64(srvflags.JSONRPCGasCap, config.DefaultGasCap, "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)")
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)")

View File

@ -1,8 +0,0 @@
package types
const (
// DefaultGasPrice is default gas price for evm transactions
DefaultGasPrice = 20
// DefaultRPCGasLimit is default gas limit for RPC call operations
DefaultRPCGasLimit = 10000000
)

View File

@ -724,7 +724,7 @@ func (suite *KeeperTestSuite) deployTestContract(owner common.Address, supply *b
res, err := suite.queryClient.EstimateGas(ctx, &types.EthCallRequest{ res, err := suite.queryClient.EstimateGas(ctx, &types.EthCallRequest{
Args: args, Args: args,
GasCap: uint64(ethermint.DefaultRPCGasLimit), GasCap: 25_000_000,
}) })
suite.Require().NoError(err) suite.Require().NoError(err)
@ -805,7 +805,7 @@ func (suite *KeeperTestSuite) TestEstimateGas() {
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() suite.SetupTest()
gasCap = ethermint.DefaultRPCGasLimit gasCap = 25_000_000
tc.malleate() tc.malleate()
args, err := json.Marshal(&args) args, err := json.Marshal(&args)