mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-18 16:44:32 +00:00
cache used spark tags in sqlite as well
This commit is contained in:
parent
d99231c973
commit
08f01d3141
8 changed files with 357 additions and 159 deletions
|
@ -717,11 +717,13 @@ class DbVersionMigrator with WalletDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _v12(SecureStorageInterface secureStore) async {
|
Future<void> _v12(SecureStorageInterface secureStore) async {
|
||||||
|
for (final identifier in ["firo", "firoTestNet"]) {
|
||||||
await DB.instance.deleteBoxFromDisk(
|
await DB.instance.deleteBoxFromDisk(
|
||||||
boxName: "firo_anonymitySetSparkCache",
|
boxName: "${identifier}_anonymitySetSparkCache",
|
||||||
);
|
);
|
||||||
await DB.instance.deleteBoxFromDisk(
|
await DB.instance.deleteBoxFromDisk(
|
||||||
boxName: "firoTestNet_anonymitySetSparkCache",
|
boxName: "${identifier}_sparkUsedCoinsTagsCache",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,8 +58,6 @@ class DB {
|
||||||
"${currency.identifier}_anonymitySetCache";
|
"${currency.identifier}_anonymitySetCache";
|
||||||
String _boxNameUsedSerialsCache({required CryptoCurrency currency}) =>
|
String _boxNameUsedSerialsCache({required CryptoCurrency currency}) =>
|
||||||
"${currency.identifier}_usedSerialsCache";
|
"${currency.identifier}_usedSerialsCache";
|
||||||
String _boxNameSparkUsedCoinsTagsCache({required CryptoCurrency currency}) =>
|
|
||||||
"${currency.identifier}_sparkUsedCoinsTagsCache";
|
|
||||||
|
|
||||||
Box<NodeModel>? _boxNodeModels;
|
Box<NodeModel>? _boxNodeModels;
|
||||||
Box<NodeModel>? _boxPrimaryNodes;
|
Box<NodeModel>? _boxPrimaryNodes;
|
||||||
|
@ -229,18 +227,6 @@ class DB {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Box<dynamic>> getSparkUsedCoinsTagsCacheBox({
|
|
||||||
required CryptoCurrency currency,
|
|
||||||
}) async {
|
|
||||||
if (_getSparkUsedCoinsTagsCacheBoxes[currency.identifier]?.isOpen != true) {
|
|
||||||
_getSparkUsedCoinsTagsCacheBoxes.remove(currency.identifier);
|
|
||||||
}
|
|
||||||
return _getSparkUsedCoinsTagsCacheBoxes[currency.identifier] ??=
|
|
||||||
await Hive.openBox<dynamic>(
|
|
||||||
_boxNameSparkUsedCoinsTagsCache(currency: currency),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> closeUsedSerialsCacheBox({
|
Future<void> closeUsedSerialsCacheBox({
|
||||||
required CryptoCurrency currency,
|
required CryptoCurrency currency,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -257,9 +243,6 @@ class DB {
|
||||||
await deleteAll<dynamic>(
|
await deleteAll<dynamic>(
|
||||||
boxName: _boxNameUsedSerialsCache(currency: currency),
|
boxName: _boxNameUsedSerialsCache(currency: currency),
|
||||||
);
|
);
|
||||||
await deleteAll<dynamic>(
|
|
||||||
boxName: _boxNameSparkUsedCoinsTagsCache(currency: currency),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,11 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||||
import 'package:sqlite3/sqlite3.dart';
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
|
|
||||||
import '../../electrumx_rpc/electrumx_client.dart';
|
import '../../electrumx_rpc/electrumx_client.dart';
|
||||||
|
import '../../utilities/extensions/extensions.dart';
|
||||||
import '../../utilities/logger.dart';
|
import '../../utilities/logger.dart';
|
||||||
import '../../utilities/stack_file_system.dart';
|
import '../../utilities/stack_file_system.dart';
|
||||||
|
|
||||||
|
@ -18,11 +20,31 @@ void _debugLog(Object? object) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper class for [FiroCache] as [FiroCache] should eventually be handled in a
|
List<String> _ffiHashTagsComputeWrapper(List<String> base64Tags) {
|
||||||
|
return LibSpark.hashTags(base64Tags: base64Tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper class for [_FiroCache] as [_FiroCache] should eventually be handled in a
|
||||||
/// background isolate and [FiroCacheCoordinator] should manage that isolate
|
/// background isolate and [FiroCacheCoordinator] should manage that isolate
|
||||||
abstract class FiroCacheCoordinator {
|
abstract class FiroCacheCoordinator {
|
||||||
static Future<void> init() => _FiroCache.init();
|
static Future<void> init() => _FiroCache.init();
|
||||||
|
|
||||||
|
static Future<void> runFetchAndUpdateSparkUsedCoinTags(
|
||||||
|
ElectrumXClient client,
|
||||||
|
) async {
|
||||||
|
final count = await FiroCacheCoordinator.getUsedCoinTagsLastAddedRowId();
|
||||||
|
final unhashedTags = await client.getSparkUnhashedUsedCoinsTags(
|
||||||
|
startNumber: count,
|
||||||
|
);
|
||||||
|
if (unhashedTags.isNotEmpty) {
|
||||||
|
final hashedTags = await compute(
|
||||||
|
_ffiHashTagsComputeWrapper,
|
||||||
|
unhashedTags,
|
||||||
|
);
|
||||||
|
await _FiroCache._updateSparkUsedTagsWith(hashedTags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> runFetchAndUpdateSparkAnonSetCacheForGroupId(
|
static Future<void> runFetchAndUpdateSparkAnonSetCacheForGroupId(
|
||||||
int groupId,
|
int groupId,
|
||||||
ElectrumXClient client,
|
ElectrumXClient client,
|
||||||
|
@ -38,7 +60,36 @@ abstract class FiroCacheCoordinator {
|
||||||
startBlockHash: blockHash.toHexReversedFromBase64,
|
startBlockHash: blockHash.toHexReversedFromBase64,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _FiroCache._updateWith(json, groupId);
|
await _FiroCache._updateSparkAnonSetCoinsWith(json, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
static Future<Set<String>> getUsedCoinTags(int startNumber) async {
|
||||||
|
final result = await _FiroCache._getSparkUsedCoinTags(
|
||||||
|
startNumber,
|
||||||
|
);
|
||||||
|
return result.map((e) => e["tag"] as String).toSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This should be the equivalent of counting the number of tags in the db.
|
||||||
|
/// Assuming the integrity of the data. Faster than actually calling count on
|
||||||
|
/// a table where no records have been deleted. None should be deleted from
|
||||||
|
/// this table in practice.
|
||||||
|
static Future<int> getUsedCoinTagsLastAddedRowId() async {
|
||||||
|
final result = await _FiroCache._getUsedCoinTagsLastAddedRowId();
|
||||||
|
if (result.isEmpty) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return result.first["highestId"] as int? ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> checkTagIsUsed(
|
||||||
|
String tag,
|
||||||
|
) async {
|
||||||
|
return await _FiroCache._checkTagIsUsed(
|
||||||
|
tag,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<ResultSet> getSetCoinsForGroupId(
|
static Future<ResultSet> getSetCoinsForGroupId(
|
||||||
|
@ -71,6 +122,14 @@ abstract class FiroCacheCoordinator {
|
||||||
timestampUTC: result.first["timestampUTC"] as int,
|
timestampUTC: result.first["timestampUTC"] as int,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<bool> checkSetInfoForGroupIdExists(
|
||||||
|
int groupId,
|
||||||
|
) async {
|
||||||
|
return await _FiroCache._checkSetInfoForGroupIdExists(
|
||||||
|
groupId,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _FiroCache {
|
abstract class _FiroCache {
|
||||||
|
@ -137,6 +196,11 @@ abstract class _FiroCache {
|
||||||
FOREIGN KEY (setId) REFERENCES SparkSet(id),
|
FOREIGN KEY (setId) REFERENCES SparkSet(id),
|
||||||
FOREIGN KEY (coinId) REFERENCES SparkCoin(id)
|
FOREIGN KEY (coinId) REFERENCES SparkCoin(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE SparkUsedCoinTags (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
tag TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
""",
|
""",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -181,10 +245,64 @@ abstract class _FiroCache {
|
||||||
return db.select("$query;");
|
return db.select("$query;");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
static Future<bool> _checkSetInfoForGroupIdExists(
|
||||||
// ===========================================================================
|
int groupId,
|
||||||
|
) async {
|
||||||
|
final query = """
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM SparkSet
|
||||||
|
WHERE groupId = $groupId
|
||||||
|
) AS setExists;
|
||||||
|
""";
|
||||||
|
|
||||||
static int _upCount = 0;
|
return db.select("$query;").first["setExists"] == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// =============== Spark used coin tags queries ==============================
|
||||||
|
|
||||||
|
static Future<ResultSet> _getSparkUsedCoinTags(
|
||||||
|
int startNumber,
|
||||||
|
) async {
|
||||||
|
String query = """
|
||||||
|
SELECT tag
|
||||||
|
FROM SparkUsedCoinTags
|
||||||
|
""";
|
||||||
|
|
||||||
|
if (startNumber > 0) {
|
||||||
|
query += " WHERE id >= $startNumber";
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.select("$query;");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<ResultSet> _getUsedCoinTagsLastAddedRowId() async {
|
||||||
|
const query = """
|
||||||
|
SELECT MAX(id) AS highestId
|
||||||
|
FROM SparkUsedCoinTags;
|
||||||
|
""";
|
||||||
|
|
||||||
|
return db.select("$query;");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> _checkTagIsUsed(String tag) async {
|
||||||
|
final query = """
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM SparkUsedCoinTags
|
||||||
|
WHERE tag = '$tag'
|
||||||
|
) AS tagExists;
|
||||||
|
""";
|
||||||
|
|
||||||
|
return db.select("$query;").first["tagExists"] == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// ================== write to spark used tags cache =========================
|
||||||
|
|
||||||
|
// debug log counter var
|
||||||
|
static int _updateTagsCount = 0;
|
||||||
|
|
||||||
/// update the sqlite cache
|
/// update the sqlite cache
|
||||||
/// Expected json format:
|
/// Expected json format:
|
||||||
|
@ -201,20 +319,123 @@ abstract class _FiroCache {
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// returns true if successful, otherwise false
|
/// returns true if successful, otherwise false
|
||||||
static Future<bool> _updateWith(
|
static Future<bool> _updateSparkUsedTagsWith(
|
||||||
|
List<String> tags,
|
||||||
|
) async {
|
||||||
|
final start = DateTime.now();
|
||||||
|
_updateTagsCount++;
|
||||||
|
|
||||||
|
if (tags.isEmpty) {
|
||||||
|
_debugLog(
|
||||||
|
"$_updateTagsCount _updateSparkUsedTagsWith(tags) called "
|
||||||
|
"where tags is empty",
|
||||||
|
);
|
||||||
|
_debugLog(
|
||||||
|
"$_updateTagsCount _updateSparkUsedTagsWith() "
|
||||||
|
"duration = ${DateTime.now().difference(start)}",
|
||||||
|
);
|
||||||
|
// nothing to add, return early
|
||||||
|
return true;
|
||||||
|
} else if (tags.length <= 10) {
|
||||||
|
_debugLog("$_updateTagsCount _updateSparkUsedTagsWith() called where "
|
||||||
|
"tags.length=${tags.length}, tags: $tags,");
|
||||||
|
} else {
|
||||||
|
_debugLog(
|
||||||
|
"$_updateTagsCount _updateSparkUsedTagsWith() called where"
|
||||||
|
" tags.length=${tags.length},"
|
||||||
|
" first 5 tags: ${tags.sublist(0, 5)},"
|
||||||
|
" last 5 tags: ${tags.sublist(tags.length - 5, tags.length)}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.execute("BEGIN;");
|
||||||
|
try {
|
||||||
|
for (final tag in tags) {
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
INSERT OR IGNORE INTO SparkUsedCoinTags (tag)
|
||||||
|
VALUES (?);
|
||||||
|
""",
|
||||||
|
[tag],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.execute("COMMIT;");
|
||||||
|
_debugLog("$_updateTagsCount _updateSparkUsedTagsWith() COMMITTED");
|
||||||
|
_debugLog(
|
||||||
|
"$_updateTagsCount _updateSparkUsedTagsWith() "
|
||||||
|
"duration = ${DateTime.now().difference(start)}",
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (e, s) {
|
||||||
|
db.execute("ROLLBACK;");
|
||||||
|
_debugLog("$_updateTagsCount _updateSparkUsedTagsWith() ROLLBACK");
|
||||||
|
_debugLog(
|
||||||
|
"$_updateTagsCount _updateSparkUsedTagsWith() "
|
||||||
|
"duration = ${DateTime.now().difference(start)}",
|
||||||
|
);
|
||||||
|
// NOTE THIS LOGGER MUST BE CALLED ON MAIN ISOLATE FOR NOW
|
||||||
|
Logging.instance.log(
|
||||||
|
"$e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// ================== write to spark anon set cache ==========================
|
||||||
|
|
||||||
|
// debug log counter var
|
||||||
|
static int _updateAnonSetCount = 0;
|
||||||
|
|
||||||
|
/// update the sqlite cache
|
||||||
|
/// Expected json format:
|
||||||
|
/// {
|
||||||
|
/// "blockHash": "someBlockHash",
|
||||||
|
/// "setHash": "someSetHash",
|
||||||
|
/// "coins": [
|
||||||
|
/// ["serliazed1", "hash1", "context1"],
|
||||||
|
/// ["serliazed2", "hash2", "context2"],
|
||||||
|
/// ...
|
||||||
|
/// ["serliazed3", "hash3", "context3"],
|
||||||
|
/// ["serliazed4", "hash4", "context4"],
|
||||||
|
/// ],
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// returns true if successful, otherwise false
|
||||||
|
static Future<bool> _updateSparkAnonSetCoinsWith(
|
||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
int groupId,
|
int groupId,
|
||||||
) async {
|
) async {
|
||||||
final start = DateTime.now();
|
final start = DateTime.now();
|
||||||
_upCount++;
|
_updateAnonSetCount++;
|
||||||
final blockHash = json["blockHash"] as String;
|
final blockHash = json["blockHash"] as String;
|
||||||
final setHash = json["setHash"] as String;
|
final setHash = json["setHash"] as String;
|
||||||
|
final coinsRaw = json["coins"] as List;
|
||||||
|
|
||||||
_debugLog(
|
_debugLog(
|
||||||
"$_upCount _updateWith() called where groupId=$groupId,"
|
"$_updateAnonSetCount _updateSparkAnonSetCoinsWith() "
|
||||||
" blockHash=$blockHash, setHash=$setHash",
|
"called where groupId=$groupId, "
|
||||||
|
"blockHash=$blockHash (${blockHash.toHexReversedFromBase64}), "
|
||||||
|
"setHash=$setHash, "
|
||||||
|
"coins.length: ${coinsRaw.isEmpty ? 0 : coinsRaw.length}",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ((json["coins"] as List).isEmpty) {
|
||||||
|
_debugLog(
|
||||||
|
"$_updateAnonSetCount _updateSparkAnonSetCoinsWith()"
|
||||||
|
" called where json[coins] is Empty",
|
||||||
|
);
|
||||||
|
_debugLog(
|
||||||
|
"$_updateAnonSetCount _updateSparkAnonSetCoinsWith()"
|
||||||
|
" duration = ${DateTime.now().difference(start)}",
|
||||||
|
);
|
||||||
|
// no coins to actually insert
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
final checkResult = db.select(
|
final checkResult = db.select(
|
||||||
"""
|
"""
|
||||||
SELECT *
|
SELECT *
|
||||||
|
@ -228,26 +449,21 @@ abstract class _FiroCache {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
_debugLog("$_upCount _updateWith() called where checkResult=$checkResult");
|
_debugLog(
|
||||||
|
"$_updateAnonSetCount _updateSparkAnonSetCoinsWith()"
|
||||||
|
" called where checkResult=$checkResult",
|
||||||
|
);
|
||||||
|
|
||||||
if (checkResult.isNotEmpty) {
|
if (checkResult.isNotEmpty) {
|
||||||
_debugLog(
|
_debugLog(
|
||||||
"$_upCount _updateWith() duration = ${DateTime.now().difference(start)}",
|
"$_updateAnonSetCount _updateSparkAnonSetCoinsWith()"
|
||||||
|
" duration = ${DateTime.now().difference(start)}",
|
||||||
);
|
);
|
||||||
// already up to date
|
// already up to date
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((json["coins"] as List).isEmpty) {
|
final coins = coinsRaw
|
||||||
_debugLog("$_upCount _updateWith() called where json[coins] is Empty");
|
|
||||||
_debugLog(
|
|
||||||
"$_upCount _updateWith() duration = ${DateTime.now().difference(start)}",
|
|
||||||
);
|
|
||||||
// no coins to actually insert
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final coins = (json["coins"] as List)
|
|
||||||
.map(
|
.map(
|
||||||
(e) => [
|
(e) => [
|
||||||
e[0] as String,
|
e[0] as String,
|
||||||
|
@ -307,16 +523,20 @@ abstract class _FiroCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
db.execute("COMMIT;");
|
db.execute("COMMIT;");
|
||||||
_debugLog("$_upCount _updateWith() COMMITTED");
|
|
||||||
_debugLog(
|
_debugLog(
|
||||||
"$_upCount _updateWith() duration = ${DateTime.now().difference(start)}",
|
"$_updateAnonSetCount _updateSparkAnonSetCoinsWith() COMMITTED",
|
||||||
|
);
|
||||||
|
_debugLog(
|
||||||
|
"$_updateAnonSetCount _updateSparkAnonSetCoinsWith() duration"
|
||||||
|
" = ${DateTime.now().difference(start)}",
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
db.execute("ROLLBACK;");
|
db.execute("ROLLBACK;");
|
||||||
_debugLog("$_upCount _updateWith() ROLLBACK");
|
_debugLog("$_updateAnonSetCount _updateSparkAnonSetCoinsWith() ROLLBACK");
|
||||||
_debugLog(
|
_debugLog(
|
||||||
"$_upCount _updateWith() duration = ${DateTime.now().difference(start)}",
|
"$_updateAnonSetCount _updateSparkAnonSetCoinsWith()"
|
||||||
|
" duration = ${DateTime.now().difference(start)}",
|
||||||
);
|
);
|
||||||
// NOTE THIS LOGGER MUST BE CALLED ON MAIN ISOLATE FOR NOW
|
// NOTE THIS LOGGER MUST BE CALLED ON MAIN ISOLATE FOR NOW
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
|
|
|
@ -220,53 +220,6 @@ class CachedElectrumXClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<String>> getSparkUsedCoinsTags({
|
|
||||||
required CryptoCurrency cryptoCurrency,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final box = await DB.instance.getSparkUsedCoinsTagsCacheBox(
|
|
||||||
currency: cryptoCurrency,
|
|
||||||
);
|
|
||||||
|
|
||||||
final _list = box.get("tags") as List?;
|
|
||||||
|
|
||||||
final Set<String> cachedTags =
|
|
||||||
_list == null ? {} : List<String>.from(_list).toSet();
|
|
||||||
|
|
||||||
final startNumber = max(
|
|
||||||
0,
|
|
||||||
cachedTags.length - 100, // 100 being some arbitrary buffer
|
|
||||||
);
|
|
||||||
|
|
||||||
final newTags = await electrumXClient.getSparkUsedCoinsTags(
|
|
||||||
startNumber: startNumber,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ensure we are getting some overlap so we know we are not missing any
|
|
||||||
if (cachedTags.isNotEmpty && newTags.isNotEmpty) {
|
|
||||||
assert(cachedTags.intersection(newTags).isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make newTags an Iterable<String>.
|
|
||||||
final Iterable<String> iterableTags = newTags.map((e) => e.toString());
|
|
||||||
|
|
||||||
cachedTags.addAll(iterableTags);
|
|
||||||
|
|
||||||
await box.put(
|
|
||||||
"tags",
|
|
||||||
cachedTags.toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return cachedTags;
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to process CachedElectrumX.getSparkUsedCoinsTags(): $e\n$s",
|
|
||||||
level: LogLevel.Error,
|
|
||||||
);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all cached transactions for the specified coin
|
/// Clear all cached transactions for the specified coin
|
||||||
Future<void> clearSharedTransactionCache({
|
Future<void> clearSharedTransactionCache({
|
||||||
required CryptoCurrency cryptoCurrency,
|
required CryptoCurrency cryptoCurrency,
|
||||||
|
|
|
@ -17,8 +17,6 @@ import 'package:electrum_adapter/electrum_adapter.dart' as electrum_adapter;
|
||||||
import 'package:electrum_adapter/electrum_adapter.dart';
|
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||||
import 'package:electrum_adapter/methods/specific/firo.dart';
|
import 'package:electrum_adapter/methods/specific/firo.dart';
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:stream_channel/stream_channel.dart';
|
import 'package:stream_channel/stream_channel.dart';
|
||||||
|
|
||||||
|
@ -922,7 +920,7 @@ class ElectrumXClient {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Finished ElectrumXClient.getSparkAnonymitySet(coinGroupId"
|
"Finished ElectrumXClient.getSparkAnonymitySet(coinGroupId"
|
||||||
"=$coinGroupId, startBlockHash=$startBlockHash). "
|
"=$coinGroupId, startBlockHash=$startBlockHash). "
|
||||||
""
|
"coins.length: ${(response["coins"] as List?)?.length}"
|
||||||
"Duration=${DateTime.now().difference(start)}",
|
"Duration=${DateTime.now().difference(start)}",
|
||||||
level: LogLevel.Info,
|
level: LogLevel.Info,
|
||||||
);
|
);
|
||||||
|
@ -934,16 +932,12 @@ class ElectrumXClient {
|
||||||
|
|
||||||
/// Takes [startNumber], if it is 0, we get the full set,
|
/// Takes [startNumber], if it is 0, we get the full set,
|
||||||
/// otherwise the used tags after that number
|
/// otherwise the used tags after that number
|
||||||
Future<Set<String>> getSparkUsedCoinsTags({
|
Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||||
String? requestID,
|
String? requestID,
|
||||||
required int startNumber,
|
required int startNumber,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
// Use electrum_adapter package's getSparkUsedCoinsTags method.
|
final start = DateTime.now();
|
||||||
Logging.instance.log(
|
|
||||||
"attempting to fetch spark.getusedcoinstags...",
|
|
||||||
level: LogLevel.Info,
|
|
||||||
);
|
|
||||||
await _checkElectrumAdapter();
|
await _checkElectrumAdapter();
|
||||||
final Map<String, dynamic> response =
|
final Map<String, dynamic> response =
|
||||||
await (getElectrumAdapter() as FiroElectrumClient)
|
await (getElectrumAdapter() as FiroElectrumClient)
|
||||||
|
@ -955,8 +949,16 @@ class ElectrumXClient {
|
||||||
level: LogLevel.Info,
|
level: LogLevel.Info,
|
||||||
);
|
);
|
||||||
final map = Map<String, dynamic>.from(response);
|
final map = Map<String, dynamic>.from(response);
|
||||||
final set = Set<String>.from(map["tags"] as List);
|
final tags = List<String>.from(map["tags"] as List);
|
||||||
return await compute(_ffiHashTagsComputeWrapper, set);
|
|
||||||
|
Logging.instance.log(
|
||||||
|
"Finished ElectrumXClient.getSparkUnhashedUsedCoinsTags(startNumber"
|
||||||
|
"=$startNumber). "
|
||||||
|
"Duration=${DateTime.now().difference(start)}",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
|
||||||
|
return tags;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logging.instance.log(e, level: LogLevel.Error);
|
Logging.instance.log(e, level: LogLevel.Error);
|
||||||
rethrow;
|
rethrow;
|
||||||
|
@ -1093,7 +1095,3 @@ class ElectrumXClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> _ffiHashTagsComputeWrapper(Set<String> base64Tags) {
|
|
||||||
return LibSpark.hashTags(base64Tags: base64Tags);
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,5 +13,6 @@ export 'impl/box_shadow.dart';
|
||||||
export 'impl/cl_transaction.dart';
|
export 'impl/cl_transaction.dart';
|
||||||
export 'impl/contract_abi.dart';
|
export 'impl/contract_abi.dart';
|
||||||
export 'impl/gradient.dart';
|
export 'impl/gradient.dart';
|
||||||
|
export 'impl/list.dart';
|
||||||
export 'impl/string.dart';
|
export 'impl/string.dart';
|
||||||
export 'impl/uint8_list.dart';
|
export 'impl/uint8_list.dart';
|
||||||
|
|
|
@ -588,7 +588,9 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> recover({required bool isRescan}) async {
|
Future<void> recover({required bool isRescan}) async {
|
||||||
|
// reset last checked values
|
||||||
groupIdTimestampUTCMap = {};
|
groupIdTimestampUTCMap = {};
|
||||||
|
|
||||||
final start = DateTime.now();
|
final start = DateTime.now();
|
||||||
final root = await getRootHDNode();
|
final root = await getRootHDNode();
|
||||||
|
|
||||||
|
@ -633,8 +635,8 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final sparkUsedCoinTagsFuture =
|
final sparkUsedCoinTagsFuture =
|
||||||
electrumXCachedClient.getSparkUsedCoinsTags(
|
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
|
||||||
cryptoCurrency: info.coin,
|
electrumXClient,
|
||||||
);
|
);
|
||||||
|
|
||||||
// receiving addresses
|
// receiving addresses
|
||||||
|
@ -754,9 +756,6 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
||||||
final usedSerialsSet = (futureResults[0] as List<String>).toSet();
|
final usedSerialsSet = (futureResults[0] as List<String>).toSet();
|
||||||
final setDataMap = futureResults[1] as Map<dynamic, dynamic>;
|
final setDataMap = futureResults[1] as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
// spark
|
|
||||||
final sparkSpentCoinTags = futureResults[2] as Set<String>;
|
|
||||||
|
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
recoverLelantusWallet(
|
recoverLelantusWallet(
|
||||||
|
@ -765,7 +764,6 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
||||||
setDataMap: setDataMap,
|
setDataMap: setDataMap,
|
||||||
),
|
),
|
||||||
recoverSparkWallet(
|
recoverSparkWallet(
|
||||||
spentCoinTags: sparkSpentCoinTags,
|
|
||||||
latestSparkCoinId: latestSparkCoinId,
|
latestSparkCoinId: latestSparkCoinId,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
@ -776,7 +774,6 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
||||||
setDataMap: setDataMap,
|
setDataMap: setDataMap,
|
||||||
);
|
);
|
||||||
await recoverSparkWallet(
|
await recoverSparkWallet(
|
||||||
spentCoinTags: sparkSpentCoinTags,
|
|
||||||
latestSparkCoinId: latestSparkCoinId,
|
latestSparkCoinId: latestSparkCoinId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -631,12 +631,41 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
|
|
||||||
Future<void> refreshSparkData() async {
|
Future<void> refreshSparkData() async {
|
||||||
try {
|
try {
|
||||||
final spentCoinTags = await electrumXCachedClient.getSparkUsedCoinsTags(
|
// start by checking if any previous sets are missing from db and add the
|
||||||
cryptoCurrency: info.coin,
|
// missing groupIds to the list if sets to check and update
|
||||||
|
final latestGroupId = await electrumXClient.getSparkLatestCoinId();
|
||||||
|
final List<int> groupIds = [];
|
||||||
|
if (latestGroupId > 1) {
|
||||||
|
for (int id = 1; id < latestGroupId; id++) {
|
||||||
|
final setExists =
|
||||||
|
await FiroCacheCoordinator.checkSetInfoForGroupIdExists(
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
if (!setExists) {
|
||||||
|
groupIds.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupIds.add(latestGroupId);
|
||||||
|
|
||||||
|
// start fetch and update process for each set groupId as required
|
||||||
|
final possibleFutures = groupIds.map(
|
||||||
|
(e) =>
|
||||||
|
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
|
||||||
|
e,
|
||||||
|
electrumXClient,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await _checkAndUpdateCoins(spentCoinTags, true);
|
// wait for each fetch and update to complete
|
||||||
|
await Future.wait([
|
||||||
|
...possibleFutures,
|
||||||
|
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
|
||||||
|
electrumXClient,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await _checkAndUpdateCoins();
|
||||||
// refresh spark balance
|
// refresh spark balance
|
||||||
await refreshSparkBalance();
|
await refreshSparkBalance();
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -697,7 +726,6 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
/// Should only be called within the standard wallet [recover] function due to
|
/// Should only be called within the standard wallet [recover] function due to
|
||||||
/// mutex locking. Otherwise behaviour MAY be undefined.
|
/// mutex locking. Otherwise behaviour MAY be undefined.
|
||||||
Future<void> recoverSparkWallet({
|
Future<void> recoverSparkWallet({
|
||||||
required Set<String> spentCoinTags,
|
|
||||||
required int latestSparkCoinId,
|
required int latestSparkCoinId,
|
||||||
}) async {
|
}) async {
|
||||||
// generate spark addresses if non existing
|
// generate spark addresses if non existing
|
||||||
|
@ -707,7 +735,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _checkAndUpdateCoins(spentCoinTags, false);
|
await _checkAndUpdateCoins();
|
||||||
|
|
||||||
// refresh spark balance
|
// refresh spark balance
|
||||||
await refreshSparkBalance();
|
await refreshSparkBalance();
|
||||||
|
@ -720,10 +748,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _checkAndUpdateCoins(
|
Future<void> _checkAndUpdateCoins() async {
|
||||||
Set<String> spentCoinTags,
|
|
||||||
bool checkUseds,
|
|
||||||
) async {
|
|
||||||
final sparkAddresses = await mainDB.isar.addresses
|
final sparkAddresses = await mainDB.isar.addresses
|
||||||
.where()
|
.where()
|
||||||
.walletIdEqualTo(walletId)
|
.walletIdEqualTo(walletId)
|
||||||
|
@ -737,15 +762,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
)
|
)
|
||||||
.toSet();
|
.toSet();
|
||||||
|
|
||||||
List<SparkCoin>? currentCoins;
|
final Map<int, List<List<String>>> rawCoinsBySetId = {};
|
||||||
if (checkUseds) {
|
|
||||||
currentCoins = await mainDB.isar.sparkCoins
|
|
||||||
.where()
|
|
||||||
.walletIdEqualToAnyLTagHash(walletId)
|
|
||||||
.filter()
|
|
||||||
.isUsedEqualTo(false)
|
|
||||||
.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
final latestSparkCoinId = await electrumXClient.getSparkLatestCoinId();
|
final latestSparkCoinId = await electrumXClient.getSparkLatestCoinId();
|
||||||
for (int i = 1; i <= latestSparkCoinId; i++) {
|
for (int i = 1; i <= latestSparkCoinId; i++) {
|
||||||
|
@ -769,34 +786,62 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (coinsRaw.isNotEmpty) {
|
if (coinsRaw.isNotEmpty) {
|
||||||
final myCoins = await compute(
|
rawCoinsBySetId[i] = coinsRaw;
|
||||||
_identifyCoins,
|
|
||||||
(
|
|
||||||
anonymitySetCoins: coinsRaw,
|
|
||||||
groupId: i,
|
|
||||||
spentCoinTags: spentCoinTags,
|
|
||||||
privateKeyHexSet: privateKeyHexSet,
|
|
||||||
walletId: walletId,
|
|
||||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (checkUseds && currentCoins != null) {
|
|
||||||
for (final coin in currentCoins) {
|
|
||||||
if (spentCoinTags.contains(coin.lTagHash)) {
|
|
||||||
myCoins.add(coin.copyWith(isUsed: true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update wallet spark coins in isar
|
|
||||||
await _addOrUpdateSparkCoins(myCoins);
|
|
||||||
}
|
|
||||||
groupIdTimestampUTCMap[i] = max(
|
groupIdTimestampUTCMap[i] = max(
|
||||||
lastCheckedTimeStampUTC,
|
lastCheckedTimeStampUTC,
|
||||||
info?.timestampUTC ?? lastCheckedTimeStampUTC,
|
info?.timestampUTC ?? lastCheckedTimeStampUTC,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<SparkCoin> newlyIdCoins = [];
|
||||||
|
for (final groupId in rawCoinsBySetId.keys) {
|
||||||
|
final myCoins = await compute(
|
||||||
|
_identifyCoins,
|
||||||
|
(
|
||||||
|
anonymitySetCoins: rawCoinsBySetId[groupId]!,
|
||||||
|
groupId: groupId,
|
||||||
|
privateKeyHexSet: privateKeyHexSet,
|
||||||
|
walletId: walletId,
|
||||||
|
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
newlyIdCoins.addAll(myCoins);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _checkAndMarkCoinsUsedInDB(coinsNotInDbYet: newlyIdCoins);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkAndMarkCoinsUsedInDB({
|
||||||
|
List<SparkCoin> coinsNotInDbYet = const [],
|
||||||
|
}) async {
|
||||||
|
final List<SparkCoin> coins = await mainDB.isar.sparkCoins
|
||||||
|
.where()
|
||||||
|
.walletIdEqualToAnyLTagHash(walletId)
|
||||||
|
.filter()
|
||||||
|
.isUsedEqualTo(false)
|
||||||
|
.findAll();
|
||||||
|
|
||||||
|
final List<SparkCoin> coinsToWrite = [];
|
||||||
|
|
||||||
|
final spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(0);
|
||||||
|
|
||||||
|
for (final coin in coins) {
|
||||||
|
if (spentCoinTags.contains(coin.lTagHash)) {
|
||||||
|
coinsToWrite.add(coin.copyWith(isUsed: true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final coin in coinsNotInDbYet) {
|
||||||
|
if (spentCoinTags.contains(coin.lTagHash)) {
|
||||||
|
coinsToWrite.add(coin.copyWith(isUsed: true));
|
||||||
|
} else {
|
||||||
|
coinsToWrite.add(coin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update wallet spark coins in isar
|
||||||
|
await _addOrUpdateSparkCoins(coinsToWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// modelled on CSparkWallet::CreateSparkMintTransactions https://github.com/firoorg/firo/blob/39c41e5e7ec634ced3700fe3f4f5509dc2e480d0/src/spark/sparkwallet.cpp#L752
|
// modelled on CSparkWallet::CreateSparkMintTransactions https://github.com/firoorg/firo/blob/39c41e5e7ec634ced3700fe3f4f5509dc2e480d0/src/spark/sparkwallet.cpp#L752
|
||||||
|
@ -1713,7 +1758,6 @@ Future<List<SparkCoin>> _identifyCoins(
|
||||||
({
|
({
|
||||||
List<dynamic> anonymitySetCoins,
|
List<dynamic> anonymitySetCoins,
|
||||||
int groupId,
|
int groupId,
|
||||||
Set<String> spentCoinTags,
|
|
||||||
Set<String> privateKeyHexSet,
|
Set<String> privateKeyHexSet,
|
||||||
String walletId,
|
String walletId,
|
||||||
bool isTestNet,
|
bool isTestNet,
|
||||||
|
@ -1756,7 +1800,7 @@ Future<List<SparkCoin>> _identifyCoins(
|
||||||
SparkCoin(
|
SparkCoin(
|
||||||
walletId: args.walletId,
|
walletId: args.walletId,
|
||||||
type: coinType,
|
type: coinType,
|
||||||
isUsed: args.spentCoinTags.contains(coin.lTagHash!),
|
isUsed: false,
|
||||||
groupId: args.groupId,
|
groupId: args.groupId,
|
||||||
nonce: coin.nonceHex?.toUint8ListFromHex,
|
nonce: coin.nonceHex?.toUint8ListFromHex,
|
||||||
address: coin.address!,
|
address: coin.address!,
|
||||||
|
|
Loading…
Reference in a new issue