From e19673bcd629cfbf918488e04124d8833e4b8f4d Mon Sep 17 00:00:00 2001 From: Torkel Rogstad Date: Mon, 13 Nov 2023 10:05:12 +0100 Subject: [PATCH] app: start mainchain on init if not running --- lib/app.dart | 115 ++++++++++++++++++++++++++++++++++++++++++++--- lib/rpc/rpc.dart | 7 +++ 2 files changed, 116 insertions(+), 6 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index cb7db42e..c531bd1d 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,10 +1,18 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; +import 'package:sail_ui/sail_ui.dart'; import 'package:sail_ui/theme/theme.dart'; import 'package:sail_ui/theme/theme_data.dart'; +import 'package:sail_ui/widgets/core/sail_text.dart'; import 'package:sail_ui/widgets/core/scaffold.dart'; import 'package:sail_ui/widgets/loading_indicator.dart'; +import 'package:sidesail/logger.dart'; +import 'package:sidesail/providers/proc_provider.dart'; import 'package:sidesail/routing/router.dart'; +import 'package:sidesail/rpc/rpc_mainchain.dart'; import 'package:sidesail/rpc/rpc_sidechain.dart'; import 'package:sidesail/storage/client_settings.dart'; import 'package:sidesail/storage/sail_settings/theme_settings.dart'; @@ -34,16 +42,24 @@ class SailApp extends StatefulWidget { } class SailAppState extends State with WidgetsBindingObserver { - SailThemeData? theme; AppRouter get router => GetIt.I.get(); SidechainRPC get _sidechain => GetIt.I.get(); + MainchainRPC get mainchain => GetIt.I.get(); + ProcessProvider get processes => GetIt.I.get(); final settings = GetIt.I.get(); + /// Unrecoverable error on startup we can't get past. + dynamic _initBinariesError; + String mainchainStartupMessage = "Checking if 'drivechaind' is started"; + bool binariesStarted = false; + SailThemeData? theme; + @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - loadTheme(); + unawaited(loadTheme()); + unawaited(initBinaries()); } void rebuildUI() { @@ -64,7 +80,63 @@ class SailAppState extends State with WidgetsBindingObserver { } } - void loadTheme([SailThemeValues? themeToLoad]) async { + Future initBinaries() async { + log.d('init binaries: checking mainchain connection'); + + // First, let the mainchain connection check finish. + await mainchain.initDone; + + // If we managed to connect, we're finished here! + if (mainchain.connected) { + log.d('init binaries: mainchain is already running, not doing anything'); + setState(() { + binariesStarted = true; + }); + return; + } + + // We have to start the mainchain + final tempDir = await Directory.systemTemp.createTemp('drivechaind'); + final tempLogFile = await File('${tempDir.path}/drivechaind.debug.log').create(recursive: true); + + if (!context.mounted) { + return; + } + + final args = [ + '-debuglogfile=${tempLogFile.path}', + '-regtest', + ]; + + log.d('init binaries: starting drivechaind $args'); + + try { + final pid = await processes.start(context, 'drivechaind', args); + log.d('init binaries: started drivechaind with PID $pid'); + setState(() { + mainchainStartupMessage = "Started 'drivechaind', waiting for successful RPC connection"; + }); + } catch (err) { + log.w('init binaries: unable to start drivechaind', error: err); + return setState(() { + _initBinariesError = err; + }); + } + + // Add timeout? + log.i('init binaries: waiting for mainchain connection'); + await waitForBoolToBeTrue(() async { + final (connected, _) = await mainchain.testConnection(); + return connected; + }); + + log.i('init binaries: mainchain connected'); + return setState(() { + binariesStarted = true; + }); + } + + Future loadTheme([SailThemeValues? themeToLoad]) async { themeToLoad ??= (await settings.getValue(ThemeSetting())).value; if (themeToLoad == SailThemeValues.platform) { // ignore: deprecated_member_use @@ -79,17 +151,37 @@ class SailAppState extends State with WidgetsBindingObserver { await settings.setValue(ThemeSetting(newValue: themeToLoad)); } + bool get inInitialSetup => theme == null || !binariesStarted; + + Widget loadingIndicator() { + if (!binariesStarted) { + return SailColumn( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + spacing: SailStyleValues.padding25, + children: [ + SailText.primary20('Mainchain starting up: $mainchainStartupMessage'), + const SailCircularProgressIndicator(), + ], + ); + } + return const SailCircularProgressIndicator(); + } + @override Widget build(BuildContext context) { - if (theme == null) { + if (inInitialSetup) { return MaterialApp( home: Material( color: _sidechain.chain.color, child: SailTheme( data: SailThemeData.lightTheme(_sidechain.chain.color), - child: const SailScaffold( + child: SailScaffold( body: Center( - child: SailCircularProgressIndicator(), + // TODO: better error message with troubleshooting tips here + child: _initBinariesError != null + ? SailText.primary24('Ran into unrecoverable error on startup: $_initBinariesError') + : loadingIndicator(), ), ), ), @@ -109,3 +201,14 @@ class SailAppState extends State with WidgetsBindingObserver { super.dispose(); } } + +Future waitForBoolToBeTrue( + Future Function() boolGetter, { + Duration pollInterval = const Duration(milliseconds: 100), +}) async { + bool result = await boolGetter(); + if (!result) { + await Future.delayed(pollInterval); + await waitForBoolToBeTrue(boolGetter, pollInterval: pollInterval); + } +} diff --git a/lib/rpc/rpc.dart b/lib/rpc/rpc.dart index 13fa6955..6ab504b6 100644 --- a/lib/rpc/rpc.dart +++ b/lib/rpc/rpc.dart @@ -20,6 +20,11 @@ abstract class RPCConnection extends ChangeNotifier { // ping method that tests whether the connection is successful // should throw if call is not successful Future ping(); + + bool _initDone = false; + + Future get initDone => _initDone ? Future.value() : Future.any([]); + Future<(bool, String?)> testConnection() async { try { await ping(); @@ -29,6 +34,8 @@ abstract class RPCConnection extends ChangeNotifier { _log.e('could not ping: ${error.toString()}!'); connectionError = error.toString(); connected = false; + } finally { + _initDone = true; } notifyListeners();