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