between 2-4x speed increase for restore, first refresh, and nth refresh

This commit is contained in:
Marco 2022-09-09 23:07:27 +08:00
parent 691c4a772d
commit d8bc213749
4 changed files with 752 additions and 693 deletions

View file

@ -399,6 +399,138 @@ class BitcoinWallet extends CoinServiceAPI {
level: LogLevel.Info);
}
Future<Map<String, dynamic>> _checkGaps(
int maxNumberOfIndexesToCheck,
int maxUnusedAddressGap,
int txCountBatchSize,
bip32.BIP32 root,
DerivePathType type,
int account) async {
List<String> addressArray = [];
int returningIndex = -1;
Map<String, Map<String, String>> derivations = {};
int gapCounter = 0;
for (int index = 0;
index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap;
index += txCountBatchSize) {
List<String> iterationsAddressArray = [];
Logging.instance.log(
"index: $index, \t GapCounter $account ${type.name}: $gapCounter",
level: LogLevel.Info);
final ID = "k_$index";
Map<String, String> txCountCallArgs = {};
final Map<String, dynamic> receivingNodes = {};
for (int j = 0; j < txCountBatchSize; j++) {
final node = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
account,
index + j,
root,
type,
),
);
String? address;
switch (type) {
case DerivePathType.bip44:
address = P2PKH(
data: PaymentData(pubkey: node.publicKey),
network: _network)
.data
.address!;
break;
case DerivePathType.bip49:
address = P2SH(
data: PaymentData(
redeem: P2WPKH(
data: PaymentData(pubkey: node.publicKey),
network: _network)
.data),
network: _network)
.data
.address!;
break;
case DerivePathType.bip84:
address = P2WPKH(
network: _network,
data: PaymentData(pubkey: node.publicKey))
.data
.address!;
break;
default:
throw Exception("No Path type $type exists");
}
receivingNodes.addAll({
"${ID}_$j": {
"node": node,
"address": address,
}
});
txCountCallArgs.addAll({
"${ID}_$j": address,
});
}
// get address tx counts
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
// check and add appropriate addresses
for (int k = 0; k < txCountBatchSize; k++) {
int count = counts["${ID}_$k"]!;
if (count > 0) {
final node = receivingNodes["${ID}_$k"];
// add address to array
addressArray.add(node["address"] as String);
iterationsAddressArray.add(node["address"] as String);
// set current index
returningIndex = index + k;
// reset counter
gapCounter = 0;
// add info to derivations
derivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
// increase counter when no tx history found
if (count == 0) {
gapCounter++;
}
}
// cache all the transactions while waiting for the current function to finish.
unawaited(getTransactionCacheEarly(iterationsAddressArray));
}
return {
"addressArray": addressArray,
"index": returningIndex,
"derivations": derivations
};
}
Future<void> getTransactionCacheEarly(List<String> allAddresses) async {
try {
final List<Map<String, dynamic>> allTxHashes =
await _fetchHistory(allAddresses);
for (final txHash in allTxHashes) {
try {
unawaited(cachedElectrumXClient.getTransaction(
txHash: txHash["tx_hash"] as String,
verbose: true,
coin: coin,
));
} catch (e) {
continue;
}
}
} catch (e, s) {
//
}
}
Future<void> _recoverWalletFromBIP32SeedPhrase({
required String mnemonic,
int maxUnusedAddressGap = 20,
@ -429,10 +561,6 @@ class BitcoinWallet extends CoinServiceAPI {
int p2shChangeIndex = -1;
int p2wpkhChangeIndex = -1;
// The gap limit will be capped at [maxUnusedAddressGap]
int receivingGapCounter = 0;
int changeGapCounter = 0;
// actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3
const txCountBatchSize = 12;
@ -440,323 +568,71 @@ class BitcoinWallet extends CoinServiceAPI {
// receiving addresses
Logging.instance
.log("checking receiving addresses...", level: LogLevel.Info);
for (int index = 0;
index < maxNumberOfIndexesToCheck &&
receivingGapCounter < maxUnusedAddressGap;
index += txCountBatchSize) {
Logging.instance.log(
"index: $index, \t receivingGapCounter: $receivingGapCounter",
level: LogLevel.Info);
Future resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0);
final receivingP2pkhID = "k_$index";
final receivingP2shID = "s_$index";
final receivingP2wpkhID = "w_$index";
Map<String, String> txCountCallArgs = {};
final Map<String, dynamic> receivingNodes = {};
Future resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0);
for (int j = 0; j < txCountBatchSize; j++) {
// bip44 / P2PKH
final node44 = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
0,
index + j,
root,
DerivePathType.bip44,
),
);
final p2pkhReceiveAddress = P2PKH(
data: PaymentData(pubkey: node44.publicKey),
network: _network)
.data
.address!;
receivingNodes.addAll({
"${receivingP2pkhID}_$j": {
"node": node44,
"address": p2pkhReceiveAddress,
}
});
txCountCallArgs.addAll({
"${receivingP2pkhID}_$j": p2pkhReceiveAddress,
});
// bip49 / P2SH
final node49 = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
0,
index + j,
root,
DerivePathType.bip49,
),
);
final p2shReceiveAddress = P2SH(
data: PaymentData(
redeem: P2WPKH(
data: PaymentData(pubkey: node49.publicKey),
network: _network)
.data),
network: _network)
.data
.address!;
receivingNodes.addAll({
"${receivingP2shID}_$j": {
"node": node49,
"address": p2shReceiveAddress,
}
});
txCountCallArgs.addAll({
"${receivingP2shID}_$j": p2shReceiveAddress,
});
// bip84 / P2WPKH
final node84 = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
0,
index + j,
root,
DerivePathType.bip84,
),
);
final p2wpkhReceiveAddress = P2WPKH(
network: _network,
data: PaymentData(pubkey: node84.publicKey))
.data
.address!;
receivingNodes.addAll({
"${receivingP2wpkhID}_$j": {
"node": node84,
"address": p2wpkhReceiveAddress,
}
});
txCountCallArgs.addAll({
"${receivingP2wpkhID}_$j": p2wpkhReceiveAddress,
});
}
// get address tx counts
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
// check and add appropriate addresses
for (int k = 0; k < txCountBatchSize; k++) {
int p2pkhTxCount = counts["${receivingP2pkhID}_$k"]!;
if (p2pkhTxCount > 0) {
final node = receivingNodes["${receivingP2pkhID}_$k"];
// add address to array
p2pkhReceiveAddressArray.add(node["address"] as String);
// set current index
p2pkhReceiveIndex = index + k;
// reset counter
receivingGapCounter = 0;
// add info to derivations
p2pkhReceiveDerivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
int p2shTxCount = counts["${receivingP2shID}_$k"]!;
if (p2shTxCount > 0) {
final node = receivingNodes["${receivingP2shID}_$k"];
// add address to array
p2shReceiveAddressArray.add(node["address"] as String);
// set current index
p2shReceiveIndex = index + k;
// reset counter
receivingGapCounter = 0;
// add info to derivations
p2shReceiveDerivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
int p2wpkhTxCount = counts["${receivingP2wpkhID}_$k"]!;
if (p2wpkhTxCount > 0) {
final node = receivingNodes["${receivingP2wpkhID}_$k"];
// add address to array
p2wpkhReceiveAddressArray.add(node["address"] as String);
// set current index
p2wpkhReceiveIndex = index + k;
// reset counter
receivingGapCounter = 0;
// add info to derivations
p2wpkhReceiveDerivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
// increase counter when no tx history found
if (p2wpkhTxCount == 0 && p2pkhTxCount == 0 && p2shTxCount == 0) {
receivingGapCounter++;
}
}
}
Future resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0);
Logging.instance
.log("checking change addresses...", level: LogLevel.Info);
// change addresses
for (int index = 0;
index < maxNumberOfIndexesToCheck &&
changeGapCounter < maxUnusedAddressGap;
index += txCountBatchSize) {
Logging.instance.log(
"index: $index, \t changeGapCounter: $changeGapCounter",
level: LogLevel.Info);
final changeP2pkhID = "k_$index";
final changeP2shID = "s_$index";
final changeP2wpkhID = "w_$index";
Map<String, String> args = {};
final Map<String, dynamic> changeNodes = {};
Future resultChange44 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1);
for (int j = 0; j < txCountBatchSize; j++) {
// bip44 / P2PKH
final node44 = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
1,
index + j,
root,
DerivePathType.bip44,
),
);
final p2pkhChangeAddress = P2PKH(
data: PaymentData(pubkey: node44.publicKey),
network: _network)
.data
.address!;
changeNodes.addAll({
"${changeP2pkhID}_$j": {
"node": node44,
"address": p2pkhChangeAddress,
}
});
args.addAll({
"${changeP2pkhID}_$j": p2pkhChangeAddress,
});
Future resultChange49 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1);
// bip49 / P2SH
final node49 = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
1,
index + j,
root,
DerivePathType.bip49,
),
);
final p2shChangeAddress = P2SH(
data: PaymentData(
redeem: P2WPKH(
data: PaymentData(pubkey: node49.publicKey),
network: _network)
.data),
network: _network)
.data
.address!;
changeNodes.addAll({
"${changeP2shID}_$j": {
"node": node49,
"address": p2shChangeAddress,
}
});
args.addAll({
"${changeP2shID}_$j": p2shChangeAddress,
});
Future resultChange84 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1);
// bip84 / P2WPKH
final node84 = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
1,
index + j,
root,
DerivePathType.bip84,
),
);
final p2wpkhChangeAddress = P2WPKH(
network: _network,
data: PaymentData(pubkey: node84.publicKey))
.data
.address!;
changeNodes.addAll({
"${changeP2wpkhID}_$j": {
"node": node84,
"address": p2wpkhChangeAddress,
}
});
args.addAll({
"${changeP2wpkhID}_$j": p2wpkhChangeAddress,
});
}
await Future.wait([
resultReceive44,
resultReceive49,
resultReceive84,
resultChange44,
resultChange49,
resultChange84
]);
// get address tx counts
final counts = await _getBatchTxCount(addresses: args);
p2pkhReceiveAddressArray =
(await resultReceive44)['addressArray'] as List<String>;
p2pkhReceiveIndex = (await resultReceive44)['index'] as int;
p2pkhReceiveDerivations = (await resultReceive44)['derivations']
as Map<String, Map<String, String>>;
// check and add appropriate addresses
for (int k = 0; k < txCountBatchSize; k++) {
int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!;
if (p2pkhTxCount > 0) {
final node = changeNodes["${changeP2pkhID}_$k"];
// add address to array
p2pkhChangeAddressArray.add(node["address"] as String);
// set current index
p2pkhChangeIndex = index + k;
// reset counter
changeGapCounter = 0;
// add info to derivations
p2pkhChangeDerivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
p2shReceiveAddressArray =
(await resultReceive49)['addressArray'] as List<String>;
p2shReceiveIndex = (await resultReceive49)['index'] as int;
p2shReceiveDerivations = (await resultReceive49)['derivations']
as Map<String, Map<String, String>>;
int p2shTxCount = counts["${changeP2shID}_$k"]!;
if (p2shTxCount > 0) {
final node = changeNodes["${changeP2shID}_$k"];
// add address to array
p2shChangeAddressArray.add(node["address"] as String);
// set current index
p2shChangeIndex = index + k;
// reset counter
changeGapCounter = 0;
// add info to derivations
p2shChangeDerivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
p2wpkhReceiveAddressArray =
(await resultReceive84)['addressArray'] as List<String>;
p2wpkhReceiveIndex = (await resultReceive84)['index'] as int;
p2wpkhReceiveDerivations = (await resultReceive84)['derivations']
as Map<String, Map<String, String>>;
int p2wpkhTxCount = counts["${changeP2wpkhID}_$k"]!;
if (p2wpkhTxCount > 0) {
final node = changeNodes["${changeP2wpkhID}_$k"];
// add address to array
p2wpkhChangeAddressArray.add(node["address"] as String);
// set current index
p2wpkhChangeIndex = index + k;
// reset counter
changeGapCounter = 0;
// add info to derivations
p2wpkhChangeDerivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
p2pkhChangeAddressArray =
(await resultChange44)['addressArray'] as List<String>;
p2pkhChangeIndex = (await resultChange44)['index'] as int;
p2pkhChangeDerivations = (await resultChange44)['derivations']
as Map<String, Map<String, String>>;
// increase counter when no tx history found
if (p2wpkhTxCount == 0 && p2pkhTxCount == 0 && p2shTxCount == 0) {
changeGapCounter++;
}
}
}
p2shChangeAddressArray =
(await resultChange49)['addressArray'] as List<String>;
p2shChangeIndex = (await resultChange49)['index'] as int;
p2shChangeDerivations = (await resultChange49)['derivations']
as Map<String, Map<String, String>>;
p2wpkhChangeAddressArray =
(await resultChange84)['addressArray'] as List<String>;
p2wpkhChangeIndex = (await resultChange84)['index'] as int;
p2wpkhChangeDerivations = (await resultChange84)['derivations']
as Map<String, Map<String, String>>;
// save the derivations (if any)
if (p2pkhReceiveDerivations.isNotEmpty) {
@ -1098,10 +974,12 @@ class BitcoinWallet extends CoinServiceAPI {
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
await _checkChangeAddressForTransactions(DerivePathType.bip84);
Future changeAddressForTransactions =
_checkChangeAddressForTransactions(DerivePathType.bip84);
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
await _checkCurrentReceivingAddressesForTransactions();
Future currentReceivingAddressesForTransactions =
_checkCurrentReceivingAddressesForTransactions();
final newTxData = _fetchTransactionData();
GlobalEventBus.instance
@ -1121,7 +999,15 @@ class BitcoinWallet extends CoinServiceAPI {
GlobalEventBus.instance
.fire(RefreshPercentChangedEvent(0.80, walletId));
await getAllTxsToWatch(await newTxData);
Future allTxsToWatch = getAllTxsToWatch(await newTxData);
await Future.wait([
newTxData,
changeAddressForTransactions,
currentReceivingAddressesForTransactions,
newUtxoData,
feeObj,
allTxsToWatch,
]);
GlobalEventBus.instance
.fire(RefreshPercentChangedEvent(0.90, walletId));
}
@ -1137,7 +1023,7 @@ class BitcoinWallet extends CoinServiceAPI {
);
if (shouldAutoSync) {
timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async {
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
Logging.instance.log(
"Periodic refresh check for $walletId $walletName in object instance: $hashCode",
level: LogLevel.Info);
@ -1972,7 +1858,7 @@ class BitcoinWallet extends CoinServiceAPI {
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
final Map<int, Map<String, List<dynamic>>> batches = {};
const batchSizeMax = 10;
const batchSizeMax = 100;
int batchNumber = 0;
for (int i = 0; i < allAddresses.length; i++) {
if (batches[batchNumber] == null) {
@ -2357,7 +2243,7 @@ class BitcoinWallet extends CoinServiceAPI {
final Map<int, Map<String, List<dynamic>>> batches = {};
final Map<String, String> requestIdToAddressMap = {};
const batchSizeMax = 10;
const batchSizeMax = 100;
int batchNumber = 0;
for (int i = 0; i < allAddresses.length; i++) {
if (batches[batchNumber] == null) {
@ -2404,6 +2290,43 @@ class BitcoinWallet extends CoinServiceAPI {
return false;
}
Future<List<Map<String, dynamic>>> fastFetch(List<String> allTxHashes) async {
List<Map<String, dynamic>> allTransactions = [];
const futureLimit = 30;
List<Future<Map<String, dynamic>>> transactionFutures = [];
int currentFutureCount = 0;
for (final txHash in allTxHashes) {
Future<Map<String, dynamic>> transactionFuture =
cachedElectrumXClient.getTransaction(
txHash: txHash,
verbose: true,
coin: coin,
);
transactionFutures.add(transactionFuture);
currentFutureCount++;
if (currentFutureCount > futureLimit) {
currentFutureCount = 0;
await Future.wait(transactionFutures);
for (final fTx in transactionFutures) {
final tx = await fTx;
allTransactions.add(tx);
}
}
}
if (currentFutureCount != 0) {
currentFutureCount = 0;
await Future.wait(transactionFutures);
for (final fTx in transactionFutures) {
final tx = await fTx;
allTransactions.add(tx);
}
}
return allTransactions;
}
Future<TransactionData> _fetchTransactionData() async {
final List<String> allAddresses = await _fetchAllOwnAddresses();
@ -2451,6 +2374,11 @@ class BitcoinWallet extends CoinServiceAPI {
}
}
Set<String> hashes = {};
for (var element in allTxHashes) {
hashes.add(element['tx_hash'] as String);
}
await fastFetch(hashes.toList());
List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
@ -2480,6 +2408,16 @@ class BitcoinWallet extends CoinServiceAPI {
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
final List<Map<String, dynamic>> midSortedArray = [];
Set<String> vHashes = {};
for (final txObject in allTransactions) {
for (int i = 0; i < (txObject["vin"] as List).length; i++) {
final input = txObject["vin"]![i] as Map;
final prevTxid = input["txid"] as String;
vHashes.add(prevTxid);
}
}
await fastFetch(vHashes.toList());
for (final txObject in allTransactions) {
List<String> sendersArray = [];
List<String> recipientsArray = [];

View file

@ -346,6 +346,120 @@ class DogecoinWallet extends CoinServiceAPI {
level: LogLevel.Info);
}
Future<Map<String, dynamic>> _checkGaps(
int maxNumberOfIndexesToCheck,
int maxUnusedAddressGap,
int txCountBatchSize,
bip32.BIP32 root,
DerivePathType type,
int account) async {
List<String> addressArray = [];
int returningIndex = -1;
Map<String, Map<String, String>> derivations = {};
int gapCounter = 0;
for (int index = 0;
index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap;
index += txCountBatchSize) {
List<String> iterationsAddressArray = [];
Logging.instance.log(
"index: $index, \t GapCounter $account ${type.name}: $gapCounter",
level: LogLevel.Info);
final ID = "k_$index";
Map<String, String> txCountCallArgs = {};
final Map<String, dynamic> receivingNodes = {};
for (int j = 0; j < txCountBatchSize; j++) {
final node = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
account,
index + j,
root,
type,
),
);
String? address;
switch (type) {
case DerivePathType.bip44:
address = P2PKH(
data: PaymentData(pubkey: node.publicKey),
network: _network)
.data
.address!;
break;
default:
throw Exception("No Path type $type exists");
}
receivingNodes.addAll({
"${ID}_$j": {
"node": node,
"address": address,
}
});
txCountCallArgs.addAll({
"${ID}_$j": address,
});
}
// get address tx counts
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
// check and add appropriate addresses
for (int k = 0; k < txCountBatchSize; k++) {
int count = counts["${ID}_$k"]!;
if (count > 0) {
final node = receivingNodes["${ID}_$k"];
// add address to array
addressArray.add(node["address"] as String);
iterationsAddressArray.add(node["address"] as String);
// set current index
returningIndex = index + k;
// reset counter
gapCounter = 0;
// add info to derivations
derivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
// increase counter when no tx history found
if (count == 0) {
gapCounter++;
}
}
// cache all the transactions while waiting for the current function to finish.
unawaited(getTransactionCacheEarly(iterationsAddressArray));
}
return {
"addressArray": addressArray,
"index": returningIndex,
"derivations": derivations
};
}
Future<void> getTransactionCacheEarly(List<String> allAddresses) async {
try {
final List<Map<String, dynamic>> allTxHashes =
await _fetchHistory(allAddresses);
for (final txHash in allTxHashes) {
try {
unawaited(cachedElectrumXClient.getTransaction(
txHash: txHash["tx_hash"] as String,
verbose: true,
coin: coin,
));
} catch (e) {
continue;
}
}
} catch (e, s) {
//
}
}
Future<void> _recoverWalletFromBIP32SeedPhrase({
required String mnemonic,
int maxUnusedAddressGap = 20,
@ -364,10 +478,6 @@ class DogecoinWallet extends CoinServiceAPI {
List<String> p2pkhChangeAddressArray = [];
int p2pkhChangeIndex = -1;
// The gap limit will be capped at [maxUnusedAddressGap]
int receivingGapCounter = 0;
int changeGapCounter = 0;
// actual size is 12 due to p2pkh so 12x1
const txCountBatchSize = 12;
@ -375,143 +485,31 @@ class DogecoinWallet extends CoinServiceAPI {
// receiving addresses
Logging.instance
.log("checking receiving addresses...", level: LogLevel.Info);
for (int index = 0;
index < maxNumberOfIndexesToCheck &&
receivingGapCounter < maxUnusedAddressGap;
index += txCountBatchSize) {
Logging.instance.log(
"index: $index, \t receivingGapCounter: $receivingGapCounter",
level: LogLevel.Info);
final receivingP2pkhID = "k_$index";
Map<String, String> txCountCallArgs = {};
final Map<String, dynamic> receivingNodes = {};
for (int j = 0; j < txCountBatchSize; j++) {
// bip44 / P2PKH
final node44 = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
0,
index + j,
root,
DerivePathType.bip44,
),
);
final p2pkhReceiveAddress = P2PKH(
data: PaymentData(pubkey: node44.publicKey),
network: _network)
.data
.address!;
receivingNodes.addAll({
"${receivingP2pkhID}_$j": {
"node": node44,
"address": p2pkhReceiveAddress,
}
});
txCountCallArgs.addAll({
"${receivingP2pkhID}_$j": p2pkhReceiveAddress,
});
}
// get address tx counts
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
// check and add appropriate addresses
for (int k = 0; k < txCountBatchSize; k++) {
int p2pkhTxCount = counts["${receivingP2pkhID}_$k"]!;
if (p2pkhTxCount > 0) {
final node = receivingNodes["${receivingP2pkhID}_$k"];
// add address to array
p2pkhReceiveAddressArray.add(node["address"] as String);
// set current index
p2pkhReceiveIndex = index + k;
// reset counter
receivingGapCounter = 0;
// add info to derivations
p2pkhReceiveDerivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
// increase counter when no tx history found
if (p2pkhTxCount == 0) {
receivingGapCounter++;
}
}
}
Future resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0);
Logging.instance
.log("checking change addresses...", level: LogLevel.Info);
// change addresses
for (int index = 0;
index < maxNumberOfIndexesToCheck &&
changeGapCounter < maxUnusedAddressGap;
index += txCountBatchSize) {
Logging.instance.log(
"index: $index, \t changeGapCounter: $changeGapCounter",
level: LogLevel.Info);
final changeP2pkhID = "k_$index";
Map<String, String> args = {};
final Map<String, dynamic> changeNodes = {};
Future resultChange44 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1);
for (int j = 0; j < txCountBatchSize; j++) {
// bip44 / P2PKH
final node44 = await compute(
getBip32NodeFromRootWrapper,
Tuple4(
1,
index + j,
root,
DerivePathType.bip44,
),
);
final p2pkhChangeAddress = P2PKH(
data: PaymentData(pubkey: node44.publicKey),
network: _network)
.data
.address!;
changeNodes.addAll({
"${changeP2pkhID}_$j": {
"node": node44,
"address": p2pkhChangeAddress,
}
});
args.addAll({
"${changeP2pkhID}_$j": p2pkhChangeAddress,
});
}
await Future.wait([
resultReceive44,
resultChange44,
]);
// get address tx counts
final counts = await _getBatchTxCount(addresses: args);
p2pkhReceiveAddressArray =
(await resultReceive44)['addressArray'] as List<String>;
p2pkhReceiveIndex = (await resultReceive44)['index'] as int;
p2pkhReceiveDerivations = (await resultReceive44)['derivations']
as Map<String, Map<String, String>>;
// check and add appropriate addresses
for (int k = 0; k < txCountBatchSize; k++) {
int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!;
if (p2pkhTxCount > 0) {
final node = changeNodes["${changeP2pkhID}_$k"];
// add address to array
p2pkhChangeAddressArray.add(node["address"] as String);
// set current index
p2pkhChangeIndex = index + k;
// reset counter
changeGapCounter = 0;
// add info to derivations
p2pkhChangeDerivations[node["address"] as String] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
}
// increase counter when no tx history found
if (p2pkhTxCount == 0) {
changeGapCounter++;
}
}
}
p2pkhChangeAddressArray =
(await resultChange44)['addressArray'] as List<String>;
p2pkhChangeIndex = (await resultChange44)['index'] as int;
p2pkhChangeDerivations = (await resultChange44)['derivations']
as Map<String, Map<String, String>>;
// save the derivations (if any)
if (p2pkhReceiveDerivations.isNotEmpty) {
@ -815,7 +813,7 @@ class DogecoinWallet extends CoinServiceAPI {
refreshMutex = false;
if (shouldAutoSync) {
timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async {
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
// chain height check currently broken
// if ((await chainHeight) != (await storedChainHeight)) {
if (await refreshIfThereIsNewData()) {
@ -1496,6 +1494,43 @@ class DogecoinWallet extends CoinServiceAPI {
await _secureStore.write(key: key, value: newReceiveDerivationsString);
}
Future<List<Map<String, dynamic>>> fastFetch(List<String> allTxHashes) async {
List<Map<String, dynamic>> allTransactions = [];
const futureLimit = 30;
List<Future<Map<String, dynamic>>> transactionFutures = [];
int currentFutureCount = 0;
for (final txHash in allTxHashes) {
Future<Map<String, dynamic>> transactionFuture =
cachedElectrumXClient.getTransaction(
txHash: txHash,
verbose: true,
coin: coin,
);
transactionFutures.add(transactionFuture);
currentFutureCount++;
if (currentFutureCount > futureLimit) {
currentFutureCount = 0;
await Future.wait(transactionFutures);
for (final fTx in transactionFutures) {
final tx = await fTx;
allTransactions.add(tx);
}
}
}
if (currentFutureCount != 0) {
currentFutureCount = 0;
await Future.wait(transactionFutures);
for (final fTx in transactionFutures) {
final tx = await fTx;
allTransactions.add(tx);
}
}
return allTransactions;
}
Future<UtxoData> _fetchUtxoData() async {
final List<String> allAddresses = await _fetchAllOwnAddresses();
@ -1503,7 +1538,7 @@ class DogecoinWallet extends CoinServiceAPI {
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
final Map<int, Map<String, List<dynamic>>> batches = {};
const batchSizeMax = 10;
const batchSizeMax = 100;
int batchNumber = 0;
for (int i = 0; i < allAddresses.length; i++) {
if (batches[batchNumber] == null) {
@ -1872,7 +1907,7 @@ class DogecoinWallet extends CoinServiceAPI {
final Map<int, Map<String, List<dynamic>>> batches = {};
final Map<String, String> requestIdToAddressMap = {};
const batchSizeMax = 10;
const batchSizeMax = 100;
int batchNumber = 0;
for (int i = 0; i < allAddresses.length; i++) {
if (batches[batchNumber] == null) {
@ -1954,6 +1989,11 @@ class DogecoinWallet extends CoinServiceAPI {
}
}
List<String> hashes = [];
for (var element in allTxHashes) {
hashes.add(element['tx_hash'] as String);
}
await fastFetch(hashes);
List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
@ -1983,6 +2023,16 @@ class DogecoinWallet extends CoinServiceAPI {
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
final List<Map<String, dynamic>> midSortedArray = [];
Set<String> vHashes = {};
for (final txObject in allTransactions) {
for (int i = 0; i < (txObject["vin"] as List).length; i++) {
final input = txObject["vin"]![i] as Map;
final prevTxid = input["txid"] as String;
vHashes.add(prevTxid);
}
}
await fastFetch(vHashes.toList());
for (final txObject in allTransactions) {
List<String> sendersArray = [];
List<String> recipientsArray = [];

View file

@ -43,6 +43,7 @@ import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:tuple/tuple.dart';
import 'package:uuid/uuid.dart';
const DUST_LIMIT = 1000;
const MINIMUM_CONFIRMATIONS = 1;
@ -145,25 +146,17 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
final setDataMap = arguments['setDataMap'] as Map;
final usedSerialNumbers = arguments['usedSerialNumbers'] as List?;
final mnemonic = arguments['mnemonic'] as String;
final transactionData =
arguments['transactionData'] as models.TransactionData;
final currency = arguments['currency'] as String;
final coin = arguments['coin'] as Coin;
final network = arguments['network'] as NetworkType?;
final currentPrice = arguments['currentPrice'] as Decimal;
final locale = arguments['locale'] as String;
if (!(usedSerialNumbers == null || network == null)) {
var restoreData = await isolateRestore(
mnemonic,
transactionData,
currency,
coin,
latestSetId,
setDataMap,
usedSerialNumbers,
network,
currentPrice,
locale);
mnemonic,
coin,
latestSetId,
setDataMap,
usedSerialNumbers,
network,
);
sendPort.send(restoreData);
return;
}
@ -240,15 +233,11 @@ Future<Map<String, dynamic>> isolateDerive(
Future<Map<String, dynamic>> isolateRestore(
String mnemonic,
models.TransactionData data,
String currency,
Coin coin,
int _latestSetId,
Map<dynamic, dynamic> _setDataMap,
List<dynamic> _usedSerialNumbers,
NetworkType network,
Decimal currentPrice,
String locale,
) async {
List<int> jindexes = [];
List<Map<dynamic, LelantusCoin>> lelantusCoins = [];
@ -373,6 +362,20 @@ Future<Map<String, dynamic>> isolateRestore(
result['_lelantus_coins'] = lelantusCoins;
result['mintIndex'] = lastFoundIndex + 1;
result['jindex'] = jindexes;
result['spendTxIds'] = spendTxIds;
return result;
}
Future<Map<dynamic, dynamic>> staticProcessRestore(
models.TransactionData data,
Map<dynamic, dynamic> result,
) async {
List<dynamic>? _l = result['_lelantus_coins'] as List?;
final List<Map<dynamic, LelantusCoin>> lelantusCoins = [];
for (var el in _l ?? []) {
lelantusCoins.add({el.keys.first: el.values.first as LelantusCoin});
}
// Edit the receive transactions with the mint fees.
Map<String, models.Transaction> editedTransactions =
@ -437,7 +440,6 @@ Future<Map<String, dynamic>> isolateRestore(
(value.height == -1 && !value.confirmedStatus));
result['newTxMap'] = transactionMap;
result['spendTxIds'] = spendTxIds;
return result;
}
@ -2193,7 +2195,7 @@ class FiroWallet extends CoinServiceAPI {
final newTxData = _fetchTransactionData();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.35, walletId));
final FeeObject feeObj = await _getFees();
final feeObj = _getFees();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
_utxoData = Future(() => newUtxoData);
@ -2217,9 +2219,6 @@ class FiroWallet extends CoinServiceAPI {
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.95, walletId));
final maxFee = await _fetchMaxFee();
_maxFee = Future(() => maxFee);
var txData = (await _txnData);
var lTxData = (await lelantusTransactionData);
await getAllTxsToWatch(txData, lTxData);
@ -2236,7 +2235,7 @@ class FiroWallet extends CoinServiceAPI {
refreshMutex = false;
if (isActive || shouldAutoSync) {
timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async {
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
bool shouldNotify = await refreshIfThereIsNewData();
if (shouldNotify) {
await refresh();
@ -2744,7 +2743,7 @@ class FiroWallet extends CoinServiceAPI {
};
}
Future<void> _refreshLelantusData() async {
Future<models.TransactionData> _refreshLelantusData() async {
final List<Map<dynamic, LelantusCoin>> lelantusCoins = getLelantusCoinMap();
final jindexes =
DB.instance.get<dynamic>(boxName: walletId, key: 'jindex') as List?;
@ -2826,6 +2825,7 @@ class FiroWallet extends CoinServiceAPI {
_lelantusTransactionData = Future(() => newTxData);
await DB.instance.put<dynamic>(
boxName: walletId, key: 'latest_lelantus_tx_model', value: newTxData);
return newTxData;
}
Future<String> _getMintHex(int amount, int index) async {
@ -3122,24 +3122,44 @@ class FiroWallet extends CoinServiceAPI {
List<String> allAddresses) async {
try {
List<Map<String, dynamic>> allTxHashes = [];
// int latestTxnBlockHeight = 0;
for (final address in allAddresses) {
final scripthash = AddressUtils.convertToScriptHash(address, _network);
final txs = await electrumXClient.getHistory(scripthash: scripthash);
for (final map in txs) {
if (!allTxHashes.contains(map)) {
map['address'] = address;
allTxHashes.add(map);
final Map<int, Map<String, List<dynamic>>> batches = {};
final Map<String, String> requestIdToAddressMap = {};
const batchSizeMax = 100;
int batchNumber = 0;
for (int i = 0; i < allAddresses.length; i++) {
if (batches[batchNumber] == null) {
batches[batchNumber] = {};
}
final scripthash =
AddressUtils.convertToScriptHash(allAddresses[i], _network);
final id = const Uuid().v1();
requestIdToAddressMap[id] = allAddresses[i];
batches[batchNumber]!.addAll({
id: [scripthash]
});
if (i % batchSizeMax == batchSizeMax - 1) {
batchNumber++;
}
}
for (int i = 0; i < batches.length; i++) {
final response =
await _electrumXClient.getBatchHistory(args: batches[i]!);
for (final entry in response.entries) {
for (int j = 0; j < entry.value.length; j++) {
entry.value[j]["address"] = requestIdToAddressMap[entry.key];
if (!allTxHashes.contains(entry.value[j])) {
allTxHashes.add(entry.value[j]);
}
}
}
}
return allTxHashes;
} catch (e, s) {
Logging.instance.log("Exception caught in _fetchHistory(): $e\n$s",
level: LogLevel.Error);
return [];
Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error);
rethrow;
}
}
@ -3178,20 +3198,11 @@ class FiroWallet extends CoinServiceAPI {
}
}
List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
final tx = await cachedElectrumXClient.getTransaction(
txHash: txHash["tx_hash"] as String,
verbose: true,
coin: coin,
);
// delete unused large parts
tx.remove("hex");
tx.remove("lelantusData");
allTransactions.add(tx);
List<String> hashes = [];
for (var element in allTxHashes) {
hashes.add(element['tx_hash'] as String);
}
List<Map<String, dynamic>> allTransactions = await fastFetch(hashes);
Logging.instance.log("allTransactions length: ${allTransactions.length}",
level: LogLevel.Info);
@ -3441,81 +3452,83 @@ class FiroWallet extends CoinServiceAPI {
}
Future<UtxoData> _fetchUtxoData() async {
final List<String> allAddresses = [];
final receivingAddresses =
DB.instance.get<dynamic>(boxName: walletId, key: 'receivingAddresses')
as List<dynamic>;
final changeAddresses =
DB.instance.get<dynamic>(boxName: walletId, key: 'changeAddresses')
as List<dynamic>;
for (var i = 0; i < receivingAddresses.length; i++) {
if (!allAddresses.contains(receivingAddresses[i])) {
allAddresses.add(receivingAddresses[i] as String);
}
}
for (var i = 0; i < changeAddresses.length; i++) {
if (!allAddresses.contains(changeAddresses[i])) {
allAddresses.add(changeAddresses[i] as String);
}
}
final List<String> allAddresses = await _fetchAllOwnAddresses();
try {
final utxoData = <List<Map<String, dynamic>>>[];
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
final Map<int, Map<String, List<dynamic>>> batches = {};
const batchSizeMax = 100;
int batchNumber = 0;
for (int i = 0; i < allAddresses.length; i++) {
if (batches[batchNumber] == null) {
batches[batchNumber] = {};
}
final scripthash =
AddressUtils.convertToScriptHash(allAddresses[i], _network);
final utxos = await electrumXClient.getUTXOs(scripthash: scripthash);
if (utxos.isNotEmpty) {
utxoData.add(utxos);
batches[batchNumber]!.addAll({
scripthash: [scripthash]
});
if (i % batchSizeMax == batchSizeMax - 1) {
batchNumber++;
}
}
Decimal currentPrice = await firoPrice;
for (int i = 0; i < batches.length; i++) {
final response =
await _electrumXClient.getBatchUTXOs(args: batches[i]!);
for (final entry in response.entries) {
if (entry.value.isNotEmpty) {
fetchedUtxoList.add(entry.value);
}
}
}
final priceData =
await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
final List<Map<String, dynamic>> outputArray = [];
int satoshiBalance = 0;
int satoshiBalancePending = 0;
for (int i = 0; i < utxoData.length; i++) {
for (int j = 0; j < utxoData[i].length; j++) {
int value = utxoData[i][j]["value"] as int;
for (int i = 0; i < fetchedUtxoList.length; i++) {
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
int value = fetchedUtxoList[i][j]["value"] as int;
satoshiBalance += value;
final txn = await cachedElectrumXClient.getTransaction(
txHash: utxoData[i][j]["tx_hash"] as String,
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
verbose: true,
coin: coin,
);
final Map<String, dynamic> tx = {};
final Map<String, dynamic> utxo = {};
final int confirmations = txn["confirmations"] as int? ?? 0;
final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS;
if (!confirmed) {
satoshiBalancePending += value;
}
tx["txid"] = txn["txid"];
tx["vout"] = utxoData[i][j]["tx_pos"];
tx["value"] = value;
utxo["txid"] = txn["txid"];
utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"];
utxo["value"] = value;
tx["status"] = <String, dynamic>{};
tx["status"]["confirmed"] = confirmed;
tx["status"]["confirmations"] = confirmations;
tx["status"]["confirmed"] =
utxo["status"] = <String, dynamic>{};
utxo["status"]["confirmed"] = confirmed;
utxo["status"]["confirmations"] = confirmations;
utxo["status"]["confirmed"] =
txn["confirmations"] == null ? false : txn["confirmations"] > 0;
tx["status"]["block_height"] = txn["height"];
tx["status"]["block_hash"] = txn["blockhash"];
tx["status"]["block_time"] = txn["blocktime"];
utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"];
utxo["status"]["block_hash"] = txn["blockhash"];
utxo["status"]["block_time"] = txn["blocktime"];
final fiatValue = ((Decimal.fromInt(value) * currentPrice) /
Decimal.fromInt(Constants.satsPerCoin))
.toDecimal(scaleOnInfinitePrecision: 2);
tx["rawWorth"] = fiatValue;
tx["fiatWorth"] = fiatValue.toString();
tx["is_coinbase"] = txn['vin'][0]['coinbase'] != null;
outputArray.add(tx);
utxo["rawWorth"] = fiatValue;
utxo["fiatWorth"] = fiatValue.toString();
utxo["is_coinbase"] = txn['vin'][0]['coinbase'] != null;
outputArray.add(utxo);
}
}
@ -3538,13 +3551,19 @@ class FiroWallet extends CoinServiceAPI {
final dataModel = UtxoData.fromJson(result);
final List<UtxoObject> allOutputs = dataModel.unspentOutputArray;
// Logging.instance.log('Outputs fetched: $allOutputs');
Logging.instance
.log('Outputs fetched: $allOutputs', level: LogLevel.Info);
await _sortOutputs(allOutputs);
await DB.instance.put<dynamic>(
boxName: walletId, key: 'latest_utxo_model', value: dataModel);
// await DB.instance.put<dynamic>(
// boxName: walletId,
// key: 'totalBalance',
// value: dataModel.satoshiBalance);
return dataModel;
} catch (e) {
// Logging.instance.log("Output fetch unsuccessful: $e\n$s");
} catch (e, s) {
Logging.instance
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
final latestTxModel =
DB.instance.get<dynamic>(boxName: walletId, key: 'latest_utxo_model')
as models.UtxoData?;
@ -4056,6 +4075,135 @@ class FiroWallet extends CoinServiceAPI {
bool longMutex = false;
Future<Map<int, dynamic>> getSetDataMap(int latestSetId) async {
final Map<int, dynamic> setDataMap = {};
final anonymitySets = await fetchAnonymitySets();
for (int setId = 1; setId <= latestSetId; setId++) {
final setData = anonymitySets
.firstWhere((element) => element["setId"] == setId, orElse: () => {});
if (setData.isNotEmpty) {
setDataMap[setId] = setData;
}
}
return setDataMap;
}
Future<void> _makeDerivations(
String suppliedMnemonic, int maxUnusedAddressGap) async {
List<String> receivingAddressArray = [];
List<String> changeAddressArray = [];
int receivingIndex = -1;
int changeIndex = -1;
// The gap limit will be capped at 20
int receivingGapCounter = 0;
int changeGapCounter = 0;
await fillAddresses(suppliedMnemonic,
numberOfThreads: Platform.numberOfProcessors - isolates.length - 1);
final receiveDerivationsString =
await _secureStore.read(key: "${walletId}_receiveDerivations");
final changeDerivationsString =
await _secureStore.read(key: "${walletId}_changeDerivations");
final receiveDerivations = Map<String, dynamic>.from(
jsonDecode(receiveDerivationsString ?? "{}") as Map);
final changeDerivations = Map<String, dynamic>.from(
jsonDecode(changeDerivationsString ?? "{}") as Map);
// log("rcv: $receiveDerivations");
// log("chg: $changeDerivations");
// Deriving and checking for receiving addresses
for (var i = 0; i < receiveDerivations.length; i++) {
// Break out of loop when receivingGapCounter hits maxUnusedAddressGap
// Same gap limit for change as for receiving, breaks when it hits maxUnusedAddressGap
if (receivingGapCounter >= maxUnusedAddressGap &&
changeGapCounter >= maxUnusedAddressGap) {
break;
}
final receiveDerivation = receiveDerivations["$i"];
final address = receiveDerivation['address'] as String;
final changeDerivation = changeDerivations["$i"];
final _address = changeDerivation['address'] as String;
Future<int>? futureNumTxs;
Future<int>? _futureNumTxs;
if (receivingGapCounter < maxUnusedAddressGap) {
futureNumTxs = _getReceivedTxCount(address: address);
}
if (changeGapCounter < maxUnusedAddressGap) {
_futureNumTxs = _getReceivedTxCount(address: _address);
}
try {
if (futureNumTxs != null) {
int numTxs = await futureNumTxs;
if (numTxs >= 1) {
receivingIndex = i;
receivingAddressArray.add(address);
} else if (numTxs == 0) {
receivingGapCounter += 1;
}
}
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s",
level: LogLevel.Error);
rethrow;
}
try {
if (_futureNumTxs != null) {
int numTxs = await _futureNumTxs;
if (numTxs >= 1) {
changeIndex = i;
changeAddressArray.add(_address);
} else if (numTxs == 0) {
changeGapCounter += 1;
}
}
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s",
level: LogLevel.Error);
rethrow;
}
}
// If restoring a wallet that never received any funds, then set receivingArray manually
// If we didn't do this, it'd store an empty array
if (receivingIndex == -1) {
final String receivingAddress = await _generateAddressForChain(0, 0);
receivingAddressArray.add(receivingAddress);
}
// If restoring a wallet that never sent any funds with change, then set changeArray
// manually. If we didn't do this, it'd store an empty array.
if (changeIndex == -1) {
final String changeAddress = await _generateAddressForChain(1, 0);
changeAddressArray.add(changeAddress);
}
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'receivingAddresses',
value: receivingAddressArray);
await DB.instance.put<dynamic>(
boxName: walletId, key: 'changeAddresses', value: changeAddressArray);
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'receivingIndex',
value: receivingIndex == -1 ? 0 : receivingIndex);
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'changeIndex',
value: changeIndex == -1 ? 0 : changeIndex);
}
/// Recovers wallet from [suppliedMnemonic]. Expects a valid mnemonic.
Future<void> _recoverWalletFromBIP32SeedPhrase(
String suppliedMnemonic, int maxUnusedAddressGap) async {
@ -4063,137 +4211,20 @@ class FiroWallet extends CoinServiceAPI {
Logging.instance
.log("PROCESSORS ${Platform.numberOfProcessors}", level: LogLevel.Info);
try {
final Map<int, dynamic> setDataMap = {};
final latestSetId = await getLatestSetId();
final anonymitySets = await fetchAnonymitySets();
for (int setId = 1; setId <= latestSetId; setId++) {
final setData = anonymitySets.firstWhere(
(element) => element["setId"] == setId,
orElse: () => {});
if (setData.isNotEmpty) {
setDataMap[setId] = setData;
}
}
final setDataMap = getSetDataMap(latestSetId);
final usedSerialNumbers = getUsedCoinSerials();
final makeDerivations =
_makeDerivations(suppliedMnemonic, maxUnusedAddressGap);
List<String> receivingAddressArray = [];
List<String> changeAddressArray = [];
int receivingIndex = -1;
int changeIndex = -1;
// The gap limit will be capped at 20
int receivingGapCounter = 0;
int changeGapCounter = 0;
await fillAddresses(suppliedMnemonic,
numberOfThreads: Platform.numberOfProcessors - isolates.length - 1);
final receiveDerivationsString =
await _secureStore.read(key: "${walletId}_receiveDerivations");
final changeDerivationsString =
await _secureStore.read(key: "${walletId}_changeDerivations");
final receiveDerivations = Map<String, dynamic>.from(
jsonDecode(receiveDerivationsString ?? "{}") as Map);
final changeDerivations = Map<String, dynamic>.from(
jsonDecode(changeDerivationsString ?? "{}") as Map);
// log("rcv: $receiveDerivations");
// log("chg: $changeDerivations");
// Deriving and checking for receiving addresses
for (var i = 0; i < receiveDerivations.length; i++) {
// Break out of loop when receivingGapCounter hits maxUnusedAddressGap
// Same gap limit for change as for receiving, breaks when it hits maxUnusedAddressGap
if (receivingGapCounter >= maxUnusedAddressGap &&
changeGapCounter >= maxUnusedAddressGap) {
break;
}
final receiveDerivation = receiveDerivations["$i"];
final address = receiveDerivation['address'] as String;
final changeDerivation = changeDerivations["$i"];
final _address = changeDerivation['address'] as String;
Future<int>? futureNumTxs;
Future<int>? _futureNumTxs;
if (receivingGapCounter < maxUnusedAddressGap) {
futureNumTxs = _getReceivedTxCount(address: address);
}
if (changeGapCounter < maxUnusedAddressGap) {
_futureNumTxs = _getReceivedTxCount(address: _address);
}
try {
if (futureNumTxs != null) {
int numTxs = await futureNumTxs;
if (numTxs >= 1) {
receivingIndex = i;
receivingAddressArray.add(address);
} else if (numTxs == 0) {
receivingGapCounter += 1;
}
}
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s",
level: LogLevel.Error);
rethrow;
}
try {
if (_futureNumTxs != null) {
int numTxs = await _futureNumTxs;
if (numTxs >= 1) {
changeIndex = i;
changeAddressArray.add(_address);
} else if (numTxs == 0) {
changeGapCounter += 1;
}
}
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s",
level: LogLevel.Error);
rethrow;
}
}
// If restoring a wallet that never received any funds, then set receivingArray manually
// If we didn't do this, it'd store an empty array
if (receivingIndex == -1) {
final String receivingAddress = await _generateAddressForChain(0, 0);
receivingAddressArray.add(receivingAddress);
}
// If restoring a wallet that never sent any funds with change, then set changeArray
// manually. If we didn't do this, it'd store an empty array.
if (changeIndex == -1) {
final String changeAddress = await _generateAddressForChain(1, 0);
changeAddressArray.add(changeAddress);
}
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'receivingAddresses',
value: receivingAddressArray);
await DB.instance.put<dynamic>(
boxName: walletId, key: 'changeAddresses', value: changeAddressArray);
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'receivingIndex',
value: receivingIndex == -1 ? 0 : receivingIndex);
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'changeIndex',
value: changeIndex == -1 ? 0 : changeIndex);
await DB.instance
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
await DB.instance
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
await _restore(latestSetId, setDataMap, await usedSerialNumbers);
await Future.wait([usedSerialNumbers, setDataMap, makeDerivations]);
await _restore(latestSetId, await setDataMap, await usedSerialNumbers);
longMutex = false;
} catch (e, s) {
longMutex = false;
@ -4207,27 +4238,23 @@ class FiroWallet extends CoinServiceAPI {
Future<void> _restore(int latestSetId, Map<dynamic, dynamic> setDataMap,
dynamic usedSerialNumbers) async {
final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic');
models.TransactionData data = await _txnData;
Future data = _txnData;
final String currency = _prefs.currency;
final Decimal currentPrice = await firoPrice;
final locale = await Devicelocale.currentLocale;
ReceivePort receivePort = await getIsolate({
"function": "restore",
"mnemonic": mnemonic,
"transactionData": data,
"currency": currency,
"coin": coin,
"latestSetId": latestSetId,
"setDataMap": setDataMap,
"usedSerialNumbers": usedSerialNumbers,
"network": _network,
"currentPrice": currentPrice,
"locale": locale,
});
var message = await receivePort.first;
if (message is String) {
await Future.wait([data]);
var result = await receivePort.first;
if (result is String) {
Logging.instance
.log("restore() ->> this is a string", level: LogLevel.Error);
stop(receivePort);
@ -4235,6 +4262,10 @@ class FiroWallet extends CoinServiceAPI {
}
stop(receivePort);
final message = await staticProcessRestore(
(await data) as models.TransactionData,
result as Map<dynamic, dynamic>);
await DB.instance.put<dynamic>(
boxName: walletId, key: 'mintIndex', value: message['mintIndex']);
await DB.instance.put<dynamic>(
@ -4274,11 +4305,18 @@ class FiroWallet extends CoinServiceAPI {
final latestSetId = await getLatestSetId();
final List<Map<String, dynamic>> sets = [];
List<Future> anonFutures = [];
for (int i = 1; i <= latestSetId; i++) {
Map<String, dynamic> set = await cachedElectrumXClient.getAnonymitySet(
final set = cachedElectrumXClient.getAnonymitySet(
groupId: "$i",
coin: coin,
);
anonFutures.add(set);
}
await Future.wait(anonFutures);
for (int i = 1; i <= latestSetId; i++) {
Map<String, dynamic> set =
(await anonFutures[i - 1]) as Map<String, dynamic>;
set["setId"] = i;
sets.add(set);
}
@ -4567,6 +4605,49 @@ class FiroWallet extends CoinServiceAPI {
return available - estimatedFee;
}
Future<List<Map<String, dynamic>>> fastFetch(List<String> allTxHashes) async {
List<Map<String, dynamic>> allTransactions = [];
const futureLimit = 30;
List<Future<Map<String, dynamic>>> transactionFutures = [];
int currentFutureCount = 0;
for (final txHash in allTxHashes) {
Future<Map<String, dynamic>> transactionFuture =
cachedElectrumXClient.getTransaction(
txHash: txHash,
verbose: true,
coin: coin,
);
transactionFutures.add(transactionFuture);
currentFutureCount++;
if (currentFutureCount > futureLimit) {
currentFutureCount = 0;
await Future.wait(transactionFutures);
for (final fTx in transactionFutures) {
final tx = await fTx;
// delete unused large parts
tx.remove("hex");
tx.remove("lelantusData");
allTransactions.add(tx);
}
}
}
if (currentFutureCount != 0) {
currentFutureCount = 0;
await Future.wait(transactionFutures);
for (final fTx in transactionFutures) {
final tx = await fTx;
// delete unused large parts
tx.remove("hex");
tx.remove("lelantusData");
allTransactions.add(tx);
}
}
return allTransactions;
}
Future<List<models.Transaction>> getJMintTransactions(
CachedElectrumX cachedClient,
List<String> transactions,
@ -4577,14 +4658,12 @@ class FiroWallet extends CoinServiceAPI {
) async {
try {
List<models.Transaction> txs = [];
List<Map<String, dynamic>> allTransactions =
await fastFetch(transactions);
for (int i = 0; i < transactions.length; i++) {
for (int i = 0; i < allTransactions.length; i++) {
try {
final tx = await cachedClient.getTransaction(
txHash: transactions[i],
verbose: true,
coin: coin,
);
final tx = allTransactions[i];
tx["confirmed_status"] =
tx["confirmations"] != null && tx["confirmations"] as int > 0;

View file

@ -66,18 +66,15 @@ void main() {
return SampleGetTransactionData.txData7;
});
final result = await isolateRestore(
final message = await isolateRestore(
TEST_MNEMONIC,
txData,
"USD",
Coin.firo,
1,
setData,
usedSerials,
firoNetwork,
Decimal.ten,
"en_US",
);
final result = await staticProcessRestore(txData, message);
expect(result, isA<Map<String, dynamic>>());
expect(result["mintIndex"], 8);
@ -88,22 +85,17 @@ void main() {
});
test("isolateRestore throws", () async {
final txData = TransactionData();
final Map setData = {};
final usedSerials = [];
expect(
() => isolateRestore(
TEST_MNEMONIC,
txData,
"USD",
Coin.firo,
1,
setData,
usedSerials,
firoNetwork,
Decimal.ten,
"en_US",
),
throwsA(isA<Error>()));
});