From e2f3465e83a4c03b48977ed367e68b0d63ca2ed1 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 | 7 ++++--- cmd/geth/usage.go | 1 + cmd/utils/flags.go | 7 +++++++ eth/api_backend.go | 4 ++++ eth/config.go | 3 +++ graphql/graphql.go | 8 ++++---- internal/ethapi/api.go | 20 ++++++++++++++------ internal/ethapi/backend.go | 1 + les/api_backend.go | 4 ++++ 9 files changed, 42 insertions(+), 13 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0e597f0301..4f3849a41b 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -57,7 +57,6 @@ var ( utils.IdentityFlag, utils.UnlockedAccountFlag, utils.PasswordFileFlag, - utils.InsecureUnlockAllowedFlag, utils.BootnodesFlag, utils.BootnodesV4Flag, utils.BootnodesV5Flag, @@ -136,8 +135,6 @@ var ( utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.ConstantinopleOverrideFlag, - utils.RPCCORSDomainFlag, - utils.RPCVirtualHostsFlag, utils.EthStatsURLFlag, utils.FakePoWFlag, utils.NoCompactionFlag, @@ -152,6 +149,8 @@ var ( utils.RPCEnabledFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, + utils.RPCCORSDomainFlag, + utils.RPCVirtualHostsFlag, utils.GraphQLEnabledFlag, utils.GraphQLListenAddrFlag, utils.GraphQLPortFlag, @@ -165,6 +164,8 @@ var ( utils.WSAllowedOriginsFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, + utils.InsecureUnlockAllowedFlag, + utils.RPCGlobalGasCap, } whisperFlags = []cli.Flag{ diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 6d039ba040..7ec1ab03f5 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -158,6 +158,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 f6e4288692..f5f4cde5b4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -448,6 +448,10 @@ var ( Name: "allow-insecure-unlock", Usage: "Allow insecure account unlocking when account-related RPCs are exposed by http", } + 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", @@ -1400,6 +1404,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 29ce19e28f..00424caed2 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -218,6 +218,10 @@ func (b *EthAPIBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } +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 a98e69053a..d97ae3070d 100644 --- a/eth/config.go +++ b/eth/config.go @@ -151,6 +151,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/graphql/graphql.go b/graphql/graphql.go index b3bcbd8a43..d22a3afb6f 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -856,7 +856,7 @@ func (b *Block) Call(ctx context.Context, args struct { } } - result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.num, vm.Config{}, 5*time.Second) + result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.num, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) status := hexutil.Uint64(1) if failed { status = 0 @@ -883,7 +883,7 @@ func (b *Block) EstimateGas(ctx context.Context, args struct { } } - gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.num) + gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.num, b.backend.RPCGasCap()) return gas, err } @@ -927,7 +927,7 @@ func (p *Pending) Account(ctx context.Context, args struct { func (p *Pending) Call(ctx context.Context, args struct { Data ethapi.CallArgs }) (*CallResult, error) { - result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, rpc.PendingBlockNumber, vm.Config{}, 5*time.Second) + result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, rpc.PendingBlockNumber, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) status := hexutil.Uint64(1) if failed { status = 0 @@ -942,7 +942,7 @@ func (p *Pending) Call(ctx context.Context, args struct { func (p *Pending) EstimateGas(ctx context.Context, args struct { Data ethapi.CallArgs }) (hexutil.Uint64, error) { - return ethapi.DoEstimateGas(ctx, p.backend, args.Data, rpc.PendingBlockNumber) + return ethapi.DoEstimateGas(ctx, p.backend, args.Data, rpc.PendingBlockNumber, p.backend.RPCGasCap()) } // Resolver is the top-level object in the GraphQL hierarchy. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e5a8124b1f..ccd918be22 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -674,7 +674,7 @@ type CallArgs struct { Data *hexutil.Bytes `json:"data"` } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error) { +func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, 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 := b.StateAndHeaderByNumber(ctx, blockNr) @@ -697,6 +697,10 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb if args.Gas != nil { gas = uint64(*args.Gas) } + if globalGasCap != nil && globalGasCap.Uint64() < gas { + log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) + gas = globalGasCap.Uint64() + } gasPrice := new(big.Int).SetUint64(defaultGasPrice) if args.GasPrice != nil { gasPrice = args.GasPrice.ToInt() @@ -752,11 +756,11 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb // 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 := DoCall(ctx, s.b, args, blockNr, vm.Config{}, 5*time.Second) + result, _, _, err := DoCall(ctx, s.b, args, blockNr, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) return (hexutil.Bytes)(result), err } -func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Uint64, error) { +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, gasCap *big.Int) (hexutil.Uint64, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( lo uint64 = params.TxGas - 1 @@ -773,13 +777,17 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } hi = block.GasLimit() } + 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 := DoCall(ctx, b, args, rpc.PendingBlockNumber, vm.Config{}, 0) + _, _, failed, err := DoCall(ctx, b, args, rpc.PendingBlockNumber, vm.Config{}, 0, gasCap) if err != nil || failed { return false } @@ -797,7 +805,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl // 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 @@ -806,7 +814,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl // EstimateGas returns an estimate of the amount of gas needed to execute the // given transaction against the current pending block. func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { - return DoEstimateGas(ctx, s.b, args, rpc.PendingBlockNumber) + return DoEstimateGas(ctx, s.b, args, rpc.PendingBlockNumber, s.b.RPCGasCap()) } // ExecutionResult groups all structured logs emitted by the EVM diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index e88207f87d..0c6c7eace8 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -45,6 +45,7 @@ type Backend interface { EventMux() *event.TypeMux AccountManager() *accounts.Manager ExtRPCEnabled() bool + 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 8b03979a29..4fe3521366 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -192,6 +192,10 @@ func (b *LesApiBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } +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