From e872ba7a9e8123a4010198ee8dfaada9f71fa24a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 8 Apr 2019 13:49:52 +0200 Subject: [PATCH] eth, les, geth: implement cli-configurable global gas cap for RPC calls (#19401) * eth, les, geth: implement cli-configurable global gas cap for RPC calls * graphql, ethapi: place gas cap in DoCall * ethapi: reformat log message --- cmd/geth/main.go | 3 +-- cmd/geth/usage.go | 1 + cmd/utils/flags.go | 7 +++++++ eth/api_backend.go | 4 ++++ eth/config.go | 3 +++ internal/ethapi/api.go | 21 +++++++++++++++------ internal/ethapi/backend.go | 1 + les/api_backend.go | 4 ++++ 8 files changed, 36 insertions(+), 8 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e60a27e43e22..458608112b0f 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -125,8 +125,6 @@ var ( utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.ConstantinopleOverrideFlag, - utils.RPCCORSDomainFlag, - utils.RPCVirtualHostsFlag, utils.EthStatsURLFlag, utils.MetricsEnabledFlag, utils.FakePoWFlag, @@ -150,6 +148,7 @@ var ( utils.WSAllowedOriginsFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, + utils.RPCGlobalGasCap, } whisperFlags = []cli.Flag{ diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 6823aa36c2e6..259e6d30a1d1 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -153,6 +153,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.RPCListenAddrFlag, utils.RPCPortFlag, utils.RPCApiFlag, + utils.RPCGlobalGasCap, utils.WSEnabledFlag, utils.WSListenAddrFlag, utils.WSPortFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 55e84b8765ba..052660ba1773 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -411,6 +411,10 @@ var ( Name: "vmdebug", Usage: "Record information useful for VM and contract debugging", } + RPCGlobalGasCap = cli.Uint64Flag{ + Name: "rpc.gascap", + Usage: "Sets a cap on gas that can be used in eth_call/estimateGas", + } // Logging and debug settings EthStatsURLFlag = cli.StringFlag{ Name: "ethstats", @@ -1256,6 +1260,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(EVMInterpreterFlag.Name) { cfg.EVMInterpreter = ctx.GlobalString(EVMInterpreterFlag.Name) } + if ctx.GlobalIsSet(RPCGlobalGasCap.Name) { + cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name)) + } // Override any default configs for hard coded networks. switch { diff --git a/eth/api_backend.go b/eth/api_backend.go index a48815e0db22..3efc09cc1834 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -213,6 +213,10 @@ func (b *EthAPIBackend) AccountManager() *accounts.Manager { return b.eth.AccountManager() } +func (b *EthAPIBackend) RPCGasCap() *big.Int { + return b.eth.config.RPCGasCap +} + func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { sections, _, _ := b.eth.bloomIndexer.Sections() return params.BloomBitsBlocks, sections diff --git a/eth/config.go b/eth/config.go index 7c041d1af73c..5b9db2502fb4 100644 --- a/eth/config.go +++ b/eth/config.go @@ -135,6 +135,9 @@ type Config struct { // Constantinople block override (TODO: remove after the fork) ConstantinopleOverride *big.Int + + // RPCGasCap is the global gas cap for eth-call variants. + RPCGasCap *big.Int `toml:",omitempty"` } type configMarshaling struct { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b732adff60dc..e1ca71104e31 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -683,7 +683,7 @@ type CallArgs struct { Data hexutil.Bytes `json:"data"` } -func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, timeout time.Duration) ([]byte, uint64, bool, error) { +func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr) @@ -700,14 +700,18 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr } } // Set default gas & gas price if none were set - gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt() + gas := uint64(args.Gas) if gas == 0 { gas = math.MaxUint64 / 2 } + if globalGasCap != nil && globalGasCap.Uint64() < gas { + log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) + gas = globalGasCap.Uint64() + } + gasPrice := args.GasPrice.ToInt() if gasPrice.Sign() == 0 { gasPrice = new(big.Int).SetUint64(defaultGasPrice) } - // Create new call message msg := types.NewMessage(addr, args.To, 0, args.Value.ToInt(), gas, gasPrice, args.Data, false) @@ -748,7 +752,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr // Call executes the given transaction on the state for the given block number. // It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { - result, _, _, err := s.doCall(ctx, args, blockNr, 5*time.Second) + result, _, _, err := s.doCall(ctx, args, blockNr, 5*time.Second, s.b.RPCGasCap()) return (hexutil.Bytes)(result), err } @@ -771,13 +775,18 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h } hi = block.GasLimit() } + gasCap := s.b.RPCGasCap() + if gasCap != nil && hi > gasCap.Uint64() { + log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) + hi = gasCap.Uint64() + } cap = hi // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) bool { args.Gas = hexutil.Uint64(gas) - _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, 0) + _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, 0, gasCap) if err != nil || failed { return false } @@ -795,7 +804,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { if !executable(hi) { - return 0, fmt.Errorf("gas required exceeds allowance or always failing transaction") + return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", cap) } } return hexutil.Uint64(hi), nil diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index e23ee03b1555..56a3daffa906 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -44,6 +44,7 @@ type Backend interface { ChainDb() ethdb.Database EventMux() *event.TypeMux AccountManager() *accounts.Manager + RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection // BlockChain API SetHead(number uint64) diff --git a/les/api_backend.go b/les/api_backend.go index 753139623526..f03d32fedc8a 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -187,6 +187,10 @@ func (b *LesApiBackend) AccountManager() *accounts.Manager { return b.eth.accountManager } +func (b *LesApiBackend) RPCGasCap() *big.Int { + return b.eth.config.RPCGasCap +} + func (b *LesApiBackend) BloomStatus() (uint64, uint64) { if b.eth.bloomIndexer == nil { return 0, 0