Skip to content

Commit dd6f126

Browse files
feat: Configure pending transaction polling intervals using remote feature flags
1 parent 85894b8 commit dd6f126

File tree

7 files changed

+350
-37
lines changed

7 files changed

+350
-37
lines changed

packages/transaction-controller/src/TransactionController.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3604,6 +3604,7 @@ export class TransactionController extends BaseController<
36043604
this.beforeCheckPendingTransaction.bind(this),
36053605
beforePublish: this.beforePublish.bind(this),
36063606
},
3607+
messenger: this.messagingSystem,
36073608
});
36083609

36093610
this.#addPendingTransactionTrackerListeners(pendingTransactionTracker);

packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import type EthQuery from '@metamask/eth-query';
33
import type { BlockTracker } from '@metamask/network-controller';
44
import { freeze } from 'immer';
55

6-
import { PendingTransactionTracker } from './PendingTransactionTracker';
7-
import { TransactionPoller } from './TransactionPoller';
6+
import type { TransactionControllerMessenger } from '../TransactionController';
87
import type { TransactionMeta } from '../types';
98
import { TransactionStatus } from '../types';
9+
import { PendingTransactionTracker } from './PendingTransactionTracker';
10+
import { TransactionPoller } from './TransactionPoller';
1011

1112
const ID_MOCK = 'testId';
1213
const CHAIN_ID_MOCK = '0x1';
@@ -74,11 +75,25 @@ function createTransactionPollerMock(): jest.Mocked<TransactionPoller> {
7475
} as unknown as jest.Mocked<TransactionPoller>;
7576
}
7677

78+
/**
79+
* Creates a mock messenger instance.
80+
*
81+
* @returns The mock messenger instance.
82+
*/
83+
function createMessengerMock(): jest.Mocked<TransactionControllerMessenger> {
84+
return {
85+
call: jest.fn().mockReturnValue({
86+
remoteFeatureFlags: {},
87+
}),
88+
} as unknown as jest.Mocked<TransactionControllerMessenger>;
89+
}
90+
7791
describe('PendingTransactionTracker', () => {
7892
const queryMock = jest.mocked(query);
7993
let blockTracker: jest.Mocked<BlockTracker>;
8094
let pendingTransactionTracker: PendingTransactionTracker;
8195
let transactionPoller: jest.Mocked<TransactionPoller>;
96+
let messenger: jest.Mocked<TransactionControllerMessenger>;
8297

8398
let options: jest.Mocked<
8499
ConstructorParameters<typeof PendingTransactionTracker>[0]
@@ -112,6 +127,7 @@ describe('PendingTransactionTracker', () => {
112127
beforeEach(() => {
113128
blockTracker = createBlockTrackerMock();
114129
transactionPoller = createTransactionPollerMock();
130+
messenger = createMessengerMock();
115131

116132
jest.mocked(TransactionPoller).mockImplementation(() => transactionPoller);
117133

@@ -123,6 +139,7 @@ describe('PendingTransactionTracker', () => {
123139
getTransactions: jest.fn(),
124140
getGlobalLock: jest.fn(() => Promise.resolve(jest.fn())),
125141
publishTransaction: jest.fn(),
142+
messenger,
126143
};
127144
});
128145

packages/transaction-controller/src/helpers/PendingTransactionTracker.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import type {
99
import EventEmitter from 'events';
1010
import { cloneDeep, merge } from 'lodash';
1111

12-
import { TransactionPoller } from './TransactionPoller';
1312
import { createModuleLogger, projectLogger } from '../logger';
13+
import type { TransactionControllerMessenger } from '../TransactionController';
1414
import type { TransactionMeta, TransactionReceipt } from '../types';
1515
import { TransactionStatus, TransactionType } from '../types';
16+
import { TransactionPoller } from './TransactionPoller';
1617

1718
/**
1819
* We wait this many blocks before emitting a 'transaction-dropped' event
@@ -107,6 +108,7 @@ export class PendingTransactionTracker {
107108
getGlobalLock,
108109
publishTransaction,
109110
hooks,
111+
messenger,
110112
}: {
111113
blockTracker: BlockTracker;
112114
getChainId: () => string;
@@ -125,6 +127,7 @@ export class PendingTransactionTracker {
125127
) => boolean;
126128
beforePublish?: (transactionMeta: TransactionMeta) => boolean;
127129
};
130+
messenger: TransactionControllerMessenger;
128131
}) {
129132
this.hub = new EventEmitter() as PendingTransactionTrackerEventEmitter;
130133

@@ -138,7 +141,11 @@ export class PendingTransactionTracker {
138141
this.#getGlobalLock = getGlobalLock;
139142
this.#publishTransaction = publishTransaction;
140143
this.#running = false;
141-
this.#transactionPoller = new TransactionPoller(blockTracker);
144+
this.#transactionPoller = new TransactionPoller(
145+
blockTracker,
146+
messenger,
147+
getChainId(),
148+
);
142149
this.#beforePublish = hooks?.beforePublish ?? (() => true);
143150
this.#beforeCheckPendingTransaction =
144151
hooks?.beforeCheckPendingTransaction ?? (() => true);

packages/transaction-controller/src/helpers/TransactionPoller.test.ts

Lines changed: 96 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
import type { BlockTracker } from '@metamask/network-controller';
22

3-
import { ACCELERATED_COUNT_MAX, TransactionPoller } from './TransactionPoller';
43
import { flushPromises } from '../../../../tests/helpers';
4+
import type { TransactionControllerMessenger } from '../TransactionController';
55
import type { TransactionMeta } from '../types';
6+
import { TransactionPoller } from './TransactionPoller';
67

78
jest.useFakeTimers();
89

910
const BLOCK_NUMBER_MOCK = '0x123';
11+
const CHAIN_ID_MOCK = '0x1';
12+
const DEFAULT_ACCELERATED_COUNT_MAX = 10;
13+
const DEFAULT_ACCELERATED_POLLING_INTERVAL_MS = 3000;
1014

1115
const BLOCK_TRACKER_MOCK = {
1216
getLatestBlock: jest.fn(),
1317
on: jest.fn(),
1418
removeListener: jest.fn(),
1519
} as unknown as jest.Mocked<BlockTracker>;
1620

21+
const MESSENGER_MOCK = {
22+
call: jest.fn().mockReturnValue({
23+
remoteFeatureFlags: {},
24+
}),
25+
} as unknown as jest.Mocked<TransactionControllerMessenger>;
26+
27+
// Mock feature flags
28+
jest.mock('../utils/feature-flags', () => ({
29+
getAcceleratedPollingParams: jest.fn().mockReturnValue({
30+
countMax: DEFAULT_ACCELERATED_COUNT_MAX,
31+
intervalMs: DEFAULT_ACCELERATED_POLLING_INTERVAL_MS,
32+
}),
33+
FEATURE_FLAG_TRANSACTIONS: 'confirmations_transactions',
34+
}));
35+
1736
/**
1837
* Creates a mock transaction metadata object.
1938
*
@@ -32,7 +51,11 @@ describe('TransactionPoller', () => {
3251

3352
describe('Accelerated Polling', () => {
3453
it('invokes listener after timeout', async () => {
35-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
54+
const poller = new TransactionPoller(
55+
BLOCK_TRACKER_MOCK,
56+
MESSENGER_MOCK,
57+
CHAIN_ID_MOCK,
58+
);
3659

3760
const listener = jest.fn();
3861
poller.start(listener);
@@ -46,21 +69,29 @@ describe('TransactionPoller', () => {
4669
});
4770

4871
it('stops creating timeouts after max reached', async () => {
49-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
72+
const poller = new TransactionPoller(
73+
BLOCK_TRACKER_MOCK,
74+
MESSENGER_MOCK,
75+
CHAIN_ID_MOCK,
76+
);
5077

5178
const listener = jest.fn();
5279
poller.start(listener);
5380

54-
for (let i = 0; i < ACCELERATED_COUNT_MAX * 3; i++) {
81+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX * 3; i++) {
5582
jest.runOnlyPendingTimers();
5683
await flushPromises();
5784
}
5885

59-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX);
86+
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX);
6087
});
6188

6289
it('invokes listener with latest block number from block tracker', async () => {
63-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
90+
const poller = new TransactionPoller(
91+
BLOCK_TRACKER_MOCK,
92+
MESSENGER_MOCK,
93+
CHAIN_ID_MOCK,
94+
);
6495

6596
BLOCK_TRACKER_MOCK.getLatestBlock.mockResolvedValue(BLOCK_NUMBER_MOCK);
6697

@@ -74,7 +105,11 @@ describe('TransactionPoller', () => {
74105
});
75106

76107
it('does not create timeout if stopped while listener being invoked', async () => {
77-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
108+
const poller = new TransactionPoller(
109+
BLOCK_TRACKER_MOCK,
110+
MESSENGER_MOCK,
111+
CHAIN_ID_MOCK,
112+
);
78113

79114
const listener = jest.fn();
80115
listener.mockImplementation(() => poller.stop());
@@ -90,12 +125,16 @@ describe('TransactionPoller', () => {
90125

91126
describe('Block Tracker Polling', () => {
92127
it('invokes listener on block tracker update after accelerated limit reached', async () => {
93-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
128+
const poller = new TransactionPoller(
129+
BLOCK_TRACKER_MOCK,
130+
MESSENGER_MOCK,
131+
CHAIN_ID_MOCK,
132+
);
94133

95134
const listener = jest.fn();
96135
poller.start(listener);
97136

98-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
137+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
99138
jest.runOnlyPendingTimers();
100139
await flushPromises();
101140
}
@@ -106,16 +145,20 @@ describe('TransactionPoller', () => {
106145
BLOCK_TRACKER_MOCK.on.mock.calls[0][1]();
107146
await flushPromises();
108147

109-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX + 2);
148+
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX + 2);
110149
});
111150

112151
it('invokes listener with latest block number from event', async () => {
113-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
152+
const poller = new TransactionPoller(
153+
BLOCK_TRACKER_MOCK,
154+
MESSENGER_MOCK,
155+
CHAIN_ID_MOCK,
156+
);
114157

115158
const listener = jest.fn();
116159
poller.start(listener);
117160

118-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
161+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
119162
jest.runOnlyPendingTimers();
120163
await flushPromises();
121164
}
@@ -129,7 +172,11 @@ describe('TransactionPoller', () => {
129172

130173
describe('start', () => {
131174
it('does nothing if already started', () => {
132-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
175+
const poller = new TransactionPoller(
176+
BLOCK_TRACKER_MOCK,
177+
MESSENGER_MOCK,
178+
CHAIN_ID_MOCK,
179+
);
133180

134181
poller.start(jest.fn());
135182
poller.start(jest.fn());
@@ -140,7 +187,11 @@ describe('TransactionPoller', () => {
140187

141188
describe('stop', () => {
142189
it('removes timeout', () => {
143-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
190+
const poller = new TransactionPoller(
191+
BLOCK_TRACKER_MOCK,
192+
MESSENGER_MOCK,
193+
CHAIN_ID_MOCK,
194+
);
144195

145196
const listener = jest.fn();
146197
poller.start(listener);
@@ -151,24 +202,32 @@ describe('TransactionPoller', () => {
151202
});
152203

153204
it('removes block tracker listener', async () => {
154-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
205+
const poller = new TransactionPoller(
206+
BLOCK_TRACKER_MOCK,
207+
MESSENGER_MOCK,
208+
CHAIN_ID_MOCK,
209+
);
155210

156211
const listener = jest.fn();
157212
poller.start(listener);
158213

159-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
214+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
160215
jest.runOnlyPendingTimers();
161216
await flushPromises();
162217
}
163218

164219
poller.stop();
165220

166221
expect(BLOCK_TRACKER_MOCK.removeListener).toHaveBeenCalledTimes(1);
167-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX);
222+
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX);
168223
});
169224

170225
it('does nothing if not started', async () => {
171-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
226+
const poller = new TransactionPoller(
227+
BLOCK_TRACKER_MOCK,
228+
MESSENGER_MOCK,
229+
CHAIN_ID_MOCK,
230+
);
172231

173232
poller.stop();
174233

@@ -191,7 +250,11 @@ describe('TransactionPoller', () => {
191250
])(
192251
'resets accelerated count if transaction IDs %s',
193252
async (_title, newPendingTransactions) => {
194-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
253+
const poller = new TransactionPoller(
254+
BLOCK_TRACKER_MOCK,
255+
MESSENGER_MOCK,
256+
CHAIN_ID_MOCK,
257+
);
195258

196259
poller.setPendingTransactions([
197260
createTransactionMetaMock('1'),
@@ -208,12 +271,14 @@ describe('TransactionPoller', () => {
208271

209272
poller.setPendingTransactions(newPendingTransactions);
210273

211-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
274+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
212275
jest.runOnlyPendingTimers();
213276
await flushPromises();
214277
}
215278

216-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX + 3);
279+
expect(listener).toHaveBeenCalledTimes(
280+
DEFAULT_ACCELERATED_COUNT_MAX + 3,
281+
);
217282
},
218283
);
219284

@@ -230,7 +295,11 @@ describe('TransactionPoller', () => {
230295
])(
231296
'resets to accelerated polling if transaction IDs added',
232297
async (_title, newPendingTransactions) => {
233-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
298+
const poller = new TransactionPoller(
299+
BLOCK_TRACKER_MOCK,
300+
MESSENGER_MOCK,
301+
CHAIN_ID_MOCK,
302+
);
234303

235304
poller.setPendingTransactions([
236305
createTransactionMetaMock('1'),
@@ -240,7 +309,7 @@ describe('TransactionPoller', () => {
240309
const listener = jest.fn();
241310
poller.start(listener);
242311

243-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
312+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
244313
jest.runOnlyPendingTimers();
245314
await flushPromises();
246315
}
@@ -253,12 +322,14 @@ describe('TransactionPoller', () => {
253322

254323
poller.setPendingTransactions(newPendingTransactions);
255324

256-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
325+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
257326
jest.runOnlyPendingTimers();
258327
await flushPromises();
259328
}
260329

261-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX * 2 + 2);
330+
expect(listener).toHaveBeenCalledTimes(
331+
DEFAULT_ACCELERATED_COUNT_MAX * 2 + 2,
332+
);
262333
},
263334
);
264335
});

0 commit comments

Comments
 (0)