From 41f2e4ab3336b5205ba0c349e1f338a626caddc7 Mon Sep 17 00:00:00 2001
From: Rafael Saes <git@rafael.saes.dev>
Date: Wed, 15 Nov 2023 18:01:18 -0300
Subject: [PATCH] feat: beginning isolate, add compute

---
 cw_bitcoin/lib/electrum_wallet.dart | 154 ++++++++++++------------
 cw_bitcoin/pubspec.lock             |  24 ++--
 lib/entities/background_tasks.dart  | 180 ++++++++++++++++++++++------
 3 files changed, 237 insertions(+), 121 deletions(-)

diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 2a5b0e607..49633abba 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -145,6 +145,10 @@ abstract class ElectrumWalletBase
   @action
   @override
   Future<void> startSync() async {
+    compute(test, {
+      "scanPrivateKeyCompressed": walletAddresses.silentAddress!.scanPrivkey.toCompressedHex(),
+      "spendPubkeyCompressed": walletAddresses.silentAddress!.spendPubkey.toCompressedHex()
+    });
     try {
       syncStatus = AttemptingSyncStatus();
       await walletAddresses.discoverAddresses();
@@ -349,13 +353,11 @@ abstract class ElectrumWalletBase
 
       generatedOutputs.forEach((recipientSilentAddress, generatedOutput) {
         generatedOutput.forEach((output) {
-          final generatedPubkey = output.$1.toCompressedHex();
-          txb.addOutput(
-              bitcoin.ECPublic.fromHex(generatedPubkey)
-                  .toTaprootAddress()
-                  .toScriptPubKey()
-                  .toBytes(),
-              amount);
+          final generatedPubkey = output.$1.toHex();
+          // TODO: pubkeyToOutputScript (?)
+          final point = bitcoin.ECPublic.fromHex(generatedPubkey).toTapPoint();
+          final p2tr = bitcoin.P2trAddress(program: point);
+          txb.addOutput(p2tr.toScriptPubKey().toBytes(), amount);
         });
       });
     }
@@ -525,69 +527,6 @@ abstract class ElectrumWalletBase
                 return null;
               }
             }).whereNotNull())));
-    final txid = "28a21e8b6373bf20928107f8f1f1ea5a98ada793f2676372d03466e874d4e762";
-    final uri = Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx/$txid');
-    // final uri = Uri(scheme: 'https', host: 'blockstream.info', path: 'testnet/api/block-height/0');
-
-    await http.get(uri).then((response) {
-      // print(response.body);
-      final obj = json.decode(response.body);
-      final scanPrivateKey = walletAddresses.silentAddress!.scanPrivkey;
-      final spendPublicKey = walletAddresses.silentAddress!.spendPubkey;
-
-      List<String> pubkeys = [];
-      List<bitcoin.Outpoint> outpoints = [];
-      obj["vin"].forEach((input) {
-        final witness = input["witness"] as List<dynamic>;
-        final pubkey = witness[1] as String;
-        pubkeys.add(pubkey);
-        outpoints.add(bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int));
-      });
-
-      Uint8List sumOfInputPublicKeys =
-          bitcoin.getSumInputPubKeys(pubkeys).toCompressedHex().fromHex;
-      final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints);
-
-      Map<String, bitcoin.Outpoint> outpointsByP2TRpubkey = {};
-      int i = 0;
-      obj['vout'].forEach((out) {
-        if (out["scriptpubkey_type"] == "v1_p2tr")
-          outpointsByP2TRpubkey[out['scriptpubkey_address'] as String] =
-              bitcoin.Outpoint(txid: txid, index: i, value: out["value"] as int);
-
-        i++;
-      });
-      final result = bitcoin.scanOutputs(
-          scanPrivateKey.toCompressedHex().fromHex,
-          spendPublicKey.toCompressedHex().fromHex,
-          sumOfInputPublicKeys,
-          outpointHash,
-          outpointsByP2TRpubkey.keys.toList());
-
-      // print(result);
-
-      result.forEach((key, value) {
-        final outpoint = outpointsByP2TRpubkey[key];
-        // if (outpoint != null) {
-        //   unspentCoins.add(BitcoinUnspent(
-        //       BitcoinAddressRecord(walletAddresses.silentAddress.toString(), index: 0),
-        //       outpoint.txid,
-        //       outpoint.value!,
-        //       outpoint.n));
-
-        //   final currentBalance = balance[currency];
-        //   if (currentBalance != null) {
-        //     balance[currency] = ElectrumBalance(
-        //         confirmed: currentBalance.confirmed + outpoint.value!,
-        //         unconfirmed: currentBalance.unconfirmed,
-        //         frozen: currentBalance.frozen);
-        //   } else {
-        //     balance[currency] =
-        //         ElectrumBalance(confirmed: outpoint.value!, unconfirmed: 0, frozen: 0);
-        //   }
-        // }
-      });
-    });
     unspentCoins = unspent.expand((e) => e).toList();
 
     if (unspentCoinsInfo.isEmpty) {
@@ -848,7 +787,7 @@ abstract class ElectrumWalletBase
   }
 
   // int _getHeightByDate(DateTime date) {
