mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-11-12 08:53:57 +01:00
The 1.0.5 build 15 macOS test crashed with SIGSEGV in WOWNERO_TransactionInfo_amount on the main thread, near the end of a restore sync. TransactionHistory_refresh runs on a background isolate under txHistoryMutex and frees the TransactionInfo objects it replaces; getAllSubaddresses() and getTransaction() read those objects without the mutex, so a read that overlaps a refresh dereferences freed memory. The wallet cache had not been stored, so the next launch synced again. Upstream fixed the same race for Monero in #3188 and in the v6.4.5 coin-control change; this ports both to cw_wownero. getAllSubaddresses() and getTransaction() take the mutex and become async, which carries through the subaddress list, its app proxy and the configure interface. The subaddress list now fetches before clearing, so the observable list is never empty while the fetch is pending, and addSubaddress and setLabelSubaddress await the update before callers read the new address. The coin-control change check compared a Transaction with 1 and was always false; it now uses the transaction's direction, as Monero does.
156 lines
4.8 KiB
Dart
156 lines
4.8 KiB
Dart
import 'package:cw_wownero/api/account_list.dart';
|
|
import 'package:cw_wownero/api/transaction_history.dart';
|
|
import 'package:cw_wownero/api/wallet.dart';
|
|
import 'package:monero/wownero.dart' as wownero;
|
|
|
|
bool isUpdating = false;
|
|
|
|
class SubaddressInfoMetadata {
|
|
SubaddressInfoMetadata({
|
|
required this.accountIndex,
|
|
});
|
|
int accountIndex;
|
|
}
|
|
|
|
SubaddressInfoMetadata? subaddress = null;
|
|
|
|
void refreshSubaddresses({required int accountIndex}) {
|
|
try {
|
|
isUpdating = true;
|
|
subaddress = SubaddressInfoMetadata(accountIndex: accountIndex);
|
|
isUpdating = false;
|
|
} catch (e) {
|
|
isUpdating = false;
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
class Subaddress {
|
|
Subaddress({
|
|
required this.addressIndex,
|
|
required this.accountIndex,
|
|
required this.txCount,
|
|
required this.received,
|
|
});
|
|
late String address = getAddress(
|
|
accountIndex: accountIndex,
|
|
addressIndex: addressIndex,
|
|
);
|
|
final int addressIndex;
|
|
final int accountIndex;
|
|
String get label => wownero.Wallet_getSubaddressLabel(wptr!,
|
|
accountIndex: accountIndex, addressIndex: addressIndex);
|
|
final int txCount;
|
|
final int received;
|
|
}
|
|
|
|
class TinyTransactionDetails {
|
|
TinyTransactionDetails({
|
|
required this.address,
|
|
required this.amount,
|
|
});
|
|
final List<String> address;
|
|
final int amount;
|
|
}
|
|
|
|
int lastWptr = 0;
|
|
int lastTxCount = 0;
|
|
List<TinyTransactionDetails> ttDetails = [];
|
|
|
|
Future<List<Subaddress>> getAllSubaddresses() async {
|
|
await txHistoryMutex.acquire();
|
|
try {
|
|
txhistory = wownero.Wallet_history(wptr!);
|
|
final txCount = wownero.TransactionHistory_count(txhistory!);
|
|
if (lastTxCount != txCount && lastWptr != wptr!.address) {
|
|
final List<TinyTransactionDetails> newttDetails = [];
|
|
lastTxCount = txCount;
|
|
lastWptr = wptr!.address;
|
|
for (var i = 0; i < txCount; i++) {
|
|
final tx = wownero.TransactionHistory_transaction(txhistory!, index: i);
|
|
final subaddrs = wownero.TransactionInfo_subaddrIndex(tx).split(",");
|
|
final account = wownero.TransactionInfo_subaddrAccount(tx);
|
|
newttDetails.add(TinyTransactionDetails(
|
|
address: List.generate(
|
|
subaddrs.length,
|
|
(index) => getAddress(
|
|
accountIndex: account, addressIndex: int.tryParse(subaddrs[index]) ?? 0)),
|
|
amount: wownero.TransactionInfo_amount(tx),
|
|
));
|
|
}
|
|
ttDetails.clear();
|
|
ttDetails.addAll(newttDetails);
|
|
}
|
|
} finally {
|
|
txHistoryMutex.release();
|
|
}
|
|
final size = wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex);
|
|
final list = List.generate(size, (index) {
|
|
final ttDetailsLocal = ttDetails.where((element) {
|
|
final address = getAddress(
|
|
accountIndex: subaddress!.accountIndex,
|
|
addressIndex: index,
|
|
);
|
|
if (address == element.address) return true;
|
|
return false;
|
|
}).toList();
|
|
int received = 0;
|
|
for (var i = 0; i < ttDetailsLocal.length; i++) {
|
|
received += ttDetailsLocal[i].amount;
|
|
}
|
|
return Subaddress(
|
|
accountIndex: subaddress!.accountIndex,
|
|
addressIndex: index,
|
|
received: received,
|
|
txCount: ttDetailsLocal.length,
|
|
);
|
|
}).reversed.toList();
|
|
if (list.isEmpty) {
|
|
list.add(Subaddress(
|
|
addressIndex: 0, accountIndex: subaddress!.accountIndex, txCount: 0, received: 0));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
void addSubaddressSync({required int accountIndex, required String label}) {
|
|
wownero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex, label: label);
|
|
refreshSubaddresses(accountIndex: accountIndex);
|
|
}
|
|
|
|
int numSubaddresses(int subaccountIndex) {
|
|
return wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaccountIndex);
|
|
}
|
|
|
|
void setLabelForSubaddressSync(
|
|
{required int accountIndex, required int addressIndex, required String label}) {
|
|
wownero.Wallet_setSubaddressLabel(wptr!,
|
|
accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
|
}
|
|
|
|
void _addSubaddress(Map<String, dynamic> args) {
|
|
final label = args['label'] as String;
|
|
final accountIndex = args['accountIndex'] as int;
|
|
|
|
addSubaddressSync(accountIndex: accountIndex, label: label);
|
|
}
|
|
|
|
void _setLabelForSubaddress(Map<String, dynamic> args) {
|
|
final label = args['label'] as String;
|
|
final accountIndex = args['accountIndex'] as int;
|
|
final addressIndex = args['addressIndex'] as int;
|
|
|
|
setLabelForSubaddressSync(accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
|
}
|
|
|
|
Future<void> addSubaddress({required int accountIndex, required String label}) async {
|
|
_addSubaddress({'accountIndex': accountIndex, 'label': label});
|
|
await store();
|
|
}
|
|
|
|
Future<void> setLabelForSubaddress(
|
|
{required int accountIndex, required int addressIndex, required String label}) async {
|
|
_setLabelForSubaddress(
|
|
{'accountIndex': accountIndex, 'addressIndex': addressIndex, 'label': label});
|
|
await store();
|
|
}
|