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