add running setter and set running to false on exception in fuse

This commit is contained in:
sneurlax 2023-10-20 13:27:17 -05:00
parent 3df33ceb17
commit 9d001c3fba
2 changed files with 190 additions and 173 deletions

View file

@ -149,6 +149,10 @@ class FusionProgressUIState extends ChangeNotifier {
/// A flag indicating that fusion is running. /// A flag indicating that fusion is running.
bool _running = false; bool _running = false;
bool get running => _running; bool get running => _running;
set running(bool running) {
_running = running;
notifyListeners();
}
/// A helper method for setting the running flag. /// A helper method for setting the running flag.
/// ///

View file

@ -417,191 +417,204 @@ mixin FusionWalletInterface {
}) async { }) async {
// Initial attempt for CashFusion integration goes here. // Initial attempt for CashFusion integration goes here.
_updateStatus(status: fusion.FusionStatus.reset); try {
_updateStatus( _updateStatus(status: fusion.FusionStatus.reset);
status: fusion.FusionStatus.connecting, _updateStatus(
info: "Connecting to the CashFusion server.", status: fusion.FusionStatus.connecting,
); info: "Connecting to the CashFusion server.",
);
// Use server host and port which ultimately come from text fields. // Use server host and port which ultimately come from text fields.
fusion.FusionParams serverParams = fusion.FusionParams( fusion.FusionParams serverParams = fusion.FusionParams(
serverHost: fusionInfo.host, serverHost: fusionInfo.host,
serverPort: fusionInfo.port, serverPort: fusionInfo.port,
serverSsl: fusionInfo.ssl, serverSsl: fusionInfo.ssl,
); );
// Instantiate a Fusion object with custom parameters. // Instantiate a Fusion object with custom parameters.
_mainFusionObject = fusion.Fusion(serverParams); _mainFusionObject = fusion.Fusion(serverParams);
// Pass wallet functions to the Fusion object // Pass wallet functions to the Fusion object
await _mainFusionObject!.initFusion( await _mainFusionObject!.initFusion(
getTransactionsByAddress: _getTransactionsByAddress, getTransactionsByAddress: _getTransactionsByAddress,
getUnusedReservedChangeAddresses: _getUnusedReservedChangeAddresses, getUnusedReservedChangeAddresses: _getUnusedReservedChangeAddresses,
getSocksProxyAddress: _getSocksProxyAddress, getSocksProxyAddress: _getSocksProxyAddress,
getChainHeight: _getChainHeight, getChainHeight: _getChainHeight,
updateStatusCallback: _updateStatus, updateStatusCallback: _updateStatus,
getTransactionJson: (String txid) async => getTransactionJson: (String txid) async =>
await _getWalletCachedElectrumX().getTransaction( await _getWalletCachedElectrumX().getTransaction(
coin: _coin, coin: _coin,
txHash: txid, txHash: txid,
), ),
getPrivateKeyForPubKey: _getPrivateKeyForPubKey, getPrivateKeyForPubKey: _getPrivateKeyForPubKey,
broadcastTransaction: (String txHex) => _getWalletCachedElectrumX() broadcastTransaction: (String txHex) => _getWalletCachedElectrumX()
.electrumXClient .electrumXClient
.broadcastTransaction(rawTx: txHex), .broadcastTransaction(rawTx: txHex),
unReserveAddresses: (List<fusion.Address> addresses) async { unReserveAddresses: (List<fusion.Address> addresses) async {
final List<Future<void>> futures = []; final List<Future<void>> futures = [];
for (final addr in addresses) { for (final addr in addresses) {
futures.add( futures.add(
_db.getAddress(_walletId, addr.address).then( _db.getAddress(_walletId, addr.address).then(
(address) async { (address) async {
if (address == null) { if (address == null) {
// matching address not found in db so cannot mark as unreserved // matching address not found in db so cannot mark as unreserved
// just ignore I guess. Should never actually happen in practice. // just ignore I guess. Should never actually happen in practice.
// Might be useful check in debugging cases? // Might be useful check in debugging cases?
return; return;
} else { } else {
await _unReserveAddress(address); await _unReserveAddress(address);
} }
}, },
), ),
); );
} }
await Future.wait(futures); await Future.wait(futures);
}, },
); );
// Reset internal and UI counts and flag. // Reset internal and UI counts and flag.
_completedFuseCount = 0; _completedFuseCount = 0;
_uiState?.fusionRoundsCompleted = 0; _uiState?.fusionRoundsCompleted = 0;
_failedFuseCount = 0; _failedFuseCount = 0;
_uiState?.fusionRoundsFailed = 0; _uiState?.fusionRoundsFailed = 0;
_stopRequested = false; _stopRequested = false;
bool shouldFuzeAgain() { bool shouldFuzeAgain() {
if (fusionInfo.rounds <= 0) { if (fusionInfo.rounds <= 0) {
// ignore count if continuous // ignore count if continuous
return !_stopRequested; return !_stopRequested;
} else {
// not continuous
// check to make sure we aren't doing more fusions than requested
return !_stopRequested && _completedFuseCount < fusionInfo.rounds;
}
}
while (shouldFuzeAgain()) {
if (_completedFuseCount > 0 || _failedFuseCount > 0) {
_updateStatus(status: fusion.FusionStatus.reset);
_updateStatus(
status: fusion.FusionStatus.connecting,
info: "Connecting to the CashFusion server.",
);
}
// refresh wallet utxos
await _updateWalletUTXOS();
// Add unfrozen stack UTXOs.
final List<UTXO> walletUtxos = await _db
.getUTXOs(_walletId)
.filter()
.isBlockedEqualTo(false)
.and()
.addressIsNotNull()
.findAll();
final List<fusion.UtxoDTO> coinList = [];
// Loop through UTXOs, checking and adding valid ones.
for (final utxo in walletUtxos) {
final String addressString = utxo.address!;
final List<String> possibleAddresses = [addressString];
if (bitbox.Address.detectFormat(addressString) ==
bitbox.Address.formatCashAddr) {
possibleAddresses.add(bitbox.Address.toLegacyAddress(addressString));
} else { } else {
possibleAddresses.add(bitbox.Address.toCashAddress(addressString)); // not continuous
// check to make sure we aren't doing more fusions than requested
return !_stopRequested && _completedFuseCount < fusionInfo.rounds;
} }
// Fetch address to get pubkey
final addr = await _db
.getAddresses(_walletId)
.filter()
.anyOf<String,
QueryBuilder<Address, Address, QAfterFilterCondition>>(
possibleAddresses, (q, e) => q.valueEqualTo(e))
.and()
.group((q) => q
.subTypeEqualTo(AddressSubType.change)
.or()
.subTypeEqualTo(AddressSubType.receiving))
.and()
.typeEqualTo(AddressType.p2pkh)
.findFirst();
// depending on the address type in the query above this can be null
if (addr == null) {
// A utxo object should always have a non null address.
// If non found then just ignore the UTXO (aka don't fuse it)
Logging.instance.log(
"Ignoring utxo=$utxo for address=\"$addressString\" while selecting UTXOs for Fusion",
level: LogLevel.Info,
);
continue;
}
final dto = fusion.UtxoDTO(
txid: utxo.txid,
vout: utxo.vout,
value: utxo.value,
address: utxo.address!,
pubKey: addr.publicKey,
);
// Add UTXO to coinList.
coinList.add(dto);
} }
// Fuse UTXOs. while (shouldFuzeAgain()) {
try { if (_completedFuseCount > 0 || _failedFuseCount > 0) {
await _mainFusionObject!.fuse( _updateStatus(status: fusion.FusionStatus.reset);
inputsFromWallet: coinList,
network: _coin.isTestNet
? fusion.Utilities.testNet
: fusion.Utilities.mainNet,
);
// Increment the number of successfully completed fusion rounds.
_completedFuseCount++;
// Do the same for the UI state. This also resets the failed count (for
// the UI state only).
_uiState?.incrementFusionRoundsCompleted();
// Also reset the failed count here.
_failedFuseCount = 0;
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Error,
);
// just continue on attempt failure
// Increment the number of failed fusion rounds.
_failedFuseCount++;
// Do the same for the UI state.
_uiState?.incrementFusionRoundsFailed();
// If we fail too many times in a row, stop trying.
if (_failedFuseCount >= maxFailedFuseCount) {
_updateStatus( _updateStatus(
status: fusion.FusionStatus.failed, status: fusion.FusionStatus.connecting,
info: "Failed $maxFailedFuseCount times in a row, stopping."); info: "Connecting to the CashFusion server.",
_stopRequested = true; );
_uiState?.failed = true; }
// refresh wallet utxos
await _updateWalletUTXOS();
// Add unfrozen stack UTXOs.
final List<UTXO> walletUtxos = await _db
.getUTXOs(_walletId)
.filter()
.isBlockedEqualTo(false)
.and()
.addressIsNotNull()
.findAll();
final List<fusion.UtxoDTO> coinList = [];
// Loop through UTXOs, checking and adding valid ones.
for (final utxo in walletUtxos) {
final String addressString = utxo.address!;
final List<String> possibleAddresses = [addressString];
if (bitbox.Address.detectFormat(addressString) ==
bitbox.Address.formatCashAddr) {
possibleAddresses
.add(bitbox.Address.toLegacyAddress(addressString));
} else {
possibleAddresses.add(bitbox.Address.toCashAddress(addressString));
}
// Fetch address to get pubkey
final addr = await _db
.getAddresses(_walletId)
.filter()
.anyOf<String,
QueryBuilder<Address, Address, QAfterFilterCondition>>(
possibleAddresses, (q, e) => q.valueEqualTo(e))
.and()
.group((q) => q
.subTypeEqualTo(AddressSubType.change)
.or()
.subTypeEqualTo(AddressSubType.receiving))
.and()
.typeEqualTo(AddressType.p2pkh)
.findFirst();
// depending on the address type in the query above this can be null
if (addr == null) {
// A utxo object should always have a non null address.
// If non found then just ignore the UTXO (aka don't fuse it)
Logging.instance.log(
"Ignoring utxo=$utxo for address=\"$addressString\" while selecting UTXOs for Fusion",
level: LogLevel.Info,
);
continue;
}
final dto = fusion.UtxoDTO(
txid: utxo.txid,
vout: utxo.vout,
value: utxo.value,
address: utxo.address!,
pubKey: addr.publicKey,
);
// Add UTXO to coinList.
coinList.add(dto);
}
// Fuse UTXOs.
try {
await _mainFusionObject!.fuse(
inputsFromWallet: coinList,
network: _coin.isTestNet
? fusion.Utilities.testNet
: fusion.Utilities.mainNet,
);
// Increment the number of successfully completed fusion rounds.
_completedFuseCount++;
// Do the same for the UI state. This also resets the failed count (for
// the UI state only).
_uiState?.incrementFusionRoundsCompleted();
// Also reset the failed count here.
_failedFuseCount = 0;
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Error,
);
// just continue on attempt failure
// Increment the number of failed fusion rounds.
_failedFuseCount++;
// Do the same for the UI state.
_uiState?.incrementFusionRoundsFailed();
// If we fail too many times in a row, stop trying.
if (_failedFuseCount >= maxFailedFuseCount) {
_updateStatus(
status: fusion.FusionStatus.failed,
info: "Failed $maxFailedFuseCount times in a row, stopping.");
_stopRequested = true;
_uiState?.failed = true;
}
} }
} }
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Error,
);
// Stop the fusion process and update the UI state.
await _mainFusionObject?.stop();
_mainFusionObject = null;
_uiState?.running = false;
} }
} }