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