diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 6ad25c7daf..a9fd25c492 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -118,7 +118,7 @@ var ( Enable0xPrefixFlag = cli.BoolFlag{ Name: "enable-0x-prefix", Usage: "Addres use 0x-prefix (Deprecated: this is on by default, to use xdc prefix use --enable-xdc-prefix)", - } + } EnableXDCPrefixFlag = cli.BoolFlag{ Name: "enable-xdc-prefix", Usage: "Addres use xdc-prefix (default = false)", @@ -358,6 +358,11 @@ var ( Name: "vmdebug", Usage: "Record information useful for VM and contract debugging", } + RPCGlobalGasCapFlag = cli.Uint64Flag{ + Name: "rpc.gascap", + Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", + Value: eth.DefaultConfig.RPCGasCap, + } // Logging and debug settings EthStatsURLFlag = cli.StringFlag{ Name: "ethstats", @@ -1176,6 +1181,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // TODO(fjl): force-enable this in --dev mode cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name) } + if cfg.RPCGasCap != 0 { + log.Info("Set global gas cap", "cap", cfg.RPCGasCap) + } else { + log.Info("Global gas cap disabled") + } if ctx.GlobalIsSet(StoreRewardFlag.Name) { common.StoreRewardFolder = filepath.Join(stack.DataDir(), "XDC", "rewards") if _, err := os.Stat(common.StoreRewardFolder); os.IsNotExist(err) { diff --git a/eth/api_backend.go b/eth/api_backend.go index b761f67171..c087ec2d38 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -348,6 +348,10 @@ func (b *EthApiBackend) EventMux() *event.TypeMux { return b.eth.EventMux() } +func (b *EthApiBackend) RPCGasCap() uint64 { + return b.eth.config.RPCGasCap +} + func (b *EthApiBackend) AccountManager() *accounts.Manager { return b.eth.AccountManager() } @@ -377,6 +381,10 @@ func (b *EthApiBackend) GetEngine() consensus.Engine { return b.eth.engine } +func (b *EthApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) { + return b.eth.stateAtBlock(block, reexec, base, checkLive) +} + func (s *EthApiBackend) GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int { header := s.eth.blockchain.GetHeaderByHash(hash) if header != nil { diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 2fb5f6650c..78788e41bb 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -69,6 +69,13 @@ type txTraceContext struct { block common.Hash // Hash of the block containing the transaction } +// TraceCallConfig is the config for traceCall API. It holds one more +// field to override the state for tracing. +type TraceCallConfig struct { + TraceConfig + StateOverrides *ethapi.StateOverride +} + // txTraceResult is the result of a single transaction trace. type txTraceResult struct { Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer @@ -622,6 +629,59 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) } +// TraceCall lets you trace a given eth_call. It collects the structured logs +// created during the execution of EVM if the given transaction was added on +// top of the provided block and returns them as a JSON object. +// You can provide -2 as a block number to trace on top of the pending block. +func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { + // Try to retrieve the specified block + var ( + err error + block *types.Block + ) + if hash, ok := blockNrOrHash.Hash(); ok { + block, err = api.eth.ApiBackend.BlockByHash(ctx, hash) + } else if number, ok := blockNrOrHash.Number(); ok { + if number == rpc.PendingBlockNumber { + // We don't have access to the miner here. For tracing 'future' transactions, + // it can be done with block- and state-overrides instead, which offers + // more flexibility and stability than trying to trace on 'pending', since + // the contents of 'pending' is unstable and probably not a true representation + // of what the next actual block is likely to contain. + return nil, errors.New("tracing on top of pending is not supported") + } + block, err = api.eth.ApiBackend.BlockByNumber(ctx, number) + } else { + return nil, errors.New("invalid arguments; neither block nor hash specified") + } + if err != nil { + return nil, err + } + // try to recompute the state + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + statedb, err := api.eth.ApiBackend.StateAtBlock(ctx, block, reexec, nil, true) + if err != nil { + return nil, err + } + // Apply the customized state rules if required. + if config != nil { + if err := config.StateOverrides.Apply(statedb); err != nil { + return nil, err + } + } + // Execute the trace + msg := args.ToMessage(api.eth.ApiBackend, block.Number(), api.eth.ApiBackend.RPCGasCap()) + vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + var traceConfig *TraceConfig + if config != nil { + traceConfig = &config.TraceConfig + } + return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, traceConfig) +} + // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. diff --git a/eth/config.go b/eth/config.go index c932a7ae44..73a95aa675 100644 --- a/eth/config.go +++ b/eth/config.go @@ -50,7 +50,8 @@ var DefaultConfig = Config{ TrieTimeout: 5 * time.Minute, GasPrice: big.NewInt(0.25 * params.Shannon), - TxPool: core.DefaultTxPoolConfig, + TxPool: core.DefaultTxPoolConfig, + RPCGasCap: 25000000, GPO: gasprice.Config{ Blocks: 20, Percentile: 60, @@ -114,6 +115,9 @@ type Config struct { // Miscellaneous options DocRoot string `toml:"-"` + + // RPCGasCap is the global gas cap for eth-call variants. + RPCGasCap uint64 } type configMarshaling struct { diff --git a/eth/gen_config.go b/eth/gen_config.go index e58954b7b2..1959ee559d 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -4,46 +4,52 @@ package eth import ( "math/big" + "time" "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/gasprice" ) -var _ = (*configMarshaling)(nil) - +// MarshalTOML marshals as TOML. func (c Config) MarshalTOML() (interface{}, error) { type Config struct { Genesis *core.Genesis `toml:",omitempty"` NetworkId uint64 SyncMode downloader.SyncMode + NoPruning bool LightServ int `toml:",omitempty"` LightPeers int `toml:",omitempty"` SkipBcVersionCheck bool `toml:"-"` DatabaseHandles int `toml:"-"` DatabaseCache int + TrieCache int + TrieTimeout time.Duration Etherbase common.Address `toml:",omitempty"` MinerThreads int `toml:",omitempty"` - ExtraData hexutil.Bytes `toml:",omitempty"` + ExtraData []byte `toml:",omitempty"` GasPrice *big.Int Ethash ethash.Config TxPool core.TxPoolConfig GPO gasprice.Config EnablePreimageRecording bool DocRoot string `toml:"-"` + RPCGasCap uint64 } var enc Config enc.Genesis = c.Genesis enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode + enc.NoPruning = c.NoPruning enc.LightServ = c.LightServ enc.LightPeers = c.LightPeers enc.SkipBcVersionCheck = c.SkipBcVersionCheck enc.DatabaseHandles = c.DatabaseHandles enc.DatabaseCache = c.DatabaseCache + enc.TrieCache = c.TrieCache + enc.TrieTimeout = c.TrieTimeout enc.Etherbase = c.Etherbase enc.MinerThreads = c.MinerThreads enc.ExtraData = c.ExtraData @@ -53,28 +59,34 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.GPO = c.GPO enc.EnablePreimageRecording = c.EnablePreimageRecording enc.DocRoot = c.DocRoot + enc.RPCGasCap = c.RPCGasCap return &enc, nil } +// UnmarshalTOML unmarshals from TOML. func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { type Config struct { Genesis *core.Genesis `toml:",omitempty"` NetworkId *uint64 SyncMode *downloader.SyncMode + NoPruning *bool LightServ *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"` SkipBcVersionCheck *bool `toml:"-"` DatabaseHandles *int `toml:"-"` DatabaseCache *int + TrieCache *int + TrieTimeout *time.Duration Etherbase *common.Address `toml:",omitempty"` MinerThreads *int `toml:",omitempty"` - ExtraData *hexutil.Bytes `toml:",omitempty"` + ExtraData []byte `toml:",omitempty"` GasPrice *big.Int Ethash *ethash.Config TxPool *core.TxPoolConfig GPO *gasprice.Config EnablePreimageRecording *bool DocRoot *string `toml:"-"` + RPCGasCap *uint64 } var dec Config if err := unmarshal(&dec); err != nil { @@ -89,6 +101,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.SyncMode != nil { c.SyncMode = *dec.SyncMode } + if dec.NoPruning != nil { + c.NoPruning = *dec.NoPruning + } if dec.LightServ != nil { c.LightServ = *dec.LightServ } @@ -104,6 +119,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.DatabaseCache != nil { c.DatabaseCache = *dec.DatabaseCache } + if dec.TrieCache != nil { + c.TrieCache = *dec.TrieCache + } + if dec.TrieTimeout != nil { + c.TrieTimeout = *dec.TrieTimeout + } if dec.Etherbase != nil { c.Etherbase = *dec.Etherbase } @@ -111,7 +132,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { c.MinerThreads = *dec.MinerThreads } if dec.ExtraData != nil { - c.ExtraData = *dec.ExtraData + c.ExtraData = dec.ExtraData } if dec.GasPrice != nil { c.GasPrice = dec.GasPrice @@ -131,5 +152,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.DocRoot != nil { c.DocRoot = *dec.DocRoot } + if dec.RPCGasCap != nil { + c.RPCGasCap = *dec.RPCGasCap + } return nil } diff --git a/eth/state_accessor.go b/eth/state_accessor.go new file mode 100644 index 0000000000..140830c5ea --- /dev/null +++ b/eth/state_accessor.go @@ -0,0 +1,137 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "fmt" + "time" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" + "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/trie" +) + +// stateAtBlock retrieves the state database associated with a certain block. +// If no state is locally available for the given block, a number of blocks +// are attempted to be reexecuted to generate the desired state. The optional +// base layer statedb can be passed then it's regarded as the statedb of the +// parent block. +func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (statedb *state.StateDB, err error) { + var ( + current *types.Block + database state.Database + report = true + origin = block.NumberU64() + ) + // Check the live database first if we have the state fully available, use that. + if checkLive { + statedb, err = eth.blockchain.StateAt(block.Root()) + if err == nil { + return statedb, nil + } + } + if base != nil { + // The optional base statedb is given, mark the start point as parent block + statedb, database, report = base, base.Database(), false + current = eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + } else { + // Otherwise try to reexec blocks until we find a state or reach our limit + current = block + + // Create an ephemeral trie.Database for isolating the live one. Otherwise + // the internal junks created by tracing will be persisted into the disk. + database = state.NewDatabaseWithCache(eth.chainDb, 16) + // If we didn't check the dirty database, do check the clean one, otherwise + // we would rewind past a persisted block (specific corner case is chain + // tracing from the genesis). + if !checkLive { + statedb, err = state.New(current.Root(), database) + if err == nil { + return statedb, nil + } + } + // Database does not have the state for the given block, try to regenerate + for i := uint64(0); i < reexec; i++ { + if current.NumberU64() == 0 { + return nil, errors.New("genesis state is missing") + } + parent := eth.blockchain.GetBlock(current.ParentHash(), current.NumberU64()-1) + if parent == nil { + return nil, fmt.Errorf("missing block %v %d", current.ParentHash(), current.NumberU64()-1) + } + current = parent + + statedb, err = state.New(current.Root(), database) + if err == nil { + break + } + } + if err != nil { + switch err.(type) { + case *trie.MissingNodeError: + return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) + default: + return nil, err + } + } + } + // State was available at historical point, regenerate + var ( + start = time.Now() + logged time.Time + parent common.Hash + ) + for current.NumberU64() < origin { + // Print progress logs if long enough time elapsed + if time.Since(logged) > 8*time.Second && report { + log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64()-1, "elapsed", time.Since(start)) + logged = time.Now() + } + // Retrieve the next block to regenerate and process it + next := current.NumberU64() + 1 + if current = eth.blockchain.GetBlockByNumber(next); current == nil { + return nil, fmt.Errorf("block #%d not found", next) + } + _, _, _, err := eth.blockchain.Processor().Process(current, statedb, nil, vm.Config{}, nil) + if err != nil { + return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) + } + // Finalize the state so any modifications are written to the trie + root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(current.Number())) + if err != nil { + return nil, err + } + statedb, err = state.New(root, database) + if err != nil { + return nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err) + } + database.TrieDB().Reference(root, common.Hash{}) + if parent != (common.Hash{}) { + database.TrieDB().Dereference(parent) + } + parent = root + } + if report { + nodes, imgs := database.TrieDB().Size() + log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) + } + return statedb, nil +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index eda0403c30..0f75a0bd09 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1167,7 +1167,7 @@ type CallArgs struct { // ToMessage converts CallArgs to the Message type used by the core evm // TODO: set balanceTokenFee -func (args *CallArgs) ToMessage(b Backend, number *big.Int) types.Message { +func (args *CallArgs) ToMessage(b Backend, number *big.Int, globalGasCap uint64) types.Message { // Set sender address or use a default if none specified var addr common.Address if args.From == nil || *args.From == (common.Address{}) { @@ -1181,14 +1181,17 @@ func (args *CallArgs) ToMessage(b Backend, number *big.Int) types.Message { } // Set default gas & gas price if none were set - var gas uint64 - if args.Gas != nil { - gas = *(*uint64)(args.Gas) - } + gas := globalGasCap if gas == 0 { - gas = math.MaxUint64 / 2 + gas = uint64(math.MaxUint64 / 2) + } + if args.Gas != nil { + gas = uint64(*args.Gas) + } + if globalGasCap != 0 && globalGasCap < gas { + log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) + gas = globalGasCap } - gasPrice := new(big.Int) if args.GasPrice != nil { gasPrice = args.GasPrice.ToInt() @@ -1220,7 +1223,7 @@ func (args *CallArgs) ToMessage(b Backend, number *big.Int) types.Message { return msg } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error, error) { +func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) ([]byte, uint64, bool, error, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) statedb, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) @@ -1231,7 +1234,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo return nil, 0, false, err, nil } - msg := args.ToMessage(b, header.Number) + msg := args.ToMessage(b, header.Number, globalGasCap) // Setup context so it may be cancelled the call has completed // or, in case of unmetered gas, setup a context with a timeout. @@ -1325,7 +1328,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &latest } - result, _, failed, err, vmErr := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, vm.Config{}, 5*time.Second) + result, _, failed, err, vmErr := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) if err != nil { return nil, err } @@ -1337,7 +1340,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr return (hexutil.Bytes)(result), vmErr } -func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) { +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) { // Retrieve the base state and mutate it with any overrides state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { @@ -1370,13 +1373,18 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash } hi = block.GasLimit() } + // Recap the highest gas allowance with specified gascap. + if gasCap != 0 && hi > gasCap { + log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) + hi = gasCap + } cap = hi // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) (bool, []byte, error, error) { args.Gas = (*hexutil.Uint64)(&gas) - res, _, failed, err, vmErr := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0) + res, _, failed, err, vmErr := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap) if err != nil { if errors.Is(err, vm.ErrOutOfGas) || errors.Is(err, core.ErrIntrinsicGas) { return false, nil, nil, nil // Special case, raise gas limit @@ -1451,7 +1459,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, bl if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } - return DoEstimateGas(ctx, s.b, args, bNrOrHash, overrides) + return DoEstimateGas(ctx, s.b, args, bNrOrHash, overrides, s.b.RPCGasCap()) } // ExecutionResult groups all structured logs emitted by the EVM @@ -2201,7 +2209,7 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { AccessList: args.AccessList, } pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, nil) + estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, nil, b.RPCGasCap()) if err != nil { return err } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index efad874792..00a9aea8ac 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -51,6 +51,7 @@ type Backend interface { ChainDb() ethdb.Database EventMux() *event.TypeMux AccountManager() *accounts.Manager + RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection XDCxService() *XDCx.XDCX LendingService() *XDCxlending.Lending diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index ac3b7c104d..13289a33fb 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -420,6 +420,12 @@ web3._extend({ params: 2, inputFormatter: [null, null] }), + new web3._extend.Method({ + name: 'traceCall', + call: 'debug_traceCall', + params: 3, + inputFormatter: [null, null, null] + }), new web3._extend.Method({ name: 'preimage', call: 'debug_preimage', diff --git a/les/api_backend.go b/les/api_backend.go index 493613399a..abdd952a6e 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -269,6 +269,10 @@ func (b *LesApiBackend) AccountManager() *accounts.Manager { return b.eth.accountManager } +func (b *LesApiBackend) RPCGasCap() uint64 { + return b.eth.config.RPCGasCap +} + func (b *LesApiBackend) BloomStatus() (uint64, uint64) { if b.eth.bloomIndexer == nil { return 0, 0