Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable h2c for http handler. add https handler for http2 #8610

Merged
merged 5 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion cmd/rpcdaemon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,20 @@ If the `--http.url` flag is set, then `--http.addr` and `--http.port` with both

note that this is NOT geth-style IPC. for that, read the next section, IPC endpoint(geth-compatible)


### HTTPS, HTTP2, and H2C

Erigon supports HTTPS, HTTP2, and H2C out of the box. H2C is served by the default HTTP handler.

To enable the HTTPS+HTTP2 server, add flag `--https.enabled`, along with providing flags `-https.cert="/path/to.cert"` and `--https.key=/path/to.key`

By default, the HTTPS server will run on the HTTP port + 363. use flag `--https.port` to set the port

The HTTPS server will inherit all other configuration parameters from http, for instance, enabling the websocket server, cors domains, or enabled namespaces

If the `--https.url` flag is set, then `--https.addr` and `--https.port` with both be ignored.


### IPC endpoint (geth compatible)

erigon supports the geth-style unix socket IPC. you can enable this with `--socket.enabled` flag,
Expand Down Expand Up @@ -266,7 +280,7 @@ The following table shows the current implementation status of Erigon's RPC daem
| eth_getFilterChanges | Yes | |
| eth_uninstallFilter | Yes | |
| eth_getLogs | Yes | |
| | | |
| interned spe | | |
| eth_accounts | No | deprecated |
| eth_sendRawTransaction | Yes | `remote`. |
| eth_sendTransaction | - | not yet implemented |
Expand Down
81 changes: 60 additions & 21 deletions cmd/rpcdaemon/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
rootCmd.PersistentFlags().StringVar(&cfg.PrivateApiAddr, "private.api.addr", "127.0.0.1:9090", "Erigon's components (txpool, rpcdaemon, sentry, downloader, ...) can be deployed as independent Processes on same/another server. Then components will connect to erigon by this internal grpc API. Example: 127.0.0.1:9090")
rootCmd.PersistentFlags().StringVar(&cfg.DataDir, "datadir", "", "path to Erigon working directory")
rootCmd.PersistentFlags().BoolVar(&cfg.GraphQLEnabled, "graphql", false, "enables graphql endpoint (disabled by default)")
rootCmd.PersistentFlags().StringVar(&cfg.HttpListenAddress, "http.addr", nodecfg.DefaultHTTPHost, "HTTP-RPC server listening interface")
rootCmd.PersistentFlags().StringVar(&cfg.TLSCertfile, "tls.cert", "", "certificate for client side TLS handshake")
rootCmd.PersistentFlags().StringVar(&cfg.TLSKeyFile, "tls.key", "", "key file for client side TLS handshake")
rootCmd.PersistentFlags().StringVar(&cfg.TLSCACert, "tls.cacert", "", "CA certificate for client side TLS handshake")
rootCmd.PersistentFlags().Uint64Var(&cfg.Gascap, "rpc.gascap", 50_000_000, "Sets a cap on gas that can be used in eth_call/estimateGas")
rootCmd.PersistentFlags().Uint64Var(&cfg.MaxTraces, "trace.maxtraces", 200, "Sets a limit on traces that can be returned in trace_filter")

Expand All @@ -110,17 +106,29 @@ func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
rootCmd.PersistentFlags().IntVar(&cfg.GRPCPort, "grpc.port", nodecfg.DefaultGRPCPort, "GRPC server listening port")
rootCmd.PersistentFlags().BoolVar(&cfg.GRPCHealthCheckEnabled, "grpc.healthcheck", false, "Enable GRPC health check")
rootCmd.PersistentFlags().Float64Var(&ethconfig.Defaults.RPCTxFeeCap, utils.RPCGlobalTxFeeCapFlag.Name, utils.RPCGlobalTxFeeCapFlag.Value, utils.RPCGlobalTxFeeCapFlag.Usage)
rootCmd.PersistentFlags().StringVar(&cfg.TLSCertfile, "tls.cert", "", "certificate for client side TLS handshake for GRPC")
rootCmd.PersistentFlags().StringVar(&cfg.TLSKeyFile, "tls.key", "", "key file for client side TLS handshake for GRPC")
rootCmd.PersistentFlags().StringVar(&cfg.TLSCACert, "tls.cacert", "", "CA certificate for client side TLS handshake for GRPC")

rootCmd.PersistentFlags().StringSliceVar(&cfg.API, "http.api", []string{"eth", "erigon"}, "API's offered over the RPC interface: eth,erigon,web3,net,debug,trace,txpool,db. Supported methods: https://github.com/ledgerwatch/erigon/tree/devel/cmd/rpcdaemon")

