Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.11.600] Fix wallet sync bug and sync all wallets in the background #2827

Merged
merged 9 commits into from
Apr 22, 2022
1 change: 1 addition & 0 deletions packages/yoroi-extension/app/api/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export type RefreshPendingTransactionsFunc = (

export type RemoveAllTransactionsRequest = {|
publicDeriver: IPublicDeriver<ConceptualWallet & IHasLevels>,
publicDeriverId: number,
refreshWallet: () => Promise<void>,
|};
export type RemoveAllTransactionsResponse = void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ export default class TransactionsStore extends Store<StoresMap, ActionsMap> {
@observable exportError: ?LocalizableError;
@observable shouldIncludeTxIds: boolean = false;

ongoingRefreshing: Map<number, Promise<void>> = new Map();

setup(): void {
super.setup();
const actions = this.actions.transactions;
Expand Down Expand Up @@ -281,8 +283,27 @@ export default class TransactionsStore extends Store<StoresMap, ActionsMap> {
return result ? result.totalAvailable : 0;
}

// This method ensures that at any time, there is only one refreshing process
// for each wallet.
refreshTransactionData: {|
publicDeriver: PublicDeriver<>,
localRequest: boolean,
|} => Promise<void> = async (request) => {
const { publicDeriverId } = request.publicDeriver;
if (this.ongoingRefreshing.has(publicDeriverId)) {
return this.ongoingRefreshing.get(publicDeriverId);
}
try {
const promise = this._refreshTransactionData(request);
this.ongoingRefreshing.set(publicDeriverId, promise);
await promise;
} finally {
this.ongoingRefreshing.delete(publicDeriverId);
}
}

/** Refresh transaction data for all wallets and update wallet balance */
@action refreshTransactionData: {|
@action _refreshTransactionData: {|
publicDeriver: PublicDeriver<>,
localRequest: boolean,
|} => Promise<void> = async (request) => {
Expand Down
61 changes: 47 additions & 14 deletions packages/yoroi-extension/app/stores/toplevel/WalletSettingsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,27 @@ export default class WalletSettingsStore extends Store<StoresMap, ActionsMap> {
= new Request<ChangeModelPasswordFunc>(this.api.common.changeModelPassword);

@observable clearHistory: Request<RemoveAllTransactionsFunc>
= new Request<RemoveAllTransactionsFunc>(req => {
= new Request<RemoveAllTransactionsFunc>(async (req) => {
const apiType = getApiForNetwork(req.publicDeriver.getParent().getNetworkInfo());
return this.api[apiType].removeAllTransactions(req);
const ongoingRefreshing = this.stores.transactions.ongoingRefreshing.get(
req.publicDeriverId
);
if (ongoingRefreshing) {
try {
await ongoingRefreshing;
} catch {
// ignore any error because we are going to resync anyway
}
}

const promise = this.api[apiType].removeAllTransactions(req);

this.stores.transactions.ongoingRefreshing.set(
req.publicDeriverId,
promise,
);

return promise;
});

@observable removeWalletRequest: Request<typeof _removeWalletFromDb>
Expand Down Expand Up @@ -201,19 +219,34 @@ export default class WalletSettingsStore extends Store<StoresMap, ActionsMap> {
if (withLevels == null) {
throw new Error(`${nameof(this._resyncHistory)} missing levels`);
}
await this.clearHistory.execute({
publicDeriver: withLevels,
refreshWallet: () => {
// clear cache
const txRequests = this.stores.transactions
.getTxRequests(request.publicDeriver);
for (const txRequest of Object.keys(txRequests.requests)) {
txRequests.requests[txRequest].reset();
try {
await this.clearHistory.execute({
publicDeriver: withLevels,
publicDeriverId: request.publicDeriver.publicDeriverId,
refreshWallet: () => {
// clear cache
const txRequests = this.stores.transactions
.getTxRequests(request.publicDeriver);
for (const txRequest of Object.keys(txRequests.requests)) {
txRequests.requests[txRequest].reset();
}
// currently in the map the promise for this wallet is this resyncing process,
// we need to remove it before calling refreshing otherwise it's a deadlock
this.stores.transactions.ongoingRefreshing.delete(
request.publicDeriver.publicDeriverId,
);
// refresh
return this.stores.wallets.refreshWalletFromRemote(request.publicDeriver);
}
// refresh
return this.stores.wallets.refreshWalletFromRemote(request.publicDeriver);
}
}).promise;
}).promise;
} finally {
// if everything runs without any error, the promise should have already
// been removed, but here make sure it is, so that future refreshing
// is not affected
this.stores.transactions.ongoingRefreshing.delete(
request.publicDeriver.publicDeriverId,
);
}

this.stores.transactions.clearSubmittedTransactions(request.publicDeriver);
};
Expand Down
22 changes: 9 additions & 13 deletions packages/yoroi-extension/app/stores/toplevel/WalletStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,19 +259,6 @@ export default class WalletStore extends Store<StoresMap, ActionsMap> {
publicDeriver,
localRequest: true,
});
await this.stores.transactions.reactToTxHistoryUpdate({ publicDeriver });
// if after querying local history we find nothing, we just reset the DB entirely
const txRequests = find(this.stores.transactions.transactionsRequests, { publicDeriver });
if (txRequests == null)
throw new Error(`${nameof(this.refreshWalletFromLocalOnLaunch)} should never happen`);
const { result } = txRequests.requests.allRequest;
if (result == null)
throw new Error(`${nameof(this.refreshWalletFromLocalOnLaunch)} should never happen`);
if (result.totalAvailable === 0) {
for (const txRequest of Object.keys(txRequests.requests)) {
txRequests.requests[txRequest].reset();
}
}
await this.stores.addresses.refreshAddressesFromDb(publicDeriver);
} catch (error) {
Logger.error(
Expand Down Expand Up @@ -338,6 +325,7 @@ export default class WalletStore extends Store<StoresMap, ActionsMap> {
}
this.publicDerivers.push(...newWithCachedData);
});
this._startRefreshAllWallets();
};

@action registerObserversForNewWallet: ({|
Expand Down Expand Up @@ -397,6 +385,14 @@ export default class WalletStore extends Store<StoresMap, ActionsMap> {
}
};

_startRefreshAllWallets: void => Promise<void> = async () => {
for (const publicDeriver of this.publicDerivers) {
if (this.selected !== publicDeriver) {
await this.refreshWalletFromRemote(publicDeriver);
}
}
setTimeout(this._startRefreshAllWallets, this.WALLET_REFRESH_INTERVAL);
}
// =================== NOTIFICATION ==================== //
showLedgerWalletIntegratedNotification: void => void = (): void => {
this.actions.notifications.open.trigger(WalletCreationNotifications.LedgerNotification);
Expand Down