mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Cw 1127 sp utxos not updated saved correctly (#2472)
* fix: ETA for sp scan * fix: missing SP unspent when re-open * fix: address comments * feat: check sp unspents for spendingTx * feat: resolve comments * fix: always starts scanning * minor [skip ci] * fix stuck at 1 block remaining --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
+306
-245
@@ -340,19 +340,21 @@ abstract class ElectrumWalletBase
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
|
||||
Future<void> _setListeners(int height,
|
||||
{int? chainTipParam, bool? doSingleScan, List<int>? rescanHeights}) async {
|
||||
if (this is! BitcoinWallet) return;
|
||||
if (isHardwareWallet) return;
|
||||
if (seed?.isEmpty ?? true) return;
|
||||
|
||||
final chainTip = chainTipParam ?? await getUpdatedChainTip();
|
||||
final shouldUpdateSyncStatus = rescanHeights == null || rescanHeights.isEmpty;
|
||||
|
||||
if (chainTip == height) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
syncStatus = AttemptingScanSyncStatus();
|
||||
if (shouldUpdateSyncStatus) syncStatus = AttemptingScanSyncStatus();
|
||||
|
||||
if (_isolate != null) {
|
||||
final runningIsolate = await _isolate!;
|
||||
@@ -383,6 +385,7 @@ abstract class ElectrumWalletBase
|
||||
.toList(),
|
||||
isSingleScan: doSingleScan ?? false,
|
||||
debugLogPath: debugLogPath,
|
||||
rescanHeights: rescanHeights,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -406,6 +409,7 @@ abstract class ElectrumWalletBase
|
||||
existingTxInfo.isReceivedSilentPayment = tx.isReceivedSilentPayment;
|
||||
existingTxInfo.direction = tx.direction;
|
||||
existingTxInfo.isPending = tx.isPending;
|
||||
existingTxInfo.unspents = tx.unspents;
|
||||
|
||||
final newUnspents = tx.unspents!
|
||||
.where((unspent) => !(existingTxInfo.unspents?.any((element) =>
|
||||
@@ -459,9 +463,9 @@ abstract class ElectrumWalletBase
|
||||
|
||||
if (message.syncStatus is SyncingSyncStatus) {
|
||||
var status = message.syncStatus as SyncingSyncStatus;
|
||||
syncStatus = SyncingSyncStatus(status.blocksLeft, status.ptc);
|
||||
if (shouldUpdateSyncStatus) syncStatus = SyncingSyncStatus(status.blocksLeft, status.ptc);
|
||||
} else {
|
||||
syncStatus = message.syncStatus;
|
||||
if (shouldUpdateSyncStatus) syncStatus = message.syncStatus;
|
||||
}
|
||||
|
||||
await walletInfo.updateRestoreHeight(message.height);
|
||||
@@ -491,6 +495,9 @@ abstract class ElectrumWalletBase
|
||||
);
|
||||
}
|
||||
|
||||
DateTime? _lastSilentPaymentsScan;
|
||||
static const Duration _silentPaymentsScanDelay = Duration(minutes: 1);
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
@@ -504,6 +511,30 @@ abstract class ElectrumWalletBase
|
||||
if (hasSilentPaymentsScanning) {
|
||||
silentPaymentsScanningActive = alwaysScan ?? false;
|
||||
await _setInitialHeight();
|
||||
|
||||
final now = DateTime.now();
|
||||
final shouldForceRescan = _lastSilentPaymentsScan == null ||
|
||||
now.difference(_lastSilentPaymentsScan!) >= _silentPaymentsScanDelay;
|
||||
|
||||
// Timer prevents server failure and this infinite looping and requesting
|
||||
if (shouldForceRescan) {
|
||||
_lastSilentPaymentsScan = now;
|
||||
|
||||
final rescanHeights = <int>[];
|
||||
|
||||
transactionHistory.transactions.values.forEach((tx) {
|
||||
if (tx.unspents != null && tx.unspents!.isNotEmpty)
|
||||
for (final unspent in tx.unspents!) {
|
||||
if (unspent.silentPaymentTweak != null && tx.height != null && tx.height! > 0) {
|
||||
rescanHeights.add(tx.height!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (rescanHeights.isNotEmpty)
|
||||
_setListeners(walletInfo.restoreHeight, rescanHeights: rescanHeights);
|
||||
}
|
||||
}
|
||||
|
||||
await subscribeForUpdates();
|
||||
@@ -519,7 +550,9 @@ abstract class ElectrumWalletBase
|
||||
if (alwaysScan == true) {
|
||||
setSilentPaymentsScanning(true);
|
||||
} else {
|
||||
if (syncStatus is LostConnectionSyncStatus) return;
|
||||
if (syncStatus is LostConnectionSyncStatus) {
|
||||
return;
|
||||
}
|
||||
syncStatus = SyncedSyncStatus();
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
@@ -1574,12 +1607,16 @@ abstract class ElectrumWalletBase
|
||||
}
|
||||
unspentCoins = updatedUnspentCoins;
|
||||
} else {
|
||||
unspentCoins = handleFailedUtxoFetch(
|
||||
failedCount: failedCount,
|
||||
previousUnspentCoins: previousUnspentCoins,
|
||||
updatedUnspentCoins: updatedUnspentCoins,
|
||||
results: results,
|
||||
);
|
||||
if (updatedUnspentCoins.isEmpty) {
|
||||
unspentCoins = handleFailedUtxoFetch(
|
||||
failedCount: failedCount,
|
||||
previousUnspentCoins: previousUnspentCoins,
|
||||
updatedUnspentCoins: updatedUnspentCoins,
|
||||
results: results,
|
||||
);
|
||||
} else {
|
||||
unspentCoins = updatedUnspentCoins;
|
||||
}
|
||||
}
|
||||
|
||||
final currentWalletUnspentCoins =
|
||||
@@ -2882,6 +2919,7 @@ class ScanData {
|
||||
final List<int> labelIndexes;
|
||||
final bool isSingleScan;
|
||||
final String debugLogPath;
|
||||
final List<int>? rescanHeights;
|
||||
|
||||
ScanData({
|
||||
required this.sendPort,
|
||||
@@ -2896,6 +2934,7 @@ class ScanData {
|
||||
required this.labelIndexes,
|
||||
required this.isSingleScan,
|
||||
required this.debugLogPath,
|
||||
required this.rescanHeights,
|
||||
});
|
||||
|
||||
factory ScanData.fromHeight(ScanData scanData, int newHeight) {
|
||||
@@ -2912,6 +2951,7 @@ class ScanData {
|
||||
labelIndexes: scanData.labelIndexes,
|
||||
isSingleScan: scanData.isSingleScan,
|
||||
debugLogPath: scanData.debugLogPath,
|
||||
rescanHeights: scanData.rescanHeights,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2924,6 +2964,9 @@ class SyncResponse {
|
||||
}
|
||||
|
||||
Future<void> _handleScanSilentPayments(ScanData scanData) async {
|
||||
final shouldUpdateSyncStatus = scanData.rescanHeights == null || scanData.rescanHeights!.isEmpty;
|
||||
final hasForcedRescanHeights = !shouldUpdateSyncStatus;
|
||||
|
||||
var node = Uri.parse("tcp://electrs.cakewallet.com:50001");
|
||||
|
||||
void log(String message, LogLevel level) {
|
||||
@@ -2939,9 +2982,6 @@ Future<void> _handleScanSilentPayments(ScanData scanData) async {
|
||||
|
||||
log("connected to ${node.toString()}", LogLevel.info);
|
||||
|
||||
int syncHeight = scanData.height;
|
||||
int initialSyncHeight = syncHeight;
|
||||
|
||||
final receiver = Receiver(
|
||||
scanData.silentAddress.b_scan.toHex(),
|
||||
scanData.silentAddress.B_spend.toHex(),
|
||||
@@ -2955,290 +2995,311 @@ Future<void> _handleScanSilentPayments(ScanData scanData) async {
|
||||
LogLevel.info,
|
||||
);
|
||||
|
||||
int getCountToScanPerRequest(int syncHeight) {
|
||||
if (scanData.isSingleScan) {
|
||||
return 1;
|
||||
void scan(int syncHeight, bool isSingleScan) async {
|
||||
int initialSyncHeight = syncHeight;
|
||||
|
||||
int getCountToScanPerRequest(int syncHeight) {
|
||||
if (isSingleScan) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
final amountLeft = scanData.chainTip - syncHeight + 1;
|
||||
return amountLeft;
|
||||
}
|
||||
|
||||
final amountLeft = scanData.chainTip - syncHeight + 1;
|
||||
return amountLeft;
|
||||
}
|
||||
// Initial status UI update, send how many blocks in total to scan
|
||||
if (shouldUpdateSyncStatus)
|
||||
scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight)));
|
||||
|
||||
// Initial status UI update, send how many blocks in total to scan
|
||||
scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight)));
|
||||
final req = ElectrumTweaksSubscribe(
|
||||
height: syncHeight,
|
||||
count: getCountToScanPerRequest(syncHeight),
|
||||
historicalMode: hasForcedRescanHeights,
|
||||
);
|
||||
|
||||
final req = ElectrumTweaksSubscribe(
|
||||
height: syncHeight,
|
||||
count: getCountToScanPerRequest(syncHeight),
|
||||
historicalMode: false,
|
||||
);
|
||||
|
||||
var _scanningStream = await scanningClient.subscribe(req);
|
||||
|
||||
log(
|
||||
"initial request: height: $syncHeight, count: ${getCountToScanPerRequest(syncHeight)}",
|
||||
LogLevel.info,
|
||||
);
|
||||
|
||||
void endScanningSuccesfully() {
|
||||
if (scanData.isSingleScan)
|
||||
scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus()));
|
||||
else
|
||||
scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, SyncedTipSyncStatus(scanData.chainTip)),
|
||||
);
|
||||
|
||||
_scanningStream?.close();
|
||||
_scanningStream = null;
|
||||
var _scanningStream = await scanningClient.subscribe(req);
|
||||
|
||||
log(
|
||||
"ended: syncHeight: $syncHeight, chainTip: ${scanData.chainTip}, isSingleScan: ${scanData.isSingleScan}",
|
||||
"initial request: height: $syncHeight, count: ${getCountToScanPerRequest(syncHeight)}",
|
||||
LogLevel.info,
|
||||
);
|
||||
}
|
||||
|
||||
void listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) async {
|
||||
final response = req.onResponse(event);
|
||||
void endScanningSuccesfully() {
|
||||
if (isSingleScan) {
|
||||
scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus()));
|
||||
} else {
|
||||
scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, SyncedTipSyncStatus(scanData.chainTip)),
|
||||
);
|
||||
}
|
||||
|
||||
_scanningStream?.close();
|
||||
_scanningStream = null;
|
||||
|
||||
if (response == null || _scanningStream == null) {
|
||||
log(
|
||||
"ending: response = $response, stream = $_scanningStream",
|
||||
LogLevel.error,
|
||||
"ended: syncHeight: $syncHeight, chainTip: ${scanData.chainTip}, isSingleScan: ${isSingleScan}",
|
||||
LogLevel.info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// is success or error msg
|
||||
final noData = response.message != null;
|
||||
void listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) async {
|
||||
final response = req.onResponse(event);
|
||||
|
||||
if (noData) {
|
||||
if (scanData.isSingleScan) {
|
||||
log("ending: noData and isSingleScan", LogLevel.info);
|
||||
|
||||
endScanningSuccesfully();
|
||||
if (response == null || _scanningStream == null) {
|
||||
log(
|
||||
"ending: response = $response, stream = $_scanningStream",
|
||||
LogLevel.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// re-subscribe to continue receiving messages, starting from the next unscanned height
|
||||
final nextHeight = syncHeight + 1;
|
||||
// is success or error msg
|
||||
final noData = response.message != null;
|
||||
|
||||
if (nextHeight <= scanData.chainTip) {
|
||||
log(
|
||||
"resubscribing: nextHeight: $nextHeight, count: ${getCountToScanPerRequest(nextHeight)}",
|
||||
LogLevel.info,
|
||||
);
|
||||
if (noData) {
|
||||
if (isSingleScan) {
|
||||
log("ending: noData and isSingleScan", LogLevel.info);
|
||||
|
||||
final nextStream = scanningClient.subscribe(
|
||||
ElectrumTweaksSubscribe(
|
||||
height: nextHeight,
|
||||
count: getCountToScanPerRequest(nextHeight),
|
||||
historicalMode: false,
|
||||
),
|
||||
);
|
||||
|
||||
if (nextStream != null) {
|
||||
nextStream.listen((event) => listenFn(event, req));
|
||||
} else {
|
||||
scanData.sendPort.send(
|
||||
SyncResponse(scanData.height, LostConnectionSyncStatus()),
|
||||
);
|
||||
endScanningSuccesfully();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log(
|
||||
"ending: resubscribing: nextHeight: $nextHeight, count: ${getCountToScanPerRequest(nextHeight)}",
|
||||
LogLevel.info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final tweakHeight = response.block;
|
||||
|
||||
if (initialSyncHeight < tweakHeight) initialSyncHeight = tweakHeight;
|
||||
|
||||
// Continuous status UI update, send how many blocks left to scan
|
||||
final syncingStatus = scanData.isSingleScan
|
||||
? SyncingSyncStatus(1, 0)
|
||||
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, tweakHeight);
|
||||
|
||||
scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
|
||||
|
||||
try {
|
||||
final blockTweaks = response.blockTweaks;
|
||||
|
||||
var blockDate = DateTime.now();
|
||||
bool isDateNow = true;
|
||||
|
||||
for (final txid in blockTweaks.keys) {
|
||||
final tweakData = blockTweaks[txid];
|
||||
final outputPubkeys = tweakData!.outputPubkeys;
|
||||
final tweak = tweakData.tweak;
|
||||
|
||||
try {
|
||||
final addToWallet = {};
|
||||
|
||||
// receivers.forEach((receiver) {
|
||||
// NOTE: scanOutputs, from sp_scanner package, called from rust here
|
||||
final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver);
|
||||
|
||||
if (scanResult.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (addToWallet[receiver.BSpend] == null) {
|
||||
addToWallet[receiver.BSpend] = scanResult;
|
||||
} else {
|
||||
addToWallet[receiver.BSpend].addAll(scanResult);
|
||||
}
|
||||
// });
|
||||
|
||||
if (addToWallet.isEmpty) {
|
||||
// no results tx, continue to next tx
|
||||
continue;
|
||||
}
|
||||
// re-subscribe to continue receiving messages, starting from the next unscanned height
|
||||
final nextHeight = syncHeight + 1;
|
||||
|
||||
if (nextHeight <= scanData.chainTip) {
|
||||
log(
|
||||
"FOUND: addToWallet: ${addToWallet.length}, txid: $txid, tweak: $tweak, height: $tweakHeight",
|
||||
"resubscribing: nextHeight: $nextHeight, count: ${getCountToScanPerRequest(nextHeight)}",
|
||||
LogLevel.info,
|
||||
);
|
||||
|
||||
// Every tx in the block has the same date (the block date)
|
||||
// So, if blockDate exists, reuse
|
||||
if (isDateNow) {
|
||||
try {
|
||||
final tweakBlockHash = await ProxyWrapper()
|
||||
.get(
|
||||
clearnetUri: Uri.parse(
|
||||
"https://mempool.cakewallet.com/api/v1/block-height/$tweakHeight",
|
||||
),
|
||||
)
|
||||
.timeout(Duration(seconds: 15));
|
||||
final blockResponse = await ProxyWrapper()
|
||||
.get(
|
||||
clearnetUri: Uri.parse(
|
||||
"https://mempool.cakewallet.com/api/v1/block/${tweakBlockHash.body}",
|
||||
),
|
||||
)
|
||||
.timeout(Duration(seconds: 15));
|
||||
|
||||
if (blockResponse.statusCode == 200 &&
|
||||
blockResponse.body.isNotEmpty &&
|
||||
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
||||
blockDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
int.parse(jsonDecode(blockResponse.body)['timestamp'].toString()) * 1000,
|
||||
);
|
||||
isDateNow = false;
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
printV(stacktrace);
|
||||
printV(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// initial placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) on the following loop
|
||||
final txInfo = ElectrumTransactionInfo(
|
||||
WalletType.bitcoin,
|
||||
id: txid,
|
||||
height: tweakHeight,
|
||||
amount: 0,
|
||||
fee: 0,
|
||||
direction: TransactionDirection.incoming,
|
||||
isReplaced: false,
|
||||
// TODO: fetch block data and get the date from it
|
||||
date: scanData.network == BitcoinNetwork.mainnet
|
||||
? (isDateNow ? getDateByBitcoinHeight(tweakHeight) : blockDate)
|
||||
: DateTime.now(),
|
||||
confirmations: scanData.chainTip - tweakHeight + 1,
|
||||
isReceivedSilentPayment: true,
|
||||
isPending: false,
|
||||
unspents: [],
|
||||
final nextStream = scanningClient.subscribe(
|
||||
ElectrumTweaksSubscribe(
|
||||
height: nextHeight,
|
||||
count: getCountToScanPerRequest(nextHeight),
|
||||
historicalMode: hasForcedRescanHeights,
|
||||
),
|
||||
);
|
||||
|
||||
List<BitcoinUnspent> unspents = [];
|
||||
if (nextStream != null) {
|
||||
nextStream.listen((event) => listenFn(event, req));
|
||||
} else {
|
||||
if (shouldUpdateSyncStatus)
|
||||
scanData.sendPort.send(
|
||||
SyncResponse(scanData.height, LostConnectionSyncStatus()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
addToWallet.forEach((BSpend, scanResultPerLabel) {
|
||||
scanResultPerLabel.forEach((label, scanOutput) {
|
||||
final labelValue = label == "None" ? null : label.toString();
|
||||
log(
|
||||
"ending: resubscribing: nextHeight: $nextHeight, count: ${getCountToScanPerRequest(nextHeight)}",
|
||||
LogLevel.info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
(scanOutput as Map<String, dynamic>).forEach((outputPubkey, tweak) {
|
||||
final t_k = tweak as String;
|
||||
final tweakHeight = response.block;
|
||||
|
||||
final receivingOutputAddress = ECPublic.fromHex(outputPubkey)
|
||||
.toTaprootAddress(tweak: false)
|
||||
.toAddress(scanData.network);
|
||||
// Continuous status UI update, send how many blocks left to scan
|
||||
final syncingStatus = isSingleScan
|
||||
? SyncingSyncStatus(1, 0)
|
||||
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, tweakHeight);
|
||||
|
||||
final matchingOutput = outputPubkeys[outputPubkey]!;
|
||||
final amount = matchingOutput.amount;
|
||||
final pos = matchingOutput.vout;
|
||||
if (shouldUpdateSyncStatus) scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
|
||||
|
||||
// final matchingSPWallet = scanData.silentPaymentsWallets.firstWhere(
|
||||
// (receiver) => receiver.B_spend.toHex() == BSpend.toString(),
|
||||
// );
|
||||
try {
|
||||
final blockTweaks = response.blockTweaks;
|
||||
|
||||
// final labelIndex = labelValue != null ? scanData.labels[label] : 0;
|
||||
// final balance = ElectrumBalance();
|
||||
// balance.confirmed = amount;
|
||||
var blockDate = DateTime.now();
|
||||
bool isDateNow = true;
|
||||
|
||||
final receivedAddressRecord = BitcoinSilentPaymentAddressRecord(
|
||||
receivingOutputAddress,
|
||||
index: 0,
|
||||
isHidden: false,
|
||||
isUsed: true,
|
||||
network: scanData.network,
|
||||
silentPaymentTweak: t_k,
|
||||
type: SegwitAddresType.p2tr,
|
||||
txCount: 1,
|
||||
balance: amount,
|
||||
);
|
||||
for (final txid in blockTweaks.keys) {
|
||||
final tweakData = blockTweaks[txid];
|
||||
final outputPubkeys = tweakData!.outputPubkeys;
|
||||
final tweak = tweakData.tweak;
|
||||
|
||||
final unspent = BitcoinSilentPaymentsUnspent(
|
||||
receivedAddressRecord,
|
||||
txid,
|
||||
amount,
|
||||
pos,
|
||||
silentPaymentTweak: t_k,
|
||||
silentPaymentLabel: labelValue,
|
||||
);
|
||||
try {
|
||||
final addToWallet = {};
|
||||
|
||||
unspents.add(unspent);
|
||||
txInfo.unspents!.add(unspent);
|
||||
txInfo.amount += unspent.value;
|
||||
// receivers.forEach((receiver) {
|
||||
// NOTE: scanOutputs, from sp_scanner package, called from rust here
|
||||
final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver);
|
||||
|
||||
if (scanResult.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (addToWallet[receiver.BSpend] == null) {
|
||||
addToWallet[receiver.BSpend] = scanResult;
|
||||
} else {
|
||||
addToWallet[receiver.BSpend].addAll(scanResult);
|
||||
}
|
||||
// });
|
||||
|
||||
if (addToWallet.isEmpty) {
|
||||
// no results tx, continue to next tx
|
||||
continue;
|
||||
}
|
||||
|
||||
log(
|
||||
"FOUND: addToWallet: ${addToWallet.length}, txid: $txid, tweak: $tweak, height: $tweakHeight",
|
||||
LogLevel.info,
|
||||
);
|
||||
|
||||
// Every tx in the block has the same date (the block date)
|
||||
// So, if blockDate exists, reuse
|
||||
if (isDateNow) {
|
||||
try {
|
||||
final tweakBlockHash = await ProxyWrapper()
|
||||
.get(
|
||||
clearnetUri: Uri.parse(
|
||||
"https://mempool.cakewallet.com/api/v1/block-height/$tweakHeight",
|
||||
),
|
||||
)
|
||||
.timeout(Duration(seconds: 15));
|
||||
final blockResponse = await ProxyWrapper()
|
||||
.get(
|
||||
clearnetUri: Uri.parse(
|
||||
"https://mempool.cakewallet.com/api/v1/block/${tweakBlockHash.body}",
|
||||
),
|
||||
)
|
||||
.timeout(Duration(seconds: 15));
|
||||
|
||||
if (blockResponse.statusCode == 200 &&
|
||||
blockResponse.body.isNotEmpty &&
|
||||
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
||||
blockDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
int.parse(jsonDecode(blockResponse.body)['timestamp'].toString()) * 1000,
|
||||
);
|
||||
isDateNow = false;
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
printV(stacktrace);
|
||||
printV(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// initial placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) on the following loop
|
||||
final txInfo = ElectrumTransactionInfo(
|
||||
WalletType.bitcoin,
|
||||
id: txid,
|
||||
height: tweakHeight,
|
||||
amount: 0,
|
||||
fee: 0,
|
||||
direction: TransactionDirection.incoming,
|
||||
isReplaced: false,
|
||||
// TODO: fetch block data and get the date from it
|
||||
date: scanData.network == BitcoinNetwork.mainnet
|
||||
? (isDateNow ? getDateByBitcoinHeight(tweakHeight) : blockDate)
|
||||
: DateTime.now(),
|
||||
confirmations: scanData.chainTip - tweakHeight + 1,
|
||||
isReceivedSilentPayment: true,
|
||||
isPending: false,
|
||||
unspents: [],
|
||||
);
|
||||
|
||||
List<BitcoinUnspent> unspents = [];
|
||||
|
||||
addToWallet.forEach((BSpend, scanResultPerLabel) {
|
||||
scanResultPerLabel.forEach((label, scanOutput) {
|
||||
final labelValue = label == "None" ? null : label.toString();
|
||||
|
||||
(scanOutput as Map<String, dynamic>).forEach((outputPubkey, tweak) {
|
||||
final t_k = tweak as String;
|
||||
|
||||
final receivingOutputAddress = ECPublic.fromHex(outputPubkey)
|
||||
.toTaprootAddress(tweak: false)
|
||||
.toAddress(scanData.network);
|
||||
|
||||
final matchingOutput = outputPubkeys[outputPubkey]!;
|
||||
final amount = matchingOutput.amount;
|
||||
final pos = matchingOutput.vout;
|
||||
final spent = matchingOutput.spendingInput;
|
||||
|
||||
// final matchingSPWallet = scanData.silentPaymentsWallets.firstWhere(
|
||||
// (receiver) => receiver.B_spend.toHex() == BSpend.toString(),
|
||||
// );
|
||||
|
||||
// final labelIndex = labelValue != null ? scanData.labels[label] : 0;
|
||||
// final balance = ElectrumBalance();
|
||||
// balance.confirmed = amount;
|
||||
|
||||
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(
|
||||
receivedAddressRecord,
|
||||
txid,
|
||||
amount,
|
||||
pos,
|
||||
silentPaymentTweak: t_k,
|
||||
silentPaymentLabel: labelValue,
|
||||
);
|
||||
|
||||
if (spent == null) {
|
||||
unspents.add(unspent);
|
||||
txInfo.unspents!.add(unspent);
|
||||
}
|
||||
|
||||
txInfo.amount += unspent.value;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
scanData.sendPort.send({txInfo.id: txInfo});
|
||||
} catch (e, stacktrace) {
|
||||
scanData.sendPort.send({txInfo.id: txInfo});
|
||||
} catch (e, stacktrace) {
|
||||
if (shouldUpdateSyncStatus)
|
||||
scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, LostConnectionSyncStatus()),
|
||||
);
|
||||
|
||||
log(stacktrace.toString(), LogLevel.error);
|
||||
log(e.toString(), LogLevel.error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
if (shouldUpdateSyncStatus)
|
||||
scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, LostConnectionSyncStatus()),
|
||||
);
|
||||
|
||||
log(stacktrace.toString(), LogLevel.error);
|
||||
log(e.toString(), LogLevel.error);
|
||||
return;
|
||||
}
|
||||
log(stacktrace.toString(), LogLevel.error);
|
||||
log(e.toString(), LogLevel.error);
|
||||
return;
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, LostConnectionSyncStatus()),
|
||||
);
|
||||
|
||||
log(stacktrace.toString(), LogLevel.error);
|
||||
log(e.toString(), LogLevel.error);
|
||||
return;
|
||||
syncHeight = tweakHeight;
|
||||
|
||||
if ((tweakHeight >= scanData.chainTip) || isSingleScan) {
|
||||
endScanningSuccesfully();
|
||||
}
|
||||
}
|
||||
|
||||
syncHeight = tweakHeight;
|
||||
|
||||
if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) {
|
||||
endScanningSuccesfully();
|
||||
}
|
||||
_scanningStream?.listen((event) => listenFn(event, req));
|
||||
}
|
||||
|
||||
_scanningStream?.listen((event) => listenFn(event, req));
|
||||
if (scanData.rescanHeights != null) {
|
||||
for (final height in scanData.rescanHeights!) {
|
||||
log("rescanning from height: $height", LogLevel.info);
|
||||
scan(height, true);
|
||||
}
|
||||
} else {
|
||||
scan(scanData.height, scanData.isSingleScan);
|
||||
}
|
||||
} catch (e) {
|
||||
log("Error in _handleScanSilentPayments: $e", LogLevel.error);
|
||||
scanData.sendPort.send(SyncResponse(scanData.height, LostConnectionSyncStatus()));
|
||||
if (shouldUpdateSyncStatus)
|
||||
scanData.sendPort.send(SyncResponse(scanData.height, LostConnectionSyncStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,29 +99,29 @@ class SyncingSyncStatus extends SyncStatus {
|
||||
return newDuration;
|
||||
}
|
||||
|
||||
final currentSeconds = lastEtaDuration!.inSeconds;
|
||||
final newSeconds = newDuration.inSeconds;
|
||||
final diff = (newSeconds - currentSeconds).abs();
|
||||
final currentMs = lastEtaDuration!.inMilliseconds;
|
||||
final newMs = newDuration.inMilliseconds;
|
||||
final diff = ((newMs - currentMs) / 1000).abs();
|
||||
|
||||
// Apply different smoothing based on the magnitude of change
|
||||
if (diff > 3600) {
|
||||
// If it's more than 1 hour difference, it's a large change so we move by max 30 minutes
|
||||
final direction = newSeconds > currentSeconds ? 1 : -1;
|
||||
final maxChange = 30 * 60;
|
||||
final adjustedSeconds = currentSeconds + (direction * maxChange);
|
||||
return Duration(seconds: adjustedSeconds);
|
||||
final direction = newMs > currentMs ? 1 : -1;
|
||||
final maxChange = 30 * 60 * 1000;
|
||||
final adjustedMs = currentMs + (direction * maxChange);
|
||||
return Duration(milliseconds: adjustedMs);
|
||||
} else if (diff > 300) {
|
||||
// If it's more than 5 minutes difference, it's a medium change so we move by max 2 minutes
|
||||
final direction = newSeconds > currentSeconds ? 1 : -1;
|
||||
final maxChange = 2 * 60;
|
||||
final adjustedSeconds = currentSeconds + (direction * maxChange);
|
||||
return Duration(seconds: adjustedSeconds);
|
||||
final direction = newMs > currentMs ? 1 : -1;
|
||||
final maxChange = 2 * 60 * 1000;
|
||||
final adjustedMs = currentMs + (direction * maxChange);
|
||||
return Duration(milliseconds: adjustedMs);
|
||||
} else if (diff > 60) {
|
||||
// If it's more than 1 minute difference, it's a small change so we move by max 30 seconds
|
||||
final direction = newSeconds > currentSeconds ? 1 : -1;
|
||||
final maxChange = 30;
|
||||
final adjustedSeconds = currentSeconds + (direction * maxChange);
|
||||
return Duration(seconds: adjustedSeconds);
|
||||
// If it's more than 1 minute difference, it's a small change so we move by max 30 ms
|
||||
final direction = newMs > currentMs ? 1 : -1;
|
||||
final maxChange = 30 * 1000;
|
||||
final adjustedMs = currentMs + (direction * maxChange);
|
||||
return Duration(milliseconds: adjustedMs);
|
||||
}
|
||||
|
||||
return newDuration;
|
||||
@@ -146,8 +146,8 @@ class SyncingSyncStatus extends SyncStatus {
|
||||
return DateTime.now().add(const Duration(days: 2));
|
||||
}
|
||||
int remainingBlocks = this.blocksLeft;
|
||||
double timeRemainingSeconds = remainingBlocks / rate;
|
||||
return DateTime.now().add(Duration(seconds: timeRemainingSeconds.round()));
|
||||
double timeRemainingMs = remainingBlocks / rate;
|
||||
return DateTime.now().add(Duration(milliseconds: timeRemainingMs.round()));
|
||||
}
|
||||
|
||||
// Enhanced block rate calculation with weighted averages
|
||||
@@ -176,12 +176,12 @@ class SyncingSyncStatus extends SyncStatus {
|
||||
|
||||
final timeDifference = next.key.difference(current.key);
|
||||
|
||||
if (timeDifference.inSeconds <= 0) continue; // Skip invalid time
|
||||
if (timeDifference.inMilliseconds <= 0) continue; // Skip invalid time
|
||||
|
||||
// Weight recent data more heavily (exponential decay)
|
||||
final weight = 1.0 / (1.0 + (sortedData.length - 1 - i) * 0.1);
|
||||
|
||||
totalWeightedTime += timeDifference.inSeconds * weight;
|
||||
totalWeightedTime += timeDifference.inMilliseconds * weight;
|
||||
totalWeightedBlocks += blocksProcessed * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user