-
-
Notifications
You must be signed in to change notification settings - Fork 252
refactor(multichain-account-service): Improved performance across package classes and improved error messages #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
base: main
Are you sure you want to change the base?
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
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[]; | ||
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. IMO we should re-introduce the generic parameter for those providers. It was meant to be "extensible" and would align it with the |
||
|
||
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); | ||
} | ||
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) | ||
if (this.#initialized) { | ||
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; | ||
} | ||
|
||
/** | ||
* Gets the account IDs for this multichain account. | ||
* | ||
* @returns The account IDs. | ||
*/ | ||
getAccountIds(): Account['id'][] { | ||
return [...this.#providerToAccounts.values()].flat(); | ||
} | ||
|
||
/** | ||
* 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; | ||
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 need the cast because the providers are not using the |
||
} | ||
|
||
/** | ||
|
@@ -228,13 +230,33 @@ export class MultichainAccountGroup< | |
groupIndex: this.groupIndex, | ||
}); | ||
} | ||
return Promise.resolve(); | ||
return Promise.reject(new Error('Already aligned')); | ||
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 would not use an error message in that case. Maybe we can re-use what you did in the Like
hmalik88 marked this conversation as resolved.
Show resolved
Hide resolved
hmalik88 marked this conversation as resolved.
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}`, | ||
); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably also split this from this PR. For similar reasons (see my other comment about
packages/keyring-controller/src/KeyringController.ts
file)