From d8215fcad9eedaa5303773e1db5961222b5e187a Mon Sep 17 00:00:00 2001 From: DanGould Date: Thu, 19 Dec 2024 23:53:09 -0500 Subject: [PATCH] Spawn runSender logic in an Isolate This still ignores UI updates from the spawned isolate and doesn't even have a proper splash screen for a confirmed send. It has also yet to be smoke tested --- lib/_pkg/payjoin/manager.dart | 108 +++++++++++++++++++++++++++++++++- lib/locator.dart | 2 +- lib/send/bloc/send_cubit.dart | 32 ++-------- 3 files changed, 112 insertions(+), 30 deletions(-) diff --git a/lib/_pkg/payjoin/manager.dart b/lib/_pkg/payjoin/manager.dart index abdc56a4..65b1363f 100644 --- a/lib/_pkg/payjoin/manager.dart +++ b/lib/_pkg/payjoin/manager.dart @@ -1,9 +1,15 @@ import 'dart:async'; +import 'dart:isolate'; import 'dart:math'; +import 'package:bb_mobile/_model/transaction.dart'; +import 'package:bb_mobile/_model/wallet.dart'; +import 'package:bb_mobile/_pkg/error.dart'; +import 'package:bb_mobile/_pkg/wallet/transaction.dart'; import 'package:dio/dio.dart'; import 'package:payjoin_flutter/common.dart'; import 'package:payjoin_flutter/send.dart'; +import 'package:payjoin_flutter/src/generated/frb_generated.dart'; import 'package:payjoin_flutter/uri.dart' as pj_uri; const List _ohttpRelayUrls = [ @@ -12,6 +18,11 @@ const List _ohttpRelayUrls = [ ]; class PayjoinManager { + PayjoinManager(this._walletTx); + final WalletTx _walletTx; + final Map _activePollers = {}; + final Map _activePorts = {}; + Future initSender( String pjUriString, int networkFeesSatPerVb, @@ -42,10 +53,98 @@ class PayjoinManager { } } + Future spawnSender({ + required bool isTestnet, + required Sender sender, + required Wallet wallet, + required Transaction transaction, + required String address, + }) async { + try { + final completer = Completer(); + final receivePort = ReceivePort(); + + // TODO Create unique ID for this payjoin session + const sessionId = 'TODO_SENDER_ENDPOINT'; + + receivePort.listen((message) async { + if (message is Map) { + if (message['type'] == 'psbt_to_sign') { + final proposalPsbt = message['psbt'] as String; + final (wtxid, err) = await _walletTx.signAndBroadcastPsbt( + psbt: proposalPsbt, + wallet: wallet, + transaction: transaction, + address: address, + ); + // TODO propagate success or failure to the UI including this logic + // from when the signing and broadcasting was done in the send cubit + + // final txWithId = state.tx?.copyWith(txid: wtxid?.$2 ?? ''); + // emit(state.copyWith(tx: txWithId)); + + // final (updatedWallet, _) = wtxid!; + // state.selectedWalletBloc!.add( + // UpdateWallet( + // updatedWallet, + // updateTypes: [ + // UpdateWalletTypes.addresses, + // UpdateWalletTypes.transactions, + // UpdateWalletTypes.swaps, + // ], + // ), + // ); + if (err != null) { + completer.complete(err); + return; + } + await _cleanupSession(sessionId); + } else if (message is Err) { + // TODO propagate this error to the UI + await _cleanupSession(sessionId); + } + } + }); + + final args = [ + receivePort.sendPort, + sender.toJson(), + ]; + + await Isolate.spawn( + _isolateSender, + args, + ); + + return completer.future; + } catch (e) { + return Err(e.toString()); + } + } + + Future _isolateSender(List args) async { + await core.init(); + print('_isolateSender'); + final sendPort = args[0] as SendPort; + final senderJson = args[1] as String; + + final sender = Sender.fromJson(senderJson); + try { + final proposalPsbt = await _runPayjoinSender(sender); + print('proposalPsbt: $proposalPsbt'); + sendPort.send({ + 'type': 'psbt_to_sign', + 'psbt': proposalPsbt, + }); + } catch (e) { + sendPort.send(Err(e.toString())); + } + } + /// Sends a payjoin using the v2 protocol given an initialized Sender. /// V2 protocol first attempts a v2 request, but if one cannot be extracted /// from the given bitcoin URI, it will attempt to send a v1 request. - Future runPayjoinSender(Sender sender) async { + Future _runPayjoinSender(Sender sender) async { Request postReq; V2PostContext postReqCtx; final dio = Dio(); @@ -119,4 +218,11 @@ class PayjoinManager { data: req.body, ); } + + Future _cleanupSession(String sessionId) async { + _activePollers[sessionId]?.kill(); + _activePollers.remove(sessionId); + _activePorts[sessionId]?.close(); + _activePorts.remove(sessionId); + } } diff --git a/lib/locator.dart b/lib/locator.dart index 81b3fa92..31ec3b97 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -261,7 +261,7 @@ Future _setupBlocs() async { ); locator.registerSingleton( - PayjoinManager(), + PayjoinManager(locator()), ); locator.registerSingleton( diff --git a/lib/send/bloc/send_cubit.dart b/lib/send/bloc/send_cubit.dart index 7dec9ffd..1061ab4e 100644 --- a/lib/send/bloc/send_cubit.dart +++ b/lib/send/bloc/send_cubit.dart @@ -956,37 +956,13 @@ class SendCubit extends Cubit { // TODO copy originalPsbt.extractTx() to state.tx // emit(state.copyWith(tx: originalPsbtTxWithId)); emit(state.copyWith(sending: true, sent: false)); - final proposalPsbt = await _payjoinManager.runPayjoinSender( - state.payjoinSender!, - ); - final (wtxid, errSignBroadcast) = await _walletTx.signAndBroadcastPsbt( + await _payjoinManager.spawnSender( + isTestnet: _networkCubit.state.testnet, + sender: state.payjoinSender!, wallet: wallet, - psbt: proposalPsbt!, - address: address, - note: state.note, transaction: state.tx!, + address: address, ); - if (errSignBroadcast != null) { - emit(state.copyWith( - errSending: errSignBroadcast.toString(), sending: false)); - return; - } - - final txWithId = state.tx?.copyWith(txid: wtxid?.$2 ?? ''); - emit(state.copyWith(tx: txWithId)); - - final (updatedWallet, _) = wtxid!; - state.selectedWalletBloc!.add( - UpdateWallet( - updatedWallet, - updateTypes: [ - UpdateWalletTypes.addresses, - UpdateWalletTypes.transactions, - UpdateWalletTypes.swaps, - ], - ), - ); - Future.delayed(150.ms); state.selectedWalletBloc!.add(SyncWallet());