various firo restore/rescan fixes and tweaks

This commit is contained in:
julian 2023-11-27 11:50:35 -06:00
parent e5043dfe90
commit 5b3a998091
7 changed files with 363 additions and 51 deletions

View file

@ -422,8 +422,8 @@ class MainDB {
await isar.transactionV2s.where().walletIdEqualTo(walletId).count(); await isar.transactionV2s.where().walletIdEqualTo(walletId).count();
final addressCount = await getAddresses(walletId).count(); final addressCount = await getAddresses(walletId).count();
final utxoCount = await getUTXOs(walletId).count(); final utxoCount = await getUTXOs(walletId).count();
final lelantusCoinCount = // final lelantusCoinCount =
await isar.lelantusCoins.where().walletIdEqualTo(walletId).count(); // await isar.lelantusCoins.where().walletIdEqualTo(walletId).count();
await isar.writeTxn(() async { await isar.writeTxn(() async {
const paginateLimit = 50; const paginateLimit = 50;
@ -471,16 +471,17 @@ class MainDB {
} }
// lelantusCoins // lelantusCoins
for (int i = 0; i < lelantusCoinCount; i += paginateLimit) { await isar.lelantusCoins.where().walletIdEqualTo(walletId).deleteAll();
final lelantusCoinIds = await isar.lelantusCoins // for (int i = 0; i < lelantusCoinCount; i += paginateLimit) {
.where() // final lelantusCoinIds = await isar.lelantusCoins
.walletIdEqualTo(walletId) // .where()
.offset(i) // .walletIdEqualTo(walletId)
.limit(paginateLimit) // .offset(i)
.idProperty() // .limit(paginateLimit)
.findAll(); // .idProperty()
await isar.lelantusCoins.deleteAll(lelantusCoinIds); // .findAll();
} // await isar.lelantusCoins.deleteAll(lelantusCoinIds);
// }
}); });
} }

View file

@ -0,0 +1,236 @@
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class LelantusCoinsView extends ConsumerStatefulWidget {
const LelantusCoinsView({
Key? key,
required this.walletId,
}) : super(key: key);
static const String routeName = "/lelantusCoinsView";
final String walletId;
@override
ConsumerState<LelantusCoinsView> createState() => _LelantusCoinsViewState();
}
class _LelantusCoinsViewState extends ConsumerState<LelantusCoinsView> {
List<LelantusCoin> _coins = [];
Stream<List<LelantusCoin>>? lelantusCoinsCollectionWatcher;
void _onLelantusCoinsCollectionWatcherEvent(List<LelantusCoin> coins) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_coins = coins;
});
}
});
}
@override
void initState() {
lelantusCoinsCollectionWatcher = ref
.read(mainDBProvider)
.isar
.lelantusCoins
.where()
.walletIdEqualTo(widget.walletId)
.sortByMintIndexDesc()
.watch(fireImmediately: true);
lelantusCoinsCollectionWatcher!
.listen((data) => _onLelantusCoinsCollectionWatcherEvent(data));
super.initState();
}
@override
void dispose() {
lelantusCoinsCollectionWatcher = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return DesktopScaffold(
appBar: DesktopAppBar(
background: Theme.of(context).extension<StackColors>()!.popupBG,
leading: Expanded(
child: Row(
children: [
const SizedBox(
width: 32,
),
AppBarIconButton(
size: 32,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
shadows: const [],
icon: SvgPicture.asset(
Assets.svg.arrowLeft,
width: 18,
height: 18,
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
onPressed: Navigator.of(context).pop,
),
const SizedBox(
width: 12,
),
Text(
"Lelantus Coins",
style: STextStyles.desktopH3(context),
),
const Spacer(),
],
),
),
useSpacers: false,
isCompactHeight: true,
),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(4),
child: RoundedWhiteContainer(
child: Row(
children: [
Expanded(
flex: 9,
child: Text(
"TXID",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.left,
),
),
Expanded(
flex: 3,
child: Text(
"Value (sats)",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.right,
),
),
Expanded(
flex: 2,
child: Text(
"Index",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.right,
),
),
Expanded(
flex: 2,
child: Text(
"Is JMint",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.right,
),
),
Expanded(
flex: 2,
child: Text(
"Used",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.right,
),
),
],
),
),
),
Expanded(
child: ListView.separated(
shrinkWrap: true,
itemCount: _coins.length,
separatorBuilder: (_, __) => Container(
height: 1,
color: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
),
itemBuilder: (_, index) => Padding(
padding: const EdgeInsets.all(4),
child: RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
flex: 9,
child: SelectableText(
_coins[index].txid,
style: STextStyles.itemSubtitle12(context),
),
),
Expanded(
flex: 3,
child: SelectableText(
_coins[index].value,
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
),
Expanded(
flex: 2,
child: SelectableText(
_coins[index].mintIndex.toString(),
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
),
Expanded(
flex: 2,
child: SelectableText(
_coins[index].isJMint.toString(),
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
),
Expanded(
flex: 2,
child: SelectableText(
_coins[index].isUsed.toString(),
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
),
],
),
),
),
),
),
],
),
),
);
}
}

View file

@ -10,12 +10,14 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart';
import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart'; import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart';
import 'package:stackwallet/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart';
import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
@ -29,7 +31,8 @@ enum _WalletOptions {
addressList, addressList,
deleteWallet, deleteWallet,
changeRepresentative, changeRepresentative,
showXpub; showXpub,
lelantusCoins;
String get prettyName { String get prettyName {
switch (this) { switch (this) {
@ -41,6 +44,8 @@ enum _WalletOptions {
return "Change representative"; return "Change representative";
case _WalletOptions.showXpub: case _WalletOptions.showXpub:
return "Show xPub"; return "Show xPub";
case _WalletOptions.lelantusCoins:
return "Lelantus Coins";
} }
} }
} }
@ -81,6 +86,9 @@ class WalletOptionsButton extends StatelessWidget {
onShowXpubPressed: () async { onShowXpubPressed: () async {
Navigator.of(context).pop(_WalletOptions.showXpub); Navigator.of(context).pop(_WalletOptions.showXpub);
}, },
onFiroShowLelantusCoins: () async {
Navigator.of(context).pop(_WalletOptions.lelantusCoins);
},
walletId: walletId, walletId: walletId,
); );
}, },
@ -174,6 +182,15 @@ class WalletOptionsButton extends StatelessWidget {
} }
} }
break; break;
case _WalletOptions.lelantusCoins:
unawaited(
Navigator.of(context).pushNamed(
LelantusCoinsView.routeName,
arguments: walletId,
),
);
break;
} }
} }
}, },
@ -206,6 +223,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
required this.onAddressListPressed, required this.onAddressListPressed,
required this.onShowXpubPressed, required this.onShowXpubPressed,
required this.onChangeRepPressed, required this.onChangeRepPressed,
required this.onFiroShowLelantusCoins,
required this.walletId, required this.walletId,
}) : super(key: key); }) : super(key: key);
@ -213,12 +231,16 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
final VoidCallback onAddressListPressed; final VoidCallback onAddressListPressed;
final VoidCallback onShowXpubPressed; final VoidCallback onShowXpubPressed;
final VoidCallback onChangeRepPressed; final VoidCallback onChangeRepPressed;
final VoidCallback onFiroShowLelantusCoins;
final String walletId; final String walletId;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final coin = ref.watch(pWalletCoin(walletId)); final coin = ref.watch(pWalletCoin(walletId));
final firoDebug =
kDebugMode && (coin == Coin.firo || coin == Coin.firoTestNet);
// TODO: [prio=low] // TODO: [prio=low]
// final bool xpubEnabled = manager.hasXPub; // final bool xpubEnabled = manager.hasXPub;
final bool xpubEnabled = false; final bool xpubEnabled = false;
@ -315,6 +337,43 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
), ),
), ),
), ),
if (firoDebug)
const SizedBox(
height: 8,
),
if (firoDebug)
TransparentButton(
onPressed: onFiroShowLelantusCoins,
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgPicture.asset(
Assets.svg.eye,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconLeft,
),
const SizedBox(width: 14),
Expanded(
child: Text(
_WalletOptions.lelantusCoins.prettyName,
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
),
],
),
),
),
if (xpubEnabled) if (xpubEnabled)
const SizedBox( const SizedBox(
height: 8, height: 8,

View file

@ -146,6 +146,7 @@ import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
import 'package:stackwallet/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
@ -1843,6 +1844,20 @@ class RouteGenerator {
} }
return _routeError("${settings.name} invalid args: ${args.toString()}"); return _routeError("${settings.name} invalid args: ${args.toString()}");
case LelantusCoinsView.routeName:
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => LelantusCoinsView(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case DesktopCoinControlView.routeName: case DesktopCoinControlView.routeName:
if (args is String) { if (args is String) {
return getRoute( return getRoute(

View file

@ -31,7 +31,7 @@ abstract final class LelantusFfiWrapper {
required final Bip39HDCurrency cryptoCurrency, required final Bip39HDCurrency cryptoCurrency,
required final int latestSetId, required final int latestSetId,
required final Map<dynamic, dynamic> setDataMap, required final Map<dynamic, dynamic> setDataMap,
required final List<String> usedSerialNumbers, required final Set<String> usedSerialNumbers,
required final String walletId, required final String walletId,
required final String partialDerivationPath, required final String partialDerivationPath,
}) async { }) async {
@ -59,16 +59,17 @@ abstract final class LelantusFfiWrapper {
// partialDerivationPath should be something like "m/$purpose'/$coinType'/$account'/" // partialDerivationPath should be something like "m/$purpose'/$coinType'/$account'/"
static Future<({List<String> spendTxIds, List<LelantusCoin> lelantusCoins})> static Future<({List<String> spendTxIds, List<LelantusCoin> lelantusCoins})>
_restore( _restore(
({ ({
String hexRootPrivateKey, String hexRootPrivateKey,
Uint8List chaincode, Uint8List chaincode,
Bip39HDCurrency cryptoCurrency, Bip39HDCurrency cryptoCurrency,
int latestSetId, int latestSetId,
Map<dynamic, dynamic> setDataMap, Map<dynamic, dynamic> setDataMap,
List<String> usedSerialNumbers, Set<String> usedSerialNumbers,
String walletId, String walletId,
String partialDerivationPath, String partialDerivationPath,
}) args) async { }) args,
) async {
List<int> jindexes = []; List<int> jindexes = [];
List<isar_models.LelantusCoin> lelantusCoins = []; List<isar_models.LelantusCoin> lelantusCoins = [];
@ -76,23 +77,20 @@ abstract final class LelantusFfiWrapper {
int lastFoundIndex = 0; int lastFoundIndex = 0;
int currentIndex = 0; int currentIndex = 0;
Set<String> usedSerialNumbersSet = args.usedSerialNumbers.toSet();
final root = BIP32.fromPrivateKey( final root = BIP32.fromPrivateKey(
args.hexRootPrivateKey.toUint8ListFromHex, args.hexRootPrivateKey.toUint8ListFromHex,
args.chaincode, args.chaincode,
); );
while (currentIndex < lastFoundIndex + 50) { while (currentIndex < lastFoundIndex + 50) {
final _derivePath = final mintKeyPair = root.derivePath(
"${args.partialDerivationPath}$MINT_INDEX/$currentIndex"; "${args.partialDerivationPath}$MINT_INDEX/$currentIndex",
);
final mintKeyPair = root.derivePath(_derivePath);
final String mintTag = lelantus.CreateTag( final String mintTag = lelantus.CreateTag(
mintKeyPair.privateKey!.toHex, mintKeyPair.privateKey!.toHex,
currentIndex, currentIndex,
Format.uint8listToString(mintKeyPair.identifier), mintKeyPair.identifier.toHex,
isTestnet: args.cryptoCurrency.network == CryptoCurrencyNetwork.test, isTestnet: args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
); );
@ -121,7 +119,7 @@ abstract final class LelantusFfiWrapper {
isTestnet: isTestnet:
args.cryptoCurrency.network == CryptoCurrencyNetwork.test, args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
); );
final bool isUsed = usedSerialNumbersSet.contains(serialNumber); final bool isUsed = args.usedSerialNumbers.contains(serialNumber);
lelantusCoins.removeWhere((e) => lelantusCoins.removeWhere((e) =>
e.txid == txId && e.txid == txId &&
@ -141,14 +139,13 @@ abstract final class LelantusFfiWrapper {
publicCoin, // not really needed but saved just in case publicCoin, // not really needed but saved just in case
), ),
); );
debugPrint("amount $amount used $isUsed"); debugPrint("serial=$serialNumber amount=$amount used=$isUsed");
} else if (thirdValue is String) { } else if (thirdValue is String) {
final int keyPath = lelantus.GetAesKeyPath(publicCoin); final int keyPath = lelantus.GetAesKeyPath(publicCoin);
final derivePath = final aesKeyPair = root.derivePath(
"${args.partialDerivationPath}$JMINT_INDEX/$keyPath"; "${args.partialDerivationPath}$JMINT_INDEX/$keyPath",
);
final aesKeyPair = root.derivePath(derivePath);
try { try {
final String aesPrivateKey = aesKeyPair.privateKey!.toHex; final String aesPrivateKey = aesKeyPair.privateKey!.toHex;
@ -165,7 +162,8 @@ abstract final class LelantusFfiWrapper {
isTestnet: isTestnet:
args.cryptoCurrency.network == CryptoCurrencyNetwork.test, args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
); );
bool isUsed = usedSerialNumbersSet.contains(serialNumber); final bool isUsed = args.usedSerialNumbers.contains(serialNumber);
lelantusCoins.removeWhere((e) => lelantusCoins.removeWhere((e) =>
e.txid == txId && e.txid == txId &&
e.mintIndex == currentIndex && e.mintIndex == currentIndex &&
@ -188,8 +186,7 @@ abstract final class LelantusFfiWrapper {
spendTxIds.add(txId); spendTxIds.add(txId);
} catch (_) { } catch (_) {
debugPrint( debugPrint("AES keypair derivation issue for key path: $keyPath");
"AES keypair derivation issue for derive path: $derivePath");
} }
} else { } else {
debugPrint("Unexpected coin found: $foundCoin"); debugPrint("Unexpected coin found: $foundCoin");
@ -312,9 +309,9 @@ abstract final class LelantusFfiWrapper {
isTestNet: arg.cryptoCurrency.network == CryptoCurrencyNetwork.test, isTestNet: arg.cryptoCurrency.network == CryptoCurrencyNetwork.test,
), ),
); );
var changeToMint = estimateJoinSplitFee.changeToMint; final changeToMint = estimateJoinSplitFee.changeToMint;
var fee = estimateJoinSplitFee.fee; final fee = estimateJoinSplitFee.fee;
var spendCoinIndexes = estimateJoinSplitFee.spendCoinIndexes; final spendCoinIndexes = estimateJoinSplitFee.spendCoinIndexes;
debugPrint("$changeToMint $fee $spendCoinIndexes"); debugPrint("$changeToMint $fee $spendCoinIndexes");
if (spendCoinIndexes.isEmpty) { if (spendCoinIndexes.isEmpty) {
throw Exception("Error, Not enough funds."); throw Exception("Error, Not enough funds.");

View file

@ -796,14 +796,17 @@ class FiroWallet extends Bip39HDWallet
setDataMapFuture, setDataMapFuture,
]); ]);
final usedSerialsSet = (futureResults[0] as List<String>).toSet();
final setDataMap = futureResults[1] as Map<dynamic, dynamic>;
await recoverLelantusWallet( await recoverLelantusWallet(
latestSetId: latestSetId, latestSetId: latestSetId,
setDataMap: futureResults[1] as Map<dynamic, dynamic>, usedSerialNumbers: usedSerialsSet,
usedSerialNumbers: futureResults[0] as List<String>, setDataMap: setDataMap,
); );
await updateBalance();
}); });
await refresh();
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"Exception rethrown from electrumx_mixin recover(): $e\n$s", "Exception rethrown from electrumx_mixin recover(): $e\n$s",

View file

@ -559,7 +559,7 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
Future<void> recoverLelantusWallet({ Future<void> recoverLelantusWallet({
required int latestSetId, required int latestSetId,
required Map<dynamic, dynamic> setDataMap, required Map<dynamic, dynamic> setDataMap,
required List<String> usedSerialNumbers, required Set<String> usedSerialNumbers,
}) async { }) async {
final root = await getRootHDNode(); final root = await getRootHDNode();
@ -568,6 +568,8 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
chain: 0, chain: 0,
index: 0, index: 0,
); );
// get "m/$purpose'/$coinType'/$account'/" from "m/$purpose'/$coinType'/$account'/0/0"
final partialDerivationPath = derivePath.substring( final partialDerivationPath = derivePath.substring(
0, 0,
derivePath.length - 3, derivePath.length - 3,
@ -593,12 +595,11 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
.or() .or()
.isLelantusEqualTo(false) .isLelantusEqualTo(false)
.findAll(); .findAll();
final lelantusCoins = result.lelantusCoins;
// Edit the receive transactions with the mint fees. // Edit the receive transactions with the mint fees.
List<Transaction> editedTransactions = []; List<Transaction> editedTransactions = [];
for (final coin in lelantusCoins) { for (final coin in result.lelantusCoins) {
String txid = coin.txid; String txid = coin.txid;
Transaction? tx; Transaction? tx;
try { try {
@ -675,7 +676,7 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
} }
transactionMap.removeWhere((key, value) => transactionMap.removeWhere((key, value) =>
lelantusCoins.any((element) => element.txid == key) || result.lelantusCoins.any((element) => element.txid == key) ||
((value.height == -1 || value.height == null) && ((value.height == -1 || value.height == null) &&
!value.isConfirmed(currentHeight, cryptoCurrency.minConfirms))); !value.isConfirmed(currentHeight, cryptoCurrency.minConfirms)));