Merge remote-tracking branch 'origin_SW/ui-fixes' into ordinals

# Conflicts:
#	lib/db/isar/main_db.dart
This commit is contained in:
julian 2023-07-26 15:11:03 -06:00
commit 80dad5156a
19 changed files with 2339 additions and 919 deletions

View file

@ -177,6 +177,9 @@ class DB {
}
Future<Box<dynamic>> getTxCacheBox({required Coin coin}) async {
if (_txCacheBoxes[coin]?.isOpen != true) {
_txCacheBoxes.remove(coin);
}
return _txCacheBoxes[coin] ??=
await Hive.openBox<dynamic>(_boxNameTxCache(coin: coin));
}
@ -186,6 +189,9 @@ class DB {
}
Future<Box<dynamic>> getAnonymitySetCacheBox({required Coin coin}) async {
if (_setCacheBoxes[coin]?.isOpen != true) {
_setCacheBoxes.remove(coin);
}
return _setCacheBoxes[coin] ??=
await Hive.openBox<dynamic>(_boxNameSetCache(coin: coin));
}
@ -195,6 +201,9 @@ class DB {
}
Future<Box<dynamic>> getUsedSerialsCacheBox({required Coin coin}) async {
if (_usedSerialsCacheBoxes[coin]?.isOpen != true) {
_usedSerialsCacheBoxes.remove(coin);
}
return _usedSerialsCacheBoxes[coin] ??=
await Hive.openBox<dynamic>(_boxNameUsedSerialsCache(coin: coin));
}
@ -265,8 +274,15 @@ class DB {
{required dynamic key, required String boxName}) async =>
await mutex.protect(() async => await Hive.box<T>(boxName).delete(key));
Future<void> deleteAll<T>({required String boxName}) async =>
await mutex.protect(() async => await Hive.box<T>(boxName).clear());
Future<void> deleteAll<T>({required String boxName}) async {
await mutex.protect(() async {
Box<T> box = Hive.box<T>(boxName);
if (!box.isOpen) {
box = await Hive.openBox(boxName);
}
await box.clear();
});
}
Future<void> deleteBoxFromDisk({required String boxName}) async =>
await mutex.protect(() async => await Hive.deleteBoxFromDisk(boxName));

View file

@ -56,6 +56,7 @@ class MainDB {
StackThemeSchema,
ContactEntrySchema,
OrdinalSchema,
LelantusCoinSchema,
],
directory: (await StackFileSystem.applicationIsarDirectory()).path,
// inspector: kDebugMode,
@ -377,6 +378,8 @@ class MainDB {
final transactionCount = await getTransactions(walletId).count();
final addressCount = await getAddresses(walletId).count();
final utxoCount = await getUTXOs(walletId).count();
final lelantusCoinCount =
await isar.lelantusCoins.where().walletIdEqualTo(walletId).count();
await isar.writeTxn(() async {
const paginateLimit = 50;
@ -410,6 +413,18 @@ class MainDB {
.findAll();
await isar.utxos.deleteAll(utxoIds);
}
// lelantusCoins
for (int i = 0; i < lelantusCoinCount; i += paginateLimit) {
final lelantusCoinIds = await isar.lelantusCoins
.where()
.walletIdEqualTo(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.lelantusCoins.deleteAll(lelantusCoinIds);
}
});
}
@ -504,4 +519,15 @@ class MainDB {
isar.writeTxn(() async {
await isar.ethContracts.putAll(contracts);
});
// ========== Lelantus =======================================================
Future<int?> getHighestUsedMintIndex({required String walletId}) async {
return await isar.lelantusCoins
.where()
.walletIdEqualTo(walletId)
.sortByMintIndexDesc()
.mintIndexProperty()
.findFirst();
}
}

View file

@ -164,14 +164,16 @@ class CachedElectrumX {
final _list = box.get("serials") as List?;
List<String> cachedSerials =
_list == null ? [] : List<String>.from(_list);
Set<String> cachedSerials =
_list == null ? {} : List<String>.from(_list).toSet();
final startNumber = cachedSerials.length;
// startNumber is broken currently
final startNumber = 0; // cachedSerials.length;
final serials =
await electrumXClient.getUsedCoinSerials(startNumber: startNumber);
List<String> newSerials = [];
final serials = await electrumXClient.getUsedCoinSerials(
startNumber: startNumber,
);
Set<String> newSerials = {};
for (final element in (serials["serials"] as List)) {
if (!isHexadecimal(element as String)) {
@ -182,12 +184,14 @@ class CachedElectrumX {
}
cachedSerials.addAll(newSerials);
final resultingList = cachedSerials.toList();
await box.put(
"serials",
cachedSerials,
resultingList,
);
return cachedSerials;
return resultingList;
} catch (e, s) {
Logging.instance.log(
"Failed to process CachedElectrumX.getTransaction(): $e\n$s",

View file

@ -159,8 +159,8 @@ class ElectrumX {
throw Exception(
"JSONRPC response\n"
" command: $command\n"
" args: $args\n"
" error: ${response.data}",
" error: ${response.data}"
" args: $args\n",
);
}

View file

@ -79,7 +79,7 @@ class JsonRPC {
// TODO different timeout length?
req.initiateTimeout(
const Duration(seconds: 10),
Duration(seconds: connectionTimeout.inSeconds ~/ 2),
onTimedOut: () {
_requestQueue.remove(req);
},

View file

@ -0,0 +1,81 @@
import 'package:isar/isar.dart';
part 'lelantus_coin.g.dart';
@collection
class LelantusCoin {
Id id = Isar.autoIncrement;
@Index()
final String walletId;
final String txid;
final String value; // can't use BigInt in isar :shrug:
@Index(
unique: true,
replace: false,
composite: [
CompositeIndex("walletId"),
],
)
final int mintIndex;
final int anonymitySetId;
final bool isUsed;
final bool isJMint;
final String? otherData;
LelantusCoin({
required this.walletId,
required this.txid,
required this.value,
required this.mintIndex,
required this.anonymitySetId,
required this.isUsed,
required this.isJMint,
required this.otherData,
});
LelantusCoin copyWith({
String? walletId,
String? publicCoin,
String? txid,
String? value,
int? mintIndex,
int? anonymitySetId,
bool? isUsed,
bool? isJMint,
String? otherData,
}) {
return LelantusCoin(
walletId: walletId ?? this.walletId,
txid: txid ?? this.txid,
value: value ?? this.value,
mintIndex: mintIndex ?? this.mintIndex,
anonymitySetId: anonymitySetId ?? this.anonymitySetId,
isUsed: isUsed ?? this.isUsed,
isJMint: isJMint ?? this.isJMint,
otherData: otherData ?? this.otherData,
);
}
@override
String toString() {
return 'LelantusCoin{'
'id: $id, '
'walletId: $walletId, '
'txid: $txid, '
'value: $value, '
'mintIndex: $mintIndex, '
'anonymitySetId: $anonymitySetId, '
'otherData: $otherData, '
'isJMint: $isJMint, '
'isUsed: $isUsed'
'}';
}
}

File diff suppressed because it is too large Load diff

View file

@ -15,5 +15,6 @@ export 'blockchain_data/output.dart';
export 'blockchain_data/transaction.dart';
export 'blockchain_data/utxo.dart';
export 'ethereum/eth_contract.dart';
export 'firo_specific/lelantus_coin.dart';
export 'log.dart';
export 'transaction_note.dart';

View file

@ -12,6 +12,7 @@ import 'package:hive/hive.dart';
part 'type_adaptors/lelantus_coin.g.dart';
@Deprecated("Use Isar object instead")
// @HiveType(typeId: 9)
class LelantusCoin {
// @HiveField(0)
@ -27,6 +28,7 @@ class LelantusCoin {
// @HiveField(5)
bool isUsed;
@Deprecated("Use Isar object instead")
LelantusCoin(
this.index,
this.value,

File diff suppressed because it is too large Load diff

View file

@ -1,61 +0,0 @@
/*
* 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:stackwallet/db/hive/db.dart';
mixin FiroHive {
late final String _walletId;
void initFiroHive(String walletId) {
_walletId = walletId;
}
// jindex
List? firoGetJIndex() {
return DB.instance.get<dynamic>(boxName: _walletId, key: "jindex") as List?;
}
Future<void> firoUpdateJIndex(List jIndex) async {
await DB.instance.put<dynamic>(
boxName: _walletId,
key: "jindex",
value: jIndex,
);
}
// _lelantus_coins
List? firoGetLelantusCoins() {
return DB.instance.get<dynamic>(boxName: _walletId, key: "_lelantus_coins")
as List?;
}
Future<void> firoUpdateLelantusCoins(List lelantusCoins) async {
await DB.instance.put<dynamic>(
boxName: _walletId,
key: "_lelantus_coins",
value: lelantusCoins,
);
}
// mintIndex
int firoGetMintIndex() {
return DB.instance.get<dynamic>(boxName: _walletId, key: "mintIndex")
as int? ??
0;
}
Future<void> firoUpdateMintIndex(int mintIndex) async {
await DB.instance.put<dynamic>(
boxName: _walletId,
key: "mintIndex",
value: mintIndex,
);
}
}

View file

@ -77,8 +77,10 @@ class NotificationApi {
final id = prefs.currentNotificationId;
String confirms = "";
if (txid != null) {
confirms = " (${confirmations!}/${requiredConfirmations!})";
if (txid != null &&
confirmations != null &&
requiredConfirmations != null) {
confirms = " ($confirmations/$requiredConfirmations)";
}
final NotificationModel model = NotificationModel(

View file

@ -58,7 +58,7 @@ abstract class Constants {
// Enable Logger.print statements
static const bool disableLogger = false;
static const int currentDataVersion = 10;
static const int currentDataVersion = 11;
static const int rescanV1 = 1;

View file

@ -180,7 +180,6 @@ class DbVersionMigrator with WalletDB {
// clear possible broken firo cache
await DB.instance.clearSharedTransactionCache(coin: Coin.firo);
// update version
await DB.instance.put<dynamic>(
boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 4);
@ -343,12 +342,85 @@ class DbVersionMigrator with WalletDB {
// try to continue migrating
return await migrate(10, secureStore: secureStore);
case 10:
// migrate
await _v10(secureStore);
// update version
await DB.instance.put<dynamic>(
boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 11);
// try to continue migrating
return await migrate(11, secureStore: secureStore);
default:
// finally return
return;
}
}
Future<void> _v10(SecureStorageInterface secureStore) async {
await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
await Hive.openBox<dynamic>(DB.boxNamePrefs);
final walletsService = WalletsService(secureStorageInterface: secureStore);
final prefs = Prefs.instance;
final walletInfoList = await walletsService.walletNames;
await prefs.init();
await MainDB.instance.initMainDB();
for (final walletId in walletInfoList.keys) {
final info = walletInfoList[walletId]!;
assert(info.walletId == walletId);
if (info.coin == Coin.firo &&
MainDB.instance.isar.lelantusCoins
.where()
.walletIdEqualTo(walletId)
.countSync() ==
0) {
final walletBox = await Hive.openBox<dynamic>(walletId);
final hiveLCoins = DB.instance.get<dynamic>(
boxName: walletId,
key: "_lelantus_coins",
) as List? ??
[];
final jindexes = (DB.instance
.get<dynamic>(boxName: walletId, key: "jindex") as List? ??
[])
.cast<int>();
final List<isar_models.LelantusCoin> coins = [];
for (final e in hiveLCoins) {
final map = e as Map;
final lcoin = map.values.first as LelantusCoin;
final isJMint = jindexes.contains(lcoin.index);
final coin = isar_models.LelantusCoin(
walletId: walletId,
txid: lcoin.txId,
value: lcoin.value.toString(),
mintIndex: lcoin.index,
anonymitySetId: lcoin.anonymitySetId,
isUsed: lcoin.isUsed,
isJMint: isJMint,
otherData: null,
);
coins.add(coin);
}
if (coins.isNotEmpty) {
await MainDB.instance.isar.writeTxn(() async {
await MainDB.instance.isar.lelantusCoins.putAll(coins);
});
}
}
}
}
Future<void> _v4(SecureStorageInterface secureStore) async {
await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
await Hive.openBox<dynamic>(DB.boxNamePrefs);

View file

@ -13,6 +13,7 @@ import 'dart:async';
import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/themes/coin_icon_provider.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -84,6 +85,8 @@ class _CryptoNotificationsState extends ConsumerState<CryptoNotifications> {
@override
void initState() {
NotificationApi.prefs = ref.read(prefsChangeNotifierProvider);
NotificationApi.notificationsService = ref.read(notificationsProvider);
_streamSubscription = CryptoNotificationsEventBus.instance
.on<CryptoNotificationEvent>()
.listen(

View file

@ -11,9 +11,7 @@ import 'package:mockito/mockito.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
import 'package:stackwallet/models/lelantus_coin.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/lelantus_fee_data.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart' as old;
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
@ -72,6 +70,7 @@ void main() {
setData,
List<String>.from(usedSerials),
firoNetwork,
"walletId",
);
const currentHeight = 100000000000;
@ -113,10 +112,7 @@ void main() {
final result = await staticProcessRestore(txData, message, currentHeight);
expect(result, isA<Map<String, dynamic>>());
expect(result["mintIndex"], 8);
expect(result["jindex"], [2, 4, 6]);
expect(
result["_lelantus_coins"], isA<List<Map<dynamic, LelantusCoin>>>());
expect(result["_lelantus_coins"], isA<List<LelantusCoin>>());
expect(result["newTxMap"], isA<Map<String, Transaction>>());
});
@ -133,6 +129,7 @@ void main() {
setData,
List<String>.from(usedSerials),
firoNetwork,
"walletId",
),
throwsA(isA<Error>()));
});
@ -530,18 +527,10 @@ void main() {
group("FiroWallet service class functions that depend on shared storage", () {
const testWalletId = "testWalletID";
const testWalletName = "Test Wallet";
bool hiveAdaptersRegistered = false;
setUp(() async {
await setUpTestHive();
if (!hiveAdaptersRegistered) {
hiveAdaptersRegistered = true;
// Registering Lelantus Model Adapters
Hive.registerAdapter(LelantusCoinAdapter());
}
final wallets = await Hive.openBox<dynamic>('wallets');
await wallets.put('currentWalletName', testWalletName);
});
@ -1202,13 +1191,33 @@ void main() {
txHash: BuildMintTxTestParams.utxoInfo["txid"] as String,
coin: Coin.firo,
)).thenAnswer((_) async => BuildMintTxTestParams.cachedClientResponse);
when(cachedClient.getAnonymitySet(
groupId: "1",
coin: Coin.firo,
)).thenAnswer(
(_) async => GetAnonymitySetSampleData.data,
);
when(cachedClient.getAnonymitySet(
groupId: "2",
coin: Coin.firo,
)).thenAnswer(
(_) async => GetAnonymitySetSampleData.data,
);
when(client.getBlockHeadTip()).thenAnswer(
(_) async => {"height": 455873, "hex": "this value not used here"});
when(client.getLatestCoinId()).thenAnswer((_) async => 2);
when(mainDB.getAddress("${testWalletId}buildMintTransaction", any))
.thenAnswer((realInvocation) async => null);
when(mainDB.getHighestUsedMintIndex(
walletId: "${testWalletId}submitHexToNetwork"))
.thenAnswer((_) async => null);
when(mainDB.getHighestUsedMintIndex(
walletId: "testWalletIDbuildMintTransaction"))
.thenAnswer((_) async => null);
final firo = FiroWallet(
walletName: testWalletName,
walletId: "${testWalletId}buildMintTransaction",

View file

@ -1151,4 +1151,14 @@ class MockMainDB extends _i1.Mock implements _i9.MainDB {
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<int?> getHighestUsedMintIndex({required String? walletId}) =>
(super.noSuchMethod(
Invocation.method(
#getHighestUsedMintIndex,
[],
{#walletId: walletId},
),
returnValue: _i5.Future<int?>.value(),
) as _i5.Future<int?>);
}

View file

@ -14,7 +14,6 @@ import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i5;
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4;
import 'package:stackwallet/models/balance.dart' as _i6;
import 'package:stackwallet/models/isar/models/isar_models.dart' as _i13;
import 'package:stackwallet/models/lelantus_coin.dart' as _i15;
import 'package:stackwallet/models/paymint/fee_object_model.dart' as _i3;
import 'package:stackwallet/models/signing_data.dart' as _i14;
import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as _i10;
@ -589,15 +588,6 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
returnValueForMissingStub: _i11.Future<void>.value(),
) as _i11.Future<void>);
@override
List<Map<dynamic, _i15.LelantusCoin>> getLelantusCoinMap() =>
(super.noSuchMethod(
Invocation.method(
#getLelantusCoinMap,
[],
),
returnValue: <Map<dynamic, _i15.LelantusCoin>>[],
) as List<Map<dynamic, _i15.LelantusCoin>>);
@override
_i11.Future<void> anonymizeAllPublicFunds() => (super.noSuchMethod(
Invocation.method(
#anonymizeAllPublicFunds,
@ -755,15 +745,6 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
returnValueForMissingStub: _i11.Future<void>.value(),
) as _i11.Future<void>);
@override
_i11.Future<dynamic> getCoinsToJoinSplit(int? required) =>
(super.noSuchMethod(
Invocation.method(
#getCoinsToJoinSplit,
[required],
),
returnValue: _i11.Future<dynamic>.value(),
) as _i11.Future<dynamic>);
@override
_i11.Future<int> estimateJoinSplitFee(int? spendAmount) =>
(super.noSuchMethod(
Invocation.method(
@ -1061,51 +1042,6 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
),
returnValueForMissingStub: null,
);
@override
void initFiroHive(String? walletId) => super.noSuchMethod(
Invocation.method(
#initFiroHive,
[walletId],
),
returnValueForMissingStub: null,
);
@override
_i11.Future<void> firoUpdateJIndex(List<dynamic>? jIndex) =>
(super.noSuchMethod(
Invocation.method(
#firoUpdateJIndex,
[jIndex],
),
returnValue: _i11.Future<void>.value(),
returnValueForMissingStub: _i11.Future<void>.value(),
) as _i11.Future<void>);
@override
_i11.Future<void> firoUpdateLelantusCoins(List<dynamic>? lelantusCoins) =>
(super.noSuchMethod(
Invocation.method(
#firoUpdateLelantusCoins,
[lelantusCoins],
),
returnValue: _i11.Future<void>.value(),
returnValueForMissingStub: _i11.Future<void>.value(),
) as _i11.Future<void>);
@override
int firoGetMintIndex() => (super.noSuchMethod(
Invocation.method(
#firoGetMintIndex,
[],
),
returnValue: 0,
) as int);
@override
_i11.Future<void> firoUpdateMintIndex(int? mintIndex) => (super.noSuchMethod(
Invocation.method(
#firoUpdateMintIndex,
[mintIndex],
),
returnValue: _i11.Future<void>.value(),
returnValueForMissingStub: _i11.Future<void>.value(),
) as _i11.Future<void>);
}
/// A class which mocks [ElectrumX].

View file

@ -1607,15 +1607,6 @@ class MockFiroWallet extends _i1.Mock implements _i23.FiroWallet {
returnValueForMissingStub: _i19.Future<void>.value(),
) as _i19.Future<void>);
@override
List<Map<dynamic, _i8.LelantusCoin>> getLelantusCoinMap() =>
(super.noSuchMethod(
Invocation.method(
#getLelantusCoinMap,
[],
),
returnValue: <Map<dynamic, _i8.LelantusCoin>>[],
) as List<Map<dynamic, _i8.LelantusCoin>>);
@override
_i19.Future<void> anonymizeAllPublicFunds() => (super.noSuchMethod(
Invocation.method(
#anonymizeAllPublicFunds,
@ -1773,15 +1764,6 @@ class MockFiroWallet extends _i1.Mock implements _i23.FiroWallet {
returnValueForMissingStub: _i19.Future<void>.value(),
) as _i19.Future<void>);
@override
_i19.Future<dynamic> getCoinsToJoinSplit(int? required) =>
(super.noSuchMethod(
Invocation.method(
#getCoinsToJoinSplit,
[required],
),
returnValue: _i19.Future<dynamic>.value(),
) as _i19.Future<dynamic>);
@override
_i19.Future<int> estimateJoinSplitFee(int? spendAmount) =>
(super.noSuchMethod(
Invocation.method(
@ -2080,51 +2062,6 @@ class MockFiroWallet extends _i1.Mock implements _i23.FiroWallet {
),
returnValueForMissingStub: null,
);
@override
void initFiroHive(String? walletId) => super.noSuchMethod(
Invocation.method(
#initFiroHive,
[walletId],
),
returnValueForMissingStub: null,
);
@override
_i19.Future<void> firoUpdateJIndex(List<dynamic>? jIndex) =>
(super.noSuchMethod(
Invocation.method(
#firoUpdateJIndex,
[jIndex],
),
returnValue: _i19.Future<void>.value(),
returnValueForMissingStub: _i19.Future<void>.value(),
) as _i19.Future<void>);
@override
_i19.Future<void> firoUpdateLelantusCoins(List<dynamic>? lelantusCoins) =>
(super.noSuchMethod(
Invocation.method(
#firoUpdateLelantusCoins,
[lelantusCoins],
),
returnValue: _i19.Future<void>.value(),
returnValueForMissingStub: _i19.Future<void>.value(),
) as _i19.Future<void>);
@override
int firoGetMintIndex() => (super.noSuchMethod(
Invocation.method(
#firoGetMintIndex,
[],
),
returnValue: 0,
) as int);
@override
_i19.Future<void> firoUpdateMintIndex(int? mintIndex) => (super.noSuchMethod(
Invocation.method(
#firoUpdateMintIndex,
[mintIndex],
),
returnValue: _i19.Future<void>.value(),
returnValueForMissingStub: _i19.Future<void>.value(),
) as _i19.Future<void>);
}
/// A class which mocks [LocaleService].
@ -3564,4 +3501,14 @@ class MockMainDB extends _i1.Mock implements _i14.MainDB {
returnValue: _i19.Future<void>.value(),
returnValueForMissingStub: _i19.Future<void>.value(),
) as _i19.Future<void>);
@override
_i19.Future<int?> getHighestUsedMintIndex({required String? walletId}) =>
(super.noSuchMethod(
Invocation.method(
#getHighestUsedMintIndex,
[],
{#walletId: walletId},
),
returnValue: _i19.Future<int?>.value(),
) as _i19.Future<int?>);
}