docker compose
This commit is contained in:
parent
6b794b24db
commit
454fc0a576
|
@ -28,14 +28,12 @@ RUN apk add alpine-sdk
|
||||||
WORKDIR /wd
|
WORKDIR /wd
|
||||||
COPY ["go.mod","go.sum","/wd/"]
|
COPY ["go.mod","go.sum","/wd/"]
|
||||||
COPY cmd cmd
|
COPY cmd cmd
|
||||||
RUN go build -o rpcdaemon ./cmd/rpcdaemon
|
RUN go build -o otter ./cmd/otter
|
||||||
RUN go build -o server ./cmd/server
|
|
||||||
|
|
||||||
FROM alpine:3.15.0
|
FROM alpine:3.15.0
|
||||||
RUN apk add alpine-sdk
|
RUN apk add alpine-sdk
|
||||||
WORKDIR /wd
|
WORKDIR /wd
|
||||||
COPY --from=gobuilder /wd/rpcdaemon /usr/bin/rpcdaemon
|
COPY --from=gobuilder /wd/otter /usr/bin/otter
|
||||||
COPY --from=gobuilder /wd/server /usr/bin/server
|
|
||||||
COPY --from=builder /otterscan-build/dist /wd/dist
|
COPY --from=builder /otterscan-build/dist /wd/dist
|
||||||
COPY --from=logobuilder /assets /wd/dist
|
COPY --from=logobuilder /assets /wd/dist
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,15 @@ package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/ledgerwatch/erigon-lib/common/dir"
|
"github.com/ledgerwatch/erigon-lib/common/dir"
|
||||||
libstate "github.com/ledgerwatch/erigon-lib/state"
|
libstate "github.com/ledgerwatch/erigon-lib/state"
|
||||||
"github.com/ledgerwatch/erigon/eth/ethconfig"
|
"github.com/ledgerwatch/erigon/eth/ethconfig"
|
||||||
|
@ -20,9 +18,9 @@ import (
|
||||||
|
|
||||||
"github.com/ledgerwatch/erigon/turbo/debug"
|
"github.com/ledgerwatch/erigon/turbo/debug"
|
||||||
"github.com/ledgerwatch/erigon/turbo/logging"
|
"github.com/ledgerwatch/erigon/turbo/logging"
|
||||||
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/cli/httpcfg"
|
"github.com/wmitsuda/otterscan/cmd/otter/cli/httpcfg"
|
||||||
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/health"
|
"github.com/wmitsuda/otterscan/cmd/otter/health"
|
||||||
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/rpcservices"
|
"github.com/wmitsuda/otterscan/cmd/otter/rpcservices"
|
||||||
|
|
||||||
"github.com/ledgerwatch/erigon-lib/direct"
|
"github.com/ledgerwatch/erigon-lib/direct"
|
||||||
"github.com/ledgerwatch/erigon-lib/gointerfaces"
|
"github.com/ledgerwatch/erigon-lib/gointerfaces"
|
||||||
|
@ -35,8 +33,6 @@ import (
|
||||||
"github.com/ledgerwatch/erigon-lib/kv/remotedb"
|
"github.com/ledgerwatch/erigon-lib/kv/remotedb"
|
||||||
"github.com/ledgerwatch/erigon-lib/kv/remotedbserver"
|
"github.com/ledgerwatch/erigon-lib/kv/remotedbserver"
|
||||||
"github.com/ledgerwatch/erigon/cmd/utils"
|
"github.com/ledgerwatch/erigon/cmd/utils"
|
||||||
"github.com/ledgerwatch/erigon/common"
|
|
||||||
"github.com/ledgerwatch/erigon/common/hexutil"
|
|
||||||
"github.com/ledgerwatch/erigon/common/paths"
|
"github.com/ledgerwatch/erigon/common/paths"
|
||||||
"github.com/ledgerwatch/erigon/core/rawdb"
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
||||||
"github.com/ledgerwatch/erigon/node"
|
"github.com/ledgerwatch/erigon/node"
|
||||||
|
@ -57,14 +53,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "rpcdaemon",
|
Use: "otter",
|
||||||
Short: "rpcdaemon is JSON RPC server that connects to Erigon node for remote DB access",
|
Short: "otterscan is a chain explorer that can also host a custom JSON RPC server that connects to an Erigon node",
|
||||||
}
|
}
|
||||||
|
|
||||||
func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
|
func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
|
||||||
utils.CobraFlags(rootCmd, debug.Flags, utils.MetricFlags, logging.Flags)
|
utils.CobraFlags(rootCmd, debug.Flags, utils.MetricFlags, logging.Flags)
|
||||||
|
|
||||||
cfg := &httpcfg.HttpCfg{Enabled: true, StateCache: kvcache.DefaultCoherentConfig}
|
cfg := &httpcfg.HttpCfg{}
|
||||||
|
cfg.Enabled = true
|
||||||
|
cfg.StateCache = kvcache.DefaultCoherentConfig
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&cfg.PrivateApiAddr, "private.api.addr", "127.0.0.1:9090", "private api network address, for example: 127.0.0.1:9090")
|
rootCmd.PersistentFlags().StringVar(&cfg.PrivateApiAddr, "private.api.addr", "127.0.0.1:9090", "private api network address, for example: 127.0.0.1:9090")
|
||||||
rootCmd.PersistentFlags().StringVar(&cfg.DataDir, "datadir", "", "path to Erigon working directory")
|
rootCmd.PersistentFlags().StringVar(&cfg.DataDir, "datadir", "", "path to Erigon working directory")
|
||||||
rootCmd.PersistentFlags().StringVar(&cfg.HttpListenAddress, "http.addr", nodecfg.DefaultHTTPHost, "HTTP-RPC server listening interface")
|
rootCmd.PersistentFlags().StringVar(&cfg.HttpListenAddress, "http.addr", nodecfg.DefaultHTTPHost, "HTTP-RPC server listening interface")
|
||||||
|
@ -105,6 +104,16 @@ func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// otterscan server setting
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.OtsServerDisable, "ots.server.addr", false, "disable ots server to run rpc daemon only")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.OtsStaticDir, "ots.static.dir", "./dist", "dir to serve static files from")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.DisableRpcDaemon, "disable.rpc.daemon", false, "dont run rpc daemon, for use when specifying external rtpc daemon")
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.OtsBeaconApiUrl, "ots.beaconapi.url", "http://localhost:3500", "where the website will make request for beacon api")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.OtsRpcDaemonUrl, "ots.rpcdaemon.url", "/rpc", "where the website will make request for beacon api")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.OtsExternalAssetUrl, "ots.externalasset.url", "/", "where website will make request for assets")
|
||||||
|
|
||||||
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
if err := debug.SetupCobra(cmd); err != nil {
|
if err := debug.SetupCobra(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -442,23 +451,14 @@ func RemoteServices(ctx context.Context, cfg httpcfg.HttpCfg, logger log.Logger,
|
||||||
return db, borDb, eth, txPool, mining, stateCache, blockReader, ff, agg, err
|
return db, borDb, eth, txPool, mining, stateCache, blockReader, ff, agg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rpc.API, authAPI []rpc.API) error {
|
func StartRpcServer(ctx context.Context, r chi.Router, cfg httpcfg.HttpCfg, rpcAPI []rpc.API) error {
|
||||||
if len(authAPI) > 0 {
|
|
||||||
engineInfo, err := startAuthenticatedRpcServer(cfg, authAPI)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go stopAuthenticatedRpcServer(ctx, engineInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Enabled {
|
if cfg.Enabled {
|
||||||
return startRegularRpcServer(ctx, cfg, rpcAPI)
|
return startServer(ctx, r, cfg, rpcAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startRegularRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rpc.API) error {
|
func startServer(ctx context.Context, r chi.Router, cfg httpcfg.HttpCfg, rpcAPI []rpc.API) error {
|
||||||
// register apis and create handler stack
|
// register apis and create handler stack
|
||||||
httpEndpoint := fmt.Sprintf("%s:%d", cfg.HttpListenAddress, cfg.HttpPort)
|
httpEndpoint := fmt.Sprintf("%s:%d", cfg.HttpListenAddress, cfg.HttpPort)
|
||||||
|
|
||||||
|
@ -500,126 +500,60 @@ func startRegularRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rp
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
r.Mount("/rpc", apiHandler)
|
||||||
listener, _, err := node.StartHTTPEndpoint(httpEndpoint, cfg.HTTPTimeouts, apiHandler)
|
listener, _, err := node.StartHTTPEndpoint(httpEndpoint, cfg.HTTPTimeouts, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not start RPC api: %w", err)
|
return fmt.Errorf("could not start RPC api: %w", err)
|
||||||
}
|
}
|
||||||
info := []interface{}{"url", httpEndpoint, "ws", cfg.WebsocketEnabled,
|
info := &[]interface{}{"url", httpEndpoint, "ws", cfg.WebsocketEnabled,
|
||||||
"ws.compression", cfg.WebsocketCompression, "grpc", cfg.GRPCServerEnabled}
|
"ws.compression", cfg.WebsocketCompression, "grpc", cfg.GRPCServerEnabled}
|
||||||
|
|
||||||
var (
|
|
||||||
healthServer *grpcHealth.Server
|
|
||||||
grpcServer *grpc.Server
|
|
||||||
grpcListener net.Listener
|
|
||||||
grpcEndpoint string
|
|
||||||
)
|
|
||||||
if cfg.GRPCServerEnabled {
|
if cfg.GRPCServerEnabled {
|
||||||
grpcEndpoint = fmt.Sprintf("%s:%d", cfg.GRPCListenAddress, cfg.GRPCPort)
|
startGrpcServer(ctx, info, cfg)
|
||||||
if grpcListener, err = net.Listen("tcp", grpcEndpoint); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not start GRPC listener: %w", err)
|
return fmt.Errorf("could not start GRPC api: %w", err)
|
||||||
}
|
}
|
||||||
grpcServer = grpc.NewServer()
|
|
||||||
if cfg.GRPCHealthCheckEnabled {
|
|
||||||
healthServer = grpcHealth.NewServer()
|
|
||||||
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
|
|
||||||
}
|
|
||||||
go grpcServer.Serve(grpcListener)
|
|
||||||
info = append(info, "grpc.port", cfg.GRPCPort)
|
|
||||||
}
|
}
|
||||||
|
log.Info("HTTP endpoint opened", *info...)
|
||||||
log.Info("HTTP endpoint opened", info...)
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
srv.Stop()
|
srv.Stop()
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_ = listener.Shutdown(shutdownCtx)
|
_ = listener.Shutdown(shutdownCtx)
|
||||||
log.Info("HTTP endpoint closed", "url", httpEndpoint)
|
log.Info("HTTP endpoint closed", "url", httpEndpoint)
|
||||||
|
|
||||||
if cfg.GRPCServerEnabled {
|
|
||||||
if cfg.GRPCHealthCheckEnabled {
|
|
||||||
healthServer.Shutdown()
|
|
||||||
}
|
|
||||||
grpcServer.GracefulStop()
|
|
||||||
_ = grpcListener.Close()
|
|
||||||
log.Info("GRPC endpoint closed", "url", grpcEndpoint)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
log.Info("Exiting...")
|
log.Info("Exiting...")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type engineInfo struct {
|
func startGrpcServer(ctx context.Context, info *[]any, cfg httpcfg.HttpCfg) (err error) {
|
||||||
Srv *rpc.Server
|
var (
|
||||||
EngineSrv *rpc.Server
|
healthServer *grpcHealth.Server
|
||||||
EngineListener *http.Server
|
grpcServer *grpc.Server
|
||||||
EngineHttpEndpoint string
|
grpcListener net.Listener
|
||||||
}
|
grpcEndpoint string
|
||||||
|
)
|
||||||
func startAuthenticatedRpcServer(cfg httpcfg.HttpCfg, rpcAPI []rpc.API) (*engineInfo, error) {
|
grpcEndpoint = fmt.Sprintf("%s:%d", cfg.GRPCListenAddress, cfg.GRPCPort)
|
||||||
log.Trace("TraceRequests = %t\n", cfg.TraceRequests)
|
if grpcListener, err = net.Listen("tcp", grpcEndpoint); err != nil {
|
||||||
srv := rpc.NewServer(cfg.RpcBatchConcurrency, cfg.TraceRequests, cfg.RpcStreamingDisable)
|
return fmt.Errorf("could not start GRPC listener: %w", err)
|
||||||
|
|
||||||
engineListener, engineSrv, engineHttpEndpoint, err := createEngineListener(cfg, rpcAPI)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not start RPC api for engine: %w", err)
|
|
||||||
}
|
}
|
||||||
return &engineInfo{Srv: srv, EngineSrv: engineSrv, EngineListener: engineListener, EngineHttpEndpoint: engineHttpEndpoint}, nil
|
grpcServer = grpc.NewServer()
|
||||||
}
|
if cfg.GRPCHealthCheckEnabled {
|
||||||
|
healthServer = grpcHealth.NewServer()
|
||||||
|
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
|
||||||
|
}
|
||||||
|
go grpcServer.Serve(grpcListener)
|
||||||
|
*info = append(*info, "grpc.port", cfg.GRPCPort)
|
||||||
|
|
||||||
func stopAuthenticatedRpcServer(ctx context.Context, engineInfo *engineInfo) {
|
|
||||||
defer func() {
|
defer func() {
|
||||||
engineInfo.Srv.Stop()
|
if cfg.GRPCHealthCheckEnabled {
|
||||||
if engineInfo.EngineSrv != nil {
|
healthServer.Shutdown()
|
||||||
engineInfo.EngineSrv.Stop()
|
|
||||||
}
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if engineInfo.EngineListener != nil {
|
|
||||||
_ = engineInfo.EngineListener.Shutdown(shutdownCtx)
|
|
||||||
log.Info("Engine HTTP endpoint close", "url", engineInfo.EngineHttpEndpoint)
|
|
||||||
}
|
}
|
||||||
|
grpcServer.GracefulStop()
|
||||||
|
_ = grpcListener.Close()
|
||||||
|
log.Info("GRPC endpoint closed", "url", grpcEndpoint)
|
||||||
}()
|
}()
|
||||||
<-ctx.Done()
|
return nil
|
||||||
log.Info("Exiting Engine...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// isWebsocket checks the header of a http request for a websocket upgrade request.
|
|
||||||
func isWebsocket(r *http.Request) bool {
|
|
||||||
return strings.EqualFold(r.Header.Get("Upgrade"), "websocket") &&
|
|
||||||
strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
|
|
||||||
}
|
|
||||||
|
|
||||||
// obtainJWTSecret loads the jwt-secret, either from the provided config,
|
|
||||||
// or from the default location. If neither of those are present, it generates
|
|
||||||
// a new secret and stores to the default location.
|
|
||||||
func obtainJWTSecret(cfg httpcfg.HttpCfg) ([]byte, error) {
|
|
||||||
// try reading from file
|
|
||||||
log.Info("Reading JWT secret", "path", cfg.JWTSecretPath)
|
|
||||||
// If we run the rpcdaemon and datadir is not specified we just use jwt.hex in current directory.
|
|
||||||
if len(cfg.JWTSecretPath) == 0 {
|
|
||||||
cfg.JWTSecretPath = "jwt.hex"
|
|
||||||
}
|
|
||||||
if data, err := os.ReadFile(cfg.JWTSecretPath); err == nil {
|
|
||||||
jwtSecret := common.FromHex(strings.TrimSpace(string(data)))
|
|
||||||
if len(jwtSecret) == 32 {
|
|
||||||
return jwtSecret, nil
|
|
||||||
}
|
|
||||||
log.Error("Invalid JWT secret", "path", cfg.JWTSecretPath, "length", len(jwtSecret))
|
|
||||||
return nil, errors.New("invalid JWT secret")
|
|
||||||
}
|
|
||||||
// Need to generate one
|
|
||||||
jwtSecret := make([]byte, 32)
|
|
||||||
rand.Read(jwtSecret)
|
|
||||||
|
|
||||||
if err := os.WriteFile(cfg.JWTSecretPath, []byte(hexutil.Encode(jwtSecret)), 0600); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Info("Generated JWT secret", "path", cfg.JWTSecretPath)
|
|
||||||
return jwtSecret, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler, jwtSecret []byte) (http.Handler, error) {
|
func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler, jwtSecret []byte) (http.Handler, error) {
|
||||||
|
@ -643,36 +577,8 @@ func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Hand
|
||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createEngineListener(cfg httpcfg.HttpCfg, engineApi []rpc.API) (*http.Server, *rpc.Server, string, error) {
|
// isWebsocket checks the header of a http request for a websocket upgrade request.
|
||||||
engineHttpEndpoint := fmt.Sprintf("%s:%d", cfg.AuthRpcHTTPListenAddress, cfg.AuthRpcPort)
|
func isWebsocket(r *http.Request) bool {
|
||||||
|
return strings.EqualFold(r.Header.Get("Upgrade"), "websocket") &&
|
||||||
engineSrv := rpc.NewServer(cfg.RpcBatchConcurrency, cfg.TraceRequests, true)
|
strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
|
||||||
|
|
||||||
if err := node.RegisterApisFromWhitelist(engineApi, nil, engineSrv, true); err != nil {
|
|
||||||
return nil, nil, "", fmt.Errorf("could not start register RPC engine api: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
jwtSecret, err := obtainJWTSecret(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
wsHandler := engineSrv.WebsocketHandler([]string{"*"}, jwtSecret, cfg.WebsocketCompression)
|
|
||||||
|
|
||||||
engineHttpHandler := node.NewHTTPHandlerStack(engineSrv, nil /* authCors */, cfg.AuthRpcVirtualHost, cfg.HttpCompression)
|
|
||||||
|
|
||||||
engineApiHandler, err := createHandler(cfg, engineApi, engineHttpHandler, wsHandler, jwtSecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
engineListener, _, err := node.StartHTTPEndpoint(engineHttpEndpoint, cfg.AuthRpcTimeouts, engineApiHandler)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, "", fmt.Errorf("could not start RPC api: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
engineInfo := []interface{}{"url", engineHttpEndpoint, "ws", true, "ws.compression", cfg.WebsocketCompression}
|
|
||||||
log.Info("HTTP endpoint opened for Engine API", engineInfo...)
|
|
||||||
|
|
||||||
return engineListener, engineSrv, engineHttpEndpoint, nil
|
|
||||||
}
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package httpcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli/httpcfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpCfg struct {
|
||||||
|
httpcfg.HttpCfg
|
||||||
|
|
||||||
|
DisableRpcDaemon bool
|
||||||
|
|
||||||
|
OtsServerDisable bool
|
||||||
|
|
||||||
|
OtsStaticDir string
|
||||||
|
OtsExternalAssetUrl string
|
||||||
|
OtsRpcDaemonUrl string
|
||||||
|
OtsBeaconApiUrl string
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
||||||
"github.com/ledgerwatch/erigon/turbo/services"
|
"github.com/ledgerwatch/erigon/turbo/services"
|
||||||
|
|
||||||
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/cli/httpcfg"
|
"github.com/wmitsuda/otterscan/cmd/otter/cli/httpcfg"
|
||||||
|
|
||||||
erigonHttpcfg "github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli/httpcfg"
|
erigonHttpcfg "github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli/httpcfg"
|
||||||
)
|
)
|
||||||
|
@ -130,27 +130,3 @@ func APIList(db kv.RoDB, borDb kv.RoDB, eth rpchelper.ApiBackend, txPool txpool.
|
||||||
|
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthAPIList(db kv.RoDB, eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient,
|
|
||||||
filters *rpchelper.Filters, stateCache kvcache.Cache, blockReader services.FullBlockReader,
|
|
||||||
agg *libstate.Aggregator22,
|
|
||||||
cfg httpcfg.HttpCfg) (list []rpc.API) {
|
|
||||||
base := commands.NewBaseApi(filters, stateCache, blockReader, agg, cfg.WithDatadir, cfg.EvmCallTimeout)
|
|
||||||
|
|
||||||
ethImpl := commands.NewEthAPI(base, db, eth, txPool, mining, cfg.Gascap)
|
|
||||||
engineImpl := commands.NewEngineAPI(base, db, eth)
|
|
||||||
|
|
||||||
list = append(list, rpc.API{
|
|
||||||
Namespace: "eth",
|
|
||||||
Public: true,
|
|
||||||
Service: commands.EthAPI(ethImpl),
|
|
||||||
Version: "1.0",
|
|
||||||
}, rpc.API{
|
|
||||||
Namespace: "engine",
|
|
||||||
Public: true,
|
|
||||||
Service: commands.EngineAPI(engineImpl),
|
|
||||||
Version: "1.0",
|
|
||||||
})
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
|
@ -19,13 +19,11 @@ import (
|
||||||
"github.com/ledgerwatch/erigon/core/vm"
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
"github.com/ledgerwatch/erigon/params"
|
"github.com/ledgerwatch/erigon/params"
|
||||||
"github.com/ledgerwatch/erigon/rpc"
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/adapter/ethapi"
|
||||||
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
||||||
"github.com/ledgerwatch/erigon/turbo/transactions"
|
"github.com/ledgerwatch/erigon/turbo/transactions"
|
||||||
"github.com/ledgerwatch/erigon/turbo/adapter/ethapi"
|
|
||||||
|
|
||||||
|
|
||||||
"github.com/ledgerwatch/log/v3"
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// API_LEVEL Must be incremented every time new additions are made
|
// API_LEVEL Must be incremented every time new additions are made
|
|
@ -3,12 +3,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/ledgerwatch/erigon-lib/common"
|
"github.com/ledgerwatch/erigon-lib/common"
|
||||||
"github.com/ledgerwatch/log/v3"
|
"github.com/ledgerwatch/log/v3"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/cli"
|
"github.com/wmitsuda/otterscan/cmd/otter/cli"
|
||||||
mycmds "github.com/wmitsuda/otterscan/cmd/rpcdaemon/commands"
|
mycmds "github.com/wmitsuda/otterscan/cmd/otter/commands"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -26,16 +27,15 @@ func main() {
|
||||||
if borDb != nil {
|
if borDb != nil {
|
||||||
defer borDb.Close()
|
defer borDb.Close()
|
||||||
}
|
}
|
||||||
|
r := chi.NewRouter()
|
||||||
|
RouteServer(r, *cfg)
|
||||||
apiList := mycmds.APIList(db, borDb, backend, txPool, mining, ff, stateCache, blockReader, agg, *cfg)
|
apiList := mycmds.APIList(db, borDb, backend, txPool, mining, ff, stateCache, blockReader, agg, *cfg)
|
||||||
if err := cli.StartRpcServer(ctx, *cfg, apiList, nil); err != nil {
|
if err := cli.StartRpcServer(ctx, r, *cfg, apiList); err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cmd.ExecuteContext(rootCtx); err != nil {
|
if err := cmd.ExecuteContext(rootCtx); err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
|
@ -0,0 +1,48 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gfx.cafe/open/4bytes/sigs"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/wmitsuda/otterscan/cmd/otter/cli/httpcfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RouteServer(r chi.Router, cfg httpcfg.HttpCfg) {
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
filesDir := http.Dir(cfg.OtsStaticDir)
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
r.Use(middleware.Recoverer)
|
||||||
|
r.Handle("/signatures/{hash}", &sigs.HttpServer{})
|
||||||
|
r.HandleFunc("/config.json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"erigonURL": cfg.OtsRpcDaemonUrl,
|
||||||
|
"beaconAPI": cfg.OtsBeaconApiUrl,
|
||||||
|
"assetsURLPrefix": cfg.OtsBeaconApiUrl,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
FileServer(r, "/", filesDir)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileServer conveniently sets up a http.FileServer handler to serve
|
||||||
|
// static files from a http.FileSystem.
|
||||||
|
func FileServer(r chi.Router, path string, root http.FileSystem) {
|
||||||
|
if strings.ContainsAny(path, "{}*") {
|
||||||
|
panic("FileServer does not permit any URL parameters.")
|
||||||
|
}
|
||||||
|
if path != "/" && path[len(path)-1] != '/' {
|
||||||
|
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
|
||||||
|
path += "/"
|
||||||
|
}
|
||||||
|
path += "*"
|
||||||
|
r.Get(path, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rctx := chi.RouteContext(r.Context())
|
||||||
|
pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
|
||||||
|
fs := http.StripPrefix(pathPrefix, http.FileServer(root))
|
||||||
|
fs.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,52 +0,0 @@
|
||||||
package httpcfg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ledgerwatch/erigon-lib/kv/kvcache"
|
|
||||||
"github.com/ledgerwatch/erigon/eth/ethconfig"
|
|
||||||
"github.com/ledgerwatch/erigon/node/nodecfg/datadir"
|
|
||||||
"github.com/ledgerwatch/erigon/rpc/rpccfg"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HttpCfg struct {
|
|
||||||
Enabled bool
|
|
||||||
PrivateApiAddr string
|
|
||||||
WithDatadir bool // Erigon's database can be read by separated processes on same machine - in read-only mode - with full support of transactions. It will share same "OS PageCache" with Erigon process.
|
|
||||||
DataDir string
|
|
||||||
Dirs datadir.Dirs
|
|
||||||
HttpListenAddress string
|
|
||||||
AuthRpcHTTPListenAddress string
|
|
||||||
TLSCertfile string
|
|
||||||
TLSCACert string
|
|
||||||
TLSKeyFile string
|
|
||||||
HttpPort int
|
|
||||||
AuthRpcPort int
|
|
||||||
HttpCORSDomain []string
|
|
||||||
HttpVirtualHost []string
|
|
||||||
AuthRpcVirtualHost []string
|
|
||||||
HttpCompression bool
|
|
||||||
API []string
|
|
||||||
Gascap uint64
|
|
||||||
MaxTraces uint64
|
|
||||||
WebsocketEnabled bool
|
|
||||||
WebsocketCompression bool
|
|
||||||
RpcAllowListFilePath string
|
|
||||||
RpcBatchConcurrency uint
|
|
||||||
RpcStreamingDisable bool
|
|
||||||
DBReadConcurrency int
|
|
||||||
TraceCompatibility bool // Bug for bug compatibility for trace_ routines with OpenEthereum
|
|
||||||
TxPoolApiAddr string
|
|
||||||
StateCache kvcache.CoherentConfig
|
|
||||||
Snap ethconfig.Snapshot
|
|
||||||
Sync ethconfig.Sync
|
|
||||||
GRPCServerEnabled bool
|
|
||||||
GRPCListenAddress string
|
|
||||||
GRPCPort int
|
|
||||||
GRPCHealthCheckEnabled bool
|
|
||||||
StarknetGRPCAddress string
|
|
||||||
JWTSecretPath string // Engine API Authentication
|
|
||||||
TraceRequests bool // Always trace requests in INFO level
|
|
||||||
HTTPTimeouts rpccfg.HTTPTimeouts
|
|
||||||
AuthRpcTimeouts rpccfg.HTTPTimeouts
|
|
||||||
EvmCallTimeout time.Duration
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gfx.cafe/open/4bytes/sigs"
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
root := "dist"
|
|
||||||
port := os.Getenv("PORT")
|
|
||||||
if port == "" {
|
|
||||||
port = "3001"
|
|
||||||
}
|
|
||||||
if os.Getenv("ROOTDIR") != "" {
|
|
||||||
root = os.Getenv("ROOTDIR")
|
|
||||||
}
|
|
||||||
if len(os.Args) > 1 {
|
|
||||||
if os.Args[1] != "" {
|
|
||||||
root = os.Args[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filesDir := http.Dir(root)
|
|
||||||
|
|
||||||
r := chi.NewRouter()
|
|
||||||
r.Use(middleware.Logger)
|
|
||||||
r.Use(middleware.Recoverer)
|
|
||||||
r.Use(middleware.SetHeader("Access-Control-Allow-Origin", "*"))
|
|
||||||
|
|
||||||
r.Handle("/signatures/{hash}", &sigs.HttpServer{})
|
|
||||||
|
|
||||||
FileServer(r, "/", filesDir)
|
|
||||||
|
|
||||||
s := http.Server{
|
|
||||||
Addr: ":" + port,
|
|
||||||
Handler: r,
|
|
||||||
}
|
|
||||||
s.SetKeepAlivesEnabled(false)
|
|
||||||
s.ReadHeaderTimeout = 250 * time.Millisecond
|
|
||||||
s.MaxHeaderBytes = 8192
|
|
||||||
|
|
||||||
log.Println("static asset server running on " + port)
|
|
||||||
// Start the server.
|
|
||||||
if err := s.ListenAndServe(); err != nil {
|
|
||||||
log.Fatalf("error in ListenAndServe: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileServer conveniently sets up a http.FileServer handler to serve
|
|
||||||
// static files from a http.FileSystem.
|
|
||||||
func FileServer(r chi.Router, path string, root http.FileSystem) {
|
|
||||||
if strings.ContainsAny(path, "{}*") {
|
|
||||||
panic("FileServer does not permit any URL parameters.")
|
|
||||||
}
|
|
||||||
if path != "/" && path[len(path)-1] != '/' {
|
|
||||||
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
|
|
||||||
path += "/"
|
|
||||||
}
|
|
||||||
path += "*"
|
|
||||||
r.Get(path, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
rctx := chi.RouteContext(r.Context())
|
|
||||||
pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
|
|
||||||
fs := http.StripPrefix(pathPrefix, http.FileServer(root))
|
|
||||||
fs.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -2,11 +2,11 @@ version: '3.9'
|
||||||
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
rpcdaemon:
|
otterscan:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
command: |
|
command: |
|
||||||
rpcdaemon
|
otter
|
||||||
--http.addr=0.0.0.0 --http.vhosts=* --http.corsdomain=* --ws
|
--http.addr=0.0.0.0 --http.vhosts=* --http.corsdomain=* --ws
|
||||||
--http.api=eth,erigon,web3,net,trace,debug,txpool,ots
|
--http.api=eth,erigon,web3,net,trace,debug,txpool,ots
|
||||||
--metrics --metrics.addr=0.0.0.0 --metrics.port=6064
|
--metrics --metrics.addr=0.0.0.0 --metrics.port=6064
|
||||||
|
@ -14,11 +14,4 @@ services:
|
||||||
--private.api.addr=172.105.22.234:9191
|
--private.api.addr=172.105.22.234:9191
|
||||||
##--datadir=/erigon
|
##--datadir=/erigon
|
||||||
ports:
|
ports:
|
||||||
- 8545:8545
|
- 3000:8545
|
||||||
server:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
command: |
|
|
||||||
server
|
|
||||||
ports:
|
|
||||||
- 3001:3001
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"erigonURL": "http://localhost:8545",
|
|
||||||
"beaconAPI": "http://localhost:3500",
|
|
||||||
"assetsURLPrefix": "/"
|
|
||||||
}
|
|
Loading…
Reference in New Issue