diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b232b8e80..f1e8e2421 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -186,6 +186,7 @@ var ( utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, utils.RPCGlobalGasCap, + utils.RPCGlobalTxFeeCap, } whisperFlags = []cli.Flag{ diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 5c429056e..ee97e1a97 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -176,6 +176,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, utils.RPCGlobalGasCap, + utils.RPCGlobalTxFeeCap, utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 19d7e99a4..dbc1cdad1 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -478,6 +478,11 @@ var ( Name: "rpc.gascap", Usage: "Sets a cap on gas that can be used in eth_call/estimateGas", } + RPCGlobalTxFeeCap = cli.Float64Flag{ + Name: "rpc.txfeecap", + Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)", + Value: eth.DefaultConfig.RPCTxFeeCap, + } // Logging and debug settings EthStatsURLFlag = cli.StringFlag{ Name: "ethstats", @@ -1560,6 +1565,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(RPCGlobalGasCap.Name) { cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name)) } + if ctx.GlobalIsSet(RPCGlobalTxFeeCap.Name) { + cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCap.Name) + } if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { urls := ctx.GlobalString(DNSDiscoveryFlag.Name) if urls == "" { diff --git a/eth/api_backend.go b/eth/api_backend.go index a82aee4eb..b6d727076 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -293,6 +293,10 @@ func (b *EthAPIBackend) RPCGasCap() *big.Int { return b.eth.config.RPCGasCap } +func (b *EthAPIBackend) RPCTxFeeCap() float64 { + return b.eth.config.RPCTxFeeCap +} + 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 45767810d..84a2f140a 100644 --- a/eth/config.go +++ b/eth/config.go @@ -64,6 +64,7 @@ var DefaultConfig = Config{ Blocks: 20, Percentile: 60, }, + RPCTxFeeCap: 1, // 1 ether } func init() { @@ -159,6 +160,10 @@ type Config struct { // RPCGasCap is the global gas cap for eth-call variants. RPCGasCap *big.Int `toml:",omitempty"` + // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for + // send-transction variants. The unit is ether. + RPCTxFeeCap float64 `toml:",omitempty"` + // Checkpoint is a hardcoded checkpoint which can be nil. Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` diff --git a/eth/gen_config.go b/eth/gen_config.go index 56980bc50..f3d189185 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -40,6 +40,7 @@ func (c Config) MarshalTOML() (interface{}, error) { TrieCleanCache int TrieDirtyCache int TrieTimeout time.Duration + SnapshotCache int Miner miner.Config Ethash ethash.Config TxPool core.TxPoolConfig @@ -49,10 +50,9 @@ func (c Config) MarshalTOML() (interface{}, error) { EWASMInterpreter string EVMInterpreter string RPCGasCap *big.Int `toml:",omitempty"` + RPCTxFeeCap float64 `toml:",omitempty"` Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` - OverrideIstanbul *big.Int `toml:",omitempty"` - OverrideMuirGlacier *big.Int `toml:",omitempty"` } var enc Config enc.Genesis = c.Genesis @@ -77,6 +77,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.TrieCleanCache = c.TrieCleanCache enc.TrieDirtyCache = c.TrieDirtyCache enc.TrieTimeout = c.TrieTimeout + enc.SnapshotCache = c.SnapshotCache enc.Miner = c.Miner enc.Ethash = c.Ethash enc.TxPool = c.TxPool @@ -86,6 +87,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.EWASMInterpreter = c.EWASMInterpreter enc.EVMInterpreter = c.EVMInterpreter enc.RPCGasCap = c.RPCGasCap + enc.RPCTxFeeCap = c.RPCTxFeeCap enc.Checkpoint = c.Checkpoint enc.CheckpointOracle = c.CheckpointOracle return &enc, nil @@ -116,6 +118,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { TrieCleanCache *int TrieDirtyCache *int TrieTimeout *time.Duration + SnapshotCache *int Miner *miner.Config Ethash *ethash.Config TxPool *core.TxPoolConfig @@ -125,10 +128,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { EWASMInterpreter *string EVMInterpreter *string RPCGasCap *big.Int `toml:",omitempty"` + RPCTxFeeCap *float64 `toml:",omitempty"` Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` - OverrideIstanbul *big.Int `toml:",omitempty"` - OverrideMuirGlacier *big.Int `toml:",omitempty"` } var dec Config if err := unmarshal(&dec); err != nil { @@ -200,6 +202,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.TrieTimeout != nil { c.TrieTimeout = *dec.TrieTimeout } + if dec.SnapshotCache != nil { + c.SnapshotCache = *dec.SnapshotCache + } if dec.Miner != nil { c.Miner = *dec.Miner } @@ -227,6 +232,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.RPCGasCap != nil { c.RPCGasCap = dec.RPCGasCap } + if dec.RPCTxFeeCap != nil { + c.RPCTxFeeCap = *dec.RPCTxFeeCap + } if dec.Checkpoint != nil { c.Checkpoint = dec.Checkpoint } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 7d17c9cd2..f26691710 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1520,6 +1520,13 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { // SubmitTransaction is a helper function that submits tx to txPool and logs a message. func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { + // If the transaction fee cap is already specified, ensure the + // fee of the given transaction is _reasonable_. + feeEth := new(big.Float).Quo(new(big.Float).SetInt(new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))), new(big.Float).SetInt(big.NewInt(params.Ether))) + feeFloat, _ := feeEth.Float64() + if b.RPCTxFeeCap() != 0 && feeFloat > b.RPCTxFeeCap() { + return common.Hash{}, fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, b.RPCTxFeeCap()) + } if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 245091df3..5f5f9a6ee 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -45,7 +45,8 @@ type Backend interface { ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool - RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection + RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection + RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs // Blockchain API SetHead(number uint64) diff --git a/les/api_backend.go b/les/api_backend.go index 756beaf6a..75f255cd9 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -262,6 +262,10 @@ func (b *LesApiBackend) RPCGasCap() *big.Int { return b.eth.config.RPCGasCap } +func (b *LesApiBackend) RPCTxFeeCap() float64 { + return b.eth.config.RPCTxFeeCap +} + func (b *LesApiBackend) BloomStatus() (uint64, uint64) { if b.eth.bloomIndexer == nil { return 0, 0