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	"strings"
  8	"sync"
  9	"time"
 10
 11	ethCommon "github.com/ethereum/go-ethereum/common"
 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/txselector"
 31	"github.com/hermeznetwork/tracerr"
 32	"github.com/jmoiron/sqlx"
 33)
 34
 35// Mode sets the working mode of the node (synchronizer or coordinator)
 36type Mode string
 37
 38const (
 39	// ModeCoordinator defines the mode of the HermezNode as Coordinator, which
 40	// means that the node is set to forge (which also will be synchronizing with
 41	// the L1 blockchain state)
 42	ModeCoordinator Mode = "coordinator"
 43
 44	// ModeSynchronizer defines the mode of the HermezNode as Synchronizer, which
 45	// means that the node is set to only synchronize with the L1 blockchain state
 46	// and will not forge
 47	ModeSynchronizer Mode = "synchronizer"
 48)
 49
 50// Node is the Hermez Node
 51type Node struct {
 52	nodeAPI      *NodeAPI
 53	debugAPI     *debugapi.DebugAPI
 54	priceUpdater *priceupdater.PriceUpdater
 55	// Coordinator
 56	coord *coordinator.Coordinator
 57
 58	// Synchronizer
 59	sync *synchronizer.Synchronizer
 60
 61	// General
 62	cfg     *config.Node
 63	mode    Mode
 64	sqlConn *sqlx.DB
 65	ctx     context.Context
 66	wg      sync.WaitGroup
 67	cancel  context.CancelFunc
 68}
 69
 70// NewNode creates a Node
 71func NewNode(mode Mode, cfg *config.Node) (*Node, error) {
 72	// Stablish DB connection
 73	db, err := dbUtils.InitSQLDB(
 74		cfg.PostgreSQL.Port,
 75		cfg.PostgreSQL.Host,
 76		cfg.PostgreSQL.User,
 77		cfg.PostgreSQL.Password,
 78		cfg.PostgreSQL.Name,
 79	)
 80	if err != nil {
 81		return nil, tracerr.Wrap(err)
 82	}
 83
 84	historyDB := historydb.NewHistoryDB(db)
 85
 86	stateDB, err := statedb.NewStateDB(cfg.StateDB.Path, statedb.TypeSynchronizer, 32)
 87	if err != nil {
 88		return nil, tracerr.Wrap(err)
 89	}
 90
 91	ethClient, err := ethclient.Dial(cfg.Web3.URL)
 92	if err != nil {
 93		return nil, tracerr.Wrap(err)
 94	}
 95	var ethCfg eth.EthereumConfig
 96	if mode == ModeCoordinator {
 97		ethCfg = eth.EthereumConfig{
 98			CallGasLimit:        cfg.Coordinator.EthClient.CallGasLimit,
 99			DeployGasLimit:      cfg.Coordinator.EthClient.DeployGasLimit,
100			GasPriceDiv:         cfg.Coordinator.EthClient.GasPriceDiv,
101			ReceiptTimeout:      cfg.Coordinator.EthClient.ReceiptTimeout.Duration,
102			IntervalReceiptLoop: cfg.Coordinator.EthClient.ReceiptLoopInterval.Duration,
103		}
104	}
105	client, err := eth.NewClient(ethClient, nil, nil, &eth.ClientConfig{
106		Ethereum: ethCfg,
107		Rollup: eth.RollupConfig{
108			Address: cfg.SmartContracts.Rollup,
109		},
110		Auction: eth.AuctionConfig{
111			Address: cfg.SmartContracts.Auction,
112			TokenHEZ: eth.TokenConfig{
113				Address: cfg.SmartContracts.TokenHEZ,
114				Name:    cfg.SmartContracts.TokenHEZName,
115			},
116		},
117		WDelayer: eth.WDelayerConfig{
118			Address: cfg.SmartContracts.WDelayer,
119		},
120	})
121	if err != nil {
122		return nil, tracerr.Wrap(err)
123	}
124
125	sync, err := synchronizer.NewSynchronizer(client, historyDB, stateDB, synchronizer.Config{
126		StatsRefreshPeriod: cfg.Synchronizer.StatsRefreshPeriod.Duration,
127	})
128	if err != nil {
129		return nil, tracerr.Wrap(err)
130	}
131	initSCVars := sync.SCVars()
132
133	scConsts := synchronizer.SCConsts{
134		Rollup:   *sync.RollupConstants(),
135		Auction:  *sync.AuctionConstants(),
136		WDelayer: *sync.WDelayerConstants(),
137	}
138
139	var coord *coordinator.Coordinator
140	var l2DB *l2db.L2DB
141	if mode == ModeCoordinator {
142		l2DB = l2db.NewL2DB(
143			db,
144			cfg.Coordinator.L2DB.SafetyPeriod,
145			cfg.Coordinator.L2DB.MaxTxs,
146			cfg.Coordinator.L2DB.TTL.Duration,
147		)
148		// TODO: Get (maxL1UserTxs, maxL1OperatorTxs, maxTxs) from the smart contract
149		coordAccount := &txselector.CoordAccount{ // TODO TMP
150			Addr:                ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370"),
151			BJJ:                 nil,
152			AccountCreationAuth: nil,
153		}
154		txSelector, err := txselector.NewTxSelector(coordAccount, cfg.Coordinator.TxSelector.Path, stateDB, l2DB)
155		if err != nil {
156			return nil, tracerr.Wrap(err)
157		}
158		// TODO: Get (configCircuits []ConfigCircuit, batchNum common.BatchNum, nLevels uint64) from smart contract
159		nLevels := uint64(32) //nolint:gomnd
160		batchBuilder, err := batchbuilder.NewBatchBuilder(cfg.Coordinator.BatchBuilder.Path, stateDB, nil, 0, nLevels)
161		if err != nil {
162			return nil, tracerr.Wrap(err)
163		}
164		if err != nil {
165			return nil, tracerr.Wrap(err)
166		}
167		serverProofs := make([]prover.Client, len(cfg.Coordinator.ServerProofs))
168		for i, serverProofCfg := range cfg.Coordinator.ServerProofs {
169			serverProofs[i] = prover.NewProofServerClient(serverProofCfg.URL,
170				cfg.Coordinator.ProofServerPollInterval.Duration)
171		}
172
173		coord, err = coordinator.NewCoordinator(
174			coordinator.Config{
175				ForgerAddress:          cfg.Coordinator.ForgerAddress,
176				ConfirmBlocks:          cfg.Coordinator.ConfirmBlocks,
177				L1BatchTimeoutPerc:     cfg.Coordinator.L1BatchTimeoutPerc,
178				SyncRetryInterval:      cfg.Coordinator.SyncRetryInterval.Duration,
179				EthClientAttempts:      cfg.Coordinator.EthClient.Attempts,
180				EthClientAttemptsDelay: cfg.Coordinator.EthClient.AttemptsDelay.Duration,
181				TxManagerCheckInterval: cfg.Coordinator.EthClient.CheckLoopInterval.Duration,
182				DebugBatchPath:         cfg.Coordinator.Debug.BatchPath,
183				Purger: coordinator.PurgerCfg{
184					PurgeBatchDelay:      cfg.Coordinator.L2DB.PurgeBatchDelay,
185					InvalidateBatchDelay: cfg.Coordinator.L2DB.InvalidateBatchDelay,
186					PurgeBlockDelay:      cfg.Coordinator.L2DB.PurgeBlockDelay,
187					InvalidateBlockDelay: cfg.Coordinator.L2DB.InvalidateBlockDelay,
188				},
189			},
190			historyDB,
191			l2DB,
192			txSelector,
193			batchBuilder,
194			serverProofs,
195			client,
196			&scConsts,
197			&synchronizer.SCVariables{
198				Rollup:   *initSCVars.Rollup,
199				Auction:  *initSCVars.Auction,
200				WDelayer: *initSCVars.WDelayer,
201			},
202		)
203		if err != nil {
204			return nil, tracerr.Wrap(err)
205		}
206	}
207	var nodeAPI *NodeAPI
208	if cfg.API.Address != "" {
209		if cfg.API.UpdateMetricsInterval.Duration == 0 {
210			return nil, tracerr.Wrap(fmt.Errorf("invalid cfg.API.UpdateMetricsInterval: %v",
211				cfg.API.UpdateMetricsInterval.Duration))
212		}
213		if cfg.API.UpdateRecommendedFeeInterval.Duration == 0 {
214			return nil, tracerr.Wrap(fmt.Errorf("invalid cfg.API.UpdateRecommendedFeeInterval: %v",
215				cfg.API.UpdateRecommendedFeeInterval.Duration))
216		}
217		server := gin.Default()
218		coord := false
219		if mode == ModeCoordinator {
220			coord = cfg.Coordinator.API.Coordinator
221		}
222		var err error
223		nodeAPI, err = NewNodeAPI(
224			cfg.API.Address,
225			coord, cfg.API.Explorer,
226			server,
227			historyDB,
228			stateDB,
229			l2DB,
230			&api.Config{
231				RollupConstants:   scConsts.Rollup,
232				AuctionConstants:  scConsts.Auction,
233				WDelayerConstants: scConsts.WDelayer,
234			},
235		)
236		if err != nil {
237			return nil, tracerr.Wrap(err)
238		}
239		nodeAPI.api.SetRollupVariables(*initSCVars.Rollup)
240		nodeAPI.api.SetAuctionVariables(*initSCVars.Auction)
241		nodeAPI.api.SetWDelayerVariables(*initSCVars.WDelayer)
242	}
243	var debugAPI *debugapi.DebugAPI
244	if cfg.Debug.APIAddress != "" {
245		debugAPI = debugapi.NewDebugAPI(cfg.Debug.APIAddress, stateDB, sync)
246	}
247	priceUpdater, err := priceupdater.NewPriceUpdater(cfg.PriceUpdater.URL,
248		priceupdater.APIType(cfg.PriceUpdater.Type), historyDB)
249	if err != nil {
250		return nil, tracerr.Wrap(err)
251	}
252	ctx, cancel := context.WithCancel(context.Background())
253	return &Node{
254		nodeAPI:      nodeAPI,
255		debugAPI:     debugAPI,
256		priceUpdater: priceUpdater,
257		coord:        coord,
258		sync:         sync,
259		cfg:          cfg,
260		mode:         mode,
261		sqlConn:      db,
262		ctx:          ctx,
263		cancel:       cancel,
264	}, nil
265}
266
267// NodeAPI holds the node http API
268type NodeAPI struct { //nolint:golint
269	api    *api.API
270	engine *gin.Engine
271	addr   string
272}
273
274func handleNoRoute(c *gin.Context) {
275	c.JSON(http.StatusNotFound, gin.H{
276		"error": "404 page not found",
277	})
278}
279
280// NewNodeAPI creates a new NodeAPI (which internally calls api.NewAPI)
281func NewNodeAPI(
282	addr string,
283	coordinatorEndpoints, explorerEndpoints bool,
284	server *gin.Engine,
285	hdb *historydb.HistoryDB,
286	sdb *statedb.StateDB,
287	l2db *l2db.L2DB,
288	config *api.Config,
289) (*NodeAPI, error) {
290	engine := gin.Default()
291	engine.NoRoute(handleNoRoute)
292	engine.Use(cors.Default())
293	_api, err := api.NewAPI(
294		coordinatorEndpoints, explorerEndpoints,
295		engine,
296		hdb,
297		sdb,
298		l2db,
299		config,
300	)
301	if err != nil {
302		return nil, tracerr.Wrap(err)
303	}
304	return &NodeAPI{
305		addr:   addr,
306		api:    _api,
307		engine: engine,
308	}, nil
309}
310
311// Run starts the http server of the NodeAPI.  To stop it, pass a context with
312// cancelation.
313func (a *NodeAPI) Run(ctx context.Context) error {
314	server := &http.Server{
315		Addr:    a.addr,
316		Handler: a.engine,
317		// TODO: Figure out best parameters for production
318		ReadTimeout:    30 * time.Second, //nolint:gomnd
319		WriteTimeout:   30 * time.Second, //nolint:gomnd
320		MaxHeaderBytes: 1 << 20,          //nolint:gomnd
321	}
322	go func() {
323		log.Infof("NodeAPI is ready at %v", a.addr)
324		if err := server.ListenAndServe(); err != nil && tracerr.Unwrap(err) != http.ErrServerClosed {
325			log.Fatalf("Listen: %s\n", err)
326		}
327	}()
328
329	<-ctx.Done()
330	log.Info("Stopping NodeAPI...")
331	ctxTimeout, cancel := context.WithTimeout(context.Background(), 10*time.Second) //nolint:gomnd
332	defer cancel()
333	if err := server.Shutdown(ctxTimeout); err != nil {
334		return tracerr.Wrap(err)
335	}
336	log.Info("NodeAPI done")
337	return nil
338}
339
340func (n *Node) handleNewBlock(stats *synchronizer.Stats, vars synchronizer.SCVariablesPtr,
341	batches []common.BatchData) {
342	if n.mode == ModeCoordinator {
343		n.coord.SendMsg(coordinator.MsgSyncBlock{
344			Stats:   *stats,
345			Batches: batches,
346			Vars: synchronizer.SCVariablesPtr{
347				Rollup:   vars.Rollup,
348				Auction:  vars.Auction,
349				WDelayer: vars.WDelayer,
350			},
351		})
352	}
353	if n.nodeAPI != nil {
354		if vars.Rollup != nil {
355			n.nodeAPI.api.SetRollupVariables(*vars.Rollup)
356		}
357		if vars.Auction != nil {
358			n.nodeAPI.api.SetAuctionVariables(*vars.Auction)
359		}
360		if vars.WDelayer != nil {
361			n.nodeAPI.api.SetWDelayerVariables(*vars.WDelayer)
362		}
363
364		if stats.Synced() {
365			if err := n.nodeAPI.api.UpdateNetworkInfo(
366				stats.Eth.LastBlock, stats.Sync.LastBlock,
367				common.BatchNum(stats.Eth.LastBatch),
368				stats.Sync.Auction.CurrentSlot.SlotNum,
369			); err != nil {
370				log.Errorw("API.UpdateNetworkInfo", "err", err)
371			}
372		}
373	}
374}
375
376func (n *Node) handleReorg(stats *synchronizer.Stats) {
377	if n.mode == ModeCoordinator {
378		n.coord.SendMsg(coordinator.MsgSyncReorg{
379			Stats: *stats,
380		})
381	}
382	if n.nodeAPI != nil {
383		vars := n.sync.SCVars()
384		n.nodeAPI.api.SetRollupVariables(*vars.Rollup)
385		n.nodeAPI.api.SetAuctionVariables(*vars.Auction)
386		n.nodeAPI.api.SetWDelayerVariables(*vars.WDelayer)
387		n.nodeAPI.api.UpdateNetworkInfoBlock(
388			stats.Eth.LastBlock, stats.Sync.LastBlock,
389		)
390	}
391}
392
393// TODO(Edu): Consider keeping the `lastBlock` inside synchronizer so that we
394// don't have to pass it around.
395func (n *Node) syncLoopFn(lastBlock *common.Block) (*common.Block, time.Duration) {
396	blockData, discarded, err := n.sync.Sync2(n.ctx, lastBlock)
397	stats := n.sync.Stats()
398	if err != nil {
399		// case: error
400		if strings.Contains(err.Error(), "context canceled") {
401			log.Warnw("Synchronizer.Sync", "err", err)
402		} else {
403			log.Errorw("Synchronizer.Sync", "err", err)
404		}
405		return nil, n.cfg.Synchronizer.SyncLoopInterval.Duration
406	} else if discarded != nil {
407		// case: reorg
408		log.Infow("Synchronizer.Sync reorg", "discarded", *discarded)
409		n.handleReorg(stats)
410		return nil, time.Duration(0)
411	} else if blockData != nil {
412		// case: new block
413		n.handleNewBlock(stats, synchronizer.SCVariablesPtr{
414			Rollup:   blockData.Rollup.Vars,
415			Auction:  blockData.Auction.Vars,
416			WDelayer: blockData.WDelayer.Vars,
417		}, blockData.Rollup.Batches)
418		return &blockData.Block, time.Duration(0)
419	} else {
420		// case: no block
421		return lastBlock, n.cfg.Synchronizer.SyncLoopInterval.Duration
422	}
423}
424
425// StartSynchronizer starts the synchronizer
426func (n *Node) StartSynchronizer() {
427	log.Info("Starting Synchronizer...")
428
429	// Trigger a manual call to handleNewBlock with the loaded state of the
430	// synchronizer in order to quickly activate the API and Coordinator
431	// and avoid waiting for the next block.  Without this, the API and
432	// Coordinator will not react until the following block (starting from
433	// the last synced one) is synchronized
434	stats := n.sync.Stats()
435	vars := n.sync.SCVars()
436	n.handleNewBlock(stats, vars, []common.BatchData{})
437
438	n.wg.Add(1)
439	go func() {
440		var lastBlock *common.Block
441		waitDuration := time.Duration(0)
442		for {
443			select {
444			case <-n.ctx.Done():
445				log.Info("Synchronizer done")
446				n.wg.Done()
447				return
448			case <-time.After(waitDuration):
449				lastBlock, waitDuration = n.syncLoopFn(lastBlock)
450			}
451		}
452	}()
453
454	n.wg.Add(1)
455	go func() {
456		for {
457			select {
458			case <-n.ctx.Done():
459				log.Info("PriceUpdater done")
460				n.wg.Done()
461				return
462			case <-time.After(n.cfg.PriceUpdater.Interval.Duration):
463				if err := n.priceUpdater.UpdateTokenList(); err != nil {
464					log.Errorw("PriceUpdater.UpdateTokenList()", "err", err)
465				}
466				n.priceUpdater.UpdatePrices(n.ctx)
467			}
468		}
469	}()
470}
471
472// StartDebugAPI starts the DebugAPI
473func (n *Node) StartDebugAPI() {
474	log.Info("Starting DebugAPI...")
475	n.wg.Add(1)
476	go func() {
477		defer func() {
478			log.Info("DebugAPI routine stopped")
479			n.wg.Done()
480		}()
481		if err := n.debugAPI.Run(n.ctx); err != nil {
482			log.Fatalw("DebugAPI.Run", "err", err)
483		}
484	}()
485}
486
487// StartNodeAPI starts the NodeAPI
488func (n *Node) StartNodeAPI() {
489	log.Info("Starting NodeAPI...")
490	n.wg.Add(1)
491	go func() {
492		defer func() {
493			log.Info("NodeAPI routine stopped")
494			n.wg.Done()
495		}()
496		if err := n.nodeAPI.Run(n.ctx); err != nil {
497			log.Fatalw("NodeAPI.Run", "err", err)
498		}
499	}()
500
501	n.wg.Add(1)
502	go func() {
503		for {
504			select {
505			case <-n.ctx.Done():
506				log.Info("API.UpdateMetrics loop done")
507				n.wg.Done()
508				return
509			case <-time.After(n.cfg.API.UpdateMetricsInterval.Duration):
510				if err := n.nodeAPI.api.UpdateMetrics(); err != nil {
511					log.Errorw("API.UpdateMetrics", "err", err)
512				}
513			}
514		}
515	}()
516
517	n.wg.Add(1)
518	go func() {
519		for {
520			select {
521			case <-n.ctx.Done():
522				log.Info("API.UpdateRecommendedFee loop done")
523				n.wg.Done()
524				return
525			case <-time.After(n.cfg.API.UpdateRecommendedFeeInterval.Duration):
526				if err := n.nodeAPI.api.UpdateRecommendedFee(); err != nil {
527					log.Errorw("API.UpdateRecommendedFee", "err", err)
528				}
529			}
530		}
531	}()
532}
533
534// Start the node
535func (n *Node) Start() {
536	log.Infow("Starting node...", "mode", n.mode)
537	if n.debugAPI != nil {
538		n.StartDebugAPI()
539	}
540	if n.nodeAPI != nil {
541		n.StartNodeAPI()
542	}
543	if n.mode == ModeCoordinator {
544		log.Info("Starting Coordinator...")
545		n.coord.Start()
546	}
547	n.StartSynchronizer()
548}
549
550// Stop the node
551func (n *Node) Stop() {
552	log.Infow("Stopping node...")
553	n.cancel()
554	if n.mode == ModeCoordinator {
555		log.Info("Stopping Coordinator...")
556		n.coord.Stop()
557	}
558	n.wg.Wait()
559}