-  //   final nodeHeight = monero_wallet.getNodeHeightSync();
+  //   final currentHeight = await electrumClient.getCurrentBlockChainTip();
   //   final heightDistance = _getHeightDistance(date);
 
   //   if (nodeHeight <= 0) {
@@ -868,10 +807,73 @@ abstract class ElectrumWalletBase
     final currentHeight = await electrumClient.getCurrentBlockChainTip();
     if (currentHeight == null) return;
 
-    // if (currentHeight <= 1) {
-    //   final height = _getHeightByDate(walletInfo.date);
-    //   monero_wallet.setRecoveringFromSeed(isRecovery: true);
-    //   monero_wallet.setRefreshFromBlockHeight(height: height);
-    // }
+    if (currentHeight <= 1) {
+      // final height = _getHeightByDate(walletInfo.date);
+      // monero_wallet.setRecoveringFromSeed(isRecovery: true);
+      // monero_wallet.setRefreshFromBlockHeight(height: height);
+    }
   }
 }
+
+void test(Map<String, String> addrs) async {
+  final txid = "920244856cba9d8421ea01f3930baec80642b2b908c0e83a7e6c918040d5656e";
+  final uri = Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx/$txid');
+  // final uri = Uri(scheme: 'https', host: 'blockstream.info', path: 'testnet/api/block-height/0');
+
+  await http.get(uri).then((response) {
+    // print(response.body);
+    final obj = json.decode(response.body);
+
+    List<String> pubkeys = [];
+    List<bitcoin.Outpoint> outpoints = [];
+    obj["vin"].forEach((input) {
+      final witness = input["witness"] as List<dynamic>;
+      final pubkey = witness[1] as String;
+      pubkeys.add(pubkey);
+      outpoints.add(bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int));
+    });
+
+    Uint8List sumOfInputPublicKeys = bitcoin.getSumInputPubKeys(pubkeys).toCompressedHex().fromHex;
+    final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints);
+
+    Map<String, bitcoin.Outpoint> outpointsByP2TRpubkey = {};
+    int i = 0;
+    obj['vout'].forEach((out) {
+      if (out["scriptpubkey_type"] == "v1_p2tr")
+        outpointsByP2TRpubkey[out['scriptpubkey_address'] as String] =
+            bitcoin.Outpoint(txid: txid, index: i, value: out["value"] as int);
+
+      i++;
+    });
+    final result = bitcoin.scanOutputs(
+        (addrs["scanPrivateKeyCompressed"] as String).fromHex,
+        (addrs["spendPubkeyCompressed"] as String).fromHex,
+        sumOfInputPublicKeys,
+        outpointHash,
+        outpointsByP2TRpubkey.keys.toList());
+
+    print(["RESULT:", result]);
+
+    result.forEach((key, value) {
+      final outpoint = outpointsByP2TRpubkey[key];
+      // if (outpoint != null) {
+      //   unspentCoins.add(BitcoinUnspent(
+      //       BitcoinAddressRecord(walletAddresses.silentAddress.toString(), index: 0),
+      //       outpoint.txid,
+      //       outpoint.value!,
+      //       outpoint.n));
+
+      //   final currentBalance = balance[currency];
+      //   if (currentBalance != null) {
+      //     balance[currency] = ElectrumBalance(
+      //         confirmed: currentBalance.confirmed + outpoint.value!,
+      //         unconfirmed: currentBalance.unconfirmed,
+      //         frozen: currentBalance.frozen);
+      //   } else {
+      //     balance[currency] =
+      //         ElectrumBalance(confirmed: outpoint.value!, unconfirmed: 0, frozen: 0);
+      //   }
+      // }
+    });
+  });
+}
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index ffe97b979..4da2a42b4 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -44,11 +44,9 @@ packages:
   bech32:
     dependency: transitive
     description:
-      path: "."
-      ref: "cake-0.2.2"
-      resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192"
-      url: "https://github.com/cake-tech/bech32.git"
-    source: git
+      path: "/home/rafael/Storage/Repositories/bech32"
+      relative: false
+    source: path
     version: "0.2.2"
   bip32:
     dependency: transitive
@@ -82,6 +80,13 @@ packages:
       relative: false
     source: path
     version: "2.0.2"
+  blockchain_utils:
+    dependency: transitive
+    description:
+      path: "/home/rafael/Storage/Repositories/blockchain_utils"
+      relative: false
+    source: path
+    version: "1.0.3"
   boolean_selector:
     dependency: transitive
     description:
@@ -253,10 +258,9 @@ packages:
   dart_bech32:
     dependency: transitive
     description:
-      name: dart_bech32
-      sha256: "0e1dc1ff39c9669c9ffeafd5d675104918f7b50799692491badfea7e1fb40888"
-      url: "https://pub.dev"
-    source: hosted
+      path: "/home/rafael/Storage/Repositories/dart-bech32"
+      relative: false
+    source: path
     version: "2.0.0"
   dart_style:
     dependency: transitive
@@ -803,5 +807,5 @@ packages:
     source: hosted
     version: "3.1.2"
 sdks:
-  dart: ">=3.0.0 <4.0.0"
+  dart: ">=3.0.6 <4.0.0"
   flutter: ">=3.7.0"
diff --git a/lib/entities/background_tasks.dart b/lib/entities/background_tasks.dart
index ce1e2f6d8..8be01c703 100644
--- a/lib/entities/background_tasks.dart
+++ b/lib/entities/background_tasks.dart
@@ -16,6 +16,7 @@ import 'package:cake_wallet/main.dart';
 import 'package:cake_wallet/di.dart';
 
 const moneroSyncTaskKey = "com.fotolockr.cakewallet.monero_sync_task";
+const bitcoinSilentPaymentsSyncTaskKey = "com.fotolockr.cakewallet.bitcoin_sync_task";
 
 @pragma('vm:entry-point')
 void callbackDispatcher() {
@@ -68,6 +69,62 @@ void callbackDispatcher() {
             return Future.error("No Monero wallet found");
           }
 
+          for (int i = 0;; i++) {
+            await Future<void>.delayed(const Duration(seconds: 1));
+            if (wallet?.syncStatus.progress() == 1.0) {
+              break;
+            }
+            if (i > 600) {
+              return Future.error("Synchronization Timed out");
+            }
+          }
+          break;
+
+        case bitcoinSilentPaymentsSyncTaskKey:
+
+          /// The work manager runs on a separate isolate from the main flutter isolate.
+          /// thus we initialize app configs first; hive, getIt, etc...
+          await initializeAppConfigs();
+
+          final walletLoadingService = getIt.get<WalletLoadingService>();
+
+          final node = getIt.get<SettingsStore>().getCurrentNode(WalletType.bitcoin);
+
+          final typeRaw = getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType);
+
+          WalletBase? wallet;
+
+          if (inputData!['sync_all'] as bool) {
+            // /// get all Monero wallets of the user and sync them
+            // final List<WalletListItem> moneroWallets = getIt
+            //     .get<WalletListViewModel>()
+            //     .wallets
+            //     .where((element) => element.type == WalletType.monero)
+            //     .toList();
+
+            // for (int i = 0; i < moneroWallets.length; i++) {
+            //   wallet = await walletLoadingService.load(WalletType.monero, moneroWallets[i].name);
+
+            //   await wallet.connectToNode(node: node);
+            //   await wallet.startSync();
+            // }
+          } else {
+            /// if the user chose to sync only active wallet
+            /// if the current wallet is monero; sync it only
+            if (typeRaw == WalletType.bitcoin.index) {
+              final name =
+                  getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName);
+
+              wallet = await walletLoadingService.load(WalletType.bitcoin, name!);
+
+              await wallet.startSync();
+            }
+          }
+
+          if (wallet?.syncStatus.progress() == null) {
+            return Future.error("No Bitcoin wallet with silent payments enabled found");
+          }
+
           for (int i = 0;; i++) {
             await Future<void>.delayed(const Duration(seconds: 1));
             if (wallet?.syncStatus.progress() == 1.0) {
@@ -98,55 +155,108 @@ class BackgroundTasks {
           .any((element) => element.type == WalletType.monero);
 
       /// if its not android nor ios, or the user has no monero wallets; exit
-      if (!DeviceInfo.instance.isMobile || !hasMonero) {
-        return;
-      }
+      if (DeviceInfo.instance.isMobile && hasMonero) {
+        final settingsStore = getIt.get<SettingsStore>();
 
-      final settingsStore = getIt.get<SettingsStore>();
+        final SyncMode syncMode = settingsStore.currentSyncMode;
+        final bool syncAll = settingsStore.currentSyncAll;
 
-      final SyncMode syncMode = settingsStore.currentSyncMode;
-      final bool syncAll = settingsStore.currentSyncAll;
+        if (syncMode.type == SyncType.disabled) {
+          cancelSyncTask();
+          return;
+        }
 
-      if (syncMode.type == SyncType.disabled) {
-        cancelSyncTask();
-        return;
-      }
+        await Workmanager().initialize(
+          callbackDispatcher,
+          isInDebugMode: kDebugMode,
+        );
 
-      await Workmanager().initialize(
-        callbackDispatcher,
-        isInDebugMode: kDebugMode,
-      );
+        final inputData = <String, dynamic>{"sync_all": syncAll};
+        final constraints = Constraints(
+          networkType:
+              syncMode.type == SyncType.unobtrusive ? NetworkType.unmetered : NetworkType.connected,
+          requiresBatteryNotLow: syncMode.type == SyncType.unobtrusive,
+          requiresCharging: syncMode.type == SyncType.unobtrusive,
+          requiresDeviceIdle: syncMode.type == SyncType.unobtrusive,
+        );
 
-      final inputData = <String, dynamic>{"sync_all": syncAll};
-      final constraints = Constraints(
-        networkType:
-            syncMode.type == SyncType.unobtrusive ? NetworkType.unmetered : NetworkType.connected,
-        requiresBatteryNotLow: syncMode.type == SyncType.unobtrusive,
-        requiresCharging: syncMode.type == SyncType.unobtrusive,
-        requiresDeviceIdle: syncMode.type == SyncType.unobtrusive,
-      );
+        if (Platform.isIOS) {
+          await Workmanager().registerOneOffTask(
+            moneroSyncTaskKey,
+            moneroSyncTaskKey,
+            initialDelay: syncMode.frequency,
+            existingWorkPolicy: ExistingWorkPolicy.replace,
+            inputData: inputData,
+            constraints: constraints,
+          );
+          return;
+        }
 
-      if (Platform.isIOS) {
-        await Workmanager().registerOneOffTask(
+        await Workmanager().registerPeriodicTask(
           moneroSyncTaskKey,
           moneroSyncTaskKey,
           initialDelay: syncMode.frequency,
-          existingWorkPolicy: ExistingWorkPolicy.replace,
+          frequency: syncMode.frequency,
+          existingWorkPolicy: changeExisting ? ExistingWorkPolicy.replace : ExistingWorkPolicy.keep,
           inputData: inputData,
           constraints: constraints,
         );
-        return;
       }
 
-      await Workmanager().registerPeriodicTask(
-        moneroSyncTaskKey,
-        moneroSyncTaskKey,
-        initialDelay: syncMode.frequency,
-        frequency: syncMode.frequency,
-        existingWorkPolicy: changeExisting ? ExistingWorkPolicy.replace : ExistingWorkPolicy.keep,
-        inputData: inputData,
-        constraints: constraints,
-      );
+      bool hasBitcoin = getIt
+          .get<WalletListViewModel>()
+          .wallets
+          .any((element) => element.type == WalletType.bitcoin);
+
+      /// if its not android nor ios, or the user has no monero wallets; exit
+      // if (hasBitcoin) {
+      if (false) {
+        final settingsStore = getIt.get<SettingsStore>();
+
+        final SyncMode syncMode = settingsStore.currentSyncMode;
+        final bool syncAll = settingsStore.currentSyncAll;
+
+        if (syncMode.type == SyncType.disabled) {
+          cancelSyncTask();
+          return;
+        }
+
+        await Workmanager().initialize(
+          callbackDispatcher,
+          isInDebugMode: kDebugMode,
+        );
+
+        final inputData = <String, dynamic>{"sync_all": syncAll};
+        final constraints = Constraints(
+          networkType:
+              syncMode.type == SyncType.unobtrusive ? NetworkType.unmetered : NetworkType.connected,
+          requiresBatteryNotLow: syncMode.type == SyncType.unobtrusive,
+          requiresCharging: syncMode.type == SyncType.unobtrusive,
+          requiresDeviceIdle: syncMode.type == SyncType.unobtrusive,
+        );
+
+        if (Platform.isIOS) {
+          await Workmanager().registerOneOffTask(
+            bitcoinSilentPaymentsSyncTaskKey,
+            bitcoinSilentPaymentsSyncTaskKey,
+            initialDelay: syncMode.frequency,
+            existingWorkPolicy: ExistingWorkPolicy.replace,
+            inputData: inputData,
+            constraints: constraints,
+          );
+          return;
+        }
+
+        await Workmanager().registerPeriodicTask(
+          bitcoinSilentPaymentsSyncTaskKey,
+          bitcoinSilentPaymentsSyncTaskKey,
+          initialDelay: syncMode.frequency,
+          frequency: syncMode.frequency,
+          existingWorkPolicy: changeExisting ? ExistingWorkPolicy.replace : ExistingWorkPolicy.keep,
+          inputData: inputData,
+          constraints: constraints,
+        );
+      }
     } catch (error, stackTrace) {
       print(error);
       print(stackTrace);