imp(rpc): restrict unprotected txs on the node RPC (#1143)

* imp(rpc): restrict unprotected txs on the node RPC

* lint

* send raw transaction

* c++
This commit is contained in:
Federico Kunze Küllmer 2022-06-22 12:50:36 +02:00 committed by GitHub
parent 8817e8052a
commit 3b852f723e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 97 additions and 54 deletions

View File

@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### API Breaking
* (rpc) [\#1143](https://github.com/evmos/ethermint/pull/1143) Restrict unprotected txs on the node JSON-RPC configuration.
* (all) [\#1137](https://github.com/evmos/ethermint/pull/1137) Rename go module to `evmos/ethermint`
### Improvements

View File

@ -44,16 +44,21 @@ const (
)
// APICreator creates the JSON-RPC API implementations.
type APICreator = func(*server.Context, client.Context, *rpcclient.WSClient) []rpc.API
type APICreator = func(
ctx *server.Context,
clientCtx client.Context,
tendermintWebsocketClient *rpcclient.WSClient,
allowUnprotectedTxs bool,
) []rpc.API
// apiCreators defines the JSON-RPC API namespaces.
var apiCreators map[string]APICreator
func init() {
apiCreators = map[string]APICreator{
EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient) []rpc.API {
EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
nonceLock := new(types.AddrLocker)
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx)
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
return []rpc.API{
{
Namespace: EthNamespace,
@ -69,7 +74,7 @@ func init() {
},
}
},
Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient) []rpc.API {
Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient, bool) []rpc.API {
return []rpc.API{
{
Namespace: Web3Namespace,
@ -79,7 +84,7 @@ func init() {
},
}
},
NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, _ bool) []rpc.API {
return []rpc.API{
{
Namespace: NetNamespace,
@ -89,8 +94,8 @@ func init() {
},
}
},
PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx)
PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
return []rpc.API{
{
Namespace: PersonalNamespace,
@ -100,7 +105,7 @@ func init() {
},
}
},
TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient) []rpc.API {
TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient, _ bool) []rpc.API {
return []rpc.API{
{
Namespace: TxPoolNamespace,
@ -110,8 +115,8 @@ func init() {
},
}
},
DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx)
DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
return []rpc.API{
{
Namespace: DebugNamespace,
@ -121,8 +126,8 @@ func init() {
},
}
},
MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx)
MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
return []rpc.API{
{
Namespace: MinerNamespace,
@ -136,12 +141,12 @@ func init() {
}
// GetRPCAPIs returns the list of all APIs
func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, selectedAPIs []string) []rpc.API {
func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool, selectedAPIs []string) []rpc.API {
var apis []rpc.API
for _, ns := range selectedAPIs {
if creator, ok := apiCreators[ns]; ok {
apis = append(apis, creator(ctx, clientCtx, tmWSClient)...)
apis = append(apis, creator(ctx, clientCtx, tmWSClient, allowUnprotectedTxs)...)
} else {
ctx.Logger.Error("invalid namespace value", "namespace", ns)
}

View File

@ -44,6 +44,7 @@ type EVMBackend interface {
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
RPCTxFeeCap() float64 // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for send-transaction variants. The unit is ether.
UnprotectedAllowed() bool
RPCMinGasPrice() int64
SuggestGasTipCap(baseFee *big.Int) (*big.Int, error)
@ -86,16 +87,17 @@ var _ BackendI = (*Backend)(nil)
// Backend implements the BackendI interface
type Backend struct {
ctx context.Context
clientCtx client.Context
queryClient *types.QueryClient // gRPC query client
logger log.Logger
chainID *big.Int
cfg config.Config
ctx context.Context
clientCtx client.Context
queryClient *types.QueryClient // gRPC query client
logger log.Logger
chainID *big.Int
cfg config.Config
allowUnprotectedTxs bool
}
// NewBackend creates a new Backend instance for cosmos and ethereum namespaces
func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context) *Backend {
func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context, allowUnprotectedTxs bool) *Backend {
chainID, err := ethermint.ParseChainID(clientCtx.ChainID)
if err != nil {
panic(err)
@ -104,11 +106,12 @@ func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context
appConf := config.GetConfig(ctx.Viper)
return &Backend{
ctx: context.Background(),
clientCtx: clientCtx,
queryClient: types.NewQueryClient(clientCtx),
logger: logger.With("module", "backend"),
chainID: chainID,
cfg: appConf,
ctx: context.Background(),
clientCtx: clientCtx,
queryClient: types.NewQueryClient(clientCtx),
logger: logger.With("module", "backend"),
chainID: chainID,
cfg: appConf,
allowUnprotectedTxs: allowUnprotectedTxs,
}
}

View File

@ -638,7 +638,15 @@ func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, e
return common.Hash{}, err
}
txHash := msg.AsTransaction().Hash()
ethTx := msg.AsTransaction()
// check the local node config in case unprotected txs are disabled
if !b.UnprotectedAllowed() && !ethTx.Protected() {
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
}
txHash := ethTx.Hash()
// Broadcast transaction in sync mode (default)
// NOTE: If error is encountered on the node, the broadcast will not return an error
@ -956,3 +964,9 @@ func (b *Backend) GetEthereumMsgsFromTendermintBlock(resBlock *tmrpctypes.Result
return result
}
// UnprotectedAllowed returns the node configuration value for allowing
// unprotected transactions (i.e not replay-protected)
func (b Backend) UnprotectedAllowed() bool {
return b.allowUnprotectedTxs
}

View File

@ -525,6 +525,12 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
return common.Hash{}, err
}
// check the local node config in case unprotected txs are disabled
if !e.backend.UnprotectedAllowed() && !tx.Protected() {
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
}
ethereumTx := &evmtypes.MsgEthereumTx{}
if err := ethereumTx.FromEthereumTx(tx); err != nil {
e.logger.Error("transaction converting failed", "error", err.Error())

View File

@ -47,6 +47,8 @@ const (
DefaultHTTPTimeout = 30 * time.Second
DefaultHTTPIdleTimeout = 120 * time.Second
// DefaultAllowUnprotectedTxs value is false
DefaultAllowUnprotectedTxs = false
)
var evmTracers = []string{"json", "markdown", "struct", "access_list"}
@ -98,6 +100,9 @@ type JSONRPCConfig struct {
HTTPTimeout time.Duration `mapstructure:"http-timeout"`
// HTTPIdleTimeout is the idle timeout of http json-rpc server.
HTTPIdleTimeout time.Duration `mapstructure:"http-idle-timeout"`
// AllowUnprotectedTxs restricts unprotected (non EIP155 signed) transactions to be submitted via
// the node's RPC when global parameter is disabled.
AllowUnprotectedTxs bool `mapstructure:"allow-unprotected-txs"`
}
// TLSConfig defines the certificate and matching private key for the server.
@ -183,19 +188,20 @@ func GetAPINamespaces() []string {
// DefaultJSONRPCConfig returns an EVM config with the JSON-RPC API enabled by default
func DefaultJSONRPCConfig() *JSONRPCConfig {
return &JSONRPCConfig{
Enable: true,
API: GetDefaultAPINamespaces(),
Address: DefaultJSONRPCAddress,
WsAddress: DefaultJSONRPCWsAddress,
GasCap: DefaultGasCap,
EVMTimeout: DefaultEVMTimeout,
TxFeeCap: DefaultTxFeeCap,
FilterCap: DefaultFilterCap,
FeeHistoryCap: DefaultFeeHistoryCap,
BlockRangeCap: DefaultBlockRangeCap,
LogsCap: DefaultLogsCap,
HTTPTimeout: DefaultHTTPTimeout,
HTTPIdleTimeout: DefaultHTTPIdleTimeout,
Enable: true,
API: GetDefaultAPINamespaces(),
Address: DefaultJSONRPCAddress,
WsAddress: DefaultJSONRPCWsAddress,
GasCap: DefaultGasCap,
EVMTimeout: DefaultEVMTimeout,
TxFeeCap: DefaultTxFeeCap,
FilterCap: DefaultFilterCap,
FeeHistoryCap: DefaultFeeHistoryCap,
BlockRangeCap: DefaultBlockRangeCap,
LogsCap: DefaultLogsCap,
HTTPTimeout: DefaultHTTPTimeout,
HTTPIdleTimeout: DefaultHTTPIdleTimeout,
AllowUnprotectedTxs: DefaultAllowUnprotectedTxs,
}
}

View File

@ -62,6 +62,10 @@ http-timeout = "{{ .JSONRPC.HTTPTimeout }}"
# HTTPIdleTimeout is the idle timeout of http json-rpc server.
http-idle-timeout = "{{ .JSONRPC.HTTPIdleTimeout }}"
# AllowUnprotectedTxs restricts unprotected (non EIP155 signed) transactions to be submitted via
# the node's RPC when the global parameter is disabled.
allow-unprotected-txs = {{ .JSONRPC.AllowUnprotectedTxs }}
###############################################################################
### TLS Configuration ###
###############################################################################

View File

@ -32,18 +32,19 @@ const (
// JSON-RPC flags
const (
JSONRPCEnable = "json-rpc.enable"
JSONRPCAPI = "json-rpc.api"
JSONRPCAddress = "json-rpc.address"
JSONWsAddress = "json-rpc.ws-address"
JSONRPCGasCap = "json-rpc.gas-cap"
JSONRPCEVMTimeout = "json-rpc.evm-timeout"
JSONRPCTxFeeCap = "json-rpc.txfee-cap"
JSONRPCFilterCap = "json-rpc.filter-cap"
JSONRPCLogsCap = "json-rpc.logs-cap"
JSONRPCBlockRangeCap = "json-rpc.block-range-cap"
JSONRPCHTTPTimeout = "json-rpc.http-timeout"
JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout"
JSONRPCEnable = "json-rpc.enable"
JSONRPCAPI = "json-rpc.api"
JSONRPCAddress = "json-rpc.address"
JSONWsAddress = "json-rpc.ws-address"
JSONRPCGasCap = "json-rpc.gas-cap"
JSONRPCEVMTimeout = "json-rpc.evm-timeout"
JSONRPCTxFeeCap = "json-rpc.txfee-cap"
JSONRPCFilterCap = "json-rpc.filter-cap"
JSONRPCLogsCap = "json-rpc.logs-cap"
JSONRPCBlockRangeCap = "json-rpc.block-range-cap"
JSONRPCHTTPTimeout = "json-rpc.http-timeout"
JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout"
JSONRPCAllowUnprotectedTxs = "json-rpc.allow-unprotected-txs"
)
// EVM flags

View File

@ -36,8 +36,10 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn
rpcServer := ethrpc.NewServer()
allowUnprotectedTxs := config.JSONRPC.AllowUnprotectedTxs
rpcAPIArr := config.JSONRPC.API
apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient, rpcAPIArr)
apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient, allowUnprotectedTxs, rpcAPIArr)
for _, api := range apis {
if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {

View File

@ -162,6 +162,7 @@ which accepts a path for the resulting pprof file.
cmd.Flags().Duration(srvflags.JSONRPCEVMTimeout, config.DefaultEVMTimeout, "Sets a timeout used for eth_call (0=infinite)")
cmd.Flags().Duration(srvflags.JSONRPCHTTPTimeout, config.DefaultHTTPTimeout, "Sets a read/write timeout for json-rpc http server (0=infinite)")
cmd.Flags().Duration(srvflags.JSONRPCHTTPIdleTimeout, config.DefaultHTTPIdleTimeout, "Sets a idle timeout for json-rpc http server (0=infinite)")
cmd.Flags().Bool(srvflags.JSONRPCAllowUnprotectedTxs, config.DefaultAllowUnprotectedTxs, "Allow for unprotected (non EIP155 signed) transactions to be submitted via the node's RPC when the global parameter is disabled")
cmd.Flags().Int32(srvflags.JSONRPCLogsCap, config.DefaultLogsCap, "Sets the max number of results can be returned from single `eth_getLogs` query")
cmd.Flags().Int32(srvflags.JSONRPCBlockRangeCap, config.DefaultBlockRangeCap, "Sets the max block range allowed for `eth_getLogs` query")