rootCmd.PersistentFlags().BoolVar(&cfg.HttpServerEnabled, "http.enabled", true, "enable http server")
rootCmd.PersistentFlags().StringVar(&cfg.HttpListenAddress, "http.addr", nodecfg.DefaultHTTPHost, "HTTP server listening interface")
rootCmd.PersistentFlags().IntVar(&cfg.HttpPort, "http.port", nodecfg.DefaultHTTPPort, "HTTP server listening port")
rootCmd.PersistentFlags().StringVar(&cfg.HttpURL, "http.url", "", "HTTP server listening url. will OVERRIDE http.addr and http.port. will NOT respect http paths. prefix supported are tcp, unix")
rootCmd.PersistentFlags().IntVar(&cfg.HttpPort, "http.port", nodecfg.DefaultHTTPPort, "HTTP-RPC server listening port")
rootCmd.PersistentFlags().StringSliceVar(&cfg.HttpCORSDomain, "http.corsdomain", []string{}, "Comma separated list of domains from which to accept cross origin requests (browser enforced)")
rootCmd.PersistentFlags().StringSliceVar(&cfg.HttpVirtualHost, "http.vhosts", nodecfg.DefaultConfig.HTTPVirtualHosts, "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.")
rootCmd.PersistentFlags().BoolVar(&cfg.HttpCompression, "http.compression", true, "Disable http compression")
rootCmd.PersistentFlags().StringSliceVar(&cfg.API, "http.api", []string{"eth", "erigon"}, "API's offered over the HTTP-RPC interface: eth,erigon,web3,net,debug,trace,txpool,db. Supported methods: https://github.com/ledgerwatch/erigon/tree/devel/cmd/rpcdaemon")
rootCmd.PersistentFlags().BoolVar(&cfg.WebsocketEnabled, "ws", false, "Enable Websockets - Same port as HTTP")
rootCmd.PersistentFlags().BoolVar(&cfg.WebsocketEnabled, "ws", false, "Enable Websockets - Same port as HTTP[S]")
rootCmd.PersistentFlags().BoolVar(&cfg.WebsocketCompression, "ws.compression", false, "Enable Websocket compression (RFC 7692)")

rootCmd.PersistentFlags().BoolVar(&cfg.HttpsServerEnabled, "https.enabled", false, "enable http server")
rootCmd.PersistentFlags().StringVar(&cfg.HttpsListenAddress, "https.addr", nodecfg.DefaultHTTPHost, "rpc HTTPS server listening interface")
rootCmd.PersistentFlags().IntVar(&cfg.HttpsPort, "https.port", 0, "rpc HTTPS server listening port. default to http+363 if not set")
rootCmd.PersistentFlags().StringVar(&cfg.HttpsURL, "https.url", "", "rpc HTTPS server listening url. will OVERRIDE https.addr and https.port. will NOT respect paths. prefix supported are tcp, unix")
rootCmd.PersistentFlags().StringVar(&cfg.HttpsCertfile, "https.cert", "", "certificate for rpc HTTPS server")
rootCmd.PersistentFlags().StringVar(&cfg.HttpsKeyFile, "https.key", "", "key file for rpc HTTPS server")

rootCmd.PersistentFlags().BoolVar(&cfg.SocketServerEnabled, "socket.enabled", false, "Enable IPC server")
rootCmd.PersistentFlags().StringVar(&cfg.SocketListenUrl, "socket.url", "unix:///var/run/erigon.sock", "IPC server listening url. prefix supported are tcp, unix")

Expand Down Expand Up @@ -610,24 +618,25 @@ func startRegularRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rp
logger.Info("Socket Endpoint opened", "url", socketUrl)
}

if cfg.HttpServerEnabled {
httpHandler := node.NewHTTPHandlerStack(srv, cfg.HttpCORSDomain, cfg.HttpVirtualHost, cfg.HttpCompression)
var wsHandler http.Handler
if cfg.WebsocketEnabled {
wsHandler = srv.WebsocketHandler([]string{"*"}, nil, cfg.WebsocketCompression, logger)
}

graphQLHandler := graphql.CreateHandler(defaultAPIList)
httpHandler := node.NewHTTPHandlerStack(srv, cfg.HttpCORSDomain, cfg.HttpVirtualHost, cfg.HttpCompression)
var wsHandler http.Handler
if cfg.WebsocketEnabled {
wsHandler = srv.WebsocketHandler([]string{"*"}, nil, cfg.WebsocketCompression, logger)
}
graphQLHandler := graphql.CreateHandler(defaultAPIList)
apiHandler, err := createHandler(cfg, defaultAPIList, httpHandler, wsHandler, graphQLHandler, nil)
if err != nil {
return err
}

apiHandler, err := createHandler(cfg, defaultAPIList, httpHandler, wsHandler, graphQLHandler, nil)
if err != nil {
return err
}
if cfg.HttpServerEnabled {
httpEndpoint := fmt.Sprintf("tcp://%s:%d", cfg.HttpListenAddress, cfg.HttpPort)
if cfg.HttpURL != "" {
httpEndpoint = cfg.HttpURL
}
listener, httpAddr, err := node.StartHTTPEndpoint(httpEndpoint, cfg.HTTPTimeouts, apiHandler)
listener, httpAddr, err := node.StartHTTPEndpoint(httpEndpoint, &node.HttpEndpointConfig{
Timeouts: cfg.HTTPTimeouts,
}, apiHandler)
if err != nil {
return fmt.Errorf("could not start RPC api: %w", err)
}
Expand All @@ -639,6 +648,34 @@ func startRegularRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rp
logger.Info("HTTP endpoint closed", "url", httpAddr)
}()
}
if cfg.HttpsURL != "" {
cfg.HttpsServerEnabled = true
}
if cfg.HttpsServerEnabled {
if cfg.HttpsPort == 0 {
cfg.HttpsPort = cfg.HttpPort + 363
}
httpsEndpoint := fmt.Sprintf("tcp://%s:%d", cfg.HttpsListenAddress, cfg.HttpsPort)
if cfg.HttpsURL != "" {
httpsEndpoint = cfg.HttpsURL
}
listener, httpAddr, err := node.StartHTTPEndpoint(httpsEndpoint, &node.HttpEndpointConfig{
Timeouts: cfg.HTTPTimeouts,
HTTPS: true,
CertFile: cfg.HttpsCertfile,
KeyFile: cfg.HttpsKeyFile,
}, apiHandler)
if err != nil {
return fmt.Errorf("could not start RPC api: %w", err)
}
info = append(info, "https.url", httpAddr)
defer func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = listener.Shutdown(shutdownCtx)
logger.Info("HTTPS endpoint closed", "url", httpAddr)
}()
}

var (
healthServer *grpcHealth.Server
Expand Down Expand Up @@ -797,7 +834,9 @@ func createEngineListener(cfg httpcfg.HttpCfg, engineApi []rpc.API, logger log.L
return nil, nil, "", err
}

engineListener, engineAddr, err := node.StartHTTPEndpoint(engineHttpEndpoint, cfg.AuthRpcTimeouts, engineApiHandler)
engineListener, engineAddr, err := node.StartHTTPEndpoint(engineHttpEndpoint, &node.HttpEndpointConfig{
Timeouts: cfg.AuthRpcTimeouts,
}, engineApiHandler)
if err != nil {
return nil, nil, "", fmt.Errorf("could not start RPC api: %w", err)
}
Expand Down
7 changes: 7 additions & 0 deletions cmd/rpcdaemon/cli/httpcfg/http_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ type HttpCfg struct {
AuthRpcVirtualHost []string
HttpCompression bool

HttpsServerEnabled bool
HttpsURL string
HttpsListenAddress string
HttpsPort int
HttpsCertfile string
HttpsKeyFile string

AuthRpcPort int
PrivateApiAddr string

Expand Down
7 changes: 6 additions & 1 deletion cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,12 @@ var (
}
HTTPEnabledFlag = cli.BoolFlag{
Name: "http",
Usage: "HTTP-RPC server (enabled by default). Use --http=false to disable it",
Usage: "JSON-RPC server (enabled by default). Use --http=false to disable it",
Value: true,
}
HTTPServerEnabledFlag = cli.BoolFlag{
Name: "http.enabled",
Usage: "JSON-RPC HTTP server (enabled by default). Use --http.enabled=false to disable it",
Value: true,
}
HTTPListenAddrFlag = cli.StringFlag{
Expand Down
74 changes: 71 additions & 3 deletions node/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,73 @@ import (
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/rpc/rpccfg"
"github.com/ledgerwatch/log/v3"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)

type HttpEndpointConfig struct {
Timeouts rpccfg.HTTPTimeouts
HTTPS bool
CertFile string
KeyFile string
}

// StartHTTPEndpoint starts the HTTP RPC endpoint.
func StartHTTPEndpoint(urlEndpoint string, cfg *HttpEndpointConfig, handler http.Handler) (*http.Server, net.Addr, error) {
// start the HTTP listener
var (
listener net.Listener
err error
)
socketUrl, err := url.Parse(urlEndpoint)
if err != nil {
return nil, nil, fmt.Errorf("malformatted http listen url %s: %w", urlEndpoint, err)
}
if listener, err = net.Listen(socketUrl.Scheme, socketUrl.Host+socketUrl.EscapedPath()); err != nil {
return nil, nil, err
}
// make sure timeout values are meaningful
CheckTimeouts(&cfg.Timeouts)
// create the http2 server for handling h2c
h2 := &http2.Server{}
// enable h2c support
handler = h2c.NewHandler(handler, h2)
// Bundle the http server
httpSrv := &http.Server{
Handler: handler,
ReadTimeout: cfg.Timeouts.ReadTimeout,
WriteTimeout: cfg.Timeouts.WriteTimeout,
IdleTimeout: cfg.Timeouts.IdleTimeout,
ReadHeaderTimeout: cfg.Timeouts.ReadTimeout,
}
// start the HTTP server
go func() {
var serveErr error
if cfg.HTTPS {
serveErr = httpSrv.ServeTLS(listener, cfg.CertFile, cfg.KeyFile)
if serveErr != nil && !isIgnoredHttpServerError(serveErr) {
log.Warn("Failed to serve https endpoint", "err", serveErr)
}
} else {
serveErr = httpSrv.Serve(listener)
if serveErr != nil && !isIgnoredHttpServerError(serveErr) {
log.Warn("Failed to serve http endpoint", "err", serveErr)
}
}
}()
return httpSrv, listener.Addr(), err
}

func isIgnoredHttpServerError(serveErr error) bool {
return (errors.Is(serveErr, context.Canceled) || errors.Is(serveErr, libcommon.ErrStopped) || errors.Is(serveErr, http.ErrServerClosed))

}

// StartHTTPEndpoint starts the HTTP RPC endpoint.
func StartHTTPEndpoint(urlEndpoint string, timeouts rpccfg.HTTPTimeouts, handler http.Handler) (*http.Server, net.Addr, error) {
func StartHTTPSEndpoint(urlEndpoint string,
keyFile string, certFile string,
timeouts rpccfg.HTTPTimeouts, handler http.Handler,
) (*http.Server, net.Addr, error) {
// start the HTTP listener
var (
listener net.Listener
Expand All @@ -47,16 +110,21 @@ func StartHTTPEndpoint(urlEndpoint string, timeouts rpccfg.HTTPTimeouts, handler
}
// make sure timeout values are meaningful
CheckTimeouts(&timeouts)
// Bundle and start the HTTP server
// create the http2 server for handling h2c
h2 := &http2.Server{}
// enable h2c support
handler = h2c.NewHandler(handler, h2)
// Bundle the http server
httpSrv := &http.Server{
Handler: handler,
ReadTimeout: timeouts.ReadTimeout,
WriteTimeout: timeouts.WriteTimeout,
IdleTimeout: timeouts.IdleTimeout,
ReadHeaderTimeout: timeouts.ReadTimeout,
}
// start the HTTP server
go func() {
serveErr := httpSrv.Serve(listener)
serveErr := httpSrv.ServeTLS(listener, certFile, keyFile)
if serveErr != nil &&
!(errors.Is(serveErr, context.Canceled) || errors.Is(serveErr, libcommon.ErrStopped) || errors.Is(serveErr, http.ErrServerClosed)) {
log.Warn("Failed to serve http endpoint", "err", serveErr)
Expand Down
1 change: 1 addition & 0 deletions turbo/cli/default_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ var DefaultFlags = []cli.Flag{
&BadBlockFlag,

&utils.HTTPEnabledFlag,
&utils.HTTPServerEnabledFlag,
&utils.GraphQLEnabledFlag,
&utils.HTTPListenAddrFlag,
&utils.HTTPPortFlag,
Expand Down
8 changes: 5 additions & 3 deletions turbo/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package cli

import (
"fmt"
"github.com/ledgerwatch/erigon-lib/common/hexutil"
"time"

"github.com/ledgerwatch/erigon-lib/common/hexutil"

"github.com/ledgerwatch/erigon-lib/txpool/txpoolcfg"

libcommon "github.com/ledgerwatch/erigon-lib/common"
Expand Down Expand Up @@ -356,8 +357,9 @@ func setEmbeddedRpcDaemon(ctx *cli.Context, cfg *nodecfg.Config, logger log.Logg
logger.Info("starting HTTP APIs", "APIs", apis)

c := &httpcfg.HttpCfg{
Enabled: ctx.Bool(utils.HTTPEnabledFlag.Name),
Dirs: cfg.Dirs,
Enabled: ctx.Bool(utils.HTTPEnabledFlag.Name),
HttpServerEnabled: ctx.Bool(utils.HTTPServerEnabledFlag.Name),
Dirs: cfg.Dirs,

TLSKeyFile: cfg.TLSKeyFile,
TLSCACert: cfg.TLSCACert,
Expand Down
Loading