Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

launcher: restore starter functionality to launch with l2 #545

Merged
merged 12 commits into from
Jan 29, 2025
6 changes: 2 additions & 4 deletions clients/launcher/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,8 @@ Future<void> initDependencies(Logger log) async {
QuotesProvider(prefs),
);

// Register services
GetIt.I.registerLazySingleton<WalletService>(
() => WalletService(),
);
// Register wallet service
GetIt.I.registerSingleton<WalletService>(WalletService());
}

Future<List<Binary>> _loadBinaries(Directory appDir) async {
Expand Down
2 changes: 0 additions & 2 deletions clients/launcher/lib/pages/overview_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ class _OverviewPageState extends State<OverviewPage> {
WidgetsBinding.instance.addPostFrameCallback((_) {
_binaryProvider.addListener(_onBinaryProviderUpdate);
_processProvider.addListener(_onBinaryProviderUpdate);
_walletService.addListener(_onBinaryProviderUpdate);
_blockchainProvider.addListener(_onBinaryProviderUpdate);
});
}
Expand All @@ -57,7 +56,6 @@ class _OverviewPageState extends State<OverviewPage> {
void dispose() {
_binaryProvider.removeListener(_onBinaryProviderUpdate);
_processProvider.removeListener(_onBinaryProviderUpdate);
_walletService.removeListener(_onBinaryProviderUpdate);
_blockchainProvider.removeListener(_onBinaryProviderUpdate);
super.dispose();
}
Expand Down
2 changes: 1 addition & 1 deletion clients/launcher/lib/services/wallet_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,4 @@ class WalletService extends ChangeNotifier {
_logger.e('Error generating starters for downloaded chains: $e\n$stack');
}
}
}
}
81 changes: 35 additions & 46 deletions clients/launcher/lib/widgets/welcome_modal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,14 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> {
}

final words = _currentWalletData['mnemonic'].split(' ');
final binaryStrings = _currentWalletData['binary_strings'] as List<String>;
final binaryString = _currentWalletData['bip39_binary'] ?? '';
final binaryStrings = <String>[];

// Split binary string into 11-bit chunks
for (int i = 0; i < binaryString.length; i += 11) {
final end = i + 11 > binaryString.length ? binaryString.length : i + 11;
binaryStrings.add(binaryString.substring(i, end).padRight(11, '0'));
}

return SailRawCard(
padding: true,
Expand All @@ -396,24 +403,10 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> {
children: [
SailText.primary10(words[row * 6 + col], bold: true),
if (row * 6 + col < binaryStrings.length)
row * 6 + col == words.length - 1
? Row(
mainAxisSize: MainAxisSize.min,
children: [
SailText.primary10(
binaryStrings[row * 6 + col].substring(0, 7),
color: SailTheme.of(context).colors.textSecondary,
),
SailText.primary10(
binaryStrings[row * 6 + col].substring(7),
color: SailTheme.of(context).colors.success,
),
],
)
: SailText.primary10(
binaryStrings[row * 6 + col],
color: SailTheme.of(context).colors.textSecondary,
),
SailText.primary10(
binaryStrings[row * 6 + col],
color: SailTheme.of(context).colors.textSecondary,
),
],
),
),
Expand All @@ -430,7 +423,6 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> {

Widget _buildInfoPanel() {
final theme = SailTheme.of(context);
final binaryString = _currentWalletData['bip39_binary'] as String?;

return SailRawCard(
padding: true,
Expand All @@ -449,12 +441,34 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> {
),
Expanded(
child: SailText.primary10(
binaryString ?? '',
_currentWalletData['bip39_binary'] ?? '',
color: theme.colors.textSecondary,
),
),
],
),
// BIP39 Checksum
SailRow(
spacing: SailStyleValues.padding08,
children: [
SizedBox(
width: 100,
child: SailText.primary10('Checksum:', bold: true),
),
SailText.primary10(
_currentWalletData['bip39_checksum'] ?? '',
color: theme.colors.textSecondary,
),
const SizedBox(width: SailStyleValues.padding16),
SailText.primary10('Hex:', bold: true),
const SizedBox(width: SailStyleValues.padding04),
SailText.primary10(
_currentWalletData['bip39_checksum_hex'] ?? '',
color: theme.colors.textSecondary,
),
Expanded(child: Container()),
],
),
// Master Key
SailRow(
spacing: SailStyleValues.padding08,
Expand Down Expand Up @@ -707,31 +721,6 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> {
return;
}

// Process binary representation
final binaryString = wallet['bip39_binary'] as String;
final words = (wallet['mnemonic'] as String).split(' ');
final binaryStrings = <String>[];

// For 12 words: 11 words × 11 bits + 7 bits + 4 checksum bits = 132 bits
final checksumBits = wallet['bip39_checksum'] as String;
final entropyBits = binaryString.substring(0, binaryString.length - checksumBits.length);

// Split into 11-bit chunks for each word
for (int i = 0; i < words.length - 1; i++) {
final start = i * 11;
final end = start + 11;
final chunk = entropyBits.substring(start, end);
binaryStrings.add(chunk);
}

// Handle last word specially (7 bits entropy + 4 bits checksum)
final lastEntropyBits = entropyBits.substring(entropyBits.length - 7);
final lastWord = lastEntropyBits + checksumBits;
binaryStrings.add(lastWord);

// Update wallet data with processed binary strings
wallet['binary_strings'] = binaryStrings;

setState(() {
_currentWalletData = Map<String, dynamic>.from(wallet);
_isValidInput = true;
Expand Down
119 changes: 92 additions & 27 deletions clients/sail_ui/lib/providers/binary_provider.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
Expand All @@ -12,7 +13,6 @@ import 'package:sail_ui/rpcs/bitwindow_api.dart';
import 'package:sail_ui/rpcs/enforcer_rpc.dart';
import 'package:sail_ui/rpcs/mainchain_rpc.dart';
import 'package:sail_ui/rpcs/thunder_rpc.dart';

/// Represents the current status of a binary download
class DownloadState {
final double progress; // Only used during installing
Expand Down Expand Up @@ -164,10 +164,40 @@ class BinaryProvider extends ChangeNotifier {
_statusController.add(Map.from(_downloadStates));
}

/// Check if a binary can be started based on its dependencies
String? canStart(Binary binary) {
final mainchainReady = mainchainConnected && !inIBD;
return switch (binary) {
Enforcer() =>
mainchainReady ? null : 'Mainchain must be started and fully synced before starting Enforcer',
BitWindow() =>
enforcerConnected && mainchainReady ? null : 'Mainchain and Enforcer must be running and fully synced before starting BitWindow',
Thunder() =>
enforcerConnected && mainchainReady ? null : 'Mainchain and Enforcer must be running and fully synced before starting Thunder',
Bitnames() =>
enforcerConnected && mainchainReady ? null : 'Mainchain and Enforcer must be running and fully synced before starting Bitnames',
_ => null, // No requirements for mainchain
};
}

// Start a binary, and set starter seeds (if set)
Future<void> startBinary(BuildContext context, Binary binary, {bool useStarter = false}) async {
if (!context.mounted) return;

final startError = canStart(binary);
if (startError != null) {
log.e('Cannot start ${binary.name}: $startError');
throw Exception(startError);
}
Comment on lines +187 to +191
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check is overkill imo, because it's also added as a check on the 'launch'-button.

but not a big deal, only remove it if you want


if (useStarter && (binary is Thunder || binary is Bitnames)) {
try {
await _setStarterSeed(binary);
} catch (e) {
log.e('Error setting starter seed: $e');
}
}

switch (binary) {
case ParentChain():
await mainchainRPC.initBinary(context);
Expand All @@ -179,24 +209,20 @@ class BinaryProvider extends ChangeNotifier {
await bitwindowRPC.initBinary(context);

case Thunder():
await thunderRPC.initBinary(context);
if (useStarter) {
try {
await _setStarterSeed(binary);
} catch (e) {
log.e('Error setting starter seed: $e');
}
}
await thunderRPC.initBinary(
context,
arg: binary.mnemonicSeedPhrasePath != null
? ['--mnemonic-seed-phrase-path', binary.mnemonicSeedPhrasePath!]
: null,
);

case Bitnames():
await bitnamesRPC.initBinary(context);
if (useStarter) {
try {
await _setStarterSeed(binary);
} catch (e) {
log.e('Error setting starter seed: $e');
}
}
await bitnamesRPC.initBinary(
context,
arg: binary.mnemonicSeedPhrasePath != null
? ['--mnemonic-seed-phrase-path', binary.mnemonicSeedPhrasePath!]
: null,
);

default:
log.i('is $binary');
Expand Down Expand Up @@ -270,22 +296,57 @@ class BinaryProvider extends ChangeNotifier {
await downloadsDir.delete(recursive: true);
}

/// Check if a binary can be started based on its dependencies
String? canStart(Binary binary) {
return switch (binary) {
Enforcer() =>
mainchainConnected && !inIBD ? null : 'Mainchain must be started and fully synced before starting Enforcer',
BitWindow() => enforcerConnected ? null : 'Enforcer must be running and fully synced before starting BitWindow',
Thunder() => enforcerConnected ? null : 'Enforcer must be running and fully synced before starting Thunder',
Bitnames() => enforcerConnected ? null : 'Enforcer must be running and fully synced before starting Bitnames',
_ => null, // No requirements for mainchain
};
Future<bool> _isSidechainInitialized(int slot) async {
try {
final masterStarterPath = path.join(appDir.path, 'wallet_starters', 'master_starter.json');
final masterStarterFile = File(masterStarterPath);

if (!masterStarterFile.existsSync()) {
return false;
}

final masterData = jsonDecode(await masterStarterFile.readAsString()) as Map<String, dynamic>;
return masterData['sidechain_${slot}_init'] ?? false;
} catch (e) {
log.e('Error checking sidechain initialization: $e');
return false;
}
}

Future<void> _setSidechainInitialized(int slot) async {
try {
final masterStarterPath = path.join(appDir.path, 'wallet_starters', 'master_starter.json');
final masterStarterFile = File(masterStarterPath);

if (!masterStarterFile.existsSync()) {
return;
}

final masterData = jsonDecode(await masterStarterFile.readAsString()) as Map<String, dynamic>;
masterData['sidechain_${slot}_init'] = true;
await masterStarterFile.writeAsString(jsonEncode(masterData));
} catch (e) {
log.e('Error setting sidechain initialization: $e');
}
}

Future<void> _setStarterSeed(Binary binary) async {
if (binary is! Sidechain) return;

try {
// Check if this sidechain has already been initialized
final isInitialized = await _isSidechainInitialized(binary.slot);
if (isInitialized) {
log.i('Sidechain ${binary.name} already initialized, skipping seed setup');
return;
}

// Skip if mnemonic path is already set
if (binary.mnemonicSeedPhrasePath != null) {
log.i('Sidechain ${binary.name} already has mnemonic path set, skipping seed setup');
return;
}

final starterDir = path.join(appDir.path, 'wallet_starters');
final starterFile = File(
path.join(
Expand All @@ -302,6 +363,10 @@ class BinaryProvider extends ChangeNotifier {
log.i('Found starter file, setting mnemonic seed phrase path');
binary.mnemonicSeedPhrasePath = starterFile.path;
log.i('Successfully set mnemonic seed phrase path to: ${starterFile.path}');

// Mark this sidechain as initialized
await _setSidechainInitialized(binary.slot);
notifyListeners();
} catch (e, st) {
log.e('Error setting starter seed', error: e, stackTrace: st);
rethrow;
Expand Down
9 changes: 9 additions & 0 deletions clients/sidesail/lib/rpc/rpc_ethereum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:sail_ui/sail_ui.dart';
import 'package:sidesail/rpc/rpc_sidechain.dart';
import 'package:web3dart/json_rpc.dart' as jsonrpc;
import 'package:web3dart/web3dart.dart';
import 'package:flutter/material.dart';

abstract class EthereumRPC extends SidechainRPC {
EthereumRPC({
Expand Down Expand Up @@ -194,6 +195,14 @@ class EthereumRPCLive extends EthereumRPC {
warnings: [],
);
}

@override
Future<void> initBinary(
BuildContext context, {
List<String>? arg,
}) async {
await super.initBinary(context, arg: arg);
}
}

/// List of all known RPC methods available /
Expand Down
9 changes: 9 additions & 0 deletions clients/sidesail/lib/rpc/rpc_testchain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:sidesail/rpc/models/bmm_result.dart';
import 'package:sidesail/rpc/models/bundle_info.dart';
import 'package:sidesail/rpc/rpc_sidechain.dart';
import 'package:sidesail/rpc/rpc_withdrawal_bundle.dart';
import 'package:flutter/material.dart';

/// RPC connection the sidechain node.
abstract class TestchainRPC extends SidechainRPC {
Expand Down Expand Up @@ -272,6 +273,14 @@ class TestchainRPCLive extends TestchainRPC {
final confirmedFut = await _client().call('getblockchaininfo');
return BlockchainInfo.fromMap(confirmedFut);
}

@override
Future<void> initBinary(
BuildContext context, {
List<String>? arg,
}) async {
await super.initBinary(context, arg: arg);
}
}

class TestchainRPCError {
Expand Down
Loading