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