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