Skip to content

Commit

Permalink
Spawn runSender logic in an Isolate
Browse files Browse the repository at this point in the history
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
  • Loading branch information
DanGould committed Dec 20, 2024
1 parent 18d6dd5 commit d8215fc
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 30 deletions.
108 changes: 107 additions & 1 deletion lib/_pkg/payjoin/manager.dart
Original file line number Diff line number Diff line change
@@ -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<String> _ohttpRelayUrls = [
Expand All @@ -12,6 +18,11 @@ const List<String> _ohttpRelayUrls = [
];

class PayjoinManager {
PayjoinManager(this._walletTx);
final WalletTx _walletTx;
final Map<String, Isolate> _activePollers = {};
final Map<String, ReceivePort> _activePorts = {};

Future<Sender> initSender(
String pjUriString,
int networkFeesSatPerVb,
Expand Down Expand Up @@ -42,10 +53,98 @@ class PayjoinManager {
}
}

Future<Err?> spawnSender({
required bool isTestnet,
required Sender sender,
required Wallet wallet,
required Transaction transaction,
required String address,
}) async {
try {
final completer = Completer<Err?>();
final receivePort = ReceivePort();

// TODO Create unique ID for this payjoin session
const sessionId = 'TODO_SENDER_ENDPOINT';

receivePort.listen((message) async {
if (message is Map<String, dynamic>) {
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<void> _isolateSender(List<dynamic> 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<String?> runPayjoinSender(Sender sender) async {
Future<String?> _runPayjoinSender(Sender sender) async {
Request postReq;
V2PostContext postReqCtx;
final dio = Dio();
Expand Down Expand Up @@ -119,4 +218,11 @@ class PayjoinManager {
data: req.body,
);
}

Future<void> _cleanupSession(String sessionId) async {
_activePollers[sessionId]?.kill();
_activePollers.remove(sessionId);
_activePorts[sessionId]?.close();
_activePorts.remove(sessionId);
}
}
2 changes: 1 addition & 1 deletion lib/locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ Future _setupBlocs() async {
);

locator.registerSingleton<PayjoinManager>(
PayjoinManager(),
PayjoinManager(locator<WalletTx>()),
);

locator.registerSingleton<NetworkFeesCubit>(
Expand Down
32 changes: 4 additions & 28 deletions lib/send/bloc/send_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -956,37 +956,13 @@ class SendCubit extends Cubit<SendState> {
// 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());

Expand Down

0 comments on commit d8215fc

Please sign in to comment.