75b02dff3d
* op-signer: add to this repo * circleci: add op-signer jobs/workflows * ops: update tag service to include op-signer * readme: add op-signer one sentence description * ci: add op-signer option to github action * ops: add op-signer min version
240 lines
6.6 KiB
Go
240 lines
6.6 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sync/atomic"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
"github.com/ethereum-optimism/optimism/op-service/cliapp"
|
|
"github.com/ethereum-optimism/optimism/op-service/httputil"
|
|
oplog "github.com/ethereum-optimism/optimism/op-service/log"
|
|
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
|
|
"github.com/ethereum-optimism/optimism/op-service/oppprof"
|
|
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
|
|
"github.com/ethereum-optimism/optimism/op-service/tls/certman"
|
|
|
|
"github.com/ethereum-optimism/infra/op-signer/client"
|
|
"github.com/ethereum-optimism/infra/op-signer/service"
|
|
)
|
|
|
|
type SignerApp struct {
|
|
log log.Logger
|
|
|
|
version string
|
|
|
|
pprofServer *oppprof.Service
|
|
metricsServer *httputil.HTTPServer
|
|
registry *prometheus.Registry
|
|
|
|
signer *service.SignerService
|
|
|
|
rpc *oprpc.Server
|
|
|
|
stopped atomic.Bool
|
|
}
|
|
|
|
func InitFromConfig(ctx context.Context, log log.Logger, cfg *Config, version string) (*SignerApp, error) {
|
|
if err := cfg.Check(); err != nil {
|
|
return nil, fmt.Errorf("invalid config: %w", err)
|
|
}
|
|
app := &SignerApp{log: log, version: version}
|
|
if err := app.init(cfg); err != nil {
|
|
return nil, errors.Join(err, app.Stop(ctx)) // clean up the failed init attempt
|
|
}
|
|
return app, nil
|
|
}
|
|
|
|
func (s *SignerApp) init(cfg *Config) error {
|
|
if err := s.initPprof(cfg); err != nil {
|
|
return fmt.Errorf("pprof error: %w", err)
|
|
}
|
|
if err := s.initMetrics(cfg); err != nil {
|
|
return fmt.Errorf("metrics error: %w", err)
|
|
}
|
|
if err := s.initRPC(cfg); err != nil {
|
|
return fmt.Errorf("metrics error: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SignerApp) initPprof(cfg *Config) error {
|
|
if !cfg.PprofConfig.ListenEnabled {
|
|
return nil
|
|
}
|
|
s.pprofServer = oppprof.New(
|
|
cfg.PprofConfig.ListenEnabled,
|
|
cfg.PprofConfig.ListenAddr,
|
|
cfg.PprofConfig.ListenPort,
|
|
cfg.PprofConfig.ProfileType,
|
|
cfg.PprofConfig.ProfileDir,
|
|
cfg.PprofConfig.ProfileFilename,
|
|
)
|
|
s.log.Info("Starting pprof server", "addr", cfg.PprofConfig.ListenAddr, "port", cfg.PprofConfig.ListenPort)
|
|
if err := s.pprofServer.Start(); err != nil {
|
|
return fmt.Errorf("failed to start pprof server: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SignerApp) initMetrics(cfg *Config) error {
|
|
registry := opmetrics.NewRegistry()
|
|
registry.MustRegister(service.MetricSignTransactionTotal)
|
|
s.registry = registry // some things require metrics registry
|
|
|
|
if !cfg.MetricsConfig.Enabled {
|
|
return nil
|
|
}
|
|
|
|
metricsCfg := cfg.MetricsConfig
|
|
s.log.Info("Starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
|
|
metricsServer, err := opmetrics.StartServer(registry, metricsCfg.ListenAddr, metricsCfg.ListenPort)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to start metrics server: %w", err)
|
|
}
|
|
s.log.Info("Started metrics server", "endpoint", metricsServer.Addr())
|
|
s.metricsServer = metricsServer
|
|
return nil
|
|
}
|
|
|
|
func (s *SignerApp) initRPC(cfg *Config) error {
|
|
caCert, err := os.ReadFile(cfg.TLSConfig.TLSCaCert)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read tls ca cert: %s", string(caCert))
|
|
}
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
|
|
cm, err := certman.New(s.log, cfg.TLSConfig.TLSCert, cfg.TLSConfig.TLSKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read tls cert or key: %w", err)
|
|
}
|
|
if err := cm.Watch(); err != nil {
|
|
return fmt.Errorf("failed to start certman watcher: %w", err)
|
|
}
|
|
|
|
tlsConfig := &tls.Config{
|
|
GetCertificate: cm.GetCertificate,
|
|
ClientCAs: caCertPool,
|
|
ClientAuth: tls.VerifyClientCertIfGiven, // necessary for k8s healthz probes, but we check the cert in service/auth.go
|
|
}
|
|
serverTlsConfig := &oprpc.ServerTLSConfig{
|
|
Config: tlsConfig,
|
|
CLIConfig: &cfg.TLSConfig,
|
|
}
|
|
|
|
rpcCfg := cfg.RPCConfig
|
|
s.rpc = oprpc.NewServer(
|
|
rpcCfg.ListenAddr,
|
|
rpcCfg.ListenPort,
|
|
s.version,
|
|
oprpc.WithLogger(s.log),
|
|
oprpc.WithTLSConfig(serverTlsConfig),
|
|
oprpc.WithMiddleware(service.NewAuthMiddleware()),
|
|
oprpc.WithHTTPRecorder(opmetrics.NewPromHTTPRecorder(s.registry, "signer")),
|
|
)
|
|
|
|
serviceCfg, err := service.ReadConfig(cfg.ServiceConfigPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read service config: %w", err)
|
|
}
|
|
s.signer = service.NewSignerService(s.log, serviceCfg)
|
|
s.signer.RegisterAPIs(s.rpc)
|
|
|
|
if err := s.rpc.Start(); err != nil {
|
|
return fmt.Errorf("error starting RPC server: %w", err)
|
|
}
|
|
s.log.Info("Started op-signer RPC server", "addr", s.rpc.Endpoint())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SignerApp) Start(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *SignerApp) Stop(ctx context.Context) error {
|
|
var result error
|
|
if s.rpc != nil {
|
|
if err := s.rpc.Stop(); err != nil {
|
|
result = errors.Join(result, fmt.Errorf("failed to stop RPC server: %w", err))
|
|
}
|
|
}
|
|
if s.pprofServer != nil {
|
|
if err := s.pprofServer.Stop(ctx); err != nil {
|
|
result = errors.Join(result, fmt.Errorf("failed to stop pprof server: %w", err))
|
|
}
|
|
}
|
|
if s.metricsServer != nil {
|
|
if err := s.metricsServer.Stop(ctx); err != nil {
|
|
result = errors.Join(result, fmt.Errorf("failed to stop metrics server: %w", err))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (s *SignerApp) Stopped() bool {
|
|
return s.stopped.Load()
|
|
}
|
|
|
|
var _ cliapp.Lifecycle = (*SignerApp)(nil)
|
|
|
|
func MainAppAction(version string) cliapp.LifecycleAction {
|
|
return func(cliCtx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) {
|
|
cfg := NewConfig(cliCtx)
|
|
logger := oplog.NewLogger(cliCtx.App.Writer, cfg.LogConfig)
|
|
return InitFromConfig(cliCtx.Context, logger, cfg, version)
|
|
}
|
|
}
|
|
|
|
func ClientSign(version string) func(cliCtx *cli.Context) error {
|
|
return func(cliCtx *cli.Context) error {
|
|
cfg := NewConfig(cliCtx)
|
|
if err := cfg.Check(); err != nil {
|
|
return fmt.Errorf("invalid CLI flags: %w", err)
|
|
}
|
|
|
|
l := oplog.NewLogger(os.Stdout, cfg.LogConfig)
|
|
log.Root().SetHandler(l.GetHandler())
|
|
|
|
txarg := cliCtx.Args().First()
|
|
if txarg == "" {
|
|
return errors.New("no transaction argument was provided")
|
|
}
|
|
txraw, err := hexutil.Decode(txarg)
|
|
if err != nil {
|
|
return errors.New("failed to decode transaction argument")
|
|
}
|
|
|
|
client, err := client.NewSignerClient(l, cfg.ClientEndpoint, cfg.TLSConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tx := &types.Transaction{}
|
|
if err := tx.UnmarshalBinary(txraw); err != nil {
|
|
return fmt.Errorf("failed to unmarshal transaction argument: %w", err)
|
|
}
|
|
|
|
tx, err = client.SignTransaction(context.Background(), tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result, _ := tx.MarshalJSON()
|
|
fmt.Println(string(result))
|
|
|
|
return nil
|
|
}
|
|
}
|