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