Skip to content

Commit b0b2930

Browse files
feat: Configure pending transaction polling intervals using remote feature flags
1 parent 21201bc commit b0b2930

File tree

7 files changed

+344
-32
lines changed

7 files changed

+344
-32
lines changed

packages/transaction-controller/src/TransactionController.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3632,6 +3632,7 @@ export class TransactionController extends BaseController<
36323632
this.beforeCheckPendingTransaction.bind(this),
36333633
beforePublish: this.beforePublish.bind(this),
36343634
},
3635+
messenger: this.messagingSystem,
36353636
});
36363637

36373638
this.#addPendingTransactionTrackerListeners(pendingTransactionTracker);

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { freeze } from 'immer';
55

66
import { PendingTransactionTracker } from './PendingTransactionTracker';
77
import { TransactionPoller } from './TransactionPoller';
8+
import type { TransactionControllerMessenger } from '../TransactionController';
89
import type { TransactionMeta } from '../types';
910
import { TransactionStatus } from '../types';
1011

@@ -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: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { cloneDeep, merge } from 'lodash';
1111

1212
import { TransactionPoller } from './TransactionPoller';
1313
import { createModuleLogger, projectLogger } from '../logger';
14+
import type { TransactionControllerMessenger } from '../TransactionController';
1415
import type { TransactionMeta, TransactionReceipt } from '../types';
1516
import { TransactionStatus, TransactionType } from '../types';
1617

@@ -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: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
11
import type { BlockTracker } from '@metamask/network-controller';
22

3-
import { ACCELERATED_COUNT_MAX, TransactionPoller } from './TransactionPoller';
3+
import { TransactionPoller } from './TransactionPoller';
44
import { flushPromises } from '../../../../tests/helpers';
5+
import type { TransactionControllerMessenger } from '../TransactionController';
56
import type { TransactionMeta } from '../types';
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+
jest.mock('../utils/feature-flags', () => ({
28+
getAcceleratedPollingParams: () => ({
29+
countMax: DEFAULT_ACCELERATED_COUNT_MAX,
30+
intervalMs: DEFAULT_ACCELERATED_POLLING_INTERVAL_MS,
31+
}),
32+
FEATURE_FLAG_TRANSACTIONS: 'confirmations_transactions',
33+
}));
34+
1735
/**
1836
* Creates a mock transaction metadata object.
1937
*
@@ -32,7 +50,11 @@ describe('TransactionPoller', () => {
3250

3351
describe('Accelerated Polling', () => {
3452
it('invokes listener after timeout', async () => {
35-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
53+
const poller = new TransactionPoller(
54+
BLOCK_TRACKER_MOCK,
55+
MESSENGER_MOCK,
56+
CHAIN_ID_MOCK,
57+
);
3658

3759
const listener = jest.fn();
3860
poller.start(listener);
@@ -46,21 +68,29 @@ describe('TransactionPoller', () => {
4668
});
4769

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

5177
const listener = jest.fn();
5278
poller.start(listener);
5379

54-
for (let i = 0; i < ACCELERATED_COUNT_MAX * 3; i++) {
80+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX * 3; i++) {
5581
jest.runOnlyPendingTimers();
5682
await flushPromises();
5783
}
5884

59-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX);
85+
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX);
6086
});
6187

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

6595
BLOCK_TRACKER_MOCK.getLatestBlock.mockResolvedValue(BLOCK_NUMBER_MOCK);
6696

@@ -74,7 +104,11 @@ describe('TransactionPoller', () => {
74104
});
75105

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

79113
const listener = jest.fn();
80114
listener.mockImplementation(() => poller.stop());
@@ -90,12 +124,16 @@ describe('TransactionPoller', () => {
90124

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

95133
const listener = jest.fn();
96134
poller.start(listener);
97135

98-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
136+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
99137
jest.runOnlyPendingTimers();
100138
await flushPromises();
101139
}
@@ -106,16 +144,20 @@ describe('TransactionPoller', () => {
106144
BLOCK_TRACKER_MOCK.on.mock.calls[0][1]();
107145
await flushPromises();
108146

109-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX + 2);
147+
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX + 2);
110148
});
111149

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

115157
const listener = jest.fn();
116158
poller.start(listener);
117159

118-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
160+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
119161
jest.runOnlyPendingTimers();
120162
await flushPromises();
121163
}
@@ -129,7 +171,11 @@ describe('TransactionPoller', () => {
129171

130172
describe('start', () => {
131173
it('does nothing if already started', () => {
132-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
174+
const poller = new TransactionPoller(
175+
BLOCK_TRACKER_MOCK,
176+
MESSENGER_MOCK,
177+
CHAIN_ID_MOCK,
178+
);
133179

134180
poller.start(jest.fn());
135181
poller.start(jest.fn());
@@ -140,7 +186,11 @@ describe('TransactionPoller', () => {
140186

141187
describe('stop', () => {
142188
it('removes timeout', () => {
143-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
189+
const poller = new TransactionPoller(
190+
BLOCK_TRACKER_MOCK,
191+
MESSENGER_MOCK,
192+
CHAIN_ID_MOCK,
193+
);
144194

145195
const listener = jest.fn();
146196
poller.start(listener);
@@ -151,24 +201,32 @@ describe('TransactionPoller', () => {
151201
});
152202

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

156210
const listener = jest.fn();
157211
poller.start(listener);
158212

159-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
213+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
160214
jest.runOnlyPendingTimers();
161215
await flushPromises();
162216
}
163217

164218
poller.stop();
165219

166220
expect(BLOCK_TRACKER_MOCK.removeListener).toHaveBeenCalledTimes(1);
167-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX);
221+
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX);
168222
});
169223

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

173231
poller.stop();
174232

@@ -191,7 +249,11 @@ describe('TransactionPoller', () => {
191249
])(
192250
'resets accelerated count if transaction IDs %s',
193251
async (_title, newPendingTransactions) => {
194-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
252+
const poller = new TransactionPoller(
253+
BLOCK_TRACKER_MOCK,
254+
MESSENGER_MOCK,
255+
CHAIN_ID_MOCK,
256+
);
195257

196258
poller.setPendingTransactions([
197259
createTransactionMetaMock('1'),
@@ -208,12 +270,14 @@ describe('TransactionPoller', () => {
208270

209271
poller.setPendingTransactions(newPendingTransactions);
210272

211-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
273+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
212274
jest.runOnlyPendingTimers();
213275
await flushPromises();
214276
}
215277

216-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX + 3);
278+
expect(listener).toHaveBeenCalledTimes(
279+
DEFAULT_ACCELERATED_COUNT_MAX + 3,
280+
);
217281
},
218282
);
219283

@@ -230,7 +294,11 @@ describe('TransactionPoller', () => {
230294
])(
231295
'resets to accelerated polling if transaction IDs added',
232296
async (_title, newPendingTransactions) => {
233-
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
297+
const poller = new TransactionPoller(
298+
BLOCK_TRACKER_MOCK,
299+
MESSENGER_MOCK,
300+
CHAIN_ID_MOCK,
301+
);
234302

235303
poller.setPendingTransactions([
236304
createTransactionMetaMock('1'),
@@ -240,7 +308,7 @@ describe('TransactionPoller', () => {
240308
const listener = jest.fn();
241309
poller.start(listener);
242310

243-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
311+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
244312
jest.runOnlyPendingTimers();
245313
await flushPromises();
246314
}
@@ -253,12 +321,14 @@ describe('TransactionPoller', () => {
253321

254322
poller.setPendingTransactions(newPendingTransactions);
255323

256-
for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
324+
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
257325
jest.runOnlyPendingTimers();
258326
await flushPromises();
259327
}
260328

261-
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX * 2 + 2);
329+
expect(listener).toHaveBeenCalledTimes(
330+
DEFAULT_ACCELERATED_COUNT_MAX * 2 + 2,
331+
);
262332
},
263333
);
264334
});

0 commit comments

Comments
 (0)