fix: scan fixes, add date, allow sending while scanning

This commit is contained in:
Rafael Saes 2024-05-09 17:06:39 -03:00
parent b7c942ac4e
commit 8e5d997562
9 changed files with 190 additions and 214 deletions

View file

@ -37,6 +37,7 @@ import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -359,7 +360,7 @@ abstract class ElectrumWalletBase
} }
syncStatus = message.syncStatus; syncStatus = message.syncStatus;
walletInfo.restoreHeight = message.height; await walletInfo.updateRestoreHeight(message.height);
} }
} }
} }
@ -381,6 +382,8 @@ abstract class ElectrumWalletBase
await updateBalance(); await updateBalance();
await updateFeeRates(); await updateFeeRates();
syncStatus = SyncedSyncStatus();
} catch (e, stacktrace) { } catch (e, stacktrace) {
print(stacktrace); print(stacktrace);
print(e.toString()); print(e.toString());
@ -1141,6 +1144,7 @@ abstract class ElectrumWalletBase
coin.isFrozen = coinInfo.isFrozen; coin.isFrozen = coinInfo.isFrozen;
coin.isSending = coinInfo.isSending; coin.isSending = coinInfo.isSending;
coin.note = coinInfo.note; coin.note = coinInfo.note;
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
coin.bitcoinAddressRecord.balance += coinInfo.value; coin.bitcoinAddressRecord.balance += coinInfo.value;
} else { } else {
_addCoinInfo(coin); _addCoinInfo(coin);
@ -1172,6 +1176,7 @@ abstract class ElectrumWalletBase
coin.isFrozen = coinInfo.isFrozen; coin.isFrozen = coinInfo.isFrozen;
coin.isSending = coinInfo.isSending; coin.isSending = coinInfo.isSending;
coin.note = coinInfo.note; coin.note = coinInfo.note;
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
coin.bitcoinAddressRecord.balance += coinInfo.value; coin.bitcoinAddressRecord.balance += coinInfo.value;
} else { } else {
_addCoinInfo(coin); _addCoinInfo(coin);
@ -1588,7 +1593,6 @@ abstract class ElectrumWalletBase
Future<void> updateTransactions() async { Future<void> updateTransactions() async {
try { try {
if (_isTransactionUpdating) { if (_isTransactionUpdating) {
_isTransactionUpdating = false;
return; return;
} }
@ -1734,7 +1738,7 @@ abstract class ElectrumWalletBase
_currentChainTip = height; _currentChainTip = height;
if (_currentChainTip != null && _currentChainTip! > 0 && walletInfo.restoreHeight == 0) { if (_currentChainTip != null && _currentChainTip! > 0 && walletInfo.restoreHeight == 0) {
walletInfo.restoreHeight = _currentChainTip!; await walletInfo.updateRestoreHeight(_currentChainTip!);
} }
}); });
} }
@ -1818,66 +1822,36 @@ class SyncResponse {
} }
Future<void> startRefresh(ScanData scanData) async { Future<void> startRefresh(ScanData scanData) async {
Future<ElectrumClient> getElectrumConnection() async { int syncHeight = scanData.height;
final electrumClient = scanData.electrumClient; int initialSyncHeight = syncHeight;
if (!electrumClient.isConnected) {
final node = scanData.node;
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
}
return electrumClient;
}
var lastKnownBlockHeight = 0;
var initialSyncHeight = 0;
var syncHeight = scanData.height;
var currentChainTip = scanData.chainTip;
if (syncHeight <= 0) {
syncHeight = currentChainTip;
}
if (initialSyncHeight <= 0) {
initialSyncHeight = syncHeight;
}
if (lastKnownBlockHeight == syncHeight) {
scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus()));
return;
}
BehaviorSubject<Object>? tweaksSubscription = null; BehaviorSubject<Object>? tweaksSubscription = null;
lastKnownBlockHeight = syncHeight; final syncingStatus = scanData.isSingleScan
? SyncingSyncStatus(1, 0)
SyncingSyncStatus syncingStatus; : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
if (scanData.isSingleScan) {
syncingStatus = SyncingSyncStatus(1, 0);
} else {
syncingStatus =
SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
}
// Initial status UI update, send how many blocks left to scan
scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) { final electrumClient = scanData.electrumClient;
scanData.sendPort.send(SyncResponse(scanData.chainTip, SyncedSyncStatus())); await electrumClient.connectToUri(scanData.node.uri, useSSL: scanData.node.useSSL);
return;
}
try {
final electrumClient = await getElectrumConnection();
if (tweaksSubscription == null) { if (tweaksSubscription == null) {
final count = scanData.isSingleScan ? 1 : TWEAKS_COUNT; final count = scanData.isSingleScan ? 1 : TWEAKS_COUNT;
final receiver = Receiver(
scanData.silentAddress.b_scan.toHex(),
scanData.silentAddress.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes,
scanData.labelIndexes.length,
);
try {
tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight, count: count); tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight, count: count);
tweaksSubscription?.listen((t) async { tweaksSubscription?.listen((t) async {
final tweaks = t as Map<String, dynamic>; final tweaks = t as Map<String, dynamic>;
if (tweaks["message"] != null && !scanData.isSingleScan) { if (tweaks["message"] != null) {
// re-subscribe to continue receiving messages // re-subscribe to continue receiving messages
electrumClient.tweaksSubscribe(height: syncHeight, count: count); electrumClient.tweaksSubscribe(height: syncHeight, count: count);
return; return;
@ -1898,15 +1872,9 @@ Future<void> startRefresh(ScanData scanData) async {
try { try {
// scanOutputs called from rust here // scanOutputs called from rust here
final addToWallet = scanOutputs( final addToWallet = scanOutputs(
outputPubkeys.values.map((o) => o[0].toString()).toList(), outputPubkeys.values.toList(),
tweak, tweak,
Receiver( receiver,
scanData.silentAddress.b_scan.toHex(),
scanData.silentAddress.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes,
scanData.labelIndexes.length,
),
); );
if (addToWallet.isEmpty) { if (addToWallet.isEmpty) {
@ -1923,7 +1891,9 @@ Future<void> startRefresh(ScanData scanData) async {
fee: 0, fee: 0,
direction: TransactionDirection.incoming, direction: TransactionDirection.incoming,
isPending: false, isPending: false,
date: DateTime.now(), date: scanData.network == BitcoinNetwork.mainnet
? getDateByBitcoinHeight(tweakHeight)
: DateTime.now(),
confirmations: scanData.chainTip - tweakHeight + 1, confirmations: scanData.chainTip - tweakHeight + 1,
unspents: [], unspents: [],
); );
@ -1936,17 +1906,6 @@ Future<void> startRefresh(ScanData scanData) async {
.toTaprootAddress(tweak: false) .toTaprootAddress(tweak: false)
.toAddress(scanData.network); .toAddress(scanData.network);
final receivedAddressRecord = BitcoinSilentPaymentAddressRecord(
receivingOutputAddress,
index: 0,
isHidden: false,
isUsed: true,
network: scanData.network,
silentPaymentTweak: t_k,
type: SegwitAddresType.p2tr,
txCount: 1,
);
int? amount; int? amount;
int? pos; int? pos;
outputPubkeys.entries.firstWhere((k) { outputPubkeys.entries.firstWhere((k) {
@ -1959,6 +1918,18 @@ Future<void> startRefresh(ScanData scanData) async {
return false; return false;
}); });
final receivedAddressRecord = BitcoinSilentPaymentAddressRecord(
receivingOutputAddress,
index: 0,
isHidden: false,
isUsed: true,
network: scanData.network,
silentPaymentTweak: t_k,
type: SegwitAddresType.p2tr,
txCount: 1,
balance: amount!,
);
final unspent = BitcoinSilentPaymentsUnspent( final unspent = BitcoinSilentPaymentsUnspent(
receivedAddressRecord, receivedAddressRecord,
txid, txid,
@ -1983,25 +1954,18 @@ Future<void> startRefresh(ScanData scanData) async {
SyncResponse( SyncResponse(
syncHeight, syncHeight,
SyncingSyncStatus.fromHeightValues( SyncingSyncStatus.fromHeightValues(
currentChainTip, scanData.chainTip,
initialSyncHeight, initialSyncHeight,
syncHeight, syncHeight,
), ),
), ),
); );
if (int.parse(blockHeight) >= scanData.chainTip || scanData.isSingleScan) { if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) {
scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus())); scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus()));
await tweaksSubscription!.close(); await tweaksSubscription!.close();
} }
}); });
} catch (e) {
if (e is RequestFailedTimeoutException) {
return scanData.sendPort.send(
SyncResponse(syncHeight, TimedOutSyncStatus()),
);
}
}
} }
if (tweaksSubscription == null) { if (tweaksSubscription == null) {
@ -2009,12 +1973,6 @@ Future<void> startRefresh(ScanData scanData) async {
SyncResponse(syncHeight, UnsupportedSyncStatus()), SyncResponse(syncHeight, UnsupportedSyncStatus()),
); );
} }
} catch (e, stacktrace) {
print(stacktrace);
print(e.toString());
scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus()));
}
} }
class EstimatedTxResult { class EstimatedTxResult {

View file

@ -794,7 +794,7 @@ packages:
description: description:
path: "." path: "."
ref: master ref: master
resolved-ref: a6b14bcc37ec16f56931e48afa8a8f8e6939431d resolved-ref: "4977a0d31fc8614d27193b07d92c5992d163131e"
url: "https://github.com/rafael-xmr/sp_scanner" url: "https://github.com/rafael-xmr/sp_scanner"
source: git source: git
version: "0.0.1" version: "0.0.1"

View file

@ -245,6 +245,7 @@ Future<int> getHavenCurrentHeight() async {
// Data taken from https://timechaincalendar.com/ // Data taken from https://timechaincalendar.com/
const bitcoinDates = { const bitcoinDates = {
"2024-05": 841590,
"2024-04": 837182, "2024-04": 837182,
"2024-03": 832623, "2024-03": 832623,
"2024-02": 828319, "2024-02": 828319,
@ -265,7 +266,9 @@ const bitcoinDates = {
int getBitcoinHeightByDate({required DateTime date}) { int getBitcoinHeightByDate({required DateTime date}) {
String dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}'; String dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}';
int startBlock = bitcoinDates[dateKey] ?? bitcoinDates.values.last; final closestKey = bitcoinDates.keys
.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => bitcoinDates.keys.last);
int startBlock = bitcoinDates[dateKey] ?? bitcoinDates[closestKey]!;
DateTime startOfMonth = DateTime(date.year, date.month); DateTime startOfMonth = DateTime(date.year, date.month);
int daysDifference = date.difference(startOfMonth).inDays; int daysDifference = date.difference(startOfMonth).inDays;
@ -275,3 +278,9 @@ int getBitcoinHeightByDate({required DateTime date}) {
return startBlock + estimatedBlocksSinceStartOfMonth; return startBlock + estimatedBlocksSinceStartOfMonth;
} }
DateTime getDateByBitcoinHeight(int height) {
final date = bitcoinDates.entries
.lastWhere((entry) => entry.value >= height, orElse: () => bitcoinDates.entries.last);
return formatMapKey(date.key);
}

View file

@ -80,7 +80,7 @@ class WalletInfo extends HiveObject {
this.showIntroCakePayCard, this.showIntroCakePayCard,
this.derivationInfo, this.derivationInfo,
this.hardwareWalletType, this.hardwareWalletType,
): _yatLastUsedAddressController = StreamController<String>.broadcast(); ) : _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external({ factory WalletInfo.external({
required String id, required String id,
@ -207,4 +207,9 @@ class WalletInfo extends HiveObject {
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream; Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
StreamController<String> _yatLastUsedAddressController; StreamController<String> _yatLastUsedAddressController;
Future<void> updateRestoreHeight(int height) async {
restoreHeight = height;
await save();
}
} }

View file

@ -36,7 +36,6 @@ const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultNodeUri = 'rpc.nano.to';
const nanoDefaultPowNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to';
const solanaDefaultNodeUri = 'rpc.ankr.com'; const solanaDefaultNodeUri = 'rpc.ankr.com';
const tronDefaultNodeUri = 'api.trongrid.io';
const tronDefaultNodeUri = 'tron-rpc.publicnode.com:443'; const tronDefaultNodeUri = 'tron-rpc.publicnode.com:443';
const newCakeWalletBitcoinUri = '198.58.111.154:50001'; const newCakeWalletBitcoinUri = '198.58.111.154:50001';

View file

@ -12,12 +12,11 @@ import 'package:wakelock_plus/wakelock_plus.dart';
ReactionDisposer? _onWalletSyncStatusChangeReaction; ReactionDisposer? _onWalletSyncStatusChangeReaction;
void startWalletSyncStatusChangeReaction( void startWalletSyncStatusChangeReaction(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
TransactionInfo> wallet,
FiatConversionStore fiatConversionStore) { FiatConversionStore fiatConversionStore) {
_onWalletSyncStatusChangeReaction?.reaction.dispose(); _onWalletSyncStatusChangeReaction?.reaction.dispose();
_onWalletSyncStatusChangeReaction = _onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
reaction((_) => wallet.syncStatus, (SyncStatus status) async { if (!(status is SyncingSyncStatus) || wallet.type != WalletType.bitcoin)
try { try {
if (status is ConnectedSyncStatus) { if (status is ConnectedSyncStatus) {
await wallet.startSync(); await wallet.startSync();
@ -32,7 +31,7 @@ void startWalletSyncStatusChangeReaction(
if (status is SyncedSyncStatus || status is FailedSyncStatus) { if (status is SyncedSyncStatus || status is FailedSyncStatus) {
await WakelockPlus.disable(); await WakelockPlus.disable();
} }
} catch(e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
}); });

View file

@ -252,6 +252,7 @@ class CryptoBalanceWidget extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget( child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).silent_payments, title: S.of(context).silent_payments,
subTitle: S.of(context).enable_silent_payments_scanning, subTitle: S.of(context).enable_silent_payments_scanning,
hint: Column( hint: Column(

View file

@ -13,6 +13,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
this.svgPicture, this.svgPicture,
this.icon, this.icon,
this.onClose, this.onClose,
this.customBorder,
}); });
final VoidCallback onTap; final VoidCallback onTap;
@ -22,6 +23,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
final Widget? hint; final Widget? hint;
final SvgPicture? svgPicture; final SvgPicture? svgPicture;
final Icon? icon; final Icon? icon;
final double? customBorder;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -37,7 +39,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(customBorder ?? 20),
border: Border.all( border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor, color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
), ),

View file

@ -218,7 +218,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title; isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title;
@computed @computed
bool get isReadyForSend => wallet.syncStatus is SyncedSyncStatus; bool get isReadyForSend =>
wallet.syncStatus is SyncedSyncStatus ||
// If silent payments scanning, can still send payments
(wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus);
@computed @computed
List<Template> get templates => sendTemplateViewModel.templates List<Template> get templates => sendTemplateViewModel.templates