mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-23 03:49:22 +00:00
add support for old electrumx servers that do not support batching. Also call wallet.init() on creation
This commit is contained in:
parent
bccc85c3ca
commit
c51b6be2c4
5 changed files with 282 additions and 62 deletions
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue