Skip to content

Commit f8872e5

Browse files
authored
feat: Add initial tracing to SignatureController (#4655)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> Add an optional trace constructor callback to the TransactionController to support generating Sentry traces within the controller, without coupling to the Sentry package. Add an optional traceContext argument to addTransaction to facilitate generating nested traces within a parent trace generated within a client. Add initial instrumentation of the transaction lifecycle. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? For example: * Fixes #12345 * Related to #67890 --> MetaMask/MetaMask-planning#2904 ## Changelog <!-- If you're making any consumer-facing changes, list those changes here as if you were updating a changelog, using the template below as a guide. (CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or FIXED. For security-related issues, follow the Security Advisory process.) Please take care to name the exact pieces of the API you've added or changed (e.g. types, interfaces, functions, or methods). If there are any breaking changes, make sure to offer a solution for consumers to follow once they upgrade to the changes. Finally, if you're only making changes to development scripts or tests, you may replace the template below with "None". --> ### `@metamask/controller-utils` - **ADDED**: Add `Trace` types ### `@metamask/signature-controller` - **ADDED**: Add optional `trace` callback to constructor. - **ADDED**: Add optional `traceContext` option to `newUnsignedTypedMessage` and `newUnsignedPersonalMessage` methods. ## Checklist - [X] I've updated the test suite for new or updated code as appropriate - [X] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [X] I've highlighted breaking changes using the "BREAKING" category above as appropriate
1 parent 465d3da commit f8872e5

4 files changed

Lines changed: 82 additions & 48 deletions

File tree

packages/controller-utils/src/types.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,40 @@ export type NetworkNickname =
127127
// TODO: Move to @metamask/utils
128128
export type Partialize<Type, Key extends keyof Type> = Omit<Type, Key> &
129129
Partial<Pick<Type, Key>>;
130+
131+
/** A context in which to execute a trace, in order to generate nested timings. */
132+
export type TraceContext = unknown;
133+
134+
/** Request to trace an operation. */
135+
export type TraceRequest = {
136+
/** Additional data to include in the trace. */
137+
data?: Record<string, number | string | boolean>;
138+
139+
/** Name of the operation. */
140+
name: string;
141+
142+
/**
143+
* Unique identifier for the trace.
144+
* Required if starting a trace and not providing a callback.
145+
*/
146+
id?: string;
147+
148+
/** Trace context in which to execute the operation. */
149+
parentContext?: TraceContext;
150+
151+
/** Additional tags to include in the trace to filter results. */
152+
tags?: Record<string, number | string | boolean>;
153+
};
154+
155+
/** Callback that traces the performance of an operation. */
156+
export type TraceCallback = <ReturnType>(
157+
/** Request to trace the performance of an operation. */
158+
request: TraceRequest,
159+
160+
/**
161+
* Callback to trace.
162+
* Thrown errors will not be caught, but the trace will still be recorded.
163+
* @param context - The context in which the operation is running.
164+
*/
165+
fn?: (context?: TraceContext) => ReturnType,
166+
) => Promise<ReturnType>;

packages/signature-controller/src/SignatureController.ts

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
} from '@metamask/base-controller';
1111
import { BaseController } from '@metamask/base-controller';
1212
import { ApprovalType, ORIGIN_METAMASK } from '@metamask/controller-utils';
13+
import type { TraceCallback, TraceContext } from '@metamask/controller-utils';
1314
import type {
1415
KeyringControllerSignMessageAction,
1516
KeyringControllerSignPersonalMessageAction,
@@ -109,9 +110,9 @@ export type SignatureControllerMessenger = RestrictedControllerMessenger<
109110
>;
110111

111112
export type SignatureControllerOptions = {
112-
messenger: SignatureControllerMessenger;
113-
isEthSignEnabled: () => boolean;
114113
getAllState: () => unknown;
114+
getCurrentChainId: () => Hex;
115+
messenger: SignatureControllerMessenger;
115116
securityProviderRequest?: (
116117
// TODO: Replace `any` with type
117118
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -120,7 +121,7 @@ export type SignatureControllerOptions = {
120121
// TODO: Replace `any` with type
121122
// eslint-disable-next-line @typescript-eslint/no-explicit-any
122123
) => Promise<any>;
123-
getCurrentChainId: () => Hex;
124+
trace?: TraceCallback;
124125
};
125126

126127
/**
@@ -141,6 +142,8 @@ export class SignatureController extends BaseController<
141142

142143
#typedMessageManager: TypedMessageManager;
143144

145+
#trace: TraceCallback;
146+
144147
/**
145148
* Construct a Sign controller.
146149
*
@@ -149,12 +152,14 @@ export class SignatureController extends BaseController<
149152
* @param options.getAllState - Callback to retrieve all user state.
150153
* @param options.securityProviderRequest - A function for verifying a message, whether it is malicious or not.
151154
* @param options.getCurrentChainId - A function for retrieving the current chainId.
155+
* @param options.trace - Callback to generate trace information.
152156
*/
153157
constructor({
154-
messenger,
155158
getAllState,
156-
securityProviderRequest,
157159
getCurrentChainId,
160+
messenger,
161+
securityProviderRequest,
162+
trace,
158163
}: SignatureControllerOptions) {
159164
super({
160165
name: controllerName,
@@ -178,6 +183,7 @@ export class SignatureController extends BaseController<
178183
undefined,
179184
getCurrentChainId,
180185
);
186+
this.#trace = trace ?? (((_request, fn) => fn?.()) as TraceCallback);
181187

182188
this.#handleMessageManagerEvents(
183189
this.#personalMessageManager,
@@ -276,11 +282,14 @@ export class SignatureController extends BaseController<
276282
*
277283
* @param messageParams - The params of the message to sign & return to the Dapp.
278284
* @param req - The original request, containing the origin.
285+
* @param options - An options bag for the method.
286+
* @param options.traceContext - The parent context for any new traces.
279287
* @returns Promise resolving to the raw data of the signature request.
280288
*/
281289
async newUnsignedPersonalMessage(
282290
messageParams: PersonalMessageParams,
283291
req: OriginalRequest,
292+
options: { traceContext?: TraceContext } = {},
284293
): Promise<string> {
285294
return this.#newUnsignedAbstractMessage(
286295
this.#personalMessageManager,
@@ -292,6 +301,9 @@ export class SignatureController extends BaseController<
292301
this.#signPersonalMessage.bind(this),
293302
messageParams,
294303
req,
304+
undefined,
305+
undefined,
306+
options.traceContext,
295307
);
296308
}
297309

@@ -303,13 +315,16 @@ export class SignatureController extends BaseController<
303315
* @param version - The version indicating the format of the typed data.
304316
* @param signingOpts - An options bag for signing.
305317
* @param signingOpts.parseJsonData - Whether to parse the JSON before signing.
318+
* @param options - An options bag for the method.
319+
* @param options.traceContext - The parent context for any new traces.
306320
* @returns Promise resolving to the raw data of the signature request.
307321
*/
308322
async newUnsignedTypedMessage(
309323
messageParams: TypedMessageParams,
310324
req: OriginalRequest,
311325
version: string,
312326
signingOpts: TypedMessageSigningOptions,
327+
options: { traceContext?: TraceContext } = {},
313328
): Promise<string> {
314329
const signTypeForLogger = this.#getSignTypeForLogger(version);
315330
return this.#newUnsignedAbstractMessage(
@@ -324,6 +339,7 @@ export class SignatureController extends BaseController<
324339
req,
325340
version,
326341
signingOpts,
342+
options.traceContext,
327343
);
328344
}
329345

@@ -397,6 +413,7 @@ export class SignatureController extends BaseController<
397413
req: OriginalRequest,
398414
version?: string,
399415
signingOpts?: SO,
416+
traceContext?: TraceContext,
400417
) {
401418
let resultCallbacks: AcceptResultCallbacks | undefined;
402419
try {
@@ -425,9 +442,13 @@ export class SignatureController extends BaseController<
425442
messageParamsWithId,
426443
);
427444

428-
const acceptResult = await this.#requestApproval(
429-
messageParamsWithId,
430-
approvalType,
445+
const acceptResult = await this.#trace(
446+
{ name: 'Await Approval', parentContext: traceContext },
447+
(context) =>
448+
this.#requestApproval(messageParamsWithId, approvalType, {
449+
traceContext: context,
450+
actionId: req?.id?.toString(),
451+
}),
431452
);
432453

433454
resultCallbacks = acceptResult.resultCallbacks;
@@ -445,7 +466,10 @@ export class SignatureController extends BaseController<
445466

446467
// TODO: Either fix this lint violation or explain why it's necessary to ignore.
447468
// eslint-disable-next-line @typescript-eslint/await-thenable
448-
await signMessage(messageParamsWithId, signingOpts);
469+
470+
await this.#trace({ name: 'Sign', parentContext: traceContext }, () =>
471+
signMessage(messageParamsWithId, signingOpts),
472+
);
449473

450474
const signatureResult = await signaturePromise;
451475

@@ -807,13 +831,24 @@ export class SignatureController extends BaseController<
807831
async #requestApproval(
808832
msgParams: AbstractMessageParamsMetamask,
809833
type: ApprovalType,
834+
{
835+
traceContext,
836+
actionId,
837+
}: { traceContext?: TraceContext; actionId?: string },
810838
): Promise<AddResult> {
811839
const id = msgParams.metamaskId as string;
812840
const origin = msgParams.origin || ORIGIN_METAMASK;
813841

814842
// We are explicitly cloning the message params here to prevent the mutation errors on development mode
815843
// Because sending it through the messaging system will make the object read only
816844
const clonedMsgParams = cloneDeep(msgParams);
845+
846+
await this.#trace({
847+
name: 'Notification Display',
848+
id: actionId,
849+
parentContext: traceContext,
850+
});
851+
817852
return (await this.messagingSystem.call(
818853
'ApprovalController:addRequest',
819854
{

packages/transaction-controller/src/TransactionController.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
convertHexToDecimal,
2222
isInfuraNetworkType,
2323
} from '@metamask/controller-utils';
24+
import type { TraceCallback, TraceContext } from '@metamask/controller-utils';
2425
import EthQuery from '@metamask/eth-query';
2526
import type {
2627
FetchGasFeeEstimateOptions,
@@ -78,8 +79,6 @@ import type {
7879
SimulationData,
7980
GasFeeEstimates,
8081
GasFeeFlowResponse,
81-
TraceCallback,
82-
TraceContext,
8382
GasPriceValue,
8483
FeeMarketEIP1559Values,
8584
} from './types';

packages/transaction-controller/src/types.ts

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,43 +1300,6 @@ export type SimulationData = {
13001300
tokenBalanceChanges: SimulationTokenBalanceChange[];
13011301
};
13021302

1303-
/** A context in which to execute a trace, in order to generate nested timings. */
1304-
export type TraceContext = unknown;
1305-
1306-
/** Request to trace an operation. */
1307-
export type TraceRequest = {
1308-
/** Additional data to include in the trace. */
1309-
data?: Record<string, number | string | boolean>;
1310-
1311-
/** Name of the operation. */
1312-
name: string;
1313-
1314-
/**
1315-
* Unique identifier for the trace.
1316-
* Required if starting a trace and not providing a callback.
1317-
*/
1318-
id?: string;
1319-
1320-
/** Trace context in which to execute the operation. */
1321-
parentContext?: TraceContext;
1322-
1323-
/** Additional tags to include in the trace to filter results. */
1324-
tags?: Record<string, number | string | boolean>;
1325-
};
1326-
1327-
/** Callback that traces the performance of an operation. */
1328-
export type TraceCallback = <ReturnType>(
1329-
/** Request to trace the performance of an operation. */
1330-
request: TraceRequest,
1331-
1332-
/**
1333-
* Callback to trace.
1334-
* Thrown errors will not be caught, but the trace will still be recorded.
1335-
* @param context - The context in which the operation is running.
1336-
*/
1337-
fn?: (context?: TraceContext) => ReturnType,
1338-
) => Promise<ReturnType>;
1339-
13401303
/** Gas fee properties for a legacy transaction. */
13411304
export type GasPriceValue = {
13421305
/** Price per gas for legacy transactions. */

0 commit comments

Comments
 (0)