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