add support for old electrumx servers that do not support batching. Also call wallet.init() on creation

This commit is contained in:
julian 2023-11-14 13:21:21 -06:00
parent bccc85c3ca
commit c51b6be2c4
5 changed files with 282 additions and 62 deletions

View file

@ -292,6 +292,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
mnemonic: mnemonic, mnemonic: mnemonic,
); );
await wallet.init();
await wallet.recover(isRescan: false); await wallet.recover(isRescan: false);
// check if state is still active before continuing // check if state is still active before continuing

View file

@ -414,6 +414,8 @@ abstract class SWB {
privateKey: privateKey, privateKey: privateKey,
); );
await wallet.init();
int restoreHeight = walletbackup['restoreHeight'] as int? ?? 0; int restoreHeight = walletbackup['restoreHeight'] as int? ?? 0;
if (restoreHeight <= 0) { if (restoreHeight <= 0) {
restoreHeight = walletbackup['storedChainHeight'] as int? ?? 0; restoreHeight = walletbackup['storedChainHeight'] as int? ?? 0;

View file

@ -354,7 +354,7 @@ class BitcoincashWallet extends Bip39HDWallet with ElectrumXMixin {
// not all coins need to override this. BCH does due to cash addr string formatting // not all coins need to override this. BCH does due to cash addr string formatting
@override @override
Future<({List<Address> addresses, int index})> checkGaps( Future<({List<Address> addresses, int index})> checkGapsBatched(
int txCountBatchSize, int txCountBatchSize,
coinlib.HDPrivateKey root, coinlib.HDPrivateKey root,
DerivePathType type, DerivePathType type,

View file

@ -352,9 +352,75 @@ class EcashWallet extends Bip39HDWallet with ElectrumXMixin {
return vSize * (feeRatePerKB / 1000).ceil(); return vSize * (feeRatePerKB / 1000).ceil();
} }
@override
Future<({List<Address> addresses, int index})> checkGapsLinearly(
coinlib.HDPrivateKey root,
DerivePathType type,
int chain,
) async {
List<Address> addressArray = [];
int gapCounter = 0;
int index = 0;
for (;
index < cryptoCurrency.maxNumberOfIndexesToCheck &&
gapCounter < cryptoCurrency.maxUnusedAddressGap;
index++) {
Logging.instance.log(
"index: $index, \t GapCounter chain=$chain ${type.name}: $gapCounter",
level: LogLevel.Info);
final derivePath = cryptoCurrency.constructDerivePath(
derivePathType: type,
chain: chain,
index: index,
);
final keys = root.derivePath(derivePath);
final addressData = cryptoCurrency.getAddressForPublicKey(
publicKey: keys.publicKey,
derivePathType: type,
);
// ecash specific
final addressString = bitbox.Address.toECashAddress(
addressData.address.toString(),
);
final address = Address(
walletId: walletId,
value: addressString,
publicKey: keys.publicKey.data,
type: addressData.addressType,
derivationIndex: index,
derivationPath: DerivationPath()..value = derivePath,
subType: chain == 0 ? AddressSubType.receiving : AddressSubType.change,
);
// get address tx count
final count = await fetchTxCount(
addressScriptHash: cryptoCurrency.addressToScriptHash(
address: address.value,
),
);
// check and add appropriate addresses
if (count > 0) {
// add address to array
addressArray.add(address);
// reset counter
gapCounter = 0;
// add info to derivations
} else {
// increase counter when no tx history found
gapCounter++;
}
}
return (addresses: addressArray, index: index);
}
// not all coins need to override this. ecash does due to cash addr string formatting // not all coins need to override this. ecash does due to cash addr string formatting
@override @override
Future<({List<Address> addresses, int index})> checkGaps( Future<({List<Address> addresses, int index})> checkGapsBatched(
int txCountBatchSize, int txCountBatchSize,
coinlib.HDPrivateKey root, coinlib.HDPrivateKey root,
DerivePathType type, DerivePathType type,

View file

@ -25,6 +25,9 @@ mixin ElectrumXMixin on Bip39HDWallet {
late ElectrumX electrumX; late ElectrumX electrumX;
late CachedElectrumX electrumXCached; late CachedElectrumX electrumXCached;
double? _serverVersion;
bool get serverCanBatch => _serverVersion != null && _serverVersion! >= 1.6;
List<({String address, Amount amount})> _helperRecipientsConvert( List<({String address, Amount amount})> _helperRecipientsConvert(
List<String> addrs, List<int> satValues) { List<String> addrs, List<int> satValues) {
final List<({String address, Amount amount})> results = []; final List<({String address, Amount amount})> results = [];
@ -792,7 +795,7 @@ mixin ElectrumXMixin on Bip39HDWallet {
//============================================================================ //============================================================================
Future<({List<Address> addresses, int index})> checkGaps( Future<({List<Address> addresses, int index})> checkGapsBatched(
int txCountBatchSize, int txCountBatchSize,
coinlib.HDPrivateKey root, coinlib.HDPrivateKey root,
DerivePathType type, DerivePathType type,
@ -873,40 +876,121 @@ mixin ElectrumXMixin on Bip39HDWallet {
return (index: highestIndexWithHistory, addresses: addressArray); return (index: highestIndexWithHistory, addresses: addressArray);
} }
Future<({List<Address> addresses, int index})> checkGapsLinearly(
coinlib.HDPrivateKey root,
DerivePathType type,
int chain,
) async {
List<Address> addressArray = [];
int gapCounter = 0;
int index = 0;
for (;
index < cryptoCurrency.maxNumberOfIndexesToCheck &&
gapCounter < cryptoCurrency.maxUnusedAddressGap;
index++) {
Logging.instance.log(
"index: $index, \t GapCounter chain=$chain ${type.name}: $gapCounter",
level: LogLevel.Info);
final derivePath = cryptoCurrency.constructDerivePath(
derivePathType: type,
chain: chain,
index: index,
);
final keys = root.derivePath(derivePath);
final addressData = cryptoCurrency.getAddressForPublicKey(
publicKey: keys.publicKey,
derivePathType: type,
);
final addressString = addressData.address.toString();
final address = Address(
walletId: walletId,
value: addressString,
publicKey: keys.publicKey.data,
type: addressData.addressType,
derivationIndex: index,
derivationPath: DerivationPath()..value = derivePath,
subType: chain == 0 ? AddressSubType.receiving : AddressSubType.change,
);
// get address tx count
final count = await fetchTxCount(
addressScriptHash: cryptoCurrency.addressToScriptHash(
address: address.value,
),
);
// check and add appropriate addresses
if (count > 0) {
// add address to array
addressArray.add(address);
// reset counter
gapCounter = 0;
// add info to derivations
} else {
// increase counter when no tx history found
gapCounter++;
}
}
return (addresses: addressArray, index: index);
}
Future<List<Map<String, dynamic>>> fetchHistory( Future<List<Map<String, dynamic>>> fetchHistory(
Iterable<String> allAddresses, Iterable<String> allAddresses,
) async { ) async {
try { try {
List<Map<String, dynamic>> allTxHashes = []; List<Map<String, dynamic>> allTxHashes = [];
final Map<int, Map<String, List<dynamic>>> batches = {}; if (serverCanBatch) {
final Map<String, String> requestIdToAddressMap = {}; final Map<int, Map<String, List<dynamic>>> batches = {};
const batchSizeMax = 100; final Map<String, String> requestIdToAddressMap = {};
int batchNumber = 0; const batchSizeMax = 100;
for (int i = 0; i < allAddresses.length; i++) { int batchNumber = 0;
if (batches[batchNumber] == null) { for (int i = 0; i < allAddresses.length; i++) {
batches[batchNumber] = {}; if (batches[batchNumber] == null) {
batches[batchNumber] = {};
}
final scriptHash = cryptoCurrency.addressToScriptHash(
address: allAddresses.elementAt(i),
);
final id = Logger.isTestEnv ? "$i" : const Uuid().v1();
requestIdToAddressMap[id] = allAddresses.elementAt(i);
batches[batchNumber]!.addAll({
id: [scriptHash]
});
if (i % batchSizeMax == batchSizeMax - 1) {
batchNumber++;
}
} }
final scriptHash = cryptoCurrency.addressToScriptHash(
address: allAddresses.elementAt(i),
);
final id = Logger.isTestEnv ? "$i" : const Uuid().v1();
requestIdToAddressMap[id] = allAddresses.elementAt(i);
batches[batchNumber]!.addAll({
id: [scriptHash]
});
if (i % batchSizeMax == batchSizeMax - 1) {
batchNumber++;
}
}
for (int i = 0; i < batches.length; i++) { for (int i = 0; i < batches.length; i++) {
final response = await electrumX.getBatchHistory(args: batches[i]!); final response = await electrumX.getBatchHistory(args: batches[i]!);
for (final entry in response.entries) { for (final entry in response.entries) {
for (int j = 0; j < entry.value.length; j++) { for (int j = 0; j < entry.value.length; j++) {
entry.value[j]["address"] = requestIdToAddressMap[entry.key]; entry.value[j]["address"] = requestIdToAddressMap[entry.key];
if (!allTxHashes.contains(entry.value[j])) { if (!allTxHashes.contains(entry.value[j])) {
allTxHashes.add(entry.value[j]); allTxHashes.add(entry.value[j]);
}
}
}
}
} else {
for (int i = 0; i < allAddresses.length; i++) {
final scriptHash = cryptoCurrency.addressToScriptHash(
address: allAddresses.elementAt(1),
);
final response = await electrumX.getHistory(
scripthash: scriptHash,
);
for (int j = 0; j < response.length; j++) {
response[j]["address"] = allAddresses.elementAt(1);
if (!allTxHashes.contains(response[j])) {
allTxHashes.add(response[j]);
} }
} }
} }
@ -1446,12 +1530,18 @@ mixin ElectrumXMixin on Bip39HDWallet {
for (final type in cryptoCurrency.supportedDerivationPathTypes) { for (final type in cryptoCurrency.supportedDerivationPathTypes) {
receiveFutures.add( receiveFutures.add(
checkGaps( serverCanBatch
txCountBatchSize, ? checkGapsBatched(
root, txCountBatchSize,
type, root,
receiveChain, type,
), receiveChain,
)
: checkGapsLinearly(
root,
type,
receiveChain,
),
); );
} }
@ -1462,12 +1552,18 @@ mixin ElectrumXMixin on Bip39HDWallet {
); );
for (final type in cryptoCurrency.supportedDerivationPathTypes) { for (final type in cryptoCurrency.supportedDerivationPathTypes) {
changeFutures.add( changeFutures.add(
checkGaps( serverCanBatch
txCountBatchSize, ? checkGapsBatched(
root, txCountBatchSize,
type, root,
changeChain, type,
), changeChain,
)
: checkGapsLinearly(
root,
type,
changeChain,
),
); );
} }
@ -1539,30 +1635,43 @@ mixin ElectrumXMixin on Bip39HDWallet {
try { try {
final fetchedUtxoList = <List<Map<String, dynamic>>>[]; final fetchedUtxoList = <List<Map<String, dynamic>>>[];
final Map<int, Map<String, List<dynamic>>> batches = {}; if (serverCanBatch) {
const batchSizeMax = 10; final Map<int, Map<String, List<dynamic>>> batches = {};
int batchNumber = 0; const batchSizeMax = 10;
for (int i = 0; i < allAddresses.length; i++) { int batchNumber = 0;
if (batches[batchNumber] == null) { for (int i = 0; i < allAddresses.length; i++) {
batches[batchNumber] = {}; if (batches[batchNumber] == null) {
} batches[batchNumber] = {};
final scriptHash = cryptoCurrency.addressToScriptHash( }
address: allAddresses[i].value, final scriptHash = cryptoCurrency.addressToScriptHash(
); address: allAddresses[i].value,
);
batches[batchNumber]!.addAll({ batches[batchNumber]!.addAll({
scriptHash: [scriptHash] scriptHash: [scriptHash]
}); });
if (i % batchSizeMax == batchSizeMax - 1) { if (i % batchSizeMax == batchSizeMax - 1) {
batchNumber++; batchNumber++;
}
} }
}
for (int i = 0; i < batches.length; i++) { for (int i = 0; i < batches.length; i++) {
final response = await electrumX.getBatchUTXOs(args: batches[i]!); final response = await electrumX.getBatchUTXOs(args: batches[i]!);
for (final entry in response.entries) { for (final entry in response.entries) {
if (entry.value.isNotEmpty) { if (entry.value.isNotEmpty) {
fetchedUtxoList.add(entry.value); fetchedUtxoList.add(entry.value);
}
}
}
} else {
for (int i = 0; i < allAddresses.length; i++) {
final scriptHash = cryptoCurrency.addressToScriptHash(
address: allAddresses[i].value,
);
final utxos = await electrumX.getUTXOs(scripthash: scriptHash);
if (utxos.isNotEmpty) {
fetchedUtxoList.add(utxos);
} }
} }
} }
@ -1707,6 +1816,28 @@ mixin ElectrumXMixin on Bip39HDWallet {
} }
} }
@override
Future<void> init() async {
try {
final features = await electrumX
.getServerFeatures()
.timeout(const Duration(seconds: 3));
Logging.instance.log("features: $features", level: LogLevel.Info);
_serverVersion =
_parseServerVersion(features["server_version"] as String);
if (cryptoCurrency.genesisHash != features['genesis_hash']) {
throw Exception("genesis hash does not match!");
}
} catch (e, s) {
Logging.instance.log("$e/n$s", level: LogLevel.Info);
}
await super.init();
}
// =========================================================================== // ===========================================================================
// ========== Interface functions ============================================ // ========== Interface functions ============================================
@ -1755,5 +1886,25 @@ mixin ElectrumXMixin on Bip39HDWallet {
estimatedFee; estimatedFee;
} }
// stupid + fragile
double? _parseServerVersion(String version) {
double? result;
try {
final list = version.split(" ");
if (list.isNotEmpty) {
final numberStrings = list.last.split(".");
final major = numberStrings.removeAt(0);
result = double.tryParse("$major.${numberStrings.join("")}");
}
} catch (_) {}
Logging.instance.log(
"${info.name} _parseServerVersion($version) => $result",
level: LogLevel.Info,
);
return result;
}
// =========================================================================== // ===========================================================================
} }