clean up btc restore, stop storing derivations as they aren't needed anymore, and ensure all previous unused addresses are saved.

This commit is contained in:
julian 2023-04-20 12:34:31 -06:00
parent 1714e2a7d8
commit 703ceee86d

View file

@ -423,16 +423,15 @@ class BitcoinWallet extends CoinServiceAPI
level: LogLevel.Info); level: LogLevel.Info);
} }
Future<Map<String, dynamic>> _checkGaps( Future<Tuple2<List<isar_models.Address>, DerivePathType>> _checkGaps(
int maxNumberOfIndexesToCheck, int maxNumberOfIndexesToCheck,
int maxUnusedAddressGap, int maxUnusedAddressGap,
int txCountBatchSize, int txCountBatchSize,
bip32.BIP32 root, bip32.BIP32 root,
DerivePathType type, DerivePathType type,
int chain) async { int chain,
) async {
List<isar_models.Address> addressArray = []; List<isar_models.Address> addressArray = [];
int returningIndex = -1;
Map<String, Map<String, String>> derivations = {};
int gapCounter = 0; int gapCounter = 0;
for (int index = 0; for (int index = 0;
index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap;
@ -444,7 +443,6 @@ class BitcoinWallet extends CoinServiceAPI
final _id = "k_$index"; final _id = "k_$index";
Map<String, String> txCountCallArgs = {}; Map<String, String> txCountCallArgs = {};
final Map<String, dynamic> receivingNodes = {};
for (int j = 0; j < txCountBatchSize; j++) { for (int j = 0; j < txCountBatchSize; j++) {
final derivePath = constructDerivePath( final derivePath = constructDerivePath(
@ -492,12 +490,8 @@ class BitcoinWallet extends CoinServiceAPI
: isar_models.AddressSubType.change, : isar_models.AddressSubType.change,
); );
receivingNodes.addAll({ addressArray.add(address);
"${_id}_$j": {
"node": node,
"address": address,
}
});
txCountCallArgs.addAll({ txCountCallArgs.addAll({
"${_id}_$j": addressString, "${_id}_$j": addressString,
}); });
@ -510,21 +504,10 @@ class BitcoinWallet extends CoinServiceAPI
for (int k = 0; k < txCountBatchSize; k++) { for (int k = 0; k < txCountBatchSize; k++) {
int count = counts["${_id}_$k"]!; int count = counts["${_id}_$k"]!;
if (count > 0) { if (count > 0) {
final node = receivingNodes["${_id}_$k"]; iterationsAddressArray.add(txCountCallArgs["${_id}_$k"]!);
final address = node["address"] as isar_models.Address;
// add address to array
addressArray.add(address);
iterationsAddressArray.add(address.value);
// set current index
returningIndex = index + k;
// reset counter // reset counter
gapCounter = 0; gapCounter = 0;
// add info to derivations
derivations[address.value] = {
"pubKey": Format.uint8listToString(
(node["node"] as bip32.BIP32).publicKey),
"wif": (node["node"] as bip32.BIP32).toWIF(),
};
} }
// increase counter when no tx history found // increase counter when no tx history found
@ -535,11 +518,7 @@ class BitcoinWallet extends CoinServiceAPI
// cache all the transactions while waiting for the current function to finish. // cache all the transactions while waiting for the current function to finish.
unawaited(getTransactionCacheEarly(iterationsAddressArray)); unawaited(getTransactionCacheEarly(iterationsAddressArray));
} }
return { return Tuple2(addressArray, type);
"addressArray": addressArray,
"index": returningIndex,
"derivations": derivations
};
} }
Future<void> getTransactionCacheEarly(List<String> allAddresses) async { Future<void> getTransactionCacheEarly(List<String> allAddresses) async {
@ -571,198 +550,113 @@ class BitcoinWallet extends CoinServiceAPI
}) async { }) async {
longMutex = true; longMutex = true;
Map<String, Map<String, String>> p2pkhReceiveDerivations = {};
Map<String, Map<String, String>> p2shReceiveDerivations = {};
Map<String, Map<String, String>> p2wpkhReceiveDerivations = {};
Map<String, Map<String, String>> p2pkhChangeDerivations = {};
Map<String, Map<String, String>> p2shChangeDerivations = {};
Map<String, Map<String, String>> p2wpkhChangeDerivations = {};
final root = await Bip32Utils.getBip32Root( final root = await Bip32Utils.getBip32Root(
mnemonic, mnemonic,
mnemonicPassphrase, mnemonicPassphrase,
_network, _network,
); );
List<isar_models.Address> p2pkhReceiveAddressArray = []; final deriveTypes = [
List<isar_models.Address> p2shReceiveAddressArray = []; DerivePathType.bip44,
List<isar_models.Address> p2wpkhReceiveAddressArray = []; DerivePathType.bip49,
int p2pkhReceiveIndex = -1; DerivePathType.bip84,
int p2shReceiveIndex = -1; ];
int p2wpkhReceiveIndex = -1;
List<isar_models.Address> p2pkhChangeAddressArray = []; final List<Future<Tuple2<List<isar_models.Address>, DerivePathType>>>
List<isar_models.Address> p2shChangeAddressArray = []; receiveFutures = [];
List<isar_models.Address> p2wpkhChangeAddressArray = []; final List<Future<Tuple2<List<isar_models.Address>, DerivePathType>>>
int p2pkhChangeIndex = -1; changeFutures = [];
int p2shChangeIndex = -1;
int p2wpkhChangeIndex = -1; const receiveChain = 0;
const changeChain = 1;
const indexZero = 0;
// actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3 // actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3
const txCountBatchSize = 12; const txCountBatchSize = 12;
try { try {
// receiving addresses // receiving addresses
Logging.instance Logging.instance.log(
.log("checking receiving addresses...", level: LogLevel.Info); "checking receiving addresses...",
final resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck, level: LogLevel.Info,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0); );
final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, for (final type in deriveTypes) {
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); receiveFutures.add(
_checkGaps(
maxNumberOfIndexesToCheck,
maxUnusedAddressGap,
txCountBatchSize,
root,
type,
receiveChain,
),
);
}
final resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck,
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0);
Logging.instance
.log("checking change addresses...", level: LogLevel.Info);
// change addresses // change addresses
final resultChange44 = _checkGaps(maxNumberOfIndexesToCheck, Logging.instance.log(
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1); "checking change addresses...",
level: LogLevel.Info,
);
for (final type in deriveTypes) {
changeFutures.add(
_checkGaps(
maxNumberOfIndexesToCheck,
maxUnusedAddressGap,
txCountBatchSize,
root,
type,
changeChain,
),
);
}
final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, // io limitations may require running these linearly instead
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); final futuresResult = await Future.wait([
Future.wait(receiveFutures),
final resultChange84 = _checkGaps(maxNumberOfIndexesToCheck, Future.wait(changeFutures),
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1);
await Future.wait([
resultReceive44,
resultReceive49,
resultReceive84,
resultChange44,
resultChange49,
resultChange84
]); ]);
p2pkhReceiveAddressArray = final receiveResults = futuresResult[0];
(await resultReceive44)['addressArray'] as List<isar_models.Address>; final changeResults = futuresResult[1];
p2pkhReceiveIndex = (await resultReceive44)['index'] as int;
p2pkhReceiveDerivations = (await resultReceive44)['derivations']
as Map<String, Map<String, String>>;
p2shReceiveAddressArray = final List<isar_models.Address> addressesToStore = [];
(await resultReceive49)['addressArray'] as List<isar_models.Address>;
p2shReceiveIndex = (await resultReceive49)['index'] as int;
p2shReceiveDerivations = (await resultReceive49)['derivations']
as Map<String, Map<String, String>>;
p2wpkhReceiveAddressArray =
(await resultReceive84)['addressArray'] as List<isar_models.Address>;
p2wpkhReceiveIndex = (await resultReceive84)['index'] as int;
p2wpkhReceiveDerivations = (await resultReceive84)['derivations']
as Map<String, Map<String, String>>;
p2pkhChangeAddressArray =
(await resultChange44)['addressArray'] as List<isar_models.Address>;
p2pkhChangeIndex = (await resultChange44)['index'] as int;
p2pkhChangeDerivations = (await resultChange44)['derivations']
as Map<String, Map<String, String>>;
p2shChangeAddressArray =
(await resultChange49)['addressArray'] as List<isar_models.Address>;
p2shChangeIndex = (await resultChange49)['index'] as int;
p2shChangeDerivations = (await resultChange49)['derivations']
as Map<String, Map<String, String>>;
p2wpkhChangeAddressArray =
(await resultChange84)['addressArray'] as List<isar_models.Address>;
p2wpkhChangeIndex = (await resultChange84)['index'] as int;
p2wpkhChangeDerivations = (await resultChange84)['derivations']
as Map<String, Map<String, String>>;
// save the derivations (if any)
if (p2pkhReceiveDerivations.isNotEmpty) {
await addDerivations(
chain: 0,
derivePathType: DerivePathType.bip44,
derivationsToAdd: p2pkhReceiveDerivations);
}
if (p2shReceiveDerivations.isNotEmpty) {
await addDerivations(
chain: 0,
derivePathType: DerivePathType.bip49,
derivationsToAdd: p2shReceiveDerivations);
}
if (p2wpkhReceiveDerivations.isNotEmpty) {
await addDerivations(
chain: 0,
derivePathType: DerivePathType.bip84,
derivationsToAdd: p2wpkhReceiveDerivations);
}
if (p2pkhChangeDerivations.isNotEmpty) {
await addDerivations(
chain: 1,
derivePathType: DerivePathType.bip44,
derivationsToAdd: p2pkhChangeDerivations);
}
if (p2shChangeDerivations.isNotEmpty) {
await addDerivations(
chain: 1,
derivePathType: DerivePathType.bip49,
derivationsToAdd: p2shChangeDerivations);
}
if (p2wpkhChangeDerivations.isNotEmpty) {
await addDerivations(
chain: 1,
derivePathType: DerivePathType.bip84,
derivationsToAdd: p2wpkhChangeDerivations);
}
// If restoring a wallet that never received any funds, then set receivingArray manually // 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 we didn't do this, it'd store an empty array
if (p2pkhReceiveIndex == -1) { for (final tuple in receiveResults) {
final address = if (tuple.item1.isEmpty) {
await _generateAddressForChain(0, 0, DerivePathType.bip44); final address = await _generateAddressForChain(
p2pkhReceiveAddressArray.add(address); receiveChain,
} indexZero,
if (p2shReceiveIndex == -1) { tuple.item2,
final address = );
await _generateAddressForChain(0, 0, DerivePathType.bip49); addressesToStore.add(address);
p2shReceiveAddressArray.add(address); } else {
} addressesToStore.addAll(tuple.item1);
if (p2wpkhReceiveIndex == -1) { }
final address =
await _generateAddressForChain(0, 0, DerivePathType.bip84);
p2wpkhReceiveAddressArray.add(address);
} }
// If restoring a wallet that never sent any funds with change, then set changeArray // 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. // manually. If we didn't do this, it'd store an empty array.
if (p2pkhChangeIndex == -1) { for (final tuple in changeResults) {
final address = if (tuple.item1.isEmpty) {
await _generateAddressForChain(1, 0, DerivePathType.bip44); final address = await _generateAddressForChain(
p2pkhChangeAddressArray.add(address); changeChain,
} indexZero,
if (p2shChangeIndex == -1) { tuple.item2,
final address = );
await _generateAddressForChain(1, 0, DerivePathType.bip49); addressesToStore.add(address);
p2shChangeAddressArray.add(address); } else {
} addressesToStore.addAll(tuple.item1);
if (p2wpkhChangeIndex == -1) { }
final address =
await _generateAddressForChain(1, 0, DerivePathType.bip84);
p2wpkhChangeAddressArray.add(address);
} }
if (isRescan) { if (isRescan) {
await db.updateOrPutAddresses([ await db.updateOrPutAddresses(addressesToStore);
...p2wpkhReceiveAddressArray,
...p2wpkhChangeAddressArray,
...p2pkhReceiveAddressArray,
...p2pkhChangeAddressArray,
...p2shReceiveAddressArray,
...p2shChangeAddressArray,
]);
} else { } else {
await db.putAddresses([ await db.putAddresses(addressesToStore);
...p2wpkhReceiveAddressArray,
...p2wpkhChangeAddressArray,
...p2pkhReceiveAddressArray,
...p2pkhChangeAddressArray,
...p2shReceiveAddressArray,
...p2shChangeAddressArray,
]);
} }
// get own payment code // get own payment code