arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/node/node.go
node/node.goRaw
  1package node
  2
  3import (
  4	"context"
  5	"fmt"
  6	"net/http"
  7	"sync"
  8	"time"
  9
 10	"github.com/ethereum/go-ethereum/accounts"
 11	ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
 12	"github.com/ethereum/go-ethereum/ethclient"
 13	"github.com/gin-contrib/cors"
 14	"github.com/gin-gonic/gin"
 15	"github.com/hermeznetwork/hermez-node/api"
 16	"github.com/hermeznetwork/hermez-node/batchbuilder"
 17	"github.com/hermeznetwork/hermez-node/common"
 18	"github.com/hermeznetwork/hermez-node/config"
 19	"github.com/hermeznetwork/hermez-node/coordinator"
 20	dbUtils "github.com/hermeznetwork/hermez-node/db"
 21	"github.com/hermeznetwork/hermez-node/db/historydb"
 22	"github.com/hermeznetwork/hermez-node/db/l2db"
 23	"github.com/hermeznetwork/hermez-node/db/statedb"
 24	"github.com/hermeznetwork/hermez-node/eth"
 25	"github.com/hermeznetwork/hermez-node/log"
 26	"github.com/hermeznetwork/hermez-node/priceupdater"
 27	"github.com/hermeznetwork/hermez-node/prover"
 28	"github.com/hermeznetwork/hermez-node/synchronizer"
 29	"github.com/hermeznetwork/hermez-node/test/debugapi"
 30	"github.com/hermeznetwork/hermez-node/txprocessor"
 31	"github.com/hermeznetwork/hermez-node/txselector"
 32	"github.com/hermeznetwork/tracerr"
 33	"github.com/jmoiron/sqlx"
 34	"github.com/russross/meddler"
 35)
 36
 37// Mode sets the working mode of the node (synchronizer or coordinator)
 38type Mode string
 39
 40const (
 41	// ModeCoordinator defines the mode of the HermezNode as Coordinator, which
 42	// means that the node is set to forge (which also will be synchronizing with
 43	// the L1 blockchain state)
 44	ModeCoordinator Mode = "coordinator"
 45
 46	// ModeSynchronizer defines the mode of the HermezNode as Synchronizer, which
 47	// means that the node is set to only synchronize with the L1 blockchain state
 48	// and will not forge
 49	ModeSynchronizer Mode = "synchronizer"
 50)
 51
 52// Node is the Hermez Node
 53type Node struct {
 54	nodeAPI      *NodeAPI
 55	debugAPI     *debugapi.DebugAPI
 56	priceUpdater *priceupdater.PriceUpdater
 57	// Coordinator
 58	coord *coordinator.Coordinator
 59
 60	// Synchronizer
 61	sync *synchronizer.Synchronizer
 62
 63	// General
 64	cfg     *config.Node
 65	mode    Mode
 66	sqlConn *sqlx.DB
 67	ctx     context.Context
 68	wg      sync.WaitGroup
 69	cancel  context.CancelFunc
 70}
 71
 72// NewNode creates a Node
 73func NewNode(mode Mode, cfg *config.Node) (*Node, error) {
 74	meddler.Debug = cfg.Debug.MeddlerLogs
 75	// Stablish DB connection
 76	db, err := dbUtils.InitSQLDB(
 77		cfg.PostgreSQL.Port,
 78		cfg.PostgreSQL.Host,
 79		cfg.PostgreSQL.User,
 80		cfg.PostgreSQL.Password,
 81		cfg.PostgreSQL.Name,
 82	)
 83	if err != nil {
 84		return nil, tracerr.Wrap(err)
 85	}
 86
 87	historyDB := historydb.NewHistoryDB(db)
 88
 89	ethClient, err := ethclient.Dial(cfg.Web3.URL)
 90	if err != nil {
 91		return nil, tracerr.Wrap(err)
 92	}
 93	var ethCfg eth.EthereumConfig
 94	var account *accounts.Account
 95	var keyStore *ethKeystore.KeyStore
 96	if mode == ModeCoordinator {
 97		ethCfg = eth.EthereumConfig{
 98			CallGasLimit: cfg.Coordinator.EthClient.CallGasLimit,
 99			GasPriceDiv:  cfg.Coordinator.EthClient.GasPriceDiv,
100		}
101
102		scryptN := ethKeystore.StandardScryptN
103		scryptP := ethKeystore.StandardScryptP
104		if cfg.Coordinator.Debug.LightScrypt {
105			scryptN = ethKeystore.LightScryptN
106			scryptP = ethKeystore.LightScryptP
107		}
108		keyStore = ethKeystore.NewKeyStore(cfg.Coordinator.EthClient.Keystore.Path,
109			scryptN, scryptP)
110
111		// Unlock Coordinator ForgerAddr in the keystore to make calls
112		// to ForgeBatch in the smart contract
113		if !keyStore.HasAddress(cfg.Coordinator.ForgerAddress) {
114			return nil, tracerr.Wrap(fmt.Errorf(
115				"ethereum keystore doesn't have the key for address %v",
116				cfg.Coordinator.ForgerAddress))
117		}
118		account = &accounts.Account{
119			Address: cfg.Coordinator.ForgerAddress,
120		}
121		if err := keyStore.Unlock(*account,
122			cfg.Coordinator.EthClient.Keystore.Password); err != nil {
123			return nil, tracerr.Wrap(err)
124		}
125		log.Infow("Forger ethereum account unlocked in the keystore",
126			"addr", cfg.Coordinator.ForgerAddress)
127	}
128	client, err := eth.NewClient(ethClient, account, keyStore, &eth.ClientConfig{
129		Ethereum: ethCfg,
130		Rollup: eth.RollupConfig{
131			Address: cfg.SmartContracts.Rollup,
132		},
133		Auction: eth.AuctionConfig{
134			Address: cfg.SmartContracts.Auction,
135			TokenHEZ: eth.TokenConfig{
136				Address: cfg.SmartContracts.TokenHEZ,
137				Name:    cfg.SmartContracts.TokenHEZName,
138			},
139		},
140		WDelayer: eth.WDelayerConfig{
141			Address: cfg.SmartContracts.WDelayer,
142		},
143	})
144	if err != nil {
145		return nil, tracerr.Wrap(err)
146	}
147
148	chainID, err := client.EthChainID()
149	if err != nil {
150		return nil, tracerr.Wrap(err)
151	}
152	if !chainID.IsUint64() {
153		return nil, tracerr.Wrap(fmt.Errorf("chainID cannot be represented as uint64"))
154	}
155	chainIDU64 := chainID.Uint64()
156	const maxUint16 uint64 = 0xffff
157	if chainIDU64 > maxUint16 {
158		return nil, tracerr.Wrap(fmt.Errorf("chainID overflows uint16"))
159	}
160	chainIDU16 := uint16(chainIDU64)
161
162	const safeStateDBKeep = 128
163	if cfg.StateDB.Keep < safeStateDBKeep {
164		return nil, tracerr.Wrap(fmt.Errorf("cfg.StateDB.Keep = %v < %v, which is unsafe",
165			cfg.StateDB.Keep, safeStateDBKeep))
166	}
167	stateDB, err := statedb.NewStateDB(statedb.Config{
168		Path:    cfg.StateDB.Path,
169		Keep:    cfg.StateDB.Keep,
170		Type:    statedb.TypeSynchronizer,
171		NLevels: statedb.MaxNLevels,
172	})
173	if err != nil {
174		return nil, tracerr.Wrap(err)
175	}
176
177	sync, err := synchronizer.NewSynchronizer(client, historyDB, stateDB, synchronizer.Config{
178		StatsRefreshPeriod: cfg.Synchronizer.StatsRefreshPeriod.Duration,
179		ChainID:            chainIDU16,
180	})
181	if err != nil {
182		return nil, tracerr.Wrap(err)
183	}
184	initSCVars := sync.SCVars()
185
186	scConsts := synchronizer.SCConsts{
187		Rollup:   *sync.RollupConstants(),
188		Auction:  *sync.AuctionConstants(),
189		WDelayer: *sync.WDelayerConstants(),
190	}
191
192	var coord *coordinator.Coordinator
193	var l2DB *l2db.L2DB
194	if mode == ModeCoordinator {
195		l2DB = l2db.NewL2DB(
196			db,
197			cfg.Coordinator.L2DB.SafetyPeriod,
198			cfg.Coordinator.L2DB.MaxTxs,
199			cfg.Coordinator.L2DB.TTL.Duration,
200		)
201
202		// Unlock FeeAccount EthAddr in the keystore to generate the
203		// account creation authorization
204		if !keyStore.HasAddress(cfg.Coordinator.FeeAccount.Address) {
205			return nil, tracerr.Wrap(fmt.Errorf(
206				"ethereum keystore doesn't have the key for address %v",
207				cfg.Coordinator.FeeAccount.Address))
208		}
209		feeAccount := accounts.Account{
210			Address: cfg.Coordinator.FeeAccount.Address,
211		}
212		if err := keyStore.Unlock(feeAccount,
213			cfg.Coordinator.EthClient.Keystore.Password); err != nil {
214			return nil, tracerr.Wrap(err)
215		}
216		auth := &common.AccountCreationAuth{
217			EthAddr: cfg.Coordinator.FeeAccount.Address,
218			BJJ:     cfg.Coordinator.FeeAccount.BJJ,
219		}
220		if err := auth.Sign(func(msg []byte) ([]byte, error) {
221			return keyStore.SignHash(feeAccount, msg)
222		}, chainIDU16, cfg.SmartContracts.Rollup); err != nil {
223			return nil, err
224		}
225		coordAccount := &txselector.CoordAccount{
226			Addr:                cfg.Coordinator.FeeAccount.Address,
227			BJJ:                 cfg.Coordinator.FeeAccount.BJJ,
228			AccountCreationAuth: auth.Signature,
229		}
230		txSelector, err := txselector.NewTxSelector(coordAccount, cfg.Coordinator.TxSelector.Path, stateDB, l2DB)
231		if err != nil {
232			return nil, tracerr.Wrap(err)
233		}
234		batchBuilder, err := batchbuilder.NewBatchBuilder(cfg.Coordinator.BatchBuilder.Path,
235			stateDB, 0, uint64(cfg.Coordinator.Circuit.NLevels))
236		if err != nil {
237			return nil, tracerr.Wrap(err)
238		}
239		if err != nil {
240			return nil, tracerr.Wrap(err)
241		}
242		serverProofs := make([]prover.Client, len(cfg.Coordinator.ServerProofs))
243		for i, serverProofCfg := range cfg.Coordinator.ServerProofs {
244			serverProofs[i] = prover.NewProofServerClient(serverProofCfg.URL,
245				cfg.Coordinator.ProofServerPollInterval.Duration)
246		}
247
248		txProcessorCfg := txprocessor.Config{
249			NLevels:  uint32(cfg.Coordinator.Circuit.NLevels),
250			MaxTx:    uint32(cfg.Coordinator.Circuit.MaxTx),
251			ChainID:  chainIDU16,
252			MaxFeeTx: common.RollupConstMaxFeeIdxCoordinator,
253			MaxL1Tx:  common.RollupConstMaxL1Tx,
254		}
255		var verifierIdx int
256		if cfg.Coordinator.Debug.RollupVerifierIndex == nil {
257			verifierIdx, err = scConsts.Rollup.FindVerifierIdx(
258				cfg.Coordinator.Circuit.MaxTx,
259				cfg.Coordinator.Circuit.NLevels,
260			)
261			if err != nil {
262				return nil, tracerr.Wrap(err)
263			}
264			log.Infow("Found verifier that matches circuit config", "verifierIdx", verifierIdx)
265		} else {
266			verifierIdx = *cfg.Coordinator.Debug.RollupVerifierIndex
267			log.Infow("Using debug verifier index from config", "verifierIdx", verifierIdx)
268			if verifierIdx >= len(scConsts.Rollup.Verifiers) {
269				return nil, tracerr.Wrap(
270					fmt.Errorf("verifierIdx (%v) >= "+
271						"len(scConsts.Rollup.Verifiers) (%v)",
272						verifierIdx, len(scConsts.Rollup.Verifiers)))
273			}
274			verifier := scConsts.Rollup.Verifiers[verifierIdx]
275			if verifier.MaxTx != cfg.Coordinator.Circuit.MaxTx ||
276				verifier.NLevels != cfg.Coordinator.Circuit.NLevels {
277				return nil, tracerr.Wrap(
278					fmt.Errorf("Circuit config and verifier params don't match.  "+
279						"circuit.MaxTx = %v, circuit.NLevels = %v, "+
280						"verifier.MaxTx = %v, verifier.NLevels = %v",
281						cfg.Coordinator.Circuit.MaxTx, cfg.Coordinator.Circuit.NLevels,
282						verifier.MaxTx, verifier.NLevels,
283					))
284			}
285		}
286
287		coord, err = coordinator.NewCoordinator(
288			coordinator.Config{
289				ForgerAddress:          cfg.Coordinator.ForgerAddress,
290				ConfirmBlocks:          cfg.Coordinator.ConfirmBlocks,
291				L1BatchTimeoutPerc:     cfg.Coordinator.L1BatchTimeoutPerc,
292				ForgeRetryInterval:     cfg.Coordinator.ForgeRetryInterval.Duration,
293				SyncRetryInterval:      cfg.Coordinator.SyncRetryInterval.Duration,
294				EthClientAttempts:      cfg.Coordinator.EthClient.Attempts,
295				EthClientAttemptsDelay: cfg.Coordinator.EthClient.AttemptsDelay.Duration,
296				TxManagerCheckInterval: cfg.Coordinator.EthClient.CheckLoopInterval.Duration,
297				DebugBatchPath:         cfg.Coordinator.Debug.BatchPath,
298				Purger: coordinator.PurgerCfg{
299					PurgeBatchDelay:      cfg.Coordinator.L2DB.PurgeBatchDelay,
300					InvalidateBatchDelay: cfg.Coordinator.L2DB.InvalidateBatchDelay,
301					PurgeBlockDelay:      cfg.Coordinator.L2DB.PurgeBlockDelay,
302					InvalidateBlockDelay: cfg.Coordinator.L2DB.InvalidateBlockDelay,
303				},
304				VerifierIdx:       uint8(verifierIdx),
305				TxProcessorConfig: txProcessorCfg,
306			},
307			historyDB,
308			l2DB,
309			txSelector,
310			batchBuilder,
311			serverProofs,
312			client,
313			&scConsts,
314			&synchronizer.SCVariables{
315				Rollup:   *initSCVars.Rollup,
316				Auction:  *initSCVars.Auction,
317				WDelayer: *initSCVars.WDelayer,
318			},
319		)
320		if err != nil {
321			return nil, tracerr.Wrap(err)
322		}
323	}
324	var nodeAPI *NodeAPI
325	if cfg.API.Address != "" {
326		if cfg.API.UpdateMetricsInterval.Duration == 0 {
327			return nil, tracerr.Wrap(fmt.Errorf("invalid cfg.API.UpdateMetricsInterval: %v",
328				cfg.API.UpdateMetricsInterval.Duration))
329		}
330		if cfg.API.UpdateRecommendedFeeInterval.Duration == 0 {
331			return nil, tracerr.Wrap(fmt.Errorf("invalid cfg.API.UpdateRecommendedFeeInterval: %v",
332				cfg.API.UpdateRecommendedFeeInterval.Duration))
333		}
334		server := gin.Default()
335		coord := false
336		if mode == ModeCoordinator {
337			coord = cfg.Coordinator.API.Coordinator
338		}
339		var err error
340		nodeAPI, err = NewNodeAPI(
341			cfg.API.Address,
342			coord, cfg.API.Explorer,
343			server,
344			historyDB,
345			stateDB,
346			l2DB,
347			&api.Config{
348				RollupConstants:   scConsts.Rollup,
349				AuctionConstants:  scConsts.Auction,
350				WDelayerConstants: scConsts.WDelayer,
351				ChainID:           chainIDU16,
352				HermezAddress:     cfg.SmartContracts.Rollup,
353			},
354		)
355		if err != nil {
356			return nil, tracerr.Wrap(err)
357		}
358		nodeAPI.api.SetRollupVariables(*initSCVars.Rollup)
359		nodeAPI.api.SetAuctionVariables(*initSCVars.Auction)
360		nodeAPI.api.SetWDelayerVariables(*initSCVars.WDelayer)
361	}
362	var debugAPI *debugapi.DebugAPI
363	if cfg.Debug.APIAddress != "" {
364		debugAPI = debugapi.NewDebugAPI(cfg.Debug.APIAddress, stateDB, sync)
365	}
366	priceUpdater, err := priceupdater.NewPriceUpdater(cfg.PriceUpdater.URL,
367		priceupdater.APIType(cfg.PriceUpdater.Type), historyDB)
368	if err != nil {
369		return nil, tracerr.Wrap(err)
370	}
371	ctx, cancel := context.WithCancel(context.Background())
372	return &Node{
373		nodeAPI:      nodeAPI,
374		debugAPI:     debugAPI,
375		priceUpdater: priceUpdater,
376		coord:        coord,
377		sync:         sync,
378		cfg:          cfg,
379		mode:         mode,
380		sqlConn:      db,
381		ctx:          ctx,
382		cancel:       cancel,
383	}, nil
384}
385
386// NodeAPI holds the node http API
387type NodeAPI struct { //nolint:golint
388	api    *api.API
389	engine *gin.Engine
390	addr   string
391}
392
393func handleNoRoute(c *gin.Context) {
394	c.JSON(http.StatusNotFound, gin.H{
395		"error": "404 page not found",
396	})
397}
398
399// NewNodeAPI creates a new NodeAPI (which internally calls api.NewAPI)
400func NewNodeAPI(
401	addr string,
402	coordinatorEndpoints, explorerEndpoints bool,
403	server *gin.Engine,
404	hdb *historydb.HistoryDB,
405	sdb *statedb.StateDB,
406	l2db *l2db.L2DB,
407	config *api.Config,
408) (*NodeAPI, error) {
409	engine := gin.Default()
410	engine.NoRoute(handleNoRoute)
411	engine.Use(cors.Default())
412	_api, err := api.NewAPI(
413		coordinatorEndpoints, explorerEndpoints,
414		engine,
415		hdb,
416		sdb,
417		l2db,
418		config,
419	)
420	if err != nil {
421		return nil, tracerr.Wrap(err)
422	}
423	return &NodeAPI{
424		addr:   addr,
425		api:    _api,
426		engine: engine,
427	}, nil
428}
429
430// Run starts the http server of the NodeAPI.  To stop it, pass a context with
431// cancelation.
432func (a *NodeAPI) Run(ctx context.Context) error {
433	server := &http.Server{
434		Addr:    a.addr,
435		Handler: a.engine,
436		// TODO: Figure out best parameters for production
437		ReadTimeout:    30 * time.Second, //nolint:gomnd
438		WriteTimeout:   30 * time.Second, //nolint:gomnd
439		MaxHeaderBytes: 1 << 20,          //nolint:gomnd
440	}
441	go func() {
442		log.Infof("NodeAPI is ready at %v", a.addr)
443		if err := server.ListenAndServe(); err != nil && tracerr.Unwrap(err) != http.ErrServerClosed {
444			log.Fatalf("Listen: %s\n", err)
445		}
446	}()
447
448	<-ctx.Done()
449	log.Info("Stopping NodeAPI...")
450	ctxTimeout, cancel := context.WithTimeout(context.Background(), 10*time.Second) //nolint:gomnd
451	defer cancel()
452	if err := server.Shutdown(ctxTimeout); err != nil {
453		return tracerr.Wrap(err)
454	}
455	log.Info("NodeAPI done")
456	return nil
457}
458
459func (n *Node) handleNewBlock(ctx context.Context, stats *synchronizer.Stats, vars synchronizer.SCVariablesPtr,
460	batches []common.BatchData) {
461	if n.mode == ModeCoordinator {
462		n.coord.SendMsg(ctx, coordinator.MsgSyncBlock{
463			Stats:   *stats,
464			Vars:    vars,
465			Batches: batches,
466		})
467	}
468	if n.nodeAPI != nil {
469		if vars.Rollup != nil {
470			n.nodeAPI.api.SetRollupVariables(*vars.Rollup)
471		}
472		if vars.Auction != nil {
473			n.nodeAPI.api.SetAuctionVariables(*vars.Auction)
474		}
475		if vars.WDelayer != nil {
476			n.nodeAPI.api.SetWDelayerVariables(*vars.WDelayer)
477		}
478
479		if stats.Synced() {
480			if err := n.nodeAPI.api.UpdateNetworkInfo(
481				stats.Eth.LastBlock, stats.Sync.LastBlock,
482				common.BatchNum(stats.Eth.LastBatch),
483				stats.Sync.Auction.CurrentSlot.SlotNum,
484			); err != nil {
485				log.Errorw("API.UpdateNetworkInfo", "err", err)
486			}
487		}
488	}
489}
490
491func (n *Node) handleReorg(ctx context.Context, stats *synchronizer.Stats, vars synchronizer.SCVariablesPtr) {
492	if n.mode == ModeCoordinator {
493		n.coord.SendMsg(ctx, coordinator.MsgSyncReorg{
494			Stats: *stats,
495			Vars:  vars,
496		})
497	}
498	if n.nodeAPI != nil {
499		vars := n.sync.SCVars()
500		n.nodeAPI.api.SetRollupVariables(*vars.Rollup)
501		n.nodeAPI.api.SetAuctionVariables(*vars.Auction)
502		n.nodeAPI.api.SetWDelayerVariables(*vars.WDelayer)
503		n.nodeAPI.api.UpdateNetworkInfoBlock(
504			stats.Eth.LastBlock, stats.Sync.LastBlock,
505		)
506	}
507}
508
509// TODO(Edu): Consider keeping the `lastBlock` inside synchronizer so that we
510// don't have to pass it around.
511func (n *Node) syncLoopFn(ctx context.Context, lastBlock *common.Block) (*common.Block, time.Duration, error) {
512	blockData, discarded, err := n.sync.Sync2(ctx, lastBlock)
513	stats := n.sync.Stats()
514	if err != nil {
515		// case: error
516		return nil, n.cfg.Synchronizer.SyncLoopInterval.Duration, tracerr.Wrap(err)
517	} else if discarded != nil {
518		// case: reorg
519		log.Infow("Synchronizer.Sync reorg", "discarded", *discarded)
520		vars := n.sync.SCVars()
521		n.handleReorg(ctx, stats, vars)
522		return nil, time.Duration(0), nil
523	} else if blockData != nil {
524		// case: new block
525		vars := synchronizer.SCVariablesPtr{
526			Rollup:   blockData.Rollup.Vars,
527			Auction:  blockData.Auction.Vars,
528			WDelayer: blockData.WDelayer.Vars,
529		}
530		n.handleNewBlock(ctx, stats, vars, blockData.Rollup.Batches)
531		return &blockData.Block, time.Duration(0), nil
532	} else {
533		// case: no block
534		return lastBlock, n.cfg.Synchronizer.SyncLoopInterval.Duration, nil
535	}
536}
537
538// StartSynchronizer starts the synchronizer
539func (n *Node) StartSynchronizer() {
540	log.Info("Starting Synchronizer...")
541
542	// Trigger a manual call to handleNewBlock with the loaded state of the
543	// synchronizer in order to quickly activate the API and Coordinator
544	// and avoid waiting for the next block.  Without this, the API and
545	// Coordinator will not react until the following block (starting from
546	// the last synced one) is synchronized
547	stats := n.sync.Stats()
548	vars := n.sync.SCVars()
549	n.handleNewBlock(n.ctx, stats, vars, []common.BatchData{})
550
551	n.wg.Add(1)
552	go func() {
553		var err error
554		var lastBlock *common.Block
555		waitDuration := time.Duration(0)
556		for {
557			select {
558			case <-n.ctx.Done():
559				log.Info("Synchronizer done")
560				n.wg.Done()
561				return
562			case <-time.After(waitDuration):
563				if lastBlock, waitDuration, err = n.syncLoopFn(n.ctx,
564					lastBlock); err != nil {
565					if n.ctx.Err() != nil {
566						continue
567					}
568					log.Errorw("Synchronizer.Sync", "err", err)
569				}
570			}
571		}
572	}()
573
574	n.wg.Add(1)
575	go func() {
576		for {
577			select {
578			case <-n.ctx.Done():
579				log.Info("PriceUpdater done")
580				n.wg.Done()
581				return
582			case <-time.After(n.cfg.PriceUpdater.Interval.Duration):
583				if err := n.priceUpdater.UpdateTokenList(); err != nil {
584					log.Errorw("PriceUpdater.UpdateTokenList()", "err", err)
585				}
586				n.priceUpdater.UpdatePrices(n.ctx)
587			}
588		}
589	}()
590}
591
592// StartDebugAPI starts the DebugAPI
593func (n *Node) StartDebugAPI() {
594	log.Info("Starting DebugAPI...")
595	n.wg.Add(1)
596	go func() {
597		defer func() {
598			log.Info("DebugAPI routine stopped")
599			n.wg.Done()
600		}()
601		if err := n.debugAPI.Run(n.ctx); err != nil {
602			if n.ctx.Err() != nil {
603				return
604			}
605			log.Fatalw("DebugAPI.Run", "err", err)
606		}
607	}()
608}
609
610// StartNodeAPI starts the NodeAPI
611func (n *Node) StartNodeAPI() {
612	log.Info("Starting NodeAPI...")
613	n.wg.Add(1)
614	go func() {
615		defer func() {
616			log.Info("NodeAPI routine stopped")
617			n.wg.Done()
618		}()
619		if err := n.nodeAPI.Run(n.ctx); err != nil {
620			if n.ctx.Err() != nil {
621				return
622			}
623			log.Fatalw("NodeAPI.Run", "err", err)
624		}
625	}()
626
627	n.wg.Add(1)
628	go func() {
629		for {
630			select {
631			case <-n.ctx.Done():
632				log.Info("API.UpdateMetrics loop done")
633				n.wg.Done()
634				return
635			case <-time.After(n.cfg.API.UpdateMetricsInterval.Duration):
636				if err := n.nodeAPI.api.UpdateMetrics(); err != nil {
637					log.Errorw("API.UpdateMetrics", "err", err)
638				}
639			}
640		}
641	}()
642
643	n.wg.Add(1)
644	go func() {
645		for {
646			select {
647			case <-n.ctx.Done():
648				log.Info("API.UpdateRecommendedFee loop done")
649				n.wg.Done()
650				return
651			case <-time.After(n.cfg.API.UpdateRecommendedFeeInterval.Duration):
652				if err := n.nodeAPI.api.UpdateRecommendedFee(); err != nil {
653					log.Errorw("API.UpdateRecommendedFee", "err", err)
654				}
655			}
656		}
657	}()
658}
659
660// Start the node
661func (n *Node) Start() {
662	log.Infow("Starting node...", "mode", n.mode)
663	if n.debugAPI != nil {
664		n.StartDebugAPI()
665	}
666	if n.nodeAPI != nil {
667		n.StartNodeAPI()
668	}
669	if n.mode == ModeCoordinator {
670		log.Info("Starting Coordinator...")
671		n.coord.Start()
672	}
673	n.StartSynchronizer()
674}
675
676// Stop the node
677func (n *Node) Stop() {
678	log.Infow("Stopping node...")
679	n.cancel()
680	n.wg.Wait()
681	if n.mode == ModeCoordinator {
682		log.Info("Stopping Coordinator...")
683		n.coord.Stop()
684	}
685	// Close kv DBs
686	n.sync.StateDB().Close()
687	if n.mode == ModeCoordinator {
688		n.coord.TxSelector().LocalAccountsDB().Close()
689		n.coord.BatchBuilder().LocalStateDB().Close()
690	}
691}