Skip to content

Commit 11cfe34

Browse files
Skip init if coverage id is available (#6792)
## Explanation Currently the Shield Controller calls the `/init` endpoint every time a coverage request is made. This PR implements an optimization where the `/init` endpoint is skipped if the coverage Id is already available. <!-- 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? --> ## 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? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## 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 - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [ ] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Coverage checks now accept a request object with optional coverageId to skip /init; controller, backend, types, tests, and changelog updated accordingly. > > - **Breaking API**: > - Update `ShieldBackend` to accept request objects: `checkCoverage({ txMeta, coverageId? })`, `checkSignatureCoverage({ signatureRequest, coverageId? })` with new types `CheckCoverageRequest` and `CheckSignatureCoverageRequest`. > - `CHANGELOG.md`: note breaking change for `checkCoverage`. > - **Backend** (`src/backend.ts`): > - `ShieldRemoteBackend` skips `v1/*/coverage/init` when `coverageId` is provided; otherwise performs init then polls result. > - **Controller** (`src/ShieldController.ts`): > - Pass latest stored `coverageId` via `#getLatestCoverageId` to `checkCoverage` and `checkSignatureCoverage` to enable init-skip. > - **Tests/Utils**: > - Update expectations to new call shape and add `setupCoverageResultReceived` helper; adjust simulation flow and mocks (e.g., `MOCK_COVERAGE_ID`). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d24e5e6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 6e97eef commit 11cfe34

File tree

7 files changed

+96
-47
lines changed

7 files changed

+96
-47
lines changed

packages/shield-controller/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Changed
1515

1616
- Bump `@metamask/utils` from `^11.8.0` to `^11.8.1` ([#6708](https://github.com/MetaMask/core/pull/6708))
17+
- **Breaking:** Change `checkCoverage` API to accept `coverageId` and skip `/init` if `coverageId` is provided ([#6792](https://github.com/MetaMask/core/pull/6792))
1718

1819
## [0.2.0]
1920

packages/shield-controller/src/ShieldController.test.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import {
1111
} from '@metamask/transaction-controller';
1212

1313
import { ShieldController } from './ShieldController';
14-
import { createMockBackend } from '../tests/mocks/backend';
14+
import { createMockBackend, MOCK_COVERAGE_ID } from '../tests/mocks/backend';
1515
import { createMockMessenger } from '../tests/mocks/messenger';
1616
import {
1717
generateMockSignatureRequest,
1818
generateMockTxMeta,
19+
setupCoverageResultReceived,
1920
} from '../tests/utils';
2021

2122
/**
@@ -68,7 +69,7 @@ describe('ShieldController', () => {
6869
undefined as never,
6970
);
7071
expect(await coverageResultReceived).toBeUndefined();
71-
expect(backend.checkCoverage).toHaveBeenCalledWith(txMeta);
72+
expect(backend.checkCoverage).toHaveBeenCalledWith({ txMeta });
7273
});
7374

7475
it('should no longer trigger checkCoverage when controller is stopped', async () => {
@@ -126,12 +127,7 @@ describe('ShieldController', () => {
126127
it('should check coverage when a transaction is simulated', async () => {
127128
const { baseMessenger, backend } = setup();
128129
const txMeta = generateMockTxMeta();
129-
const coverageResultReceived = new Promise<void>((resolve) => {
130-
baseMessenger.subscribe(
131-
'ShieldController:coverageResultReceived',
132-
(_coverageResult) => resolve(),
133-
);
134-
});
130+
const coverageResultReceived = setupCoverageResultReceived(baseMessenger);
135131

136132
// Add transaction.
137133
baseMessenger.publish(
@@ -140,19 +136,25 @@ describe('ShieldController', () => {
140136
undefined as never,
141137
);
142138
expect(await coverageResultReceived).toBeUndefined();
143-
expect(backend.checkCoverage).toHaveBeenCalledWith(txMeta);
139+
expect(backend.checkCoverage).toHaveBeenCalledWith({ txMeta });
144140

145141
// Simulate transaction.
146-
txMeta.simulationData = {
142+
const txMeta2 = { ...txMeta };
143+
txMeta2.simulationData = {
147144
tokenBalanceChanges: [],
148145
};
146+
const coverageResultReceived2 =
147+
setupCoverageResultReceived(baseMessenger);
149148
baseMessenger.publish(
150149
'TransactionController:stateChange',
151-
{ transactions: [txMeta] } as TransactionControllerState,
150+
{ transactions: [txMeta2] } as TransactionControllerState,
152151
undefined as never,
153152
);
154-
expect(await coverageResultReceived).toBeUndefined();
155-
expect(backend.checkCoverage).toHaveBeenCalledWith(txMeta);
153+
expect(await coverageResultReceived2).toBeUndefined();
154+
expect(backend.checkCoverage).toHaveBeenCalledWith({
155+
coverageId: MOCK_COVERAGE_ID,
156+
txMeta: txMeta2,
157+
});
156158
});
157159

158160
it('throws an error when the coverage ID has changed', async () => {
@@ -189,9 +191,9 @@ describe('ShieldController', () => {
189191
undefined as never,
190192
);
191193
expect(await coverageResultReceived).toBeUndefined();
192-
expect(backend.checkSignatureCoverage).toHaveBeenCalledWith(
194+
expect(backend.checkSignatureCoverage).toHaveBeenCalledWith({
193195
signatureRequest,
194-
);
196+
});
195197
});
196198
});
197199

@@ -214,9 +216,9 @@ describe('ShieldController', () => {
214216
undefined as never,
215217
);
216218
expect(await coverageResultReceived1).toBeUndefined();
217-
expect(backend.checkSignatureCoverage).toHaveBeenCalledWith(
218-
signatureRequest1,
219-
);
219+
expect(backend.checkSignatureCoverage).toHaveBeenCalledWith({
220+
signatureRequest: signatureRequest1,
221+
});
220222

221223
const signatureRequest2 = generateMockSignatureRequest();
222224
const coverageResultReceived2 = new Promise<void>((resolve) => {
@@ -236,9 +238,9 @@ describe('ShieldController', () => {
236238
);
237239

238240
expect(await coverageResultReceived2).toBeUndefined();
239-
expect(backend.checkSignatureCoverage).toHaveBeenCalledWith(
240-
signatureRequest2,
241-
);
241+
expect(backend.checkSignatureCoverage).toHaveBeenCalledWith({
242+
signatureRequest: signatureRequest2,
243+
});
242244
});
243245

244246
describe('logSignature', () => {

packages/shield-controller/src/ShieldController.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,11 @@ export class ShieldController extends BaseController<
291291
*/
292292
async checkCoverage(txMeta: TransactionMeta): Promise<CoverageResult> {
293293
// Check coverage
294-
const coverageResult = await this.#backend.checkCoverage(txMeta);
294+
const coverageId = this.#getLatestCoverageId(txMeta.id);
295+
const coverageResult = await this.#backend.checkCoverage({
296+
txMeta,
297+
coverageId,
298+
});
295299

296300
// Publish coverage result
297301
this.messagingSystem.publish(
@@ -315,8 +319,11 @@ export class ShieldController extends BaseController<
315319
signatureRequest: SignatureRequest,
316320
): Promise<CoverageResult> {
317321
// Check coverage
318-
const coverageResult =
319-
await this.#backend.checkSignatureCoverage(signatureRequest);
322+
const coverageId = this.#getLatestCoverageId(signatureRequest.id);
323+
const coverageResult = await this.#backend.checkSignatureCoverage({
324+
signatureRequest,
325+
coverageId,
326+
});
320327

321328
// Publish coverage result
322329
this.messagingSystem.publish(

packages/shield-controller/src/backend.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('ShieldRemoteBackend', () => {
6363
} as unknown as Response);
6464

6565
const txMeta = generateMockTxMeta();
66-
const coverageResult = await backend.checkCoverage(txMeta);
66+
const coverageResult = await backend.checkCoverage({ txMeta });
6767
expect(coverageResult).toStrictEqual({ coverageId, status });
6868
expect(fetchMock).toHaveBeenCalledTimes(2);
6969
expect(getAccessToken).toHaveBeenCalledTimes(2);
@@ -95,7 +95,7 @@ describe('ShieldRemoteBackend', () => {
9595
} as unknown as Response);
9696

9797
const txMeta = generateMockTxMeta();
98-
const coverageResult = await backend.checkCoverage(txMeta);
98+
const coverageResult = await backend.checkCoverage({ txMeta });
9999
expect(coverageResult).toStrictEqual({ coverageId, status });
100100
expect(fetchMock).toHaveBeenCalledTimes(3);
101101
expect(getAccessToken).toHaveBeenCalledTimes(2);
@@ -113,7 +113,7 @@ describe('ShieldRemoteBackend', () => {
113113
} as unknown as Response);
114114

115115
const txMeta = generateMockTxMeta();
116-
await expect(backend.checkCoverage(txMeta)).rejects.toThrow(
116+
await expect(backend.checkCoverage({ txMeta })).rejects.toThrow(
117117
`Failed to init coverage check: ${status}`,
118118
);
119119
expect(fetchMock).toHaveBeenCalledTimes(1);
@@ -139,7 +139,7 @@ describe('ShieldRemoteBackend', () => {
139139
} as unknown as Response);
140140

141141
const txMeta = generateMockTxMeta();
142-
await expect(backend.checkCoverage(txMeta)).rejects.toThrow(
142+
await expect(backend.checkCoverage({ txMeta })).rejects.toThrow(
143143
'Timeout waiting for coverage result',
144144
);
145145

@@ -167,8 +167,9 @@ describe('ShieldRemoteBackend', () => {
167167
} as unknown as Response);
168168

169169
const signatureRequest = generateMockSignatureRequest();
170-
const coverageResult =
171-
await backend.checkSignatureCoverage(signatureRequest);
170+
const coverageResult = await backend.checkSignatureCoverage({
171+
signatureRequest,
172+
});
172173
expect(coverageResult).toStrictEqual({ coverageId, status });
173174
expect(fetchMock).toHaveBeenCalledTimes(2);
174175
expect(getAccessToken).toHaveBeenCalledTimes(2);
@@ -180,7 +181,7 @@ describe('ShieldRemoteBackend', () => {
180181
const signatureRequest = generateMockSignatureRequest();
181182
signatureRequest.messageParams.data = [];
182183
await expect(
183-
backend.checkSignatureCoverage(signatureRequest),
184+
backend.checkSignatureCoverage({ signatureRequest }),
184185
).rejects.toThrow('Signature data must be a string');
185186
});
186187
});

packages/shield-controller/src/backend.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { SignatureRequest } from '@metamask/signature-controller';
22
import type { TransactionMeta } from '@metamask/transaction-controller';
33

44
import type {
5+
CheckCoverageRequest,
6+
CheckSignatureCoverageRequest,
57
CoverageResult,
68
CoverageStatus,
79
LogSignatureRequest,
@@ -74,27 +76,31 @@ export class ShieldRemoteBackend implements ShieldBackend {
7476
this.#fetch = fetchFn;
7577
}
7678

77-
async checkCoverage(txMeta: TransactionMeta): Promise<CoverageResult> {
78-
const reqBody = makeInitCoverageCheckBody(txMeta);
79-
80-
const { coverageId } = await this.#initCoverageCheck(
81-
'v1/transaction/coverage/init',
82-
reqBody,
83-
);
79+
async checkCoverage(req: CheckCoverageRequest): Promise<CoverageResult> {
80+
let { coverageId } = req;
81+
if (!coverageId) {
82+
const reqBody = makeInitCoverageCheckBody(req.txMeta);
83+
({ coverageId } = await this.#initCoverageCheck(
84+
'v1/transaction/coverage/init',
85+
reqBody,
86+
));
87+
}
8488

8589
const coverageResult = await this.#getCoverageResult(coverageId);
8690
return { coverageId, status: coverageResult.status };
8791
}
8892

8993
async checkSignatureCoverage(
90-
signatureRequest: SignatureRequest,
94+
req: CheckSignatureCoverageRequest,
9195
): Promise<CoverageResult> {
92-
const reqBody = makeInitSignatureCoverageCheckBody(signatureRequest);
93-
94-
const { coverageId } = await this.#initCoverageCheck(
95-
'v1/signature/coverage/init',
96-
reqBody,
97-
);
96+
let { coverageId } = req;
97+
if (!coverageId) {
98+
const reqBody = makeInitSignatureCoverageCheckBody(req.signatureRequest);
99+
({ coverageId } = await this.#initCoverageCheck(
100+
'v1/signature/coverage/init',
101+
reqBody,
102+
));
103+
}
98104

99105
const coverageResult = await this.#getCoverageResult(coverageId);
100106
return { coverageId, status: coverageResult.status };

packages/shield-controller/src/types.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,21 @@ export type LogTransactionRequest = {
2121
status: string;
2222
};
2323

24+
export type CheckCoverageRequest = {
25+
coverageId?: string;
26+
txMeta: TransactionMeta;
27+
};
28+
29+
export type CheckSignatureCoverageRequest = {
30+
coverageId?: string;
31+
signatureRequest: SignatureRequest;
32+
};
33+
2434
export type ShieldBackend = {
2535
logSignature: (req: LogSignatureRequest) => Promise<void>;
2636
logTransaction: (req: LogTransactionRequest) => Promise<void>;
27-
checkCoverage: (txMeta: TransactionMeta) => Promise<CoverageResult>;
37+
checkCoverage: (req: CheckCoverageRequest) => Promise<CoverageResult>;
2838
checkSignatureCoverage: (
29-
signatureRequest: SignatureRequest,
39+
req: CheckSignatureCoverageRequest,
3040
) => Promise<CoverageResult>;
3141
};

packages/shield-controller/tests/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from '@metamask/transaction-controller';
1111
import { v1 as random } from 'uuid';
1212

13+
import type { createMockMessenger } from './mocks/messenger';
1314
import { coverageStatuses, type CoverageStatus } from '../src/types';
1415

1516
/**
@@ -64,3 +65,24 @@ export function generateMockSignatureRequest(): SignatureRequest {
6465
export function getRandomCoverageStatus(): CoverageStatus {
6566
return coverageStatuses[Math.floor(Math.random() * coverageStatuses.length)];
6667
}
68+
69+
/**
70+
* Setup a coverage result received handler.
71+
*
72+
* @param baseMessenger - The base messenger.
73+
* @returns A promise that resolves when the coverage result is received.
74+
*/
75+
export function setupCoverageResultReceived(
76+
baseMessenger: ReturnType<typeof createMockMessenger>['baseMessenger'],
77+
): Promise<void> {
78+
return new Promise<void>((resolve) => {
79+
const handler = (_coverageResult: unknown) => {
80+
baseMessenger.unsubscribe(
81+
'ShieldController:coverageResultReceived',
82+
handler,
83+
);
84+
resolve();
85+
};
86+
baseMessenger.subscribe('ShieldController:coverageResultReceived', handler);
87+
});
88+
}

0 commit comments

Comments
 (0)