Implement background sync for xmr using flutter_daemon (#2094)

* Implement background sync for xmr using flutter_daemon

* - initialize app config in background thread
- initializeAppConfigs without loading the wallet.

* - properly do awaited calls in methodChannel
- prevent locking main thread during background sync

* add back background sync debug page
fix issues caused by xmr wallet being view only (and read only)

* changes from review
improve starting of bgsync task

* update stopBackgroundSync, await listener functions, ensure that listener always start (call _start in constructor)

* DO-NOT-MERGE: extre verbose monero logs

* stop background service when app is being opened

* improve monitoring of background sync

* update flutter_daemon to ensure network constraint
prevent throwing errors on isBackgroundSyncEnabled
check network before syncing

* Update lib/main.dart

* revert Update main.dart [skip ci]

* continously run network check

* disable charging requirement,
fix status reporting of background sync in UI

* Refactor background sync logic, and add UI notifications for battery optimization. Updated flutter_daemon version modified build.gradle for signing config to allow testing in both release and debug modes.

* verbose monero only when requested in code.
Do not start background sync when battery optimization is on

* fix background sync mode not properly reflecting state changes

* drop unnecessary dependency

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
cyan 2025-03-21 18:22:00 +01:00 committed by GitHub
parent d44621e6c7
commit 686580ff78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 853 additions and 352 deletions

View file

@ -37,7 +37,8 @@ List<monero.SubaddressAccountRow> getAllAccount() {
int size = monero.SubaddressAccount_getAll_size(subaddressAccount!);
if (size == 0) {
monero.Wallet_addSubaddressAccount(wptr!);
return getAllAccount();
monero.Wallet_status(wptr!);
return [];
}
return List.generate(size, (index) {
return monero.SubaddressAccount_getAll_byIndex(subaddressAccount!, index: index);

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';
import 'package:cw_core/root_dir.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
@ -108,9 +109,13 @@ Map<int, Map<int, Map<int, String>>> addressCache = {};
String getAddress({int accountIndex = 0, int addressIndex = 0}) {
// printV("getaddress: ${accountIndex}/${addressIndex}: ${monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)}: ${monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex)}");
while (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
printV("adding subaddress");
monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
// this could be a while loop, but I'm in favor of making it if to not cause freezes
if (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
if (monero.Wallet_numSubaddressAccounts(wptr!) < accountIndex) {
monero.Wallet_addSubaddressAccount(wptr!);
} else {
monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
}
}
addressCache[wptr!.address] ??= {};
addressCache[wptr!.address]![accountIndex] ??= {};
@ -149,6 +154,7 @@ Future<bool> setupNodeSync(
}
''');
final addr = wptr!.address;
printV("init: start");
await Isolate.run(() {
monero.Wallet_init(Pointer.fromAddress(addr),
daemonAddress: address,
@ -157,6 +163,7 @@ Future<bool> setupNodeSync(
daemonUsername: login ?? '',
daemonPassword: password ?? '');
});
printV("init: end");
final status = monero.Wallet_status(wptr!);
@ -168,7 +175,7 @@ Future<bool> setupNodeSync(
}
}
if (kDebugMode && debugMonero) {
if (true) {
monero.Wallet_init3(
wptr!, argv0: '',
defaultLogBaseName: 'moneroc',
@ -243,7 +250,9 @@ class SyncListener {
SyncListener(this.onNewBlock, this.onNewTransaction)
: _cachedBlockchainHeight = 0,
_lastKnownBlockHeight = 0,
_initialSyncHeight = 0;
_initialSyncHeight = 0 {
_start();
}
void Function(int, int, double) onNewBlock;
void Function() onNewTransaction;
@ -261,7 +270,7 @@ class SyncListener {
return _cachedBlockchainHeight;
}
void start() {
void _start() {
_cachedBlockchainHeight = 0;
_lastKnownBlockHeight = 0;
_initialSyncHeight = 0;
@ -282,7 +291,7 @@ class SyncListener {
}
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
// printV("syncHeight: $syncHeight, _lastKnownBlockHeight: $_lastKnownBlockHeight, bchHeight: $bchHeight");
if (_lastKnownBlockHeight == syncHeight) {
return;
}
@ -379,4 +388,4 @@ String signMessage(String message, {String address = ""}) {
bool verifyMessage(String message, String address, String signature) {
return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
}
}

View file

@ -11,6 +11,7 @@ import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart
import 'package:cw_monero/api/transaction_history.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/ledger.dart';
import 'package:flutter/foundation.dart';
import 'package:monero/monero.dart' as monero;
class MoneroCException implements Exception {
@ -50,7 +51,13 @@ final monero.WalletManager wmPtr = Pointer.fromAddress((() {
// codebase, so it will be easier to debug what happens. At least easier
// than plugging gdb in. Especially on windows/android.
monero.printStarts = false;
if (kDebugMode && debugMonero) {
monero.WalletManagerFactory_setLogLevel(4);
}
_wmPtr ??= monero.WalletManagerFactory_getWalletManager();
if (kDebugMode && debugMonero) {
monero.WalletManagerFactory_setLogLevel(4);
}
printV("ptr: $_wmPtr");
} catch (e) {
printV(e);
@ -77,10 +84,17 @@ void createWalletSync(
final newWptr = monero.WalletManager_createWallet(wmPtr,
path: path, password: password, language: language, networkType: 0);
final status = monero.Wallet_status(newWptr);
int status = monero.Wallet_status(newWptr);
if (status != 0) {
throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
}
monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
status = monero.Wallet_status(newWptr);
if (status != 0) {
throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
}
wptr = newWptr;
monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
monero.Wallet_store(wptr!, path: path);
@ -166,12 +180,19 @@ void restoreWalletFromKeysSync(
nettype: 0,
);
final status = monero.Wallet_status(newWptr);
int status = monero.Wallet_status(newWptr);
if (status != 0) {
throw WalletRestoreFromKeysException(
message: monero.Wallet_errorString(newWptr));
}
monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
status = monero.Wallet_status(newWptr);
if (status != 0) {
throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
}
// CW-712 - Try to restore deterministic wallet first, if the view key doesn't
// match the view key provided
if (spendKey != "") {
@ -190,11 +211,17 @@ void restoreWalletFromKeysSync(
spendKeyString: spendKey,
nettype: 0,
);
final status = monero.Wallet_status(newWptr);
int status = monero.Wallet_status(newWptr);
if (status != 0) {
throw WalletRestoreFromKeysException(
message: monero.Wallet_errorString(newWptr));
}
monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
status = monero.Wallet_status(newWptr);
if (status != 0) {
throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
}
}
}
@ -227,7 +254,7 @@ void restoreWalletFromPolyseedWithOffset(
kdfRounds: 1,
);
final status = monero.Wallet_status(newWptr);
int status = monero.Wallet_status(newWptr);
if (status != 0) {
final err = monero.Wallet_errorString(newWptr);
@ -240,6 +267,12 @@ void restoreWalletFromPolyseedWithOffset(
monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: seedOffset);
monero.Wallet_store(wptr!);
monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
status = monero.Wallet_status(newWptr);
if (status != 0) {
throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
}
storeSync();
openedWalletsByPath[path] = wptr!;
@ -277,7 +310,7 @@ void restoreWalletFromSpendKeySync(
restoreHeight: restoreHeight,
);
final status = monero.Wallet_status(newWptr);
int status = monero.Wallet_status(newWptr);
if (status != 0) {
final err = monero.Wallet_errorString(newWptr);
@ -290,6 +323,12 @@ void restoreWalletFromSpendKeySync(
monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
storeSync();
monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
status = monero.Wallet_status(newWptr);
if (status != 0) {
throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
}
openedWalletsByPath[path] = wptr!;
_lastOpenedWallet = path;
@ -321,6 +360,14 @@ Future<void> restoreWalletFromHardwareWallet(
final error = monero.Wallet_errorString(newWptr);
throw WalletRestoreFromSeedException(message: error);
}
// TODO: Check with upstream if we can use background sync here
// monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
// status = monero.Wallet_status(newWptr);
// if (status != 0) {
// throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
// }
wptr = newWptr;
_lastOpenedWallet = path;
openedWalletsByPath[path] = wptr!;
@ -384,7 +431,14 @@ Future<void> loadWallet(
final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
final status = monero.Wallet_status(newWptr);
int status = monero.Wallet_status(newWptr);
if (status != 0) {
final err = monero.Wallet_errorString(newWptr);
printV("loadWallet:"+err);
throw WalletOpeningException(message: err);
}
monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
status = monero.Wallet_status(newWptr);
if (status != 0) {
final err = monero.Wallet_errorString(newWptr);
printV("loadWallet:"+err);

View file

@ -218,7 +218,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
// FIXME: hardcoded value
socksProxyAddress: node.socksProxyAddress);
monero_wallet.setTrustedDaemon(node.trusted);
await monero_wallet.setTrustedDaemon(node.trusted);
syncStatus = ConnectedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
@ -226,6 +226,57 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
}
@override
Future<void> startBackgroundSync() async {
if (isBackgroundSyncRunning) {
printV("Background sync already running");
return;
}
isBackgroundSyncRunning = true;
int status = monero.Wallet_status(wptr!);
if (status != 0) {
final err = monero.Wallet_errorString(wptr!);
throw Exception("unable to setup background sync: $err");
}
await save();
monero.Wallet_startBackgroundSync(wptr!);
status = monero.Wallet_status(wptr!);
if (status != 0) {
final err = monero.Wallet_errorString(wptr!);
throw Exception("unable to start background sync: $err");
}
await save();
await init();
await startSync();
}
bool isBackgroundSyncRunning = false;
@action
@override
Future<void> stopSync() async {
if (isBackgroundSyncRunning) {
printV("Stopping background sync");
await save();
monero.Wallet_stopBackgroundSync(wptr!, '');
await save();
isBackgroundSyncRunning = false;
}
}
@action
@override
Future<void> stopBackgroundSync(String password) async {
if (isBackgroundSyncRunning) {
printV("Stopping background sync");
await save();
monero.Wallet_stopBackgroundSync(wptr!, password);
await save();
isBackgroundSyncRunning = false;
}
}
@override
Future<void> startSync() async {
try {
@ -250,7 +301,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
syncStatus = AttemptingSyncStatus();
monero_wallet.startRefresh();
_setListeners();
_listener?.start();
} catch (e) {
syncStatus = FailedSyncStatus();
printV(e);
@ -782,6 +832,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
void _onNewBlock(int height, int blocksLeft, double ptc) async {
printV("onNewBlock: $height, $blocksLeft, $ptc");
try {
if (walletInfo.isRecovery) {
await _askForUpdateTransactionHistory();