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