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