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		verifierIdx, err := scConsts.Rollup.FindVerifierIdx(
256			cfg.Coordinator.Circuit.MaxTx,
257			cfg.Coordinator.Circuit.NLevels,
258		)
259		if err != nil {
260			return nil, tracerr.Wrap(err)
261		}
262		log.Infow("Found verifier that matches circuit config", "verifierIdx", verifierIdx)
263
264		coord, err = coordinator.NewCoordinator(
265			coordinator.Config{
266				ForgerAddress:          cfg.Coordinator.ForgerAddress,
267				ConfirmBlocks:          cfg.Coordinator.ConfirmBlocks,
268				L1BatchTimeoutPerc:     cfg.Coordinator.L1BatchTimeoutPerc,
269				ForgeRetryInterval:     cfg.Coordinator.ForgeRetryInterval.Duration,
270				SyncRetryInterval:      cfg.Coordinator.SyncRetryInterval.Duration,
271				EthClientAttempts:      cfg.Coordinator.EthClient.Attempts,
272				EthClientAttemptsDelay: cfg.Coordinator.EthClient.AttemptsDelay.Duration,
273				TxManagerCheckInterval: cfg.Coordinator.EthClient.CheckLoopInterval.Duration,
274				DebugBatchPath:         cfg.Coordinator.Debug.BatchPath,
275				Purger: coordinator.PurgerCfg{
276					PurgeBatchDelay:      cfg.Coordinator.L2DB.PurgeBatchDelay,
277					InvalidateBatchDelay: cfg.Coordinator.L2DB.InvalidateBatchDelay,
278					PurgeBlockDelay:      cfg.Coordinator.L2DB.PurgeBlockDelay,
279					InvalidateBlockDelay: cfg.Coordinator.L2DB.InvalidateBlockDelay,
280				},
281				VerifierIdx:       uint8(verifierIdx),
282				TxProcessorConfig: txProcessorCfg,
283			},
284			historyDB,
285			l2DB,
286			txSelector,
287			batchBuilder,
288			serverProofs,
289			client,
290			&scConsts,
291			&synchronizer.SCVariables{
292				Rollup:   *initSCVars.Rollup,
293				Auction:  *initSCVars.Auction,
294				WDelayer: *initSCVars.WDelayer,
295			},
296		)
297		if err != nil {
298			return nil, tracerr.Wrap(err)
299		}
300	}
301	var nodeAPI *NodeAPI
302	if cfg.API.Address != "" {
303		if cfg.API.UpdateMetricsInterval.Duration == 0 {
304			return nil, tracerr.Wrap(fmt.Errorf("invalid cfg.API.UpdateMetricsInterval: %v",
305				cfg.API.UpdateMetricsInterval.Duration))
306		}
307		if cfg.API.UpdateRecommendedFeeInterval.Duration == 0 {
308			return nil, tracerr.Wrap(fmt.Errorf("invalid cfg.API.UpdateRecommendedFeeInterval: %v",
309				cfg.API.UpdateRecommendedFeeInterval.Duration))
310		}
311		server := gin.Default()
312		coord := false
313		if mode == ModeCoordinator {
314			coord = cfg.Coordinator.API.Coordinator
315		}
316		var err error
317		nodeAPI, err = NewNodeAPI(
318			cfg.API.Address,
319			coord, cfg.API.Explorer,
320			server,
321			historyDB,
322			stateDB,
323			l2DB,
324			&api.Config{
325				RollupConstants:   scConsts.Rollup,
326				AuctionConstants:  scConsts.Auction,
327				WDelayerConstants: scConsts.WDelayer,
328				ChainID:           chainIDU16,
329				HermezAddress:     cfg.SmartContracts.Rollup,
330			},
331		)
332		if err != nil {
333			return nil, tracerr.Wrap(err)
334		}
335		nodeAPI.api.SetRollupVariables(*initSCVars.Rollup)
336		nodeAPI.api.SetAuctionVariables(*initSCVars.Auction)
337		nodeAPI.api.SetWDelayerVariables(*initSCVars.WDelayer)
338	}
339	var debugAPI *debugapi.DebugAPI
340	if cfg.Debug.APIAddress != "" {
341		debugAPI = debugapi.NewDebugAPI(cfg.Debug.APIAddress, stateDB, sync)
342	}
343	priceUpdater, err := priceupdater.NewPriceUpdater(cfg.PriceUpdater.URL,
344		priceupdater.APIType(cfg.PriceUpdater.Type), historyDB)
345	if err != nil {
346		return nil, tracerr.Wrap(err)
347	}
348	ctx, cancel := context.WithCancel(context.Background())
349	return &Node{
350		nodeAPI:      nodeAPI,
351		debugAPI:     debugAPI,
352		priceUpdater: priceUpdater,
353		coord:        coord,
354		sync:         sync,
355		cfg:          cfg,
356		mode:         mode,
357		sqlConn:      db,
358		ctx:          ctx,
359		cancel:       cancel,
360	}, nil
361}
362
363// NodeAPI holds the node http API
364type NodeAPI struct { //nolint:golint
365	api    *api.API
366	engine *gin.Engine
367	addr   string
368}
369
370func handleNoRoute(c *gin.Context) {
371	c.JSON(http.StatusNotFound, gin.H{
372		"error": "404 page not found",
373	})
374}
375
376// NewNodeAPI creates a new NodeAPI (which internally calls api.NewAPI)
377func NewNodeAPI(
378	addr string,
379	coordinatorEndpoints, explorerEndpoints bool,
380	server *gin.Engine,
381	hdb *historydb.HistoryDB,
382	sdb *statedb.StateDB,
383	l2db *l2db.L2DB,
384	config *api.Config,
385) (*NodeAPI, error) {
386	engine := gin.Default()
387	engine.NoRoute(handleNoRoute)
388	engine.Use(cors.Default())
389	_api, err := api.NewAPI(
390		coordinatorEndpoints, explorerEndpoints,
391		engine,
392		hdb,
393		sdb,
394		l2db,
395		config,
396	)
397	if err != nil {
398		return nil, tracerr.Wrap(err)
399	}
400	return &NodeAPI{
401		addr:   addr,
402		api:    _api,
403		engine: engine,
404	}, nil
405}
406
407// Run starts the http server of the NodeAPI.  To stop it, pass a context with
408// cancelation.
409func (a *NodeAPI) Run(ctx context.Context) error {
410	server := &http.Server{
411		Addr:    a.addr,
412		Handler: a.engine,
413		// TODO: Figure out best parameters for production
414		ReadTimeout:    30 * time.Second, //nolint:gomnd
415		WriteTimeout:   30 * time.Second, //nolint:gomnd
416		MaxHeaderBytes: 1 << 20,          //nolint:gomnd
417	}
418	go func() {
419		log.Infof("NodeAPI is ready at %v", a.addr)
420		if err := server.ListenAndServe(); err != nil && tracerr.Unwrap(err) != http.ErrServerClosed {
421			log.Fatalf("Listen: %s\n", err)
422		}
423	}()
424
425	<-ctx.Done()
426	log.Info("Stopping NodeAPI...")
427	ctxTimeout, cancel := context.WithTimeout(context.Background(), 10*time.Second) //nolint:gomnd
428	defer cancel()
429	if err := server.Shutdown(ctxTimeout); err != nil {
430		return tracerr.Wrap(err)
431	}
432	log.Info("NodeAPI done")
433	return nil
434}
435
436func (n *Node) handleNewBlock(ctx context.Context, stats *synchronizer.Stats, vars synchronizer.SCVariablesPtr,
437	batches []common.BatchData) {
438	if n.mode == ModeCoordinator {
439		n.coord.SendMsg(ctx, coordinator.MsgSyncBlock{
440			Stats:   *stats,
441			Vars:    vars,
442			Batches: batches,
443		})
444	}
445	if n.nodeAPI != nil {
446		if vars.Rollup != nil {
447			n.nodeAPI.api.SetRollupVariables(*vars.Rollup)
448		}
449		if vars.Auction != nil {
450			n.nodeAPI.api.SetAuctionVariables(*vars.Auction)
451		}
452		if vars.WDelayer != nil {
453			n.nodeAPI.api.SetWDelayerVariables(*vars.WDelayer)
454		}
455
456		if stats.Synced() {
457			if err := n.nodeAPI.api.UpdateNetworkInfo(
458				stats.Eth.LastBlock, stats.Sync.LastBlock,
459				common.BatchNum(stats.Eth.LastBatch),
460				stats.Sync.Auction.CurrentSlot.SlotNum,
461			); err != nil {
462				log.Errorw("API.UpdateNetworkInfo", "err", err)
463			}
464		}
465	}
466}
467
468func (n *Node) handleReorg(ctx context.Context, stats *synchronizer.Stats, vars synchronizer.SCVariablesPtr) {
469	if n.mode == ModeCoordinator {
470		n.coord.SendMsg(ctx, coordinator.MsgSyncReorg{
471			Stats: *stats,
472			Vars:  vars,
473		})
474	}
475	if n.nodeAPI != nil {
476		vars := n.sync.SCVars()
477		n.nodeAPI.api.SetRollupVariables(*vars.Rollup)
478		n.nodeAPI.api.SetAuctionVariables(*vars.Auction)
479		n.nodeAPI.api.SetWDelayerVariables(*vars.WDelayer)
480		n.nodeAPI.api.UpdateNetworkInfoBlock(
481			stats.Eth.LastBlock, stats.Sync.LastBlock,
482		)
483	}
484}
485
486// TODO(Edu): Consider keeping the `lastBlock` inside synchronizer so that we
487// don't have to pass it around.
488func (n *Node) syncLoopFn(ctx context.Context, lastBlock *common.Block) (*common.Block, time.Duration, error) {
489	blockData, discarded, err := n.sync.Sync2(ctx, lastBlock)
490	stats := n.sync.Stats()
491	if err != nil {
492		// case: error
493		return nil, n.cfg.Synchronizer.SyncLoopInterval.Duration, tracerr.Wrap(err)
494	} else if discarded != nil {
495		// case: reorg
496		log.Infow("Synchronizer.Sync reorg", "discarded", *discarded)
497		vars := n.sync.SCVars()
498		n.handleReorg(ctx, stats, vars)
499		return nil, time.Duration(0), nil
500	} else if blockData != nil {
501		// case: new block
502		vars := synchronizer.SCVariablesPtr{
503			Rollup:   blockData.Rollup.Vars,
504			Auction:  blockData.Auction.Vars,
505			WDelayer: blockData.WDelayer.Vars,
506		}
507		n.handleNewBlock(ctx, stats, vars, blockData.Rollup.Batches)
508		return &blockData.Block, time.Duration(0), nil
509	} else {
510		// case: no block
511		return lastBlock, n.cfg.Synchronizer.SyncLoopInterval.Duration, nil
512	}
513}
514
515// StartSynchronizer starts the synchronizer
516func (n *Node) StartSynchronizer() {
517	log.Info("Starting Synchronizer...")
518
519	// Trigger a manual call to handleNewBlock with the loaded state of the
520	// synchronizer in order to quickly activate the API and Coordinator
521	// and avoid waiting for the next block.  Without this, the API and
522	// Coordinator will not react until the following block (starting from
523	// the last synced one) is synchronized
524	stats := n.sync.Stats()
525	vars := n.sync.SCVars()
526	n.handleNewBlock(n.ctx, stats, vars, []common.BatchData{})
527
528	n.wg.Add(1)
529	go func() {
530		var err error
531		var lastBlock *common.Block
532		waitDuration := time.Duration(0)
533		for {
534			select {
535			case <-n.ctx.Done():
536				log.Info("Synchronizer done")
537				n.wg.Done()
538				return
539			case <-time.After(waitDuration):
540				if lastBlock, waitDuration, err = n.syncLoopFn(n.ctx,
541					lastBlock); err != nil {
542					if n.ctx.Err() != nil {
543						continue
544					}
545					log.Errorw("Synchronizer.Sync", "err", err)
546				}
547			}
548		}
549	}()
550
551	n.wg.Add(1)
552	go func() {
553		for {
554			select {
555			case <-n.ctx.Done():
556				log.Info("PriceUpdater done")
557				n.wg.Done()
558				return
559			case <-time.After(n.cfg.PriceUpdater.Interval.Duration):
560				if err := n.priceUpdater.UpdateTokenList(); err != nil {
561					log.Errorw("PriceUpdater.UpdateTokenList()", "err", err)
562				}
563				n.priceUpdater.UpdatePrices(n.ctx)
564			}
565		}
566	}()
567}
568
569// StartDebugAPI starts the DebugAPI
570func (n *Node) StartDebugAPI() {
571	log.Info("Starting DebugAPI...")
572	n.wg.Add(1)
573	go func() {
574		defer func() {
575			log.Info("DebugAPI routine stopped")
576			n.wg.Done()
577		}()
578		if err := n.debugAPI.Run(n.ctx); err != nil {
579			if n.ctx.Err() != nil {
580				return
581			}
582			log.Fatalw("DebugAPI.Run", "err", err)
583		}
584	}()
585}
586
587// StartNodeAPI starts the NodeAPI
588func (n *Node) StartNodeAPI() {
589	log.Info("Starting NodeAPI...")
590	n.wg.Add(1)
591	go func() {
592		defer func() {
593			log.Info("NodeAPI routine stopped")
594			n.wg.Done()
595		}()
596		if err := n.nodeAPI.Run(n.ctx); err != nil {
597			if n.ctx.Err() != nil {
598				return
599			}
600			log.Fatalw("NodeAPI.Run", "err", err)
601		}
602	}()
603
604	n.wg.Add(1)
605	go func() {
606		for {
607			select {
608			case <-n.ctx.Done():
609				log.Info("API.UpdateMetrics loop done")
610				n.wg.Done()
611				return
612			case <-time.After(n.cfg.API.UpdateMetricsInterval.Duration):
613				if err := n.nodeAPI.api.UpdateMetrics(); err != nil {
614					log.Errorw("API.UpdateMetrics", "err", err)
615				}
616			}
617		}
618	}()
619
620	n.wg.Add(1)
621	go func() {
622		for {
623			select {
624			case <-n.ctx.Done():
625				log.Info("API.UpdateRecommendedFee loop done")
626				n.wg.Done()
627				return
628			case <-time.After(n.cfg.API.UpdateRecommendedFeeInterval.Duration):
629				if err := n.nodeAPI.api.UpdateRecommendedFee(); err != nil {
630					log.Errorw("API.UpdateRecommendedFee", "err", err)
631				}
632			}
633		}
634	}()
635}
636
637// Start the node
638func (n *Node) Start() {
639	log.Infow("Starting node...", "mode", n.mode)
640	if n.debugAPI != nil {
641		n.StartDebugAPI()
642	}
643	if n.nodeAPI != nil {
644		n.StartNodeAPI()
645	}
646	if n.mode == ModeCoordinator {
647		log.Info("Starting Coordinator...")
648		n.coord.Start()
649	}
650	n.StartSynchronizer()
651}
652
653// Stop the node
654func (n *Node) Stop() {
655	log.Infow("Stopping node...")
656	n.cancel()
657	n.wg.Wait()
658	if n.mode == ModeCoordinator {
659		log.Info("Stopping Coordinator...")
660		n.coord.Stop()
661	}
662	// Close kv DBs
663	n.sync.StateDB().Close()
664	if n.mode == ModeCoordinator {
665		n.coord.TxSelector().LocalAccountsDB().Close()
666		n.coord.BatchBuilder().LocalStateDB().Close()
667	}
668}