Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable autoreceive feature #47

Merged
merged 9 commits into from
Feb 26, 2024
74 changes: 57 additions & 17 deletions lib/blocs/auto_receive_tx_worker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:zenon_syrius_wallet_flutter/main.dart';
import 'package:zenon_syrius_wallet_flutter/model/model.dart';
import 'package:zenon_syrius_wallet_flutter/utils/account_block_utils.dart';
import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart';
import 'package:zenon_syrius_wallet_flutter/utils/constants.dart';
import 'package:zenon_syrius_wallet_flutter/utils/global.dart';
import 'package:znn_sdk_dart/znn_sdk_dart.dart';

Expand All @@ -22,7 +23,45 @@ class AutoReceiveTxWorker extends BaseBloc<WalletNotification> {
return _instance!;
}

Future<void> autoReceiveTransactionHash(Hash currentHash) async {
if (!running) {
running = true;
try {
String toAddress =
(await zenon!.ledger.getAccountBlockByHash(currentHash))!
.toAddress
.toString();
KeyPair keyPair = kKeyStore!.getKeyPair(
kDefaultAddressList.indexOf(toAddress),
);
AccountBlockTemplate transactionParams = AccountBlockTemplate.receive(
currentHash,
);
AccountBlockTemplate response =
await AccountBlockUtils.createAccountBlock(
transactionParams,
'receive transaction',
blockSigningKey: keyPair,
waitForRequiredPlasma: true,
);
_sendSuccessNotification(response, toAddress);
} on RpcException catch (e, stackTrace) {
_sendErrorNotification(e.toString());
Logger('AutoReceiveTxWorker')
.log(Level.WARNING, 'autoReceive', e, stackTrace);
}
running = false;
}
}

Future<void> autoReceive() async {
if (!sharedPrefsService!.get(
kAutoReceiveKey,
defaultValue: kAutoReceiveDefaultValue,
)) {
pool.clear();
return;
}
// Make sure that AutoUnlockHtlcWorker is not running since it should be
// given priority to send transactions.
if (pool.isNotEmpty && !running && !sl<AutoUnlockHtlcWorker>().running) {
Expand All @@ -46,8 +85,12 @@ class AutoReceiveTxWorker extends BaseBloc<WalletNotification> {
blockSigningKey: keyPair,
waitForRequiredPlasma: true,
);
pool.removeFirst();
_sendSuccessNotification(response, toAddress);
if (pool.isNotEmpty) {
KingGorrin marked this conversation as resolved.
Show resolved Hide resolved
pool.removeFirst();
}
running = false;
autoReceive();
} on RpcException catch (e, stackTrace) {
_sendErrorNotification(e.toString());
Logger('AutoReceiveTxWorker')
Expand All @@ -65,6 +108,19 @@ class AutoReceiveTxWorker extends BaseBloc<WalletNotification> {
}
running = false;
}
return;
}

Future<void> addHash(Hash hash) async {
zenon!.stats.syncInfo().then((syncInfo) {
if (!pool.contains(hash) &&
(syncInfo.state == SyncState.syncDone ||
(syncInfo.targetHeight > 0 &&
syncInfo.currentHeight > 0 &&
(syncInfo.targetHeight - syncInfo.currentHeight) < 3))) {
pool.add(hash);
}
});
}

void _sendErrorNotification(String errorText) {
Expand All @@ -89,20 +145,4 @@ class AutoReceiveTxWorker extends BaseBloc<WalletNotification> {
),
);
}

Future<void> addHash(Hash hash) async {
if (!pool.contains(hash)) {
zenon!.stats.syncInfo().then((syncInfo) {
// Verify that the pool does not already contain the hash after the
// asynchronous request has completed and that the node is in sync.
if (!pool.contains(hash) &&
(syncInfo.state == SyncState.syncDone ||
(syncInfo.targetHeight > 0 &&
syncInfo.currentHeight > 0 &&
(syncInfo.targetHeight - syncInfo.currentHeight) < 3))) {
pool.add(hash);
}
});
}
}
}
3 changes: 0 additions & 3 deletions lib/blocs/node_sync_status_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ class NodeSyncStatusBloc extends DashboardBaseBloc<SyncInfo> {
(syncInfo.targetHeight - syncInfo.currentHeight) > 3))) {
lastSyncState = syncInfo.state;
if (syncInfo.state == SyncState.syncDone) {
NodeUtils.getUnreceivedTransactions().then((value) {
sl<AutoReceiveTxWorker>().autoReceive();
});
Future.delayed(const Duration(seconds: 5)).then((value) {
NodeUtils.getUnreceivedTransactions().then((value) {
sl<AutoReceiveTxWorker>().autoReceive();
Expand Down
17 changes: 17 additions & 0 deletions lib/blocs/transfer/pending_transactions_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'dart:async';

import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart';
import 'package:zenon_syrius_wallet_flutter/main.dart';
import 'package:zenon_syrius_wallet_flutter/utils/global.dart';
import 'package:znn_sdk_dart/znn_sdk_dart.dart';

class PendingTransactionsBloc extends InfiniteScrollBloc<AccountBlock> {
@override
Future<List<AccountBlock>> getData(int pageKey, int pageSize) async =>
(await zenon!.ledger.getUnreceivedBlocksByAddress(
Address.parse(kSelectedAddress!),
pageIndex: pageKey,
pageSize: pageSize,
))
.list!;
}
20 changes: 20 additions & 0 deletions lib/blocs/transfer/receive_transaction_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart';
import 'package:zenon_syrius_wallet_flutter/main.dart';
import 'package:znn_sdk_dart/znn_sdk_dart.dart';

class ReceiveTransactionBloc extends BaseBloc<AccountBlockTemplate?> {
void receiveTransaction(String id, BuildContext context) {
try {
addEvent(null);
sl<AutoReceiveTxWorker>().autoReceiveTransactionHash(Hash.parse(id))
.onError(
(error, stackTrace) {
addError(error, stackTrace);
},
);
} catch (e, stackTrace) {
addError(e, stackTrace);
}
}
}
3 changes: 3 additions & 0 deletions lib/utils/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,12 @@ const String kAutoEraseNumAttemptsKey = 'auto_erase_num_attempts';
const String kLaunchAtStartupKey = 'launch_at_startup';
const String kEnableDesktopNotificationsKey = 'enable_desktop_notifications';
const String kEnableClipboardWatcherKey = 'enable_clipboard_watcher';
const String kAutoReceiveKey = 'enable_auto_receive';

const bool kLaunchAtStartupDefaultValue = false;
const bool kEnableDesktopNotificationsDefaultValue = false;
const bool kEnableClipboardWatcherDefaultValue = false;
const bool kAutoReceiveDefaultValue = true;

/// Node management
const String kChainIdKey = 'chain_id';
Expand Down
20 changes: 20 additions & 0 deletions lib/utils/format_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,24 @@ class FormatUtils {
)
.millisecondsSinceEpoch;
}

static String formatData(int transactionMillis) {
int currentMillis = DateTime.now().millisecondsSinceEpoch;
if (currentMillis - transactionMillis <=
const Duration(days: 1).inMilliseconds) {
return formatDataShort(currentMillis - transactionMillis);
}
return FormatUtils.formatDate(transactionMillis, dateFormat: 'MM/dd/yyyy');
}

static String formatDataShort(int i) {
Duration duration = Duration(milliseconds: i);
if (duration.inHours > 0) {
return '${duration.inHours} h ago';
}
if (duration.inMinutes > 0) {
return '${duration.inMinutes} min ago';
}
return '${duration.inSeconds} s ago';
}
}
8 changes: 7 additions & 1 deletion lib/utils/node_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class NodeUtils {
await _getSubscriptionForMomentums();
await _getSubscriptionForAllAccountEvents();
await getUnreceivedTransactions();

sl<AutoReceiveTxWorker>().autoReceive();
Future.delayed(const Duration(seconds: 30))
.then((value) => NotificationUtils.sendNodeSyncingNotification());
Expand Down Expand Up @@ -127,7 +128,12 @@ class NodeUtils {

if (unreceivedBlocks.isNotEmpty) {
for (AccountBlock unreceivedBlock in unreceivedBlocks) {
sl<AutoReceiveTxWorker>().addHash(unreceivedBlock.hash);
if (sharedPrefsService!.get(
KingGorrin marked this conversation as resolved.
Show resolved Hide resolved
kAutoReceiveKey,
defaultValue: kAutoReceiveDefaultValue,
)) {
sl<AutoReceiveTxWorker>().addHash(unreceivedBlock.hash);
}
}
}
}
Expand Down
83 changes: 76 additions & 7 deletions lib/widgets/modular_widgets/settings_widgets/wallet_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ import 'dart:io';

import 'package:flutter/material.dart';
import 'package:launch_at_startup/launch_at_startup.dart';
import 'package:logging/logging.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart';
import 'package:zenon_syrius_wallet_flutter/main.dart';
import 'package:zenon_syrius_wallet_flutter/model/model.dart';
import 'package:zenon_syrius_wallet_flutter/screens/screens.dart';
import 'package:zenon_syrius_wallet_flutter/utils/clipboard_utils.dart';
import 'package:zenon_syrius_wallet_flutter/utils/constants.dart';
import 'package:zenon_syrius_wallet_flutter/utils/navigation_utils.dart';
import 'package:zenon_syrius_wallet_flutter/utils/notification_utils.dart';
import 'package:zenon_syrius_wallet_flutter/utils/utils.dart';
import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart';

class WalletOptions extends StatefulWidget {
final VoidCallback onResyncWalletPressed;

const WalletOptions(this.onResyncWalletPressed, {Key? key}) : super(key: key);
const WalletOptions({Key? key}) : super(key: key);

@override
State<WalletOptions> createState() => _WalletOptionsState();
Expand All @@ -26,6 +22,7 @@ class _WalletOptionsState extends State<WalletOptions> {
bool? _launchAtStartup;
bool? _enableDesktopNotifications;
bool? _enabledClipboardWatcher;
bool? _autoReceive;

@override
void initState() {
Expand All @@ -42,6 +39,10 @@ class _WalletOptionsState extends State<WalletOptions> {
kEnableClipboardWatcherKey,
defaultValue: kEnableClipboardWatcherDefaultValue,
);
_autoReceive = sharedPrefsService!.get(
kAutoReceiveKey,
defaultValue: kAutoReceiveDefaultValue,
);
}

@override
Expand Down Expand Up @@ -119,6 +120,7 @@ class _WalletOptionsState extends State<WalletOptions> {
_getLaunchAtStartupWidget(),
_getEnableDesktopNotifications(),
_buildEnableClipboardWatcher(),
_getAutoReceiveWidget()
],
);
}
Expand All @@ -144,6 +146,42 @@ class _WalletOptionsState extends State<WalletOptions> {
);
}

Widget _getAutoReceiveWidget() {
return Row(
children: [
Text(
'Auto-receiver',
style: Theme.of(context).textTheme.bodyMedium,
),
SyriusCheckbox(
onChanged: (value) async {
if (value == true) {
NodeUtils.getUnreceivedTransactions().then((value) {
sl<AutoReceiveTxWorker>().autoReceive();
}).onError((error, stackTrace) {
Logger('MainAppContainer').log(
Level.WARNING, '_getAutoReceiveWidget', error, stackTrace);
});
} else if (value == false &&
sl<AutoReceiveTxWorker>().pool.isNotEmpty) {
sl<AutoReceiveTxWorker>().pool.clear();
}
setState(() {
_autoReceive = value;
_changeAutoReceiveStatus(value ?? false);
});
},
value: _autoReceive,
context: context,
),
const StandardTooltipIcon(
'Disable the auto-receiver to protect against dusting attacks',
Icons.help,
),
],
);
}

Future<void> _setupLaunchAtStartup() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
launchAtStartup.setup(
Expand All @@ -152,6 +190,37 @@ class _WalletOptionsState extends State<WalletOptions> {
);
}

Future<void> _changeAutoReceiveStatus(bool enabled) async {
try {
await _saveAutoReceiveValueToCache(enabled);
_sendAutoReceiveNotification(enabled);
} on Exception catch (e) {
NotificationUtils.sendNotificationError(
e,
'Something went wrong while setting automatic receive preference',
);
}
}

Future<void> _saveAutoReceiveValueToCache(bool enabled) async {
await sharedPrefsService!.put(
kAutoReceiveKey,
enabled,
);
}

void _sendAutoReceiveNotification(bool enabled) {
sl.get<NotificationsBloc>().addNotification(
WalletNotification(
title: 'Auto-receiver ${enabled ? 'enabled' : 'disabled'}',
details:
'Auto-receiver preference was ${enabled ? 'enabled' : 'disabled'}',
timestamp: DateTime.now().millisecondsSinceEpoch,
type: NotificationType.paymentSent,
),
);
}

Future<void> _changeLaunchAtStartupStatus(bool enabled) async {
try {
await _setupLaunchAtStartup();
Expand Down