fix firo spark cache being shared with test net

This commit is contained in:
Julian 2024-06-26 12:10:32 -06:00
parent 5b61744124
commit 0ef1726a00
11 changed files with 143 additions and 74 deletions

View file

@ -12,6 +12,7 @@ import '../../electrumx_rpc/electrumx_client.dart';
import '../../utilities/extensions/extensions.dart';
import '../../utilities/logger.dart';
import '../../utilities/stack_file_system.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
part 'firo_cache_coordinator.dart';
part 'firo_cache_reader.dart';
@ -31,29 +32,39 @@ void _debugLog(Object? object) {
abstract class _FiroCache {
static const int _setCacheVersion = 1;
static const int _tagsCacheVersion = 2;
static const String sparkSetCacheFileName =
"spark_set_v$_setCacheVersion.sqlite3";
static const String sparkUsedTagsCacheFileName =
"spark_tags_v$_tagsCacheVersion.sqlite3";
static Database? _setCacheDB;
static Database? _usedTagsCacheDB;
static Database get setCacheDB {
if (_setCacheDB == null) {
static final networks = [
CryptoCurrencyNetwork.main,
CryptoCurrencyNetwork.test,
];
static String sparkSetCacheFileName(CryptoCurrencyNetwork network) =>
network == CryptoCurrencyNetwork.main
? "spark_set_v$_setCacheVersion.sqlite3"
: "spark_set_v${_setCacheVersion}_${network.name}.sqlite3";
static String sparkUsedTagsCacheFileName(CryptoCurrencyNetwork network) =>
network == CryptoCurrencyNetwork.main
? "spark_tags_v$_tagsCacheVersion.sqlite3"
: "spark_tags_v${_tagsCacheVersion}_${network.name}.sqlite3";
static final Map<CryptoCurrencyNetwork, Database> _setCacheDB = {};
static final Map<CryptoCurrencyNetwork, Database> _usedTagsCacheDB = {};
static Database setCacheDB(CryptoCurrencyNetwork network) {
if (_setCacheDB[network] == null) {
throw Exception(
"FiroCache.init() must be called before accessing FiroCache.db!",
);
}
return _setCacheDB!;
return _setCacheDB[network]!;
}
static Database get usedTagsCacheDB {
if (_usedTagsCacheDB == null) {
static Database usedTagsCacheDB(CryptoCurrencyNetwork network) {
if (_usedTagsCacheDB[network] == null) {
throw Exception(
"FiroCache.init() must be called before accessing FiroCache.db!",
);
}
return _usedTagsCacheDB!;
return _usedTagsCacheDB[network]!;
}
static Future<void>? _initFuture;
@ -63,9 +74,12 @@ abstract class _FiroCache {
final sqliteDir =
await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final sparkSetCacheFile = File("${sqliteDir.path}/$sparkSetCacheFileName");
for (final network in networks) {
final sparkSetCacheFile =
File("${sqliteDir.path}/${sparkSetCacheFileName(network)}");
final sparkUsedTagsCacheFile =
File("${sqliteDir.path}/$sparkUsedTagsCacheFileName");
File("${sqliteDir.path}/${sparkUsedTagsCacheFileName(network)}");
if (!(await sparkSetCacheFile.exists())) {
await _createSparkSetCacheDb(sparkSetCacheFile.path);
@ -74,19 +88,20 @@ abstract class _FiroCache {
await _createSparkUsedTagsCacheDb(sparkUsedTagsCacheFile.path);
}
_setCacheDB = sqlite3.open(
_setCacheDB[network] = sqlite3.open(
sparkSetCacheFile.path,
mode: OpenMode.readWrite,
);
_usedTagsCacheDB = sqlite3.open(
_usedTagsCacheDB[network] = sqlite3.open(
sparkUsedTagsCacheFile.path,
mode: OpenMode.readWrite,
);
}
}
static Future<void> _deleteAllCache() async {
static Future<void> _deleteAllCache(CryptoCurrencyNetwork network) async {
final start = DateTime.now();
setCacheDB.execute(
setCacheDB(network).execute(
"""
DELETE FROM SparkSet;
DELETE FROM SparkCoin;
@ -94,7 +109,7 @@ abstract class _FiroCache {
VACUUM;
""",
);
usedTagsCacheDB.execute(
usedTagsCacheDB(network).execute(
"""
DELETE FROM SparkUsedCoinTags;
VACUUM;

View file

@ -5,7 +5,7 @@ typedef LTagPair = ({String tag, String txid});
/// Wrapper class for [_FiroCache] as [_FiroCache] should eventually be handled in a
/// background isolate and [FiroCacheCoordinator] should manage that isolate
abstract class FiroCacheCoordinator {
static _FiroCacheWorker? _worker;
static final Map<CryptoCurrencyNetwork, _FiroCacheWorker> _workers = {};
static bool _init = false;
static Future<void> init() async {
@ -14,20 +14,22 @@ abstract class FiroCacheCoordinator {
}
_init = true;
await _FiroCache.init();
_worker = await _FiroCacheWorker.spawn();
for (final network in _FiroCache.networks) {
_workers[network] = await _FiroCacheWorker.spawn(network);
}
}
static Future<void> clearSharedCache() async {
return await _FiroCache._deleteAllCache();
static Future<void> clearSharedCache(CryptoCurrencyNetwork network) async {
return await _FiroCache._deleteAllCache(network);
}
static Future<String> getSparkCacheSize() async {
static Future<String> getSparkCacheSize(CryptoCurrencyNetwork network) async {
final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final setCacheFile = File(
"${dir.path}/${_FiroCache.sparkSetCacheFileName}",
"${dir.path}/${_FiroCache.sparkSetCacheFileName(network)}",
);
final usedTagsCacheFile = File(
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName}",
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}",
);
final int bytes =
((await setCacheFile.exists()) ? await setCacheFile.length() : 0) +
@ -51,13 +53,14 @@ abstract class FiroCacheCoordinator {
static Future<void> runFetchAndUpdateSparkUsedCoinTags(
ElectrumXClient client,
CryptoCurrencyNetwork network,
) async {
final count = await FiroCacheCoordinator.getUsedCoinTagsCount();
final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network);
final unhashedTags = await client.getSparkUnhashedUsedCoinsTagsWithTxHashes(
startNumber: count,
);
if (unhashedTags.isNotEmpty) {
await _worker!.runTask(
await _workers[network]!.runTask(
FCTask(
func: FCFuncName._updateSparkUsedTagsWith,
data: unhashedTags,
@ -69,10 +72,12 @@ abstract class FiroCacheCoordinator {
static Future<void> runFetchAndUpdateSparkAnonSetCacheForGroupId(
int groupId,
ElectrumXClient client,
CryptoCurrencyNetwork network,
) async {
final blockhashResult =
await FiroCacheCoordinator.getLatestSetInfoForGroupId(
groupId,
network,
);
final blockHash = blockhashResult?.blockHash ?? "";
@ -81,7 +86,7 @@ abstract class FiroCacheCoordinator {
startBlockHash: blockHash.toHexReversedFromBase64,
);
await _worker!.runTask(
await _workers[network]!.runTask(
FCTask(
func: FCFuncName._updateSparkAnonSetCoinsWith,
data: (groupId, json),
@ -91,17 +96,22 @@ abstract class FiroCacheCoordinator {
// ===========================================================================
static Future<Set<String>> getUsedCoinTags(int startNumber) async {
static Future<Set<String>> getUsedCoinTags(
int startNumber,
CryptoCurrencyNetwork network,
) async {
final result = await _Reader._getSparkUsedCoinTags(
startNumber,
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
return result.map((e) => e["tag"] as String).toSet();
}
static Future<int> getUsedCoinTagsCount() async {
static Future<int> getUsedCoinTagsCount(
CryptoCurrencyNetwork network,
) async {
final result = await _Reader._getUsedCoinTagsCount(
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
if (result.isEmpty) {
return 0;
@ -111,13 +121,14 @@ abstract class FiroCacheCoordinator {
static Future<List<LTagPair>> getUsedCoinTxidsFor({
required List<String> tags,
required CryptoCurrencyNetwork network,
}) async {
if (tags.isEmpty) {
return [];
}
final result = await _Reader._getUsedCoinTxidsFor(
tags,
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
if (result.isEmpty) {
@ -135,20 +146,22 @@ abstract class FiroCacheCoordinator {
static Future<Set<String>> getUsedCoinTagsFor({
required String txid,
required CryptoCurrencyNetwork network,
}) async {
final result = await _Reader._getUsedCoinTagsFor(
txid,
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
return result.map((e) => e["tag"] as String).toSet();
}
static Future<bool> checkTagIsUsed(
String tag,
CryptoCurrencyNetwork network,
) async {
return await _Reader._checkTagIsUsed(
tag,
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
}
@ -161,10 +174,11 @@ abstract class FiroCacheCoordinator {
})>> getSetCoinsForGroupId(
int groupId, {
int? newerThanTimeStamp,
required CryptoCurrencyNetwork network,
}) async {
final resultSet = await _Reader._getSetCoinsForGroupId(
groupId,
db: _FiroCache.setCacheDB,
db: _FiroCache.setCacheDB(network),
newerThanTimeStamp: newerThanTimeStamp,
);
return resultSet
@ -187,10 +201,11 @@ abstract class FiroCacheCoordinator {
int timestampUTC,
})?> getLatestSetInfoForGroupId(
int groupId,
CryptoCurrencyNetwork network,
) async {
final result = await _Reader._getLatestSetInfoForGroupId(
groupId,
db: _FiroCache.setCacheDB,
db: _FiroCache.setCacheDB(network),
);
if (result.isEmpty) {
@ -206,10 +221,11 @@ abstract class FiroCacheCoordinator {
static Future<bool> checkSetInfoForGroupIdExists(
int groupId,
CryptoCurrencyNetwork network,
) async {
return await _Reader._checkSetInfoForGroupIdExists(
groupId,
db: _FiroCache.setCacheDB,
db: _FiroCache.setCacheDB(network),
);
}
}

View file

@ -25,11 +25,12 @@ class _FiroCacheWorker {
return await completer.future;
}
static Future<_FiroCacheWorker> spawn() async {
static Future<_FiroCacheWorker> spawn(CryptoCurrencyNetwork network) async {
final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final setCacheFilePath = "${dir.path}/${_FiroCache.sparkSetCacheFileName}";
final setCacheFilePath =
"${dir.path}/${_FiroCache.sparkSetCacheFileName(network)}";
final usedTagsCacheFilePath =
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName}";
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}";
final initPort = RawReceivePort();
final connection = Completer<(ReceivePort, SendPort)>.sync();

View file

@ -430,7 +430,9 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
),
if (coin is Firo)
FiroCacheCoordinator
.clearSharedCache(),
.clearSharedCache(
coin.network,
),
],
),
context: context,

View file

@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../db/sqlite/firo_cache.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../widgets/background.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/detail_item.dart';
@ -11,10 +12,13 @@ import '../../../../widgets/detail_item.dart';
class SparkInfoView extends ConsumerWidget {
const SparkInfoView({
super.key,
required this.walletId,
});
static const String routeName = "/sparkInfo";
final String walletId;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Background(
@ -37,7 +41,9 @@ class SparkInfoView extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(),
future: FiroCacheCoordinator.getSparkCacheSize(
ref.watch(pWalletCoin(walletId)).network,
),
builder: (_, snapshot) {
String detail = "Loading...";
if (snapshot.connectionState == ConnectionState.done) {

View file

@ -243,6 +243,7 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget {
onPressed: () {
Navigator.of(context).pushNamed(
SparkInfoView.routeName,
arguments: walletId,
);
},
child: Padding(

View file

@ -294,7 +294,9 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
width: 2,
),
FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(),
future: FiroCacheCoordinator.getSparkCacheSize(
wallet.cryptoCurrency.network,
),
builder: (_, snapshot) => Text(
snapshot.data ?? "",
),

View file

@ -19,8 +19,7 @@ import '../../../../../providers/global/wallets_provider.dart';
import '../../../../../themes/stack_colors.dart';
import '../../../../../utilities/assets.dart';
import '../../../../../utilities/text_styles.dart';
import '../../../../../wallets/crypto_currency/coins/banano.dart';
import '../../../../../wallets/crypto_currency/coins/firo.dart';
import '../../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../../wallets/isar/models/wallet_info.dart';
import '../../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
@ -187,7 +186,9 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
onPressed: () async => widget.onFusionPressed?.call(),
),
if (wallet is SparkInterface)
const _MoreFeaturesClearSparkCacheItem(),
_MoreFeaturesClearSparkCacheItem(
cryptoCurrency: wallet.cryptoCurrency,
),
if (wallet is LelantusInterface)
_MoreFeaturesItemBase(
child: Row(
@ -371,10 +372,10 @@ class _MoreFeaturesItemBase extends StatelessWidget {
class _MoreFeaturesClearSparkCacheItem extends StatefulWidget {
const _MoreFeaturesClearSparkCacheItem({
super.key,
required this.cryptoCurrency,
});
static const double iconSizeBG = 46;
static const double iconSize = 24;
final CryptoCurrency cryptoCurrency;
@override
State<_MoreFeaturesClearSparkCacheItem> createState() =>
@ -396,7 +397,9 @@ class _MoreFeaturesClearSparkCacheItemState
}
_onPressedLock = true;
try {
await FiroCacheCoordinator.clearSharedCache();
await FiroCacheCoordinator.clearSharedCache(
widget.cryptoCurrency.network,
);
setState(() {
// trigger rebuild for cache size display
});
@ -434,7 +437,9 @@ class _MoreFeaturesClearSparkCacheItemState
style: STextStyles.w600_20(context),
),
FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(),
future: FiroCacheCoordinator.getSparkCacheSize(
widget.cryptoCurrency.network,
),
builder: (_, snapshot) {
return Text(
snapshot.data ?? "",

View file

@ -1982,13 +1982,18 @@ class RouteGenerator {
return _routeError("${settings.name} invalid args: ${args.toString()}");
case SparkInfoView.routeName:
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => const SparkInfoView(),
builder: (_) => SparkInfoView(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
// == Desktop specific routes ============================================
case CreatePasswordView.routeName:

View file

@ -387,6 +387,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
parseAnonFees();
final tags = await FiroCacheCoordinator.getUsedCoinTagsFor(
txid: txData["txid"] as String,
network: cryptoCurrency.network,
);
spentSparkCoins = sparkCoinsInvolvedSpent
.where(
@ -712,12 +713,14 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
i,
electrumXClient,
cryptoCurrency.network,
),
);
}
final sparkUsedCoinTagsFuture =
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
electrumXClient,
cryptoCurrency.network,
);
// receiving addresses

View file

@ -278,13 +278,17 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> setMaps = [];
final List<({int groupId, String blockHash})> idAndBlockHashes = [];
for (int i = 1; i <= currentId; i++) {
final resultSet = await FiroCacheCoordinator.getSetCoinsForGroupId(i);
final resultSet = await FiroCacheCoordinator.getSetCoinsForGroupId(
i,
network: cryptoCurrency.network,
);
if (resultSet.isEmpty) {
continue;
}
final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
i,
cryptoCurrency.network,
);
if (info == null) {
throw Exception("The `info` should never be null here");
@ -741,6 +745,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final setExists =
await FiroCacheCoordinator.checkSetInfoForGroupIdExists(
id,
cryptoCurrency.network,
);
if (!setExists) {
groupIds.add(id);
@ -755,6 +760,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
e,
electrumXClient,
cryptoCurrency.network,
),
);
@ -763,6 +769,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
...possibleFutures,
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
electrumXClient,
cryptoCurrency.network,
),
]);
@ -782,11 +789,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
groupIdTimestampUTCMap[i.toString()] as int? ?? 0;
final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
i,
cryptoCurrency.network,
);
final anonymitySetResult =
await FiroCacheCoordinator.getSetCoinsForGroupId(
i,
newerThanTimeStamp: lastCheckedTimeStampUTC,
network: cryptoCurrency.network,
);
final coinsRaw = anonymitySetResult
.map(
@ -882,7 +891,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
// only fetch tags from db if we need them to compare against any items
// in coinsToCheck
if (coinsToCheck.isNotEmpty) {
spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(0);
spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(
0,
cryptoCurrency.network,
);
}
// check and update coins if required
@ -992,6 +1004,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final pairs = await FiroCacheCoordinator.getUsedCoinTxidsFor(
tags: tags,
network: cryptoCurrency.network,
);
pairs.removeWhere((e) => usedCoinTxidsFoundLocally.contains(e.txid));