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