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