Skip to content

Commit 86448c3

Browse files
committed
fix(account-tree-controller): enqueue backup & sync jobs using service events
1 parent 22a1921 commit 86448c3

File tree

3 files changed

+104
-12
lines changed

3 files changed

+104
-12
lines changed

packages/account-tree-controller/src/AccountTreeController.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import type {
55
AccountGroupType,
66
AccountSelector,
77
MultichainAccountWalletId,
8+
MultichainAccountGroup,
9+
Bip44Account,
810
} from '@metamask/account-api';
911
import type { MultichainAccountWalletStatus } from '@metamask/account-api';
1012
import { type AccountId } from '@metamask/accounts-controller';
1113
import type { StateMetadata } from '@metamask/base-controller';
1214
import { BaseController } from '@metamask/base-controller';
1315
import type { TraceCallback } from '@metamask/controller-utils';
14-
import { isEvmAccountType } from '@metamask/keyring-api';
16+
import { isEvmAccountType, KeyringAccount } from '@metamask/keyring-api';
1517
import type { InternalAccount } from '@metamask/keyring-internal-api';
1618

1719
import type { BackupAndSyncEmitAnalyticsEventParams } from './backup-and-sync/analytics';
@@ -223,6 +225,20 @@ export class AccountTreeController extends BaseController<
223225
},
224226
);
225227

228+
this.messagingSystem.subscribe(
229+
'MultichainAccountService:multichainAccountGroupCreated',
230+
(group) => {
231+
this.#handleMultichainAccountGroupCreatedOrUpdated(group);
232+
},
233+
);
234+
235+
this.messagingSystem.subscribe(
236+
'MultichainAccountService:multichainAccountGroupUpdated',
237+
(group) => {
238+
this.#handleMultichainAccountGroupCreatedOrUpdated(group);
239+
},
240+
);
241+
226242
this.#registerMessageHandlers();
227243
}
228244

@@ -616,6 +632,25 @@ export class AccountTreeController extends BaseController<
616632
}
617633
}
618634

635+
/**
636+
* Handles multichain account group created/updated event from
637+
* the MultichainAccountService.
638+
*
639+
* @param multichainAccountGroup - Multichain account group being that got created or updated.
640+
*/
641+
#handleMultichainAccountGroupCreatedOrUpdated(
642+
multichainAccountGroup: MultichainAccountGroup<
643+
Bip44Account<KeyringAccount>
644+
>,
645+
): void {
646+
// Trigger atomic sync for wallet and group (wallet will be synced only if it does
647+
// not exist yet)
648+
this.#backupAndSyncService.enqueueSingleWalletAndGroupSync(
649+
multichainAccountGroup.wallet.id,
650+
multichainAccountGroup.id,
651+
);
652+
}
653+
619654
/**
620655
* Helper method to prune a group if it holds no accounts and additionally
621656
* prune the wallet if it holds no groups. This action should take place
@@ -679,11 +714,6 @@ export class AccountTreeController extends BaseController<
679714
// the union tag `result.wallet.type`.
680715
} as AccountWalletObject;
681716
wallet = wallets[walletId];
682-
683-
// Trigger atomic sync for new wallet (only for entropy wallets)
684-
if (wallet.type === AccountWalletType.Entropy) {
685-
this.#backupAndSyncService.enqueueSingleWalletSync(walletId);
686-
}
687717
}
688718

689719
const groupId = result.group.id;
@@ -705,11 +735,6 @@ export class AccountTreeController extends BaseController<
705735

706736
// Map group ID to its containing wallet ID for efficient direct access
707737
this.#groupIdToWalletId.set(groupId, walletId);
708-
709-
// Trigger atomic sync for new group (only for entropy wallets)
710-
if (wallet.type === AccountWalletType.Entropy) {
711-
this.#backupAndSyncService.enqueueSingleGroupSync(groupId);
712-
}
713738
} else {
714739
group.accounts.push(account.id);
715740
}

packages/account-tree-controller/src/backup-and-sync/service/index.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,34 @@ export class BackupAndSyncService {
194194
);
195195
}
196196

197+
/**
198+
* Enqueues a single group sync operation (fire-and-forget).
199+
* If the first full sync has not yet occurred, it does nothing.
200+
*
201+
* @param walletId - The wallet ID to sync.
202+
* @param groupId - The group ID to sync.
203+
*/
204+
enqueueSingleWalletAndGroupSync(
205+
walletId: AccountWalletId,
206+
groupId: AccountGroupId,
207+
): void {
208+
if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {
209+
return;
210+
}
211+
212+
// eslint-disable-next-line no-void
213+
void this.#atomicSyncQueue.enqueue(async () => {
214+
const hasSyncedWalletAtLeastOnce =
215+
await this.hasSyncedWalletAtLeastOnce(walletId);
216+
217+
if (!hasSyncedWalletAtLeastOnce) {
218+
await this.#performSingleWalletSyncInner(walletId);
219+
}
220+
221+
await this.#performSingleGroupSyncInner(groupId);
222+
});
223+
}
224+
197225
/**
198226
* Performs a full synchronization of the local account tree with user storage, ensuring consistency
199227
* between local state and cloud-stored account data.
@@ -449,6 +477,37 @@ export class BackupAndSyncService {
449477
}
450478
}
451479

480+
/**
481+
* Performs a single wallet's bidirectional metadata sync with user storage.
482+
*
483+
* @param walletId - The wallet ID to sync.
484+
* @returns True if this wallet has been synced already, false otherwise.
485+
*/
486+
async hasSyncedWalletAtLeastOnce(
487+
walletId: AccountWalletId,
488+
): Promise<boolean> {
489+
try {
490+
const wallet = this.#getEntropyWallet(walletId);
491+
if (!wallet) {
492+
return false; // Only sync entropy wallets
493+
}
494+
495+
const entropySourceId = wallet.metadata.entropy.id;
496+
const walletFromUserStorage = await getWalletFromUserStorage(
497+
this.#context,
498+
entropySourceId,
499+
);
500+
501+
return walletFromUserStorage !== null;
502+
} catch (error) {
503+
backupAndSyncLogger(
504+
`Error in single wallet sync for ${walletId} (hasSyncWalletAtLeastOnce):`,
505+
error,
506+
);
507+
return false;
508+
}
509+
}
510+
452511
/**
453512
* Performs a single wallet's bidirectional metadata sync with user storage.
454513
*

packages/account-tree-controller/src/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ import type {
3838
AccountWalletObject,
3939
AccountTreeWalletPersistedMetadata,
4040
} from './wallet';
41-
import type { MultichainAccountServiceWalletStatusChangeEvent } from '../../multichain-account-service/src/types';
41+
import type {
42+
MultichainAccountServiceGetMultichainAccountWalletsAction,
43+
MultichainAccountServiceMultichainAccountGroupCreatedEvent,
44+
MultichainAccountServiceMultichainAccountGroupUpdatedEvent,
45+
MultichainAccountServiceWalletStatusChangeEvent,
46+
} from '../../multichain-account-service/src/types';
4247

4348
// Backward compatibility aliases using indexed access types
4449
/**
@@ -126,6 +131,7 @@ export type AllowedActions =
126131
| UserStorageController.UserStorageControllerPerformSetStorage
127132
| UserStorageController.UserStorageControllerPerformBatchSetStorage
128133
| AuthenticationController.AuthenticationControllerGetSessionProfile
134+
| MultichainAccountServiceGetMultichainAccountWalletsAction
129135
| MultichainAccountServiceCreateMultichainAccountGroupAction;
130136

131137
export type AccountTreeControllerActions =
@@ -166,6 +172,8 @@ export type AllowedEvents =
166172
| AccountsControllerAccountRemovedEvent
167173
| AccountsControllerSelectedAccountChangeEvent
168174
| UserStorageController.UserStorageControllerStateChangeEvent
175+
| MultichainAccountServiceMultichainAccountGroupCreatedEvent
176+
| MultichainAccountServiceMultichainAccountGroupUpdatedEvent
169177
| MultichainAccountServiceWalletStatusChangeEvent;
170178

171179
export type AccountTreeControllerEvents =

0 commit comments

Comments
 (0)