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