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