Revert "FIX (#2283)" (#2298)

This reverts commit 7b8ddf9685.
This commit is contained in:
Omar Hatem 2025-05-29 16:54:22 +03:00 committed by GitHub
parent b77c22b0df
commit d332377a2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 402 additions and 667 deletions

View file

@ -5,6 +5,7 @@ import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
@ -17,7 +18,7 @@ import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
import 'package:cw_bitcoin/electrum.dart' as electrum;
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart';
@ -68,7 +69,7 @@ abstract class ElectrumWalletBase
Uint8List? seedBytes,
this.passphrase,
List<BitcoinAddressRecord>? initialAddresses,
electrum.ElectrumClient? electrumClient,
ElectrumClient? electrumClient,
ElectrumBalance? initialBalance,
CryptoCurrency? currency,
this.alwaysScan,
@ -95,7 +96,7 @@ abstract class ElectrumWalletBase
this.isTestnet = !network.isMainnet,
this._mnemonic = mnemonic,
super(walletInfo) {
this.electrumClient = electrumClient ?? electrum.ElectrumClient();
this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo;
transactionHistory = ElectrumTransactionHistory(
walletInfo: walletInfo,
@ -166,7 +167,7 @@ abstract class ElectrumWalletBase
@observable
bool isEnabledAutoGenerateSubaddress;
late electrum.ElectrumClient electrumClient;
late ElectrumClient electrumClient;
Box<UnspentCoinsInfo> unspentCoinsInfo;
@override
@ -181,7 +182,7 @@ abstract class ElectrumWalletBase
SyncStatus syncStatus;
Set<String> get addressesSet => walletAddresses.allAddresses
.where((element) => element.type != SegwitAddressType.mweb)
.where((element) => element.type != SegwitAddresType.mweb)
.map((addr) => addr.address)
.toSet();
@ -332,14 +333,14 @@ abstract class ElectrumWalletBase
final receivePort = ReceivePort();
_isolate = Isolate.spawn(
_handleScanSilentPayments,
startRefresh,
ScanData(
sendPort: receivePort.sendPort,
silentAddress: walletAddresses.silentAddress!,
network: network,
height: height,
chainTip: chainTip,
electrumClient: electrum.ElectrumClient(),
electrumClient: ElectrumClient(),
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
node: (await getNodeSupportsSilentPayments()) == true
? ScanNode(node!.uri, node!.useSSL)
@ -438,6 +439,7 @@ abstract class ElectrumWalletBase
BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)),
)
: silentAddress.B_spend,
network: network,
);
final addressRecord = walletAddresses.silentAddresses
@ -562,7 +564,7 @@ abstract class ElectrumWalletBase
node!.save();
return node!.supportsSilentPayments!;
}
} on electrum.RequestFailedTimeoutException catch (_) {
} on RequestFailedTimeoutException catch (_) {
node!.supportsSilentPayments = false;
node!.save();
return node!.supportsSilentPayments!;
@ -623,9 +625,9 @@ abstract class ElectrumWalletBase
switch (coinTypeToSpendFrom) {
case UnspentCoinType.mweb:
return utx.bitcoinAddressRecord.type == SegwitAddressType.mweb;
return utx.bitcoinAddressRecord.type == SegwitAddresType.mweb;
case UnspentCoinType.nonMweb:
return utx.bitcoinAddressRecord.type != SegwitAddressType.mweb;
return utx.bitcoinAddressRecord.type != SegwitAddresType.mweb;
case UnspentCoinType.any:
return true;
}
@ -633,7 +635,7 @@ abstract class ElectrumWalletBase
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
// sort the unconfirmed coins so that mweb coins are last:
availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddressType.mweb ? 1 : -1);
availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1);
for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i];
@ -641,7 +643,7 @@ abstract class ElectrumWalletBase
if (paysToSilentPayment) {
// Check inputs for shared secret derivation
if (utx.bitcoinAddressRecord.type == SegwitAddressType.p2wsh) {
if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) {
throw BitcoinTransactionSilentPaymentsNotSupported();
}
}
@ -676,7 +678,7 @@ abstract class ElectrumWalletBase
if (privkey != null) {
inputPrivKeyInfos.add(ECPrivateInfo(
privkey,
address.type == SegwitAddressType.p2tr,
address.type == SegwitAddresType.p2tr,
tweak: !isSilentPayment,
));
@ -1162,7 +1164,7 @@ abstract class ElectrumWalletBase
throw Exception(error);
}
if (utxo.utxo.isP2tr) {
if (utxo.utxo.isP2tr()) {
hasTaprootInputs = true;
return key.privkey.signTapRoot(
txDigest,
@ -1174,18 +1176,20 @@ abstract class ElectrumWalletBase
}
});
return PendingBitcoinTransaction(transaction, type,
electrumClient: electrumClient,
amount: estimatedTx.amount,
fee: estimatedTx.fee,
feeRate: feeRateInt.toString(),
network: network,
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs,
utxos: estimatedTx.utxos,
publicKeys: estimatedTx.publicKeys)
..addListener((transaction) async {
return PendingBitcoinTransaction(
transaction,
type,
electrumClient: electrumClient,
amount: estimatedTx.amount,
fee: estimatedTx.fee,
feeRate: feeRateInt.toString(),
network: network,
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs,
utxos: estimatedTx.utxos,
publicKeys: estimatedTx.publicKeys
)..addListener((transaction) async {
transactionHistory.addOne(transaction);
if (estimatedTx.spendsSilentPayment) {
transactionHistory.transactions.values.forEach((tx) {
@ -1229,7 +1233,7 @@ abstract class ElectrumWalletBase
'change_address_index': walletAddresses.currentChangeAddressIndexByType,
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
'address_page_type': walletInfo.addressPageType == null
? SegwitAddressType.p2wpkh.toString()
? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(),
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
@ -1369,7 +1373,7 @@ abstract class ElectrumWalletBase
List<BitcoinUnspent> updatedUnspentCoins = [];
final previousUnspentCoins = List<BitcoinUnspent>.from(unspentCoins.where((utxo) =>
utxo.bitcoinAddressRecord.type != SegwitAddressType.mweb &&
utxo.bitcoinAddressRecord.type != SegwitAddresType.mweb &&
utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord));
if (hasSilentPaymentsScanning) {
@ -1383,13 +1387,13 @@ abstract class ElectrumWalletBase
// Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
walletAddresses.allAddresses
.where((element) => element.type != SegwitAddressType.mweb)
.where((element) => element.type != SegwitAddresType.mweb)
.forEach((addr) {
if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
});
final addressFutures = walletAddresses.allAddresses
.where((element) => element.type != SegwitAddressType.mweb)
.where((element) => element.type != SegwitAddresType.mweb)
.map((address) => fetchUnspent(address))
.toList();
@ -1830,7 +1834,7 @@ abstract class ElectrumWalletBase
throw Exception("Cannot find private key");
}
if (utxo.utxo.isP2tr) {
if (utxo.utxo.isP2tr()) {
return key.signTapRoot(txDigest, sighash: sighash);
} else {
return key.signInput(txDigest, sigHash: sighash);
@ -1980,7 +1984,7 @@ abstract class ElectrumWalletBase
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.litecoin) {
await Future.wait(LITECOIN_ADDRESS_TYPES
.where((type) => type != SegwitAddressType.mweb)
.where((type) => type != SegwitAddresType.mweb)
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
}
@ -2169,7 +2173,7 @@ abstract class ElectrumWalletBase
final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
(address) =>
!_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)) &&
address.type != SegwitAddressType.mweb,
address.type != SegwitAddresType.mweb,
);
await Future.wait(unsubscribedScriptHashes.map((address) async {
@ -2392,9 +2396,9 @@ abstract class ElectrumWalletBase
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
@action
void _onConnectionStatusChange(electrum.ConnectionStatus status) {
void _onConnectionStatusChange(ConnectionStatus status) {
switch (status) {
case electrum.ConnectionStatus.connected:
case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus ||
syncStatus is LostConnectionSyncStatus ||
syncStatus is ConnectingSyncStatus) {
@ -2402,19 +2406,19 @@ abstract class ElectrumWalletBase
}
break;
case electrum.ConnectionStatus.disconnected:
case ConnectionStatus.disconnected:
if (syncStatus is! NotConnectedSyncStatus &&
syncStatus is! ConnectingSyncStatus &&
syncStatus is! SyncronizingSyncStatus) {
syncStatus = NotConnectedSyncStatus();
}
break;
case electrum.ConnectionStatus.failed:
case ConnectionStatus.failed:
if (syncStatus is! LostConnectionSyncStatus) {
syncStatus = LostConnectionSyncStatus();
}
break;
case electrum.ConnectionStatus.connecting:
case ConnectionStatus.connecting:
if (syncStatus is! ConnectingSyncStatus) {
syncStatus = ConnectingSyncStatus();
}
@ -2526,7 +2530,7 @@ class ScanData {
final ScanNode? node;
final BasedUtxoNetwork network;
final int chainTip;
final electrum.ElectrumClient electrumClient;
final ElectrumClient electrumClient;
final List<String> transactionHistoryIds;
final Map<String, String> labels;
final List<int> labelIndexes;
@ -2570,234 +2574,6 @@ class SyncResponse {
SyncResponse(this.height, this.syncStatus);
}
Future<void> _handleScanSilentPayments(ScanData scanData) async {
try {
// if (scanData.shouldSwitchNodes) {
var scanningClient = await ElectrumProvider.connect(
ElectrumTCPService.connect(
Uri.parse("tcp://electrs.cakewallet.com:50001"),
),
);
// }
int syncHeight = scanData.height;
int initialSyncHeight = syncHeight;
final receiver = Receiver(
scanData.silentAddress.b_scan.toHex(),
scanData.silentAddress.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes,
);
int getCountToScanPerRequest(int syncHeight) {
if (scanData.isSingleScan) {
return 1;
}
final amountLeft = scanData.chainTip - syncHeight + 1;
return amountLeft;
}
// 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: false,
);
var _scanningStream = await scanningClient.subscribe(req);
void listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) {
final response = req.onResponse(event);
if (response == null || _scanningStream == null) {
return;
}
// is success or error msg
final noData = response.message != null;
if (noData) {
if (scanData.isSingleScan) {
return;
}
// re-subscribe to continue receiving messages, starting from the next unscanned height
final nextHeight = syncHeight + 1;
if (nextHeight <= scanData.chainTip) {
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()),
);
}
}
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;
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;
}
// 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,
date: DateTime.fromMillisecondsSinceEpoch(
DateTime.now().millisecondsSinceEpoch * 1000,
),
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 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: SegwitAddressType.p2tr,
txCount: 1,
balance: amount,
);
final unspent = BitcoinSilentPaymentsUnspent(
receivedAddressRecord,
txid,
amount,
pos,
silentPaymentTweak: t_k,
silentPaymentLabel: labelValue,
);
unspents.add(unspent);
txInfo.unspents!.add(unspent);
txInfo.amount += unspent.value;
});
});
});
scanData.sendPort.send({txInfo.id: txInfo});
} catch (e, stacktrace) {
printV(stacktrace);
printV(e.toString());
}
}
} catch (e, stacktrace) {
printV(stacktrace);
printV(e.toString());
}
syncHeight = tweakHeight;
if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) {
if (tweakHeight >= scanData.chainTip)
scanData.sendPort.send(
SyncResponse(syncHeight, SyncedTipSyncStatus(scanData.chainTip)),
);
if (scanData.isSingleScan) {
scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus()));
}
_scanningStream?.close();
_scanningStream = null;
return;
}
}
_scanningStream?.listen((event) => listenFn(event, req));
} catch (e) {
printV("Error in _handleScanSilentPayments: $e");
scanData.sendPort.send(SyncResponse(scanData.height, LostConnectionSyncStatus()));
}
}
Future<void> startRefresh(ScanData scanData) async {
int syncHeight = scanData.height;
int initialSyncHeight = syncHeight;
@ -2810,7 +2586,7 @@ Future<void> startRefresh(ScanData scanData) async {
useSSL: scanData.node?.useSSL ?? false,
);
int getCountToScanPerRequest(int syncHeight) {
int getCountPerRequest(int syncHeight) {
if (scanData.isSingleScan) {
return 1;
}
@ -2825,10 +2601,11 @@ Future<void> startRefresh(ScanData scanData) async {
scanData.silentAddress.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes,
scanData.labelIndexes.length,
);
// Initial status UI update, send how many blocks in total to scan
final initialCount = getCountToScanPerRequest(syncHeight);
final initialCount = getCountPerRequest(syncHeight);
scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight)));
tweaksSubscription = await electrumClient.tweaksSubscribe(
@ -2839,24 +2616,22 @@ Future<void> startRefresh(ScanData scanData) async {
Future<void> listenFn(t) async {
final tweaks = t as Map<String, dynamic>;
final msg = tweaks["message"];
// is success or error msg
// success or error msg
final noData = msg != null;
if (noData) {
if (scanData.isSingleScan) {
return;
}
// re-subscribe to continue receiving messages, starting from the next unscanned height
final nextHeight = syncHeight + 1;
final nextCount = getCountPerRequest(nextHeight);
if (nextHeight <= scanData.chainTip) {
final nextStream = electrumClient.tweaksSubscribe(
if (nextCount > 0) {
tweaksSubscription?.close();
final nextTweaksSubscription = electrumClient.tweaksSubscribe(
height: nextHeight,
count: getCountToScanPerRequest(nextHeight),
count: nextCount,
);
nextStream?.listen(listenFn);
nextTweaksSubscription?.listen(listenFn);
}
return;
@ -2938,7 +2713,7 @@ Future<void> startRefresh(ScanData scanData) async {
isUsed: true,
network: scanData.network,
silentPaymentTweak: t_k,
type: SegwitAddressType.p2tr,
type: SegwitAddresType.p2tr,
txCount: 1,
balance: amount!,
);
@ -3031,15 +2806,15 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
} else if (type is P2shAddress) {
return P2shAddressType.p2wpkhInP2sh;
} else if (type is P2wshAddress) {
return SegwitAddressType.p2wsh;
return SegwitAddresType.p2wsh;
} else if (type is P2trAddress) {
return SegwitAddressType.p2tr;
return SegwitAddresType.p2tr;
} else if (type is MwebAddress) {
return SegwitAddressType.mweb;
return SegwitAddresType.mweb;
} else if (type is SilentPaymentsAddresType) {
return SilentPaymentsAddresType.p2sp;
} else {
return SegwitAddressType.p2wpkh;
return SegwitAddresType.p2wpkh;
}
}