github.com/iotexproject/iotex-core@v1.14.1-rc1/server/itx/server.go (about)

     1  // Copyright (c) 2022 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package itx
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"net/http"
    12  	"net/http/pprof"
    13  	"runtime"
    14  	"sync"
    15  
    16  	"github.com/pkg/errors"
    17  	"go.uber.org/zap"
    18  
    19  	"github.com/iotexproject/iotex-core/api"
    20  	"github.com/iotexproject/iotex-core/chainservice"
    21  	"github.com/iotexproject/iotex-core/config"
    22  	"github.com/iotexproject/iotex-core/dispatcher"
    23  	"github.com/iotexproject/iotex-core/p2p"
    24  	"github.com/iotexproject/iotex-core/pkg/ha"
    25  	"github.com/iotexproject/iotex-core/pkg/log"
    26  	"github.com/iotexproject/iotex-core/pkg/probe"
    27  	"github.com/iotexproject/iotex-core/pkg/routine"
    28  	"github.com/iotexproject/iotex-core/pkg/util/httputil"
    29  	"github.com/iotexproject/iotex-core/server/itx/nodestats"
    30  )
    31  
    32  // Server is the iotex server instance containing all components.
    33  type Server struct {
    34  	cfg                  config.Config
    35  	rootChainService     *chainservice.ChainService
    36  	chainservices        map[uint32]*chainservice.ChainService
    37  	apiServers           map[uint32]*api.ServerV2
    38  	p2pAgent             p2p.Agent
    39  	dispatcher           dispatcher.Dispatcher
    40  	nodeStats            *nodestats.NodeStats
    41  	initializedSubChains map[uint32]bool
    42  	mutex                sync.RWMutex
    43  	subModuleCancel      context.CancelFunc
    44  }
    45  
    46  // NewServer creates a new server
    47  // TODO clean up config, make root config contains network, dispatch and chainservice
    48  func NewServer(cfg config.Config) (*Server, error) {
    49  	return newServer(cfg, false)
    50  }
    51  
    52  // NewInMemTestServer creates a test server in memory
    53  func NewInMemTestServer(cfg config.Config) (*Server, error) { // notest
    54  	return newServer(cfg, true)
    55  }
    56  
    57  func newServer(cfg config.Config, testing bool) (*Server, error) {
    58  	// create dispatcher instance
    59  	dispatcher, err := dispatcher.NewDispatcher(cfg.Dispatcher)
    60  	if err != nil {
    61  		return nil, errors.Wrap(err, "fail to create dispatcher")
    62  	}
    63  	var p2pAgent p2p.Agent
    64  	switch cfg.Consensus.Scheme {
    65  	case config.StandaloneScheme:
    66  		p2pAgent = p2p.NewDummyAgent()
    67  	default:
    68  		p2pAgent = p2p.NewAgent(cfg.Network, cfg.Chain.ID, cfg.Genesis.Hash(), dispatcher.HandleBroadcast, dispatcher.HandleTell)
    69  	}
    70  	chains := make(map[uint32]*chainservice.ChainService)
    71  	apiServers := make(map[uint32]*api.ServerV2)
    72  	var cs *chainservice.ChainService
    73  	builder := chainservice.NewBuilder(cfg)
    74  	builder.SetP2PAgent(p2pAgent)
    75  	rpcStats := nodestats.NewAPILocalStats()
    76  	builder.SetRPCStats(rpcStats)
    77  	if testing {
    78  		cs, err = builder.BuildForTest()
    79  	} else {
    80  		cs, err = builder.Build()
    81  	}
    82  	if err != nil {
    83  		return nil, errors.Wrap(err, "fail to create chain service")
    84  	}
    85  	nodeStats := nodestats.NewNodeStats(rpcStats, cs.BlockSync(), p2pAgent)
    86  	apiServer, err := cs.NewAPIServer(cfg.API, cfg.Plugins)
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "failed to create api server")
    89  	}
    90  	if apiServer != nil {
    91  		apiServers[cs.ChainID()] = apiServer
    92  		if err := cs.Blockchain().AddSubscriber(apiServer); err != nil {
    93  			return nil, errors.Wrap(err, "failed to add api server as subscriber")
    94  		}
    95  	}
    96  	// TODO: explorer dependency deleted here at #1085, need to revive by migrating to api
    97  	chains[cs.ChainID()] = cs
    98  	dispatcher.AddSubscriber(cs.ChainID(), cs)
    99  	svr := Server{
   100  		cfg:                  cfg,
   101  		p2pAgent:             p2pAgent,
   102  		dispatcher:           dispatcher,
   103  		rootChainService:     cs,
   104  		chainservices:        chains,
   105  		apiServers:           apiServers,
   106  		nodeStats:            nodeStats,
   107  		initializedSubChains: map[uint32]bool{},
   108  	}
   109  	// Setup sub-chain starter
   110  	// TODO: sub-chain infra should use main-chain API instead of protocol directly
   111  	return &svr, nil
   112  }
   113  
   114  // Start starts the server
   115  func (s *Server) Start(ctx context.Context) error {
   116  	cctx, cancel := context.WithCancel(ctx)
   117  	s.subModuleCancel = cancel
   118  	for id, cs := range s.chainservices {
   119  		if err := cs.Start(cctx); err != nil {
   120  			return errors.Wrap(err, "error when starting blockchain")
   121  		}
   122  		if as, ok := s.apiServers[id]; ok {
   123  			if err := as.Start(cctx); err != nil {
   124  				return errors.Wrapf(err, "failed to start api server for chain %d", id)
   125  			}
   126  		}
   127  	}
   128  	if err := s.p2pAgent.Start(cctx); err != nil {
   129  		return errors.Wrap(err, "error when starting P2P agent")
   130  	}
   131  	if err := s.dispatcher.Start(cctx); err != nil {
   132  		return errors.Wrap(err, "error when starting dispatcher")
   133  	}
   134  	if err := s.nodeStats.Start(cctx); err != nil {
   135  		return errors.Wrap(err, "error when starting node stats")
   136  	}
   137  	return nil
   138  }
   139  
   140  // Stop stops the server
   141  func (s *Server) Stop(ctx context.Context) error {
   142  	defer s.subModuleCancel()
   143  	if err := s.nodeStats.Stop(ctx); err != nil {
   144  		return errors.Wrap(err, "error when stopping node stats")
   145  	}
   146  	if err := s.p2pAgent.Stop(ctx); err != nil {
   147  		// notest
   148  		return errors.Wrap(err, "error when stopping P2P agent")
   149  	}
   150  	if err := s.dispatcher.Stop(ctx); err != nil {
   151  		// notest
   152  		return errors.Wrap(err, "error when stopping dispatcher")
   153  	}
   154  	for id, cs := range s.chainservices {
   155  		if as, ok := s.apiServers[id]; ok {
   156  			if err := as.Stop(ctx); err != nil {
   157  				return errors.Wrapf(err, "error when stopping api server")
   158  			}
   159  		}
   160  		if err := cs.Stop(ctx); err != nil {
   161  			return errors.Wrap(err, "error when stopping blockchain")
   162  		}
   163  	}
   164  	return nil
   165  }
   166  
   167  // NewSubChainService creates a new chain service in this server.
   168  func (s *Server) NewSubChainService(cfg config.Config) error {
   169  	s.mutex.Lock()
   170  	defer s.mutex.Unlock()
   171  	// TODO: explorer dependency deleted here at #1085, need to revive by migrating to api
   172  	builder := chainservice.NewBuilder(cfg)
   173  	cs, err := builder.SetP2PAgent(s.p2pAgent).BuildForSubChain()
   174  	if err != nil {
   175  		return err
   176  	}
   177  	s.chainservices[cs.ChainID()] = cs
   178  	return nil
   179  }
   180  
   181  // StopChainService stops the chain service run in the server.
   182  func (s *Server) StopChainService(ctx context.Context, id uint32) error {
   183  	s.mutex.RLock()
   184  	defer s.mutex.RUnlock()
   185  	c, ok := s.chainservices[id]
   186  	if !ok {
   187  		return errors.New("Chain ID does not match any existing chains")
   188  	}
   189  	return c.Stop(ctx)
   190  }
   191  
   192  // Config returns the server's config
   193  func (s *Server) Config() config.Config {
   194  	s.mutex.RLock()
   195  	defer s.mutex.RUnlock()
   196  	return s.cfg
   197  }
   198  
   199  // P2PAgent returns the P2P agent
   200  func (s *Server) P2PAgent() p2p.Agent {
   201  	return s.p2pAgent
   202  }
   203  
   204  // ChainService returns the chainservice hold in Server with given id.
   205  func (s *Server) ChainService(id uint32) *chainservice.ChainService {
   206  	s.mutex.RLock()
   207  	defer s.mutex.RUnlock()
   208  	return s.chainservices[id]
   209  }
   210  
   211  // APIServer returns the API Server hold in Server with given id.
   212  func (s *Server) APIServer(id uint32) *api.ServerV2 {
   213  	s.mutex.RLock()
   214  	defer s.mutex.RUnlock()
   215  	return s.apiServers[id]
   216  }
   217  
   218  // Dispatcher returns the Dispatcher
   219  func (s *Server) Dispatcher() dispatcher.Dispatcher {
   220  	return s.dispatcher
   221  }
   222  
   223  // StartServer starts a node server
   224  func StartServer(ctx context.Context, svr *Server, probeSvr *probe.Server, cfg config.Config) {
   225  	if err := svr.Start(ctx); err != nil {
   226  		log.L().Fatal("Failed to start server.", zap.Error(err))
   227  		return
   228  	}
   229  	defer func() {
   230  		if err := svr.Stop(context.Background()); err != nil {
   231  			log.L().Panic("Failed to stop server.", zap.Error(err))
   232  		}
   233  	}()
   234  	if err := probeSvr.TurnOn(); err != nil {
   235  		log.L().Panic("Failed to turn on probe server.", zap.Error(err))
   236  	}
   237  
   238  	if cfg.System.HeartbeatInterval > 0 {
   239  		task := routine.NewRecurringTask(NewHeartbeatHandler(svr, cfg.Network).Log, cfg.System.HeartbeatInterval)
   240  		if err := task.Start(ctx); err != nil {
   241  			log.L().Panic("Failed to start heartbeat routine.", zap.Error(err))
   242  		}
   243  		defer func() {
   244  			if err := task.Stop(ctx); err != nil {
   245  				log.L().Panic("Failed to stop heartbeat routine.", zap.Error(err))
   246  			}
   247  		}()
   248  	}
   249  
   250  	var adminserv http.Server
   251  	if cfg.System.HTTPAdminPort > 0 {
   252  		mux := http.NewServeMux()
   253  		log.RegisterLevelConfigMux(mux)
   254  		haCtl := ha.New(svr.rootChainService.Consensus())
   255  		mux.Handle("/ha", http.HandlerFunc(haCtl.Handle))
   256  		mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
   257  		mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
   258  		mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
   259  		mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
   260  		mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
   261  
   262  		port := fmt.Sprintf(":%d", cfg.System.HTTPAdminPort)
   263  		adminserv = httputil.NewServer(port, mux)
   264  		defer func() {
   265  			if err := adminserv.Shutdown(ctx); err != nil {
   266  				log.L().Error("Error when serving metrics data.", zap.Error(err))
   267  			}
   268  		}()
   269  		go func() {
   270  			runtime.SetMutexProfileFraction(1)
   271  			runtime.SetBlockProfileRate(1)
   272  			ln, err := httputil.LimitListener(adminserv.Addr)
   273  			if err != nil {
   274  				log.L().Error("Error when listen to profiling port.", zap.Error(err))
   275  				return
   276  			}
   277  			if err := adminserv.Serve(ln); err != nil {
   278  				log.L().Error("Error when serving performance profiling data.", zap.Error(err))
   279  			}
   280  		}()
   281  	}
   282  
   283  	<-ctx.Done()
   284  	if err := probeSvr.TurnOff(); err != nil {
   285  		log.L().Panic("Failed to turn off probe server.", zap.Error(err))
   286  	}
   287  }