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