-
-
Notifications
You must be signed in to change notification settings - Fork 276
perf(multichain-account-service)!: avoid excessive state syncing #6654
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
Changes from 13 commits
aa318fe
17e855f
924a679
be8b2f7
b49f7b4
a261e1e
6104131
c45f612
acf36e0
ce3cef4
891fa50
83060d6
01096bf
4c2e582
43972f8
7cec854
f05e6da
e325c77
6c5b86e
7d805ef
6b6e776
33e5e34
f4d79c0
350aa93
ce322e0
31a6d71
4c485a6
c82de17
7d6e76a
2d55dbd
b8a87ff
92d1c3a
8dedd1a
11cc131
d28a324
f308e9f
8bc1237
19cfdf3
82fece8
c812a82
91aa52e
4b19ed5
5bcaa53
c12435a
e9ab58d
521a71b
1162348
31f0193
7128b55
0c9324c
6b15c2d
0ec9181
98db5d3
98d6373
5733fac
eab65ca
99d70dd
6f02157
a544fb3
b7a7108
2df0f84
9f6890d
9e44c34
ba51a39
37f79b6
f56a14a
a1855a6
db00bc8
9fc2ee2
5e076b3
f8a7783
a414253
c3b8390
6bb050e
612a846
483da6e
b746565
602ce8f
a5746db
981817a
01c594c
2a4c71f
7334bbc
0e24c48
ea00380
0a0db20
82fa21d
bcd998a
21c74ac
550c11f
8e22d1d
a7483ff
a6d4432
1881a2c
b747e5f
770e25f
7d64234
176a79b
73add20
8b0cc36
cb57f4c
6bf879c
6158c7f
a2ea0f1
66051a5
6a56850
f7eec3f
5288909
b96fd74
fd4aea5
0e4b151
4a66b65
a8c05bb
903db2d
1142ae4
766f706
91e7f90
cd0d095
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,10 +8,14 @@ import type { Bip44Account } from '@metamask/account-api'; | |
| import type { AccountSelector } from '@metamask/account-api'; | ||
| import { type KeyringAccount } from '@metamask/keyring-api'; | ||
|
|
||
| import type { ServiceState, StateKeys } from './MultichainAccountService'; | ||
| import type { MultichainAccountWallet } from './MultichainAccountWallet'; | ||
| import type { NamedAccountProvider } from './providers'; | ||
| import type { BaseBip44AccountProvider } from './providers'; | ||
| import type { MultichainAccountServiceMessenger } from './types'; | ||
|
|
||
| export type GroupState = | ||
| ServiceState[StateKeys['entropySource']][StateKeys['groupIndex']]; | ||
|
|
||
| /** | ||
| * A multichain account group that holds multiple accounts. | ||
| */ | ||
|
|
@@ -25,21 +29,14 @@ export class MultichainAccountGroup< | |
|
|
||
| readonly #groupIndex: number; | ||
|
|
||
| readonly #providers: NamedAccountProvider<Account>[]; | ||
| readonly #providers: BaseBip44AccountProvider[]; | ||
hmalik88 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| readonly #providerToAccounts: Map< | ||
| NamedAccountProvider<Account>, | ||
| Account['id'][] | ||
| >; | ||
| readonly #providerToAccounts: Map<BaseBip44AccountProvider, Account['id'][]>; | ||
|
|
||
| readonly #accountToProvider: Map< | ||
| Account['id'], | ||
| NamedAccountProvider<Account> | ||
| >; | ||
| readonly #accountToProvider: Map<Account['id'], BaseBip44AccountProvider>; | ||
|
|
||
| readonly #messenger: MultichainAccountServiceMessenger; | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/prefer-readonly | ||
| #initialized = false; | ||
|
|
||
| constructor({ | ||
|
|
@@ -50,7 +47,7 @@ export class MultichainAccountGroup< | |
| }: { | ||
| groupIndex: number; | ||
| wallet: MultichainAccountWallet<Account>; | ||
| providers: NamedAccountProvider<Account>[]; | ||
| providers: BaseBip44AccountProvider[]; | ||
| messenger: MultichainAccountServiceMessenger; | ||
| }) { | ||
| this.#id = toMultichainAccountGroupId(wallet.id, groupIndex); | ||
|
|
@@ -60,44 +57,37 @@ export class MultichainAccountGroup< | |
| this.#messenger = messenger; | ||
| this.#providerToAccounts = new Map(); | ||
| this.#accountToProvider = new Map(); | ||
|
|
||
| this.sync(); | ||
| this.#initialized = true; | ||
| } | ||
|
|
||
| /** | ||
| * Force multichain account synchronization. | ||
| * Initialize the multichain account group and construct the internal representation of accounts. | ||
| * | ||
| * Note: This method can be called multiple times to update the group state. | ||
| * | ||
| * This can be used if account providers got new accounts that the multichain | ||
| * account doesn't know about. | ||
| * @param groupState - The group state. | ||
| */ | ||
| sync(): void { | ||
| // Clear reverse mapping and re-construct it entirely based on the refreshed | ||
| // list of accounts from each providers. | ||
| this.#accountToProvider.clear(); | ||
|
|
||
| init(groupState: GroupState) { | ||
| for (const provider of this.#providers) { | ||
| // Filter account only for that index. | ||
| const accounts = []; | ||
| for (const account of provider.getAccounts()) { | ||
| if ( | ||
| account.options.entropy.id === this.wallet.entropySource && | ||
| account.options.entropy.groupIndex === this.groupIndex | ||
| ) { | ||
| // We only use IDs to always fetch the latest version of accounts. | ||
| accounts.push(account.id); | ||
| } | ||
| } | ||
| this.#providerToAccounts.set(provider, accounts); | ||
| const accountIds = groupState[provider.getName()]; | ||
|
|
||
| // Reverse-mapping for fast indexing. | ||
| for (const id of accounts) { | ||
| this.#accountToProvider.set(id, provider); | ||
| if (accountIds) { | ||
| for (const accountId of accountIds) { | ||
| this.#accountToProvider.set(accountId, provider); | ||
| } | ||
hmalik88 marked this conversation as resolved.
Show resolved
Hide resolved
hmalik88 marked this conversation as resolved.
Show resolved
Hide resolved
hmalik88 marked this conversation as resolved.
Show resolved
Hide resolved
hmalik88 marked this conversation as resolved.
Show resolved
Hide resolved
hmalik88 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const providerAccounts = this.#providerToAccounts.get(provider); | ||
| if (!providerAccounts) { | ||
| this.#providerToAccounts.set(provider, accountIds); | ||
| } else { | ||
| providerAccounts.push(...accountIds); | ||
| } | ||
| // Add the accounts to the provider's internal list of account IDs | ||
| provider.addAccounts(accountIds); | ||
| } | ||
| } | ||
|
|
||
| // Emit update event when group is synced (only if initialized) | ||
hmalik88 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (this.#initialized) { | ||
hmalik88 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (!this.#initialized) { | ||
| this.#initialized = true; | ||
| } else { | ||
| this.#messenger.publish( | ||
| 'MultichainAccountService:multichainAccountGroupUpdated', | ||
| this, | ||
|
|
@@ -167,14 +157,24 @@ export class MultichainAccountGroup< | |
| // If for some reason we cannot get this account from the provider, it | ||
| // might means it has been deleted or something, so we just filter it | ||
| // out. | ||
| allAccounts.push(account); | ||
| // We cast here because TS cannot infer the type of the account from the provider | ||
| allAccounts.push(account as Account); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return allAccounts; | ||
| } | ||
|
|
||
| /** | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Disabled provider account retrieval throws exceptionWhen retrieving accounts through Additional Locations (1)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think is an issue, the methods wouldn't overlap cc: @ccharly
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's very unlikely to happen indeed 🤔 but maybe we could guard this using the wallet's mutexes we have already in place. I don't think this is necessarily a problem with your problem itself, so let's keep an eye on this one (I'll keep this unresolved) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Defensive check unlikely to trigger due to throw behaviorLow Severity The
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can double-check that in a follow-up too. |
||
| * Gets the account IDs for this multichain account. | ||
| * | ||
| * @returns The account IDs. | ||
| */ | ||
| getAccountIds(): Account['id'][] { | ||
| return [...this.#providerToAccounts.values()].flat(); | ||
hmalik88 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Gets the account for a given account ID. | ||
| * | ||
|
|
@@ -189,7 +189,9 @@ export class MultichainAccountGroup< | |
| if (!provider) { | ||
| return undefined; | ||
| } | ||
| return provider.getAccount(id); | ||
|
|
||
| // We cast here because TS cannot infer the type of the account from the provider | ||
| return provider.getAccount(id) as Account; | ||
hmalik88 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -228,13 +230,33 @@ export class MultichainAccountGroup< | |
| groupIndex: this.groupIndex, | ||
| }); | ||
| } | ||
| return Promise.resolve(); | ||
| return Promise.reject(new Error('Already aligned')); | ||
hmalik88 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
hmalik88 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
hmalik88 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }), | ||
| ); | ||
|
|
||
| const groupState = results.reduce<GroupState>((state, result, idx) => { | ||
| if (result.status === 'fulfilled') { | ||
| state[this.#providers[idx].getName()] = result.value.map( | ||
| (account) => account.id, | ||
| ); | ||
| } | ||
| return state; | ||
| }, {}); | ||
|
|
||
| // Update group state | ||
| this.init(groupState); | ||
|
|
||
| if (results.some((result) => result.status === 'rejected')) { | ||
| const rejectedResults = results.filter( | ||
| (result) => | ||
| result.status === 'rejected' && result.reason !== 'Already aligned', | ||
| ) as PromiseRejectedResult[]; | ||
| const errors = rejectedResults | ||
| .map((result) => `- ${result.reason}`) | ||
| .join('\n'); | ||
| const hasMultipleFailures = rejectedResults.length > 1; | ||
| console.warn( | ||
| `Failed to fully align multichain account group for entropy ID: ${this.wallet.entropySource} and group index: ${this.groupIndex}, some accounts might be missing`, | ||
| `Failed to fully align multichain account group for entropy ID: ${this.wallet.entropySource} and group index: ${this.groupIndex}, some accounts might be missing. ${hasMultipleFailures ? 'Providers' : 'Provider'} threw the following ${hasMultipleFailures ? 'errors' : 'error'}:\n${errors}`, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.