arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/node/node.go
node/node.goRaw
  1package node
  2
  3import (
  4	"context"
  5	"errors"
  6	"fmt"
  7	"net/http"
  8	"sync"
  9	"time"
 10
 11	"github.com/ethereum/go-ethereum/accounts"
 12	ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
 13	"github.com/ethereum/go-ethereum/ethclient"
 14	"github.com/gin-contrib/cors"
 15	"github.com/gin-gonic/gin"
 16	"github.com/hermeznetwork/hermez-node/api"
 17	"github.com/hermeznetwork/hermez-node/batchbuilder"
 18	"github.com/hermeznetwork/hermez-node/common"
 19	"github.com/hermeznetwork/hermez-node/config"
 20	"github.com/hermeznetwork/hermez-node/coordinator"
 21	dbUtils "github.com/hermeznetwork/hermez-node/db"
 22	"github.com/hermeznetwork/hermez-node/db/historydb"
 23	"github.com/hermeznetwork/hermez-node/db/l2db"
 24	"github.com/hermeznetwork/hermez-node/db/statedb"
 25	"github.com/hermeznetwork/hermez-node/eth"
 26	"github.com/hermeznetwork/hermez-node/log"
 27	"github.com/hermeznetwork/hermez-node/priceupdater"
 28	"github.com/hermeznetwork/hermez-node/prover"
 29	"github.com/hermeznetwork/hermez-node/synchronizer"
 30	"github.com/hermeznetwork/hermez-node/test/debugapi"
 31	"github.com/hermeznetwork/hermez-node/txprocessor"
 32	"github.com/hermeznetwork/hermez-node/txselector"
 33	"github.com/hermeznetwork/tracerr"
 34	"github.com/jmoiron/sqlx"
 35	"github.com/russross/meddler"
 36)
 37
 38// Mode sets the working mode of the node (synchronizer or coordinator)
 39type Mode string
 40
 41const (
 42	// ModeCoordinator defines the mode of the HermezNode as Coordinator, which
 43	// means that the node is set to forge (which also will be synchronizing with
 44	// the L1 blockchain state)
 45	ModeCoordinator Mode = "coordinator"
 46
 47	// ModeSynchronizer defines the mode of the HermezNode as Synchronizer, which
 48	// means that the node is set to only synchronize with the L1 blockchain state
 49	// and will not forge
 50	ModeSynchronizer Mode = "synchronizer"
 51)
 52
 53// Node is the Hermez Node
 54type Node struct {
 55	nodeAPI      *NodeAPI
 56	debugAPI     *debugapi.DebugAPI
 57	priceUpdater *priceupdater.PriceUpdater
 58	// Coordinator
 59	coord *coordinator.Coordinator
 60
 61	// Synchronizer
 62	sync *synchronizer.Synchronizer
 63
 64	// General
 65	cfg     *config.Node
 66	mode    Mode
 67	sqlConn *sqlx.DB
 68	ctx     context.Context
 69	wg      sync.WaitGroup
 70	cancel  context.CancelFunc
 71}
 72
 73// NewNode creates a Node
 74func NewNode(mode Mode, cfg *config.Node) (*Node, error) {
 75	meddler.Debug = cfg.Debug.MeddlerLogs
 76	// Stablish DB connection
 77	db, err := dbUtils.InitSQLDB(
 78		cfg.PostgreSQL.Port,
 79		cfg.PostgreSQL.Host,
 80		cfg.PostgreSQL.User,
 81		cfg.PostgreSQL.Password,
 82		cfg.PostgreSQL.Name,
 83	)
 84	if err != nil {
 85		return nil, tracerr.Wrap(err)
 86	}
 87	var apiConnCon *dbUtils.APIConnectionController
 88	if cfg.API.Explorer || mode == ModeCoordinator {
 89		apiConnCon = dbUtils.NewAPICnnectionController(
 90			cfg.API.MaxSQLConnections,
 91			cfg.API.SQLConnectionTimeout.Duration,
 92		)
 93	}
 94
 95	historyDB := historydb.NewHistoryDB(db, apiConnCon)
 96
 97	ethClient, err := ethclient.Dial(cfg.Web3.URL)
 98	if err != nil {
 99		return nil, tracerr.Wrap(err)
100	}
101	var ethCfg eth.EthereumConfig
102	var account *accounts.Account
103	var keyStore *ethKeystore.KeyStore
104	if mode == ModeCoordinator {
105		ethCfg = eth.EthereumConfig{
106			CallGasLimit: 0, // cfg.Coordinator.EthClient.CallGasLimit,
107			GasPriceDiv:  0, // cfg.Coordinator.EthClient.GasPriceDiv,
108		}
109
110		scryptN := ethKeystore.StandardScryptN
111		scryptP := ethKeystore.StandardScryptP
112		if cfg.Coordinator.Debug.LightScrypt {
113			scryptN = ethKeystore.LightScryptN
114			scryptP = ethKeystore.LightScryptP
115		}
116		keyStore = ethKeystore.NewKeyStore(cfg.Coordinator.EthClient.Keystore.Path,
117			scryptN, scryptP)
118
119		// Unlock Coordinator ForgerAddr in the keystore to make calls
120		// to ForgeBatch in the smart contract
121		if !keyStore.HasAddress(cfg.Coordinator.ForgerAddress) {
122			return nil, tracerr.Wrap(fmt.Errorf(
123				"ethereum keystore doesn't have the key for address %v",
124				cfg.Coordinator.ForgerAddress))
125		}
126		account = &accounts.Account{
127			Address: cfg.Coordinator.ForgerAddress,
128		}
129		if err := keyStore.Unlock(*account,
130			cfg.Coordinator.EthClient.Keystore.Password); err != nil {
131			return nil, tracerr.Wrap(err)
132		}
133		log.Infow("Forger ethereum account unlocked in the keystore",
134			"addr", cfg.Coordinator.ForgerAddress)
135	}
136	client, err := eth.NewClient(ethClient, account, keyStore, &eth.ClientConfig{
137		Ethereum: ethCfg,
138		Rollup: eth.RollupConfig{
139			Address: cfg.SmartContracts.Rollup,
140		},
141		Auction: eth.AuctionConfig{
142			Address: cfg.SmartContracts.Auction,
143			TokenHEZ: eth.TokenConfig{
144				Address: cfg.SmartContracts.TokenHEZ,
145				Name:    cfg.SmartContracts.TokenHEZName,
146			},
147		},
148		WDelayer: eth.WDelayerConfig{
149			Address: cfg.SmartContracts.WDelayer,
150		},
151	})
152	if err != nil {
153		return nil, tracerr.Wrap(err)
154	}
155
156	chainID, err := client.EthChainID()
157	if err != nil {
158		return nil, tracerr.Wrap(err)
159	}
160	if !chainID.IsUint64() {
161		return nil, tracerr.Wrap(fmt.Errorf("chainID cannot be represented as uint64"))
162	}
163	chainIDU64 := chainID.Uint64()
164	const maxUint16 uint64 = 0xffff
165	if chainIDU64 > maxUint16 {
166		return nil, tracerr.Wrap(fmt.Errorf("chainID overflows uint16"))
167	}
168	chainIDU16 := uint16(chainIDU64)
169
170	const safeStateDBKeep = 128
171	if cfg.StateDB.Keep < safeStateDBKeep {
172		return nil, tracerr.Wrap(fmt.Errorf("cfg.StateDB.Keep = %v < %v, which is unsafe",
173			cfg.StateDB.Keep, safeStateDBKeep))
174	}
175	stateDB, err := statedb.NewStateDB(statedb.Config{
176		Path:    cfg.StateDB.Path,
177		Keep:    cfg.StateDB.Keep,
178		Type:    statedb.TypeSynchronizer,
179		NLevels: statedb.MaxNLevels,
180	})
181	if err != nil {
182		return nil, tracerr.Wrap(err)
183	}
184
185	sync, err := synchronizer.NewSynchronizer(client, historyDB, stateDB, synchronizer.Config{
186		StatsRefreshPeriod:  cfg.Synchronizer.StatsRefreshPeriod.Duration,
187		StoreAccountUpdates: cfg.Synchronizer.StoreAccountUpdates,
188		ChainID:             chainIDU16,
189	})
190	if err != nil {
191		return nil, tracerr.Wrap(err)
192	}
193	initSCVars := sync.SCVars()
194
195	scConsts := synchronizer.SCConsts{
196		Rollup:   *sync.RollupConstants(),
197		Auction:  *sync.AuctionConstants(),
198		WDelayer: *sync.WDelayerConstants(),
199	}
200
201	var coord *coordinator.Coordinator
202	var l2DB *l2db.L2DB
203	if mode == ModeCoordinator {
204		l2DB = l2db.NewL2DB(
205			db,
206			cfg.Coordinator.L2DB.SafetyPeriod,
207			cfg.Coordinator.L2DB.MaxTxs,
208			cfg.Coordinator.L2DB.MinFeeUSD,
209			cfg.Coordinator.L2DB.TTL.Duration,
210			apiConnCon,
211		)
212
213		// Unlock FeeAccount EthAddr in the keystore to generate the
214		// account creation authorization
215		if !keyStore.HasAddress(cfg.Coordinator.FeeAccount.Address) {
216			return nil, tracerr.Wrap(fmt.Errorf(
217				"ethereum keystore doesn't have the key for address %v",
218				cfg.Coordinator.FeeAccount.Address))
219		}
220		feeAccount := accounts.Account{
221			Address: cfg.Coordinator.FeeAccount.Address,
222		}
223		if err := keyStore.Unlock(feeAccount,
224			cfg.Coordinator.EthClient.Keystore.Password); err != nil {
225			return nil, tracerr.Wrap(err)
226		}
227		auth := &common.AccountCreationAuth{
228			EthAddr: cfg.Coordinator.FeeAccount.Address,
229			BJJ:     cfg.Coordinator.FeeAccount.BJJ,
230		}
231		if err := auth.Sign(func(msg []byte) ([]byte, error) {
232			return keyStore.SignHash(feeAccount, msg)
233		}, chainIDU16, cfg.SmartContracts.Rollup); err != nil {
234			return nil, err
235		}
236		coordAccount := &txselector.CoordAccount{
237			Addr:                cfg.Coordinator.FeeAccount.Address,
238			BJJ:                 cfg.Coordinator.FeeAccount.BJJ,
239			AccountCreationAuth: auth.Signature,
240		}
241		txSelector, err := txselector.NewTxSelector(coordAccount, cfg.Coordinator.TxSelector.Path, stateDB, l2DB)
242		if err != nil {
243			return nil, tracerr.Wrap(err)
244		}
245		batchBuilder, err := batchbuilder.NewBatchBuilder(cfg.Coordinator.BatchBuilder.Path,
246			stateDB, 0, uint64(cfg.Coordinator.Circuit.NLevels))
247		if err != nil {
248			return nil, tracerr.Wrap(err)
249		}
250		if err != nil {
251			return nil, tracerr.Wrap(err)
252		}
253		serverProofs := make([]prover.Client, len(cfg.Coordinator.ServerProofs))
254		for i, serverProofCfg := range cfg.Coordinator.ServerProofs {
255			serverProofs[i] = prover.NewProofServerClient(serverProofCfg.URL,
256				cfg.Coordinator.ProofServerPollInterval.Duration)
257		}
258
259		txProcessorCfg := txprocessor.Config{
260			NLevels:  uint32(cfg.Coordinator.Circuit.NLevels),
261			MaxTx:    uint32(cfg.Coordinator.Circuit.MaxTx),
262			ChainID:  chainIDU16,
263			MaxFeeTx: common.RollupConstMaxFeeIdxCoordinator,
264			MaxL1Tx:  common.RollupConstMaxL1Tx,
265		}
266		var verifierIdx int
267		if cfg.Coordinator.Debug.RollupVerifierIndex == nil {
268			verifierIdx, err = scConsts.Rollup.FindVerifierIdx(
269				cfg.Coordinator.Circuit.MaxTx,
270				cfg.Coordinator.Circuit.NLevels,
271			)
272			if err != nil {
273				return nil, tracerr.Wrap(err)
274			}
275			log.Infow("Found verifier that matches circuit config", "verifierIdx", verifierIdx)
276		} else {
277			verifierIdx = *cfg.Coordinator.Debug.RollupVerifierIndex
278			log.Infow("Using debug verifier index from config", "verifierIdx", verifierIdx)
279			if verifierIdx >= len(scConsts.Rollup.Verifiers) {
280				return nil, tracerr.Wrap(
281					fmt.Errorf("verifierIdx (%v) >= "+
282						"len(scConsts.Rollup.Verifiers) (%v)",
283						verifierIdx, len(scConsts.Rollup.Verifiers)))
284			}
285			verifier := scConsts.Rollup.Verifiers[verifierIdx]
286			if verifier.MaxTx != cfg.Coordinator.Circuit.MaxTx ||
287				verifier.NLevels != cfg.Coordinator.Circuit.NLevels {
288				return nil, tracerr.Wrap(
289					fmt.Errorf("Circuit config and verifier params don't match.  "+
290						"circuit.MaxTx = %v, circuit.NLevels = %v, "+
291						"verifier.MaxTx = %v, verifier.NLevels = %v",
292						cfg.Coordinator.Circuit.MaxTx, cfg.Coordinator.Circuit.NLevels,
293						verifier.MaxTx, verifier.NLevels,
294					))
295			}
296		}
297
298		coord, err = coordinator.NewCoordinator(
299			coordinator.Config{
300				ForgerAddress:          cfg.Coordinator.ForgerAddress,
301				ConfirmBlocks:          cfg.Coordinator.ConfirmBlocks,
302				L1BatchTimeoutPerc:     cfg.Coordinator.L1BatchTimeoutPerc,
303				ForgeRetryInterval:     cfg.Coordinator.ForgeRetryInterval.Duration,
304				ForgeDelay:             cfg.Coordinator.ForgeDelay.Duration,
305				ForgeNoTxsDelay:        cfg.Coordinator.ForgeNoTxsDelay.Duration,
306				SyncRetryInterval:      cfg.Coordinator.SyncRetryInterval.Duration,
307				PurgeByExtDelInterval:  cfg.Coordinator.PurgeByExtDelInterval.Duration,
308				EthClientAttempts:      cfg.Coordinator.EthClient.Attempts,
309				EthClientAttemptsDelay: cfg.Coordinator.EthClient.AttemptsDelay.Duration,
310				EthNoReuseNonce:        cfg.Coordinator.EthClient.NoReuseNonce,
311				EthTxResendTimeout:     cfg.Coordinator.EthClient.TxResendTimeout.Duration,
312				MaxGasPrice:            cfg.Coordinator.EthClient.MaxGasPrice,
313				GasPriceIncPerc:        cfg.Coordinator.EthClient.GasPriceIncPerc,
314				TxManagerCheckInterval: cfg.Coordinator.EthClient.CheckLoopInterval.Duration,
315				DebugBatchPath:         cfg.Coordinator.Debug.BatchPath,
316				Purger: coordinator.PurgerCfg{
317					PurgeBatchDelay:      cfg.Coordinator.L2DB.PurgeBatchDelay,
318					InvalidateBatchDelay: cfg.Coordinator.L2DB.InvalidateBatchDelay,
319					PurgeBlockDelay:      cfg.Coordinator.L2DB.PurgeBlockDelay,
320					InvalidateBlockDelay: cfg.Coordinator.L2DB.InvalidateBlockDelay,
321				},
322				VerifierIdx:       uint8(verifierIdx),
323				TxProcessorConfig: txProcessorCfg,
324			},
325			historyDB,
326			l2DB,
327			txSelector,
328			batchBuilder,
329			serverProofs,
330			client,
331			&scConsts,
332			&synchronizer.SCVariables{
333				Rollup:   *initSCVars.Rollup,
334				Auction:  *initSCVars.Auction,
335				WDelayer: *initSCVars.WDelayer,
336			},
337		)
338		if err != nil {
339			return nil, tracerr.Wrap(err)
340		}
341	}
342	var nodeAPI *NodeAPI
343	if cfg.API.Address != "" {
344		if cfg.API.UpdateMetricsInterval.Duration == 0 {
345			return nil, tracerr.Wrap(fmt.Errorf("invalid cfg.API.UpdateMetricsInterval: %v",
346				cfg.API.UpdateMetricsInterval.Duration))
347		}
348		if cfg.API.UpdateRecommendedFeeInterval.Duration == 0 {
349			return nil, tracerr.Wrap(fmt.Errorf("invalid cfg.API.UpdateRecommendedFeeInterval: %v",
350				cfg.API.UpdateRecommendedFeeInterval.Duration))
351		}
352		server := gin.Default()
353		coord := false
354		if mode == ModeCoordinator {
355			coord = cfg.Coordinator.API.Coordinator
356		}
357		var err error
358		nodeAPI, err = NewNodeAPI(
359			cfg.API.Address,
360			coord, cfg.API.Explorer,
361			server,
362			historyDB,
363			stateDB,
364			l2DB,
365			&api.Config{
366				RollupConstants:   scConsts.Rollup,
367				AuctionConstants:  scConsts.Auction,
368				WDelayerConstants: scConsts.WDelayer,
369				ChainID:           chainIDU16,
370				HermezAddress:     cfg.SmartContracts.Rollup,
371			},
372		)
373		if err != nil {
374			return nil, tracerr.Wrap(err)
375		}
376		nodeAPI.api.SetRollupVariables(*initSCVars.Rollup)
377		nodeAPI.api.SetAuctionVariables(*initSCVars.Auction)
378		nodeAPI.api.SetWDelayerVariables(*initSCVars.WDelayer)
379	}
380	var debugAPI *debugapi.DebugAPI
381	if cfg.Debug.APIAddress != "" {
382		debugAPI = debugapi.NewDebugAPI(cfg.Debug.APIAddress, stateDB, sync)
383	}
384	priceUpdater, err := priceupdater.NewPriceUpdater(cfg.PriceUpdater.URL,
385		priceupdater.APIType(cfg.PriceUpdater.Type), historyDB)
386	if err != nil {
387		return nil, tracerr.Wrap(err)
388	}
389	ctx, cancel := context.WithCancel(context.Background())
390	return &Node{
391		nodeAPI:      nodeAPI,
392		debugAPI:     debugAPI,
393		priceUpdater: priceUpdater,
394		coord:        coord,
395		sync:         sync,
396		cfg:          cfg,
397		mode:         mode,
398		sqlConn:      db,
399		ctx:          ctx,
400		cancel:       cancel,
401	}, nil
402}
403
404// NodeAPI holds the node http API
405type NodeAPI struct { //nolint:golint
406	api    *api.API
407	engine *gin.Engine
408	addr   string
409}
410
411func handleNoRoute(c *gin.Context) {
412	c.JSON(http.StatusNotFound, gin.H{
413		"error": "404 page not found",
414	})
415}
416
417// NewNodeAPI creates a new NodeAPI (which internally calls api.NewAPI)
418func NewNodeAPI(
419	addr string,
420	coordinatorEndpoints, explorerEndpoints bool,
421	server *gin.Engine,
422	hdb *historydb.HistoryDB,
423	sdb *statedb.StateDB,
424	l2db *l2db.L2DB,
425	config *api.Config,
426) (*NodeAPI, error) {
427	engine := gin.Default()
428	engine.NoRoute(handleNoRoute)
429	engine.Use(cors.Default())
430	_api, err := api.NewAPI(
431		coordinatorEndpoints, explorerEndpoints,
432		engine,
433		hdb,
434		sdb,
435		l2db,
436		config,
437	)
438	if err != nil {
439		return nil, tracerr.Wrap(err)
440	}
441	return &NodeAPI{
442		addr:   addr,
443		api:    _api,
444		engine: engine,
445	}, nil
446}
447
448// Run starts the http server of the NodeAPI.  To stop it, pass a context with
449// cancelation.
450func (a *NodeAPI) Run(ctx context.Context) error {
451	server := &http.Server{
452		Addr:    a.addr,
453		Handler: a.engine,
454		// TODO: Figure out best parameters for production
455		ReadTimeout:    30 * time.Second, //nolint:gomnd
456		WriteTimeout:   30 * time.Second, //nolint:gomnd
457		MaxHeaderBytes: 1 << 20,          //nolint:gomnd
458	}
459	go func() {
460		log.Infof("NodeAPI is ready at %v", a.addr)
461		if err := server.ListenAndServe(); err != nil && tracerr.Unwrap(err) != http.ErrServerClosed {
462			log.Fatalf("Listen: %s\n", err)
463		}
464	}()
465
466	<-ctx.Done()
467	log.Info("Stopping NodeAPI...")
468	ctxTimeout, cancel := context.WithTimeout(context.Background(), 10*time.Second) //nolint:gomnd
469	defer cancel()
470	if err := server.Shutdown(ctxTimeout); err != nil {
471		return tracerr.Wrap(err)
472	}
473	log.Info("NodeAPI done")
474	return nil
475}
476
477func (n *Node) handleNewBlock(ctx context.Context, stats *synchronizer.Stats, vars synchronizer.SCVariablesPtr,
478	batches []common.BatchData) {
479	if n.mode == ModeCoordinator {
480		n.coord.SendMsg(ctx, coordinator.MsgSyncBlock{
481			Stats:   *stats,
482			Vars:    vars,
483			Batches: batches,
484		})
485	}
486	if n.nodeAPI != nil {
487		if vars.Rollup != nil {
488			n.nodeAPI.api.SetRollupVariables(*vars.Rollup)
489		}
490		if vars.Auction != nil {
491			n.nodeAPI.api.SetAuctionVariables(*vars.Auction)
492		}
493		if vars.WDelayer != nil {
494			n.nodeAPI.api.SetWDelayerVariables(*vars.WDelayer)
495		}
496
497		if stats.Synced() {
498			if err := n.nodeAPI.api.UpdateNetworkInfo(
499				stats.Eth.LastBlock, stats.Sync.LastBlock,
500				common.BatchNum(stats.Eth.LastBatchNum),
501				stats.Sync.Auction.CurrentSlot.SlotNum,
502			); err != nil {
503				log.Errorw("API.UpdateNetworkInfo", "err", err)
504			}
505		} else {
506			n.nodeAPI.api.UpdateNetworkInfoBlock(
507				stats.Eth.LastBlock, stats.Sync.LastBlock,
508			)
509		}
510	}
511}
512
513func (n *Node) handleReorg(ctx context.Context, stats *synchronizer.Stats, vars synchronizer.SCVariablesPtr) {
514	if n.mode == ModeCoordinator {
515		n.coord.SendMsg(ctx, coordinator.MsgSyncReorg{
516			Stats: *stats,
517			Vars:  vars,
518		})
519	}
520	if n.nodeAPI != nil {
521		vars := n.sync.SCVars()
522		n.nodeAPI.api.SetRollupVariables(*vars.Rollup)
523		n.nodeAPI.api.SetAuctionVariables(*vars.Auction)
524		n.nodeAPI.api.SetWDelayerVariables(*vars.WDelayer)
525		n.nodeAPI.api.UpdateNetworkInfoBlock(
526			stats.Eth.LastBlock, stats.Sync.LastBlock,
527		)
528	}
529}
530
531// TODO(Edu): Consider keeping the `lastBlock` inside synchronizer so that we
532// don't have to pass it around.
533func (n *Node) syncLoopFn(ctx context.Context, lastBlock *common.Block) (*common.Block, time.Duration, error) {
534	blockData, discarded, err := n.sync.Sync2(ctx, lastBlock)
535	stats := n.sync.Stats()
536	if err != nil {
537		// case: error
538		return nil, n.cfg.Synchronizer.SyncLoopInterval.Duration, tracerr.Wrap(err)
539	} else if discarded != nil {
540		// case: reorg
541		log.Infow("Synchronizer.Sync reorg", "discarded", *discarded)
542		vars := n.sync.SCVars()
543		n.handleReorg(ctx, stats, vars)
544		return nil, time.Duration(0), nil
545	} else if blockData != nil {
546		// case: new block
547		vars := synchronizer.SCVariablesPtr{
548			Rollup:   blockData.Rollup.Vars,
549			Auction:  blockData.Auction.Vars,
550			WDelayer: blockData.WDelayer.Vars,
551		}
552		n.handleNewBlock(ctx, stats, vars, blockData.Rollup.Batches)
553		return &blockData.Block, time.Duration(0), nil
554	} else {
555		// case: no block
556		return lastBlock, n.cfg.Synchronizer.SyncLoopInterval.Duration, nil
557	}
558}
559
560// StartSynchronizer starts the synchronizer
561func (n *Node) StartSynchronizer() {
562	log.Info("Starting Synchronizer...")
563
564	// Trigger a manual call to handleNewBlock with the loaded state of the
565	// synchronizer in order to quickly activate the API and Coordinator
566	// and avoid waiting for the next block.  Without this, the API and
567	// Coordinator will not react until the following block (starting from
568	// the last synced one) is synchronized
569	stats := n.sync.Stats()
570	vars := n.sync.SCVars()
571	n.handleNewBlock(n.ctx, stats, vars, []common.BatchData{})
572
573	n.wg.Add(1)
574	go func() {
575		var err error
576		var lastBlock *common.Block
577		waitDuration := time.Duration(0)
578		for {
579			select {
580			case <-n.ctx.Done():
581				log.Info("Synchronizer done")
582				n.wg.Done()
583				return
584			case <-time.After(waitDuration):
585				if lastBlock, waitDuration, err = n.syncLoopFn(n.ctx,
586					lastBlock); err != nil {
587					if n.ctx.Err() != nil {
588						continue
589					}
590					if errors.Is(err, eth.ErrBlockHashMismatchEvent) {
591						log.Warnw("Synchronizer.Sync", "err", err)
592					} else if errors.Is(err, synchronizer.ErrUnknownBlock) {
593						log.Warnw("Synchronizer.Sync", "err", err)
594					} else {
595						log.Errorw("Synchronizer.Sync", "err", err)
596					}
597				}
598			}
599		}
600	}()
601
602	n.wg.Add(1)
603	go func() {
604		for {
605			select {
606			case <-n.ctx.Done():
607				log.Info("PriceUpdater done")
608				n.wg.Done()
609				return
610			case <-time.After(n.cfg.PriceUpdater.Interval.Duration):
611				if err := n.priceUpdater.UpdateTokenList(); err != nil {
612					log.Errorw("PriceUpdater.UpdateTokenList()", "err", err)
613				}
614				n.priceUpdater.UpdatePrices(n.ctx)
615			}
616		}
617	}()
618}
619
620// StartDebugAPI starts the DebugAPI
621func (n *Node) StartDebugAPI() {
622	log.Info("Starting DebugAPI...")
623	n.wg.Add(1)
624	go func() {
625		defer func() {
626			log.Info("DebugAPI routine stopped")
627			n.wg.Done()
628		}()
629		if err := n.debugAPI.Run(n.ctx); err != nil {
630			if n.ctx.Err() != nil {
631				return
632			}
633			log.Fatalw("DebugAPI.Run", "err", err)
634		}
635	}()
636}
637
638// StartNodeAPI starts the NodeAPI
639func (n *Node) StartNodeAPI() {
640	log.Info("Starting NodeAPI...")
641	n.wg.Add(1)
642	go func() {
643		defer func() {
644			log.Info("NodeAPI routine stopped")
645			n.wg.Done()
646		}()
647		if err := n.nodeAPI.Run(n.ctx); err != nil {
648			if n.ctx.Err() != nil {
649				return
650			}
651			log.Fatalw("NodeAPI.Run", "err", err)
652		}
653	}()
654
655	n.wg.Add(1)
656	go func() {
657		for {
658			select {
659			case <-n.ctx.Done():
660				log.Info("API.UpdateMetrics loop done")
661				n.wg.Done()
662				return
663			case <-time.After(n.cfg.API.UpdateMetricsInterval.Duration):
664				if err := n.nodeAPI.api.UpdateMetrics(); err != nil {
665					log.Errorw("API.UpdateMetrics", "err", err)
666				}
667			}
668		}
669	}()
670
671	n.wg.Add(1)
672	go func() {
673		for {
674			select {
675			case <-n.ctx.Done():
676				log.Info("API.UpdateRecommendedFee loop done")
677				n.wg.Done()
678				return
679			case <-time.After(n.cfg.API.UpdateRecommendedFeeInterval.Duration):
680				if err := n.nodeAPI.api.UpdateRecommendedFee(); err != nil {
681					log.Errorw("API.UpdateRecommendedFee", "err", err)
682				}
683			}
684		}
685	}()
686}
687
688// Start the node
689func (n *Node) Start() {
690	log.Infow("Starting node...", "mode", n.mode)
691	if n.debugAPI != nil {
692		n.StartDebugAPI()
693	}
694	if n.nodeAPI != nil {
695		n.StartNodeAPI()
696	}
697	if n.mode == ModeCoordinator {
698		log.Info("Starting Coordinator...")
699		n.coord.Start()
700	}
701	n.StartSynchronizer()
702}
703
704// Stop the node
705func (n *Node) Stop() {
706	log.Infow("Stopping node...")
707	n.cancel()
708	n.wg.Wait()
709	if n.mode == ModeCoordinator {
710		log.Info("Stopping Coordinator...")
711		n.coord.Stop()
712	}
713	// Close kv DBs
714	n.sync.StateDB().Close()
715	if n.mode == ModeCoordinator {
716		n.coord.TxSelector().LocalAccountsDB().Close()
717		n.coord.BatchBuilder().LocalStateDB().Close()
718	}
719}