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);