From f70e4c1253bcee954024a0a6929e77f3aa93e031 Mon Sep 17 00:00:00 2001 From: Jongwhan Lee <51560997+leejw51crypto@users.noreply.github.com> Date: Thu, 21 Oct 2021 04:14:39 +0900 Subject: [PATCH] rpc: `eth_resend` (#684) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Problem: missing json rpc for eth_resend #675 add unit test restore Update server/start.go thanks~ Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> tidy up checkTxFee change comments * fix lint * Apply suggestions from code review Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- rpc/ethereum/backend/backend.go | 9 +++++++- rpc/ethereum/namespaces/eth/api.go | 35 ++++++++++++++++++++++++++++++ server/config/config.go | 5 +++++ server/flags/flags.go | 1 + server/start.go | 5 +++-- tests/rpc/rpc_test.go | 15 +++++++++++-- 6 files changed, 65 insertions(+), 5 deletions(-) diff --git a/rpc/ethereum/backend/backend.go b/rpc/ethereum/backend/backend.go index 3dfbaa00..899003a1 100644 --- a/rpc/ethereum/backend/backend.go +++ b/rpc/ethereum/backend/backend.go @@ -42,7 +42,9 @@ import ( // Implemented by EVMBackend. type Backend interface { // General Ethereum API - RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection + RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection + RPCTxFeeCap() float64 // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for, // send-transction variants. The unit is ether. + RPCMinGasPrice() int64 SuggestGasTipCap() (*big.Int, error) @@ -779,6 +781,11 @@ func (e *EVMBackend) RPCGasCap() uint64 { return e.cfg.JSONRPC.GasCap } +// RPCGasCap is the global gas cap for eth-call variants. +func (e *EVMBackend) RPCTxFeeCap() float64 { + return e.cfg.JSONRPC.TxFeeCap +} + // RPCFilterCap is the limit for total number of filters that can be created func (e *EVMBackend) RPCFilterCap() int32 { return e.cfg.JSONRPC.FilterCap diff --git a/rpc/ethereum/namespaces/eth/api.go b/rpc/ethereum/namespaces/eth/api.go index 6fca1366..56aa5de7 100644 --- a/rpc/ethereum/namespaces/eth/api.go +++ b/rpc/ethereum/namespaces/eth/api.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -416,6 +417,7 @@ func (e *PublicAPI) FillTransaction(args evmtypes.TransactionArgs) (*rpctypes.Si // Assemble the transaction and obtain rlp tx := args.ToTransaction().AsTransaction() + data, err := tx.MarshalBinary() if err != nil { return nil, err @@ -510,6 +512,26 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) return txHash, nil } +// checkTxFee is an internal function used to check whether the fee of +// the given transaction is _reasonable_(under the cap). +func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { + // Short circuit if there is no cap for transaction fee at all. + if cap == 0 { + return nil + } + totalfee := new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))) + // 1 photon in 10^18 aphoton + oneToken := new(big.Float).SetInt(big.NewInt(params.Ether)) + // quo = rounded(x/y) + feeEth := new(big.Float).Quo(totalfee, oneToken) + // no need to check error from parsing + feeFloat, _ := feeEth.Float64() + if feeFloat > cap { + return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap) + } + return nil +} + // Resend accepts an existing transaction and a new gas price and limit. It will remove // the given transaction from the pool and reinsert it with the new gas price and limit. func (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { @@ -525,6 +547,19 @@ func (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, g matchTx := args.ToTransaction().AsTransaction() + // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. + price := matchTx.GasPrice() + if gasPrice != nil { + price = gasPrice.ToInt() + } + gas := matchTx.Gas() + if gasLimit != nil { + gas = uint64(*gasLimit) + } + if err := checkTxFee(price, gas, e.backend.RPCTxFeeCap()); err != nil { + return common.Hash{}, err + } + pending, err := e.backend.PendingTransactions() if err != nil { return common.Hash{}, err diff --git a/server/config/config.go b/server/config/config.go index b1814b1f..dc289c5c 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -30,6 +30,9 @@ const ( DefaultGasCap uint64 = 25000000 DefaultFilterCap int32 = 200 + + // default 1.0 eth + DefaultTxFeeCap float64 = 1.0 ) var evmTracers = []string{DefaultEVMTracer, "markdown", "struct", "access_list"} @@ -61,6 +64,8 @@ type JSONRPCConfig struct { WsAddress string `mapstructure:"ws-address"` // GasCap is the global gas cap for eth-call variants. GasCap uint64 `mapstructure:"gas-cap"` + // TxFeeCap is the global tx-fee cap for send transaction + TxFeeCap float64 `mapstructure:"txfee-cap"` // FilterCap is the global cap for total number of filters that can be created. FilterCap int32 `mapstructure:"filter-cap"` // Enable defines if the EVM RPC server should be enabled. diff --git a/server/flags/flags.go b/server/flags/flags.go index 1383e7c8..80da9088 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -31,6 +31,7 @@ const ( JSONRPCAddress = "json-rpc.address" JSONWsAddress = "json-rpc.ws-address" JSONRPCGasCap = "json-rpc.gas-cap" + JSONRPCTxFeeCap = "json-rpc.txfee-cap" JSONRPCFilterCap = "json-rpc.filter-cap" ) diff --git a/server/start.go b/server/start.go index d71ee32f..e2283853 100644 --- a/server/start.go +++ b/server/start.go @@ -132,7 +132,7 @@ which accepts a path for the resulting pprof file. cmd.Flags().String(srvflags.Address, "tcp://0.0.0.0:26658", "Listen address") cmd.Flags().String(srvflags.Transport, "socket", "Transport protocol: socket, grpc") cmd.Flags().String(srvflags.TraceStore, "", "Enable KVStore tracing to an output file") - cmd.Flags().String(server.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)") + cmd.Flags().String(server.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photon;0.0001stake)") cmd.Flags().IntSlice(server.FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary") cmd.Flags().Uint64(server.FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node") cmd.Flags().Uint64(server.FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") @@ -155,7 +155,8 @@ which accepts a path for the resulting pprof file. cmd.Flags().StringSlice(srvflags.JSONRPCAPI, config.GetDefaultAPINamespaces(), "Defines a list of JSON-RPC namespaces that should be enabled") 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().Uint64(srvflags.JSONRPCGasCap, config.DefaultGasCap, "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)") + cmd.Flags().Uint64(srvflags.JSONRPCGasCap, config.DefaultGasCap, "Sets a cap on gas that can be used in eth_call/estimateGas unit is aphoton (0=infinite)") + cmd.Flags().Float64(srvflags.JSONRPCTxFeeCap, config.DefaultTxFeeCap, "Sets a cap on transaction fee that can be sent via the RPC APIs (1 = default 1 photon)") cmd.Flags().Int32(srvflags.JSONRPCFilterCap, config.DefaultFilterCap, "Sets the global cap for total number of filters that can be created") 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)") diff --git a/tests/rpc/rpc_test.go b/tests/rpc/rpc_test.go index ee95a0cb..d68a41b0 100644 --- a/tests/rpc/rpc_test.go +++ b/tests/rpc/rpc_test.go @@ -360,7 +360,6 @@ func TestEth_GetStorageAt(t *testing.T) { } func TestEth_GetProof(t *testing.T) { - rpcRes := call(t, "eth_sendTransaction", makeEthTxParam()) var hash hexutil.Bytes @@ -401,7 +400,6 @@ func TestEth_GetCode(t *testing.T) { } func TestEth_SendTransaction_Transfer(t *testing.T) { - rpcRes := call(t, "eth_sendTransaction", makeEthTxParam()) var hash hexutil.Bytes @@ -1027,3 +1025,16 @@ func makeEthTxParam() []map[string]string { return param } + +func TestEth_EthResend(t *testing.T) { + tx := make(map[string]string) + tx["from"] = "0x" + fmt.Sprintf("%x", from) + tx["to"] = "0x0000000000000000000000000000000012341234" + tx["value"] = "0x16345785d8a0000" + tx["nonce"] = "0x2" + tx["gasLimit"] = "0x5208" + tx["gasPrice"] = "0x55ae82600" + param := []interface{}{tx, "0x1", "0x2"} + _, rpcerror := callWithError("eth_resend", param) + require.Equal(t, "transaction 0x3bf28b46ee1bb3925e50ec6003f899f95913db4b0f579c4e7e887efebf9ecd1b not found", fmt.Sprintf("%s", rpcerror)) +}