-
-
Notifications
You must be signed in to change notification settings - Fork 252
feat: add tracing to multichain account service #7006
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 6 commits
117ee20
af3d908
d5c1eb3
3190bca
291b8c6
5170994
0f351eb
90d5e8c
82bf3f7
a06ed0a
56a9cc2
a38300d
ccec24f
019f4f8
7683d3b
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 |
|---|---|---|
|
|
@@ -6,12 +6,14 @@ import type { | |
| MultichainAccountWalletId, | ||
| Bip44Account, | ||
| } from '@metamask/account-api'; | ||
| import type { TraceCallback } from '@metamask/controller-utils'; | ||
| import type { HdKeyring } from '@metamask/eth-hd-keyring'; | ||
| import { mnemonicPhraseToBytes } from '@metamask/key-tree'; | ||
| import type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api'; | ||
| import { KeyringTypes } from '@metamask/keyring-controller'; | ||
| import { areUint8ArraysEqual } from '@metamask/utils'; | ||
|
|
||
| import { traceFallback } from './analytics'; | ||
| import { projectLogger as log } from './logger'; | ||
| import type { MultichainAccountGroup } from './MultichainAccountGroup'; | ||
| import { MultichainAccountWallet } from './MultichainAccountWallet'; | ||
|
|
@@ -26,7 +28,10 @@ import { | |
| } from './providers/AccountProviderWrapper'; | ||
| import { EvmAccountProvider } from './providers/EvmAccountProvider'; | ||
| import { SolAccountProvider } from './providers/SolAccountProvider'; | ||
| import type { MultichainAccountServiceMessenger } from './types'; | ||
| import type { | ||
| MultichainAccountServiceConfig, | ||
| MultichainAccountServiceMessenger, | ||
| } from './types'; | ||
|
|
||
| export const serviceName = 'MultichainAccountService'; | ||
|
|
||
|
|
@@ -40,6 +45,7 @@ export type MultichainAccountServiceOptions = { | |
| [EvmAccountProvider.NAME]?: EvmAccountProviderConfig; | ||
| [SolAccountProvider.NAME]?: SolAccountProviderConfig; | ||
| }; | ||
| config?: MultichainAccountServiceConfig; | ||
| }; | ||
|
|
||
| /** Reverse mapping object used to map account IDs and their wallet/multichain account. */ | ||
|
|
@@ -66,6 +72,8 @@ export class MultichainAccountService { | |
| AccountContext<Bip44Account<KeyringAccount>> | ||
| >; | ||
|
|
||
| readonly #trace: TraceCallback; | ||
|
|
||
| /** | ||
| * The name of the service. | ||
| */ | ||
|
|
@@ -79,16 +87,18 @@ export class MultichainAccountService { | |
| * MultichainAccountService. | ||
| * @param options.providers - Optional list of account | ||
| * @param options.providerConfigs - Optional provider configs | ||
| * providers. | ||
| * @param options.config - Optional config. | ||
| */ | ||
| constructor({ | ||
| messenger, | ||
| providers = [], | ||
| providerConfigs, | ||
| config, | ||
| }: MultichainAccountServiceOptions) { | ||
| this.#messenger = messenger; | ||
| this.#wallets = new Map(); | ||
| this.#accountIdToContext = new Map(); | ||
| this.#trace = config?.trace ?? traceFallback; | ||
|
|
||
| // TODO: Rely on keyring capabilities once the keyring API is used by all keyrings. | ||
| this.#providers = [ | ||
|
|
@@ -102,6 +112,7 @@ export class MultichainAccountService { | |
| this.#messenger, | ||
| providerConfigs?.[SolAccountProvider.NAME], | ||
| ), | ||
| this.#trace, | ||
|
||
| ), | ||
| // Custom account providers that can be provided by the MetaMask client. | ||
| ...providers, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './traces'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import type { TraceRequest } from '@metamask/controller-utils'; | ||
| import { TraceName } from '../constants/traces'; | ||
|
|
||
| import { traceFallback } from './traces'; | ||
|
|
||
| describe('MultichainAccountService - Traces', () => { | ||
| describe('TraceName', () => { | ||
| it('contains expected trace names', () => { | ||
| expect(TraceName).toStrictEqual({ | ||
| SnapDiscoverAccounts: 'Snap Discover Accounts', | ||
| EvmDiscoverAccounts: 'EVM Discover Accounts', | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
|
Comment on lines
+7
to
+15
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. Is this required for coverage purposes? 😮 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. Yea for coverage :( |
||
| describe('traceFallback', () => { | ||
| let mockTraceRequest: TraceRequest; | ||
|
|
||
| beforeEach(() => { | ||
| mockTraceRequest = { | ||
| name: TraceName.SnapDiscoverAccounts, | ||
| id: 'trace-id-123', | ||
| tags: {}, | ||
| }; | ||
| }); | ||
|
|
||
| it('returns undefined when no function is provided', async () => { | ||
| const result = await traceFallback(mockTraceRequest); | ||
|
|
||
| expect(result).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('executes the provided function and return its result', async () => { | ||
| const mockResult = 'test-result'; | ||
| const mockFn = jest.fn().mockReturnValue(mockResult); | ||
|
|
||
| const result = await traceFallback(mockTraceRequest, mockFn); | ||
|
|
||
| expect(mockFn).toHaveBeenCalledTimes(1); | ||
| expect(mockFn).toHaveBeenCalledWith(); | ||
| expect(result).toBe(mockResult); | ||
| }); | ||
|
|
||
| it('executes async function and return its result', async () => { | ||
| const mockResult = { data: 'async-result' }; | ||
| const mockAsyncFn = jest.fn().mockResolvedValue(mockResult); | ||
|
|
||
| const result = await traceFallback(mockTraceRequest, mockAsyncFn); | ||
|
|
||
| expect(mockAsyncFn).toHaveBeenCalledTimes(1); | ||
| expect(result).toBe(mockResult); | ||
| }); | ||
|
|
||
| it('handles function that throws an error', async () => { | ||
| const mockError = new Error('Test error'); | ||
| const mockFn = jest.fn().mockImplementation(() => { | ||
| throw mockError; | ||
| }); | ||
|
|
||
| await expect(traceFallback(mockTraceRequest, mockFn)).rejects.toThrow( | ||
| mockError, | ||
| ); | ||
| expect(mockFn).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('handles function that returns a rejected promise', async () => { | ||
| const mockError = new Error('Async error'); | ||
| const mockFn = jest.fn().mockRejectedValue(mockError); | ||
|
|
||
| await expect(traceFallback(mockTraceRequest, mockFn)).rejects.toThrow( | ||
| mockError, | ||
| ); | ||
| expect(mockFn).toHaveBeenCalledTimes(1); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import type { | ||
| TraceCallback, | ||
| TraceContext, | ||
| TraceRequest, | ||
| } from '@metamask/controller-utils'; | ||
|
|
||
| /** | ||
| * Fallback function for tracing. | ||
| * This function is used when no specific trace function is provided. | ||
| * It executes the provided function in a trace context if available. | ||
| * | ||
| * @param _request - The trace request containing additional data and context. | ||
| * @param fn - The function to execute within the trace context. | ||
| * @returns A promise that resolves to the result of the executed function. | ||
| * If no function is provided, it resolves to undefined. | ||
| */ | ||
| export const traceFallback: TraceCallback = async <ReturnType>( | ||
| _request: TraceRequest, | ||
| fn?: (context?: TraceContext) => ReturnType, | ||
| ): Promise<ReturnType> => { | ||
| if (!fn) { | ||
| return undefined as ReturnType; | ||
| } | ||
| return await Promise.resolve(fn()); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export enum TraceName { | ||
| 'SnapDiscoverAccounts' = 'Snap Discover Accounts', | ||
montelaidev marked this conversation as resolved.
Show resolved
Hide resolved
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 still think 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. @montelaidev I think my comment still stands, maybe we can just use |
||
| 'EvmDiscoverAccounts' = 'EVM Discover Accounts', | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,10 @@ | ||
| import type { Bip44Account } from '@metamask/account-api'; | ||
| import type { TraceCallback } from '@metamask/controller-utils'; | ||
| import type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api'; | ||
|
|
||
| import { BaseBip44AccountProvider } from './BaseBip44AccountProvider'; | ||
| import { traceFallback } from '../analytics'; | ||
| import { TraceName } from '../constants/traces'; | ||
| import type { MultichainAccountServiceMessenger } from '../types'; | ||
|
|
||
| /** | ||
|
|
@@ -13,12 +16,16 @@ export class AccountProviderWrapper extends BaseBip44AccountProvider { | |
|
|
||
| private readonly provider: BaseBip44AccountProvider; | ||
|
|
||
| readonly #trace: TraceCallback; | ||
|
|
||
| constructor( | ||
| messenger: MultichainAccountServiceMessenger, | ||
| provider: BaseBip44AccountProvider, | ||
| trace?: TraceCallback, | ||
| ) { | ||
| super(messenger); | ||
| this.provider = provider; | ||
| this.#trace = trace ?? traceFallback; | ||
| } | ||
|
|
||
| override getName(): string { | ||
|
|
@@ -106,7 +113,15 @@ export class AccountProviderWrapper extends BaseBip44AccountProvider { | |
| if (!this.isEnabled) { | ||
| return []; | ||
| } | ||
| return this.provider.discoverAccounts(options); | ||
| return this.#trace( | ||
| { | ||
| name: TraceName.SnapDiscoverAccounts, | ||
| data: { | ||
| providerName: this.getName(), | ||
| }, | ||
| }, | ||
| async () => this.provider.discoverAccounts(options), | ||
| ); | ||
|
||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.