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