Optimize loading in generate seed screen
What changed, and why it matters
This commit speeds up the wallet creation screen by fetching the blockchain height ahead of time and by running some setup steps in the background instead of waiting for each one to finish. The changes are framed as a performance optimization. There is no direct evidence in the commit that this fixes a security vulnerability, but it does change how sensitive wallet setup tasks are sequenced and how a shared cache is accessed.
Review the async sequencing in `create()` to ensure that `store()` cannot run before the wallet is fully connected/refreshed, and verify that the completer cache is not completed multiple times or left in an incomplete error state. Treat as a code-quality/concurrency review rather than an immediate security patch.
Security signals we found
Concurrency change: wallet creation now chains refresh/connect/store asynchronously without awaiting, which could affect error handling and ordering assumptions during wallet initialization.
Shared mutable cache: `_blockchainHeightCompleter` is exposed via a public getter and completed from an async method, creating a potential race if multiple callers interact with it.
Background network call added during wallet reset and on app start when no wallet exists, which may have privacy/timing side effects.
No input validation, cryptographic, or authentication changes observed.
Evidence from the diff
The patch introduces a _blockchainHeightCompleter cache in WalletModel. loadExistingWalletIfExists() and resetWallet() now pre-fetch the blockchain height in the background. create() reuses the cached height if available and chains refresh(), connectToDaemon(), and store() with .then() instead of awaiting them. generate_seed.dart reads the height from the completer and no longer awaits wallet.load(). These are concurrency/UX changes, not cryptographic changes.
Changed components
lib/main.dartlib/models/wallet_model.dartlib/screens/generate_seed.dartInspect captured patch +40 / −6
diff --git a/lib/main.dart b/lib/main.dart
index 9f941ed..b95438e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -65,6 +65,11 @@ Future<bool> loadExistingWalletIfExists(WalletModel wallet) async {
return true;
}
+ // No wallet exists, fetch and cache the blockchain height in the background for future wallet creation
+ if (wallet.blockchainHeightCompleter == null) {
+ wallet.fetchAndCacheBlockchainHeight();
+ }
+
return false;
}
diff --git a/lib/models/wallet_model.dart b/lib/models/wallet_model.dart
index 35481fc..a6ca0fc 100644
--- a/lib/models/wallet_model.dart
+++ b/lib/models/wallet_model.dart
@@ -150,6 +150,7 @@ class WalletModel with ChangeNotifier {
int? _unusedSubaddressIndex;
bool? _unusedSubaddressIndexIsSupported;
String? _desktopWalletPassword;
+ Completer<int>? _blockchainHeightCompleter;
Wallet2Wallet? get w2Wallet => _w2Wallet;
bool get hasAttemptedConnection => _hasAttemptedConnection;
@@ -163,6 +164,7 @@ class WalletModel with ChangeNotifier {
bool? get serverSupportsSubaddresses => _serverSupportsSubaddresses;
int? get unusedSubaddressIndex => _unusedSubaddressIndex;
bool? get unusedSubaddressIndexIsSupported => _unusedSubaddressIndexIsSupported;
+ Completer<int>? get blockchainHeightCompleter => _blockchainHeightCompleter;
WalletModel() {
_startTimers();
@@ -598,15 +600,18 @@ class WalletModel with ChangeNotifier {
// ignore: deprecated_member_use
final polyseed = await Isolate.run(() => monero.Wallet_createPolyseed());
log(LogLevel.info, 'Wallet_createPolyseed completed');
- final currentHeight = await getCurrentBlockchainHeight();
+ final isCached = _blockchainHeightCompleter != null;
+ final currentHeight = isCached
+ ? await _blockchainHeightCompleter!.future
+ : await getCurrentBlockchainHeight();
+ log(LogLevel.info, 'Using blockchain height: $currentHeight (cached: $isCached)');
+
await restoreFromMnemonic(polyseed, currentHeight);
await SharedPreferencesService.set<int>(
SharedPreferencesKeys.walletRestoreHeight,
currentHeight,
);
- await refresh();
- await connectToDaemon();
- await store();
+ refresh().then((_) => connectToDaemon().then((_) => store()));
return polyseed;
}
@@ -830,6 +835,26 @@ class WalletModel with ChangeNotifier {
await SharedPreferencesService.remove(SharedPreferencesKeys.contacts);
await SharedPreferencesService.remove(SharedPreferencesKeys.unusedSubaddressIndex);
await SharedPreferencesService.remove(SharedPreferencesKeys.unusedSubaddressIndexIsSupported);
+
+ // Fetch and cache blockchain height in the background for future wallet creation
+ fetchAndCacheBlockchainHeight();
+ }
+
+ void fetchAndCacheBlockchainHeight() async {
+ _blockchainHeightCompleter = Completer<int>();
+
+ try {
+ final height = await getCurrentBlockchainHeight();
+ log(LogLevel.info, 'Cached blockchain height: $height');
+
+ // another call might have already completed the completer
+ if (_blockchainHeightCompleter?.isCompleted == false) {
+ _blockchainHeightCompleter!.complete(height);
+ }
+ } catch (e) {
+ log(LogLevel.error, 'Failed to fetch and cache blockchain height: $e');
+ _blockchainHeightCompleter!.completeError(e);
+ }
}
Future<bool> hasExistingWallet() async {
diff --git a/lib/screens/generate_seed.dart b/lib/screens/generate_seed.dart
index 2ab1888..a16c37a 100644
--- a/lib/screens/generate_seed.dart
+++ b/lib/screens/generate_seed.dart
@@ -31,7 +31,7 @@ class _GenerateSeedScreenState extends State<GenerateSeedScreen> {
try {
final seed = await wallet.create();
- await wallet.load();
+ wallet.load();
setState(() {
_seed = seed.split(' ');
@@ -57,7 +57,11 @@ class _GenerateSeedScreenState extends State<GenerateSeedScreen> {
Future<void> _loadCurrentHeight() async {
try {
- final height = await getCurrentBlockchainHeight();
+ // get current height from cache
+ final height = await Provider.of<WalletModel>(
+ context,
+ listen: false,
+ ).blockchainHeightCompleter!.future;
if (mounted) {
setState(() {
Why this scored 16/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.