diff --git a/.travis.yml b/.travis.yml index b80027cfd..9ff875f9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,9 @@ jobs: # to still run, but currently does not work due to a bug if: type != pull_request script: - - ./build/bin/statusd -les -networkid 4 -sync-and-exit + # sync the chain first; it will time out after 20 minutes; Rinkeby is networkid=4 + - make statusgo + - ./build/bin/statusd -datadir .ethereumtest/Rinkeby -les -networkid=4 -sync-and-exit=20 -log WARN -standalone=false -discovery=false - make test-e2e networkid=4 cache: directories: diff --git a/cmd/statusd/main.go b/cmd/statusd/main.go index 78a6f2734..7006ebe5d 100644 --- a/cmd/statusd/main.go +++ b/cmd/statusd/main.go @@ -114,17 +114,25 @@ func main() { // Sync blockchain and stop. if *syncAndExit >= 0 { - exitCode := syncAndStopNode(backend.NodeManager(), *syncAndExit) + exitCode := syncAndStopNode(interruptCh, backend.NodeManager(), *syncAndExit) + // Call was interrupted. Wait for graceful shutdown. + if exitCode == -1 { + if node, err := backend.NodeManager().Node(); err == nil && node != nil { + node.Wait() + } + return + } + // Otherwise, exit immediately with a returned exit code. os.Exit(exitCode) } - // wait till node has been stopped node, err := backend.NodeManager().Node() if err != nil { log.Fatalf("Getting node failed: %v", err) return } + // wait till node has been stopped node.Wait() } @@ -170,34 +178,6 @@ func startCollectingStats(interruptCh <-chan struct{}, nodeManager common.NodeMa } } -func syncAndStopNode(nodeManager common.NodeManager, timeout int) (exitCode int) { - log.Println("Node will synchronize and exit") - if timeout < 0 { - log.Println("Sync and stop error: negative timeout value") - return 1 - } - var err error - if timeout == 0 { - err = nodeManager.EnsureSync(context.Background()) - } else { - ctx, cancel := context.WithTimeout(context.Background(), (time.Duration)(timeout)*time.Minute) - err = nodeManager.EnsureSync(ctx) - defer cancel() - } - if err != nil { - log.Printf("Sync error: %v", err) - exitCode = 1 - } - var done <-chan struct{} - done, err = nodeManager.StopNode() - if err != nil { - log.Printf("Stop node err: %v", err) - return 1 - } - <-done - return -} - // makeNodeConfig parses incoming CLI options and returns node configuration object func makeNodeConfig() (*params.NodeConfig, error) { devMode := !*prodMode diff --git a/cmd/statusd/sync.go b/cmd/statusd/sync.go new file mode 100644 index 000000000..06508ddab --- /dev/null +++ b/cmd/statusd/sync.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "log" + "time" + + "github.com/status-im/status-go/geth/common" +) + +func createContextFromTimeout(timeout int) (context.Context, context.CancelFunc) { + if timeout == 0 { + return context.WithCancel(context.Background()) + } + + return context.WithTimeout(context.Background(), time.Duration(timeout)*time.Minute) +} + +// syncAndStopNode tries to sync the blockchain and stop the node. +// It returns an exit code (`0` if successful or `1` in case of error) +// that can be used in `os.Exit` to exit immediately when the function returns. +// The special exit code `-1` is used if execution was interrupted. +func syncAndStopNode(interruptCh <-chan struct{}, nodeManager common.NodeManager, timeout int) (exitCode int) { + log.Printf("syncAndStopNode: node will synchronize the chain and exit (timeout %d mins)", timeout) + + ctx, cancel := createContextFromTimeout(timeout) + defer cancel() + + doneSync := make(chan struct{}) + errSync := make(chan error) + go func() { + if err := nodeManager.EnsureSync(ctx); err != nil { + errSync <- err + } + close(doneSync) + }() + + select { + case err := <-errSync: + log.Printf("syncAndStopNode: failed to sync the chain: %v", err) + exitCode = 1 + case <-doneSync: + case <-interruptCh: + // cancel context and return immediately if interrupted + // `-1` is used as a special exit code to denote interruption + return -1 + } + + done, err := nodeManager.StopNode() + if err != nil { + log.Printf("syncAndStopNode: failed to stop the node: %v", err) + return 1 + } + <-done + + return +} diff --git a/geth/node/manager.go b/geth/node/manager.go index e5e82aa6f..4d247363b 100644 --- a/geth/node/manager.go +++ b/geth/node/manager.go @@ -532,25 +532,33 @@ func (m *NodeManager) EnsureSync(ctx context.Context) error { if m.config.NetworkID == params.StatusChainNetworkID { return nil } - if m.lesService == nil { - return errors.New("LightEthereumService is nil") - } + return m.ensureSync(ctx) } func (m *NodeManager) ensureSync(ctx context.Context) error { - downloader := m.lesService.Downloader() + les, err := m.LightEthereumService() + if err != nil { + return fmt.Errorf("failed to get LES service: %v", err) + } + + downloader := les.Downloader() if downloader == nil { return errors.New("LightEthereumService downloader is nil") } + progress := downloader.Progress() - if progress.CurrentBlock >= progress.HighestBlock { - log.Debug("Synchronization completed") + if m.PeerCount() > 0 && progress.CurrentBlock >= progress.HighestBlock { + log.Debug("Synchronization completed", "current block", progress.CurrentBlock, "highest block", progress.HighestBlock) return nil } ticker := time.NewTicker(tickerResolution) defer ticker.Stop() + + progressTicker := time.NewTicker(time.Minute) + defer progressTicker.Stop() + for { select { case <-ctx.Done(): @@ -566,13 +574,13 @@ func (m *NodeManager) ensureSync(ctx context.Context) error { } progress = downloader.Progress() if progress.CurrentBlock >= progress.HighestBlock { - log.Debug("Synchronization completed") + log.Info("Synchronization completed", "current block", progress.CurrentBlock, "highest block", progress.HighestBlock) return nil } - log.Debug( - fmt.Sprintf("Synchronization is not finished yet: current block %d < highest block %d", - progress.CurrentBlock, progress.HighestBlock), - ) + log.Debug("Synchronization is not finished", "current", progress.CurrentBlock, "highest", progress.HighestBlock) + case <-progressTicker.C: + progress = downloader.Progress() + log.Warn("Synchronization is not finished", "current", progress.CurrentBlock, "highest", progress.HighestBlock) } } }