server.go 2.88 KB
Newer Older
1 2 3 4 5 6 7
package node

import (
	"context"
	"errors"
	"net"
	"net/http"
8
	"strconv"
9

10
	ophttp "github.com/ethereum-optimism/optimism/op-service/httputil"
11 12 13 14
	"github.com/ethereum/go-ethereum/log"
	"github.com/ethereum/go-ethereum/node"
	"github.com/ethereum/go-ethereum/rpc"

15
	"github.com/ethereum-optimism/optimism/op-node/metrics"
16 17
	"github.com/ethereum-optimism/optimism/op-node/p2p"
	"github.com/ethereum-optimism/optimism/op-node/rollup"
18
	"github.com/ethereum-optimism/optimism/op-node/sources"
19 20 21 22 23 24 25 26 27
)

type rpcServer struct {
	endpoint   string
	apis       []rpc.API
	httpServer *http.Server
	appVersion string
	listenAddr net.Addr
	log        log.Logger
28
	sources.L2Client
29 30
}

31
func newRPCServer(ctx context.Context, rpcCfg *RPCConfig, rollupCfg *rollup.Config, l2Client l2EthClient, dr driverClient, log log.Logger, appVersion string, m metrics.Metricer) (*rpcServer, error) {
32
	api := NewNodeAPI(rollupCfg, l2Client, dr, log.New("rpc", "node"), m)
33
	// TODO: extend RPC config with options for WS, IPC and HTTP RPC connections
34
	endpoint := net.JoinHostPort(rpcCfg.ListenAddr, strconv.Itoa(rpcCfg.ListenPort))
35 36 37 38 39 40 41 42 43 44 45 46 47
	r := &rpcServer{
		endpoint: endpoint,
		apis: []rpc.API{{
			Namespace:     "optimism",
			Service:       api,
			Authenticated: false,
		}},
		appVersion: appVersion,
		log:        log,
	}
	return r, nil
}

48 49 50 51 52 53 54 55 56
func (s *rpcServer) EnableAdminAPI(api *adminAPI) {
	s.apis = append(s.apis, rpc.API{
		Namespace:     "admin",
		Version:       "",
		Service:       api,
		Authenticated: false,
	})
}

57 58 59 60 61 62 63 64 65 66 67
func (s *rpcServer) EnableP2P(backend *p2p.APIBackend) {
	s.apis = append(s.apis, rpc.API{
		Namespace:     p2p.NamespaceRPC,
		Version:       "",
		Service:       backend,
		Authenticated: false,
	})
}

func (s *rpcServer) Start() error {
	srv := rpc.NewServer()
68
	if err := node.RegisterApis(s.apis, nil, srv); err != nil {
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
		return err
	}

	// The CORS and VHosts arguments below must be set in order for
	// other services to connect to the opnode. VHosts in particular
	// defaults to localhost, which will prevent containers from
	// calling into the opnode without an "invalid host" error.
	nodeHandler := node.NewHTTPHandlerStack(srv, []string{"*"}, []string{"*"}, nil)

	mux := http.NewServeMux()
	mux.Handle("/", nodeHandler)
	mux.HandleFunc("/healthz", healthzHandler(s.appVersion))

	listener, err := net.Listen("tcp", s.endpoint)
	if err != nil {
		return err
	}
	s.listenAddr = listener.Addr()

88
	s.httpServer = ophttp.NewHttpServer(mux)
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
	go func() {
		if err := s.httpServer.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { // todo improve error handling
			s.log.Error("http server failed", "err", err)
		}
	}()
	return nil
}

func (r *rpcServer) Stop() {
	_ = r.httpServer.Shutdown(context.Background())
}

func (r *rpcServer) Addr() net.Addr {
	return r.listenAddr
}

func healthzHandler(appVersion string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte(appVersion))
	}
}