Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3629,6 +3629,7 @@ export class TransactionController extends BaseController<
this.#multichainTrackingHelper.acquireNonceLockForChainIdKey({
chainId,
}),
messenger: this.messagingSystem,
publishTransaction: (_ethQuery, transactionMeta) =>
this.publishTransaction(_ethQuery, transactionMeta, {
skipSubmitHistory: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { freeze } from 'immer';

import { PendingTransactionTracker } from './PendingTransactionTracker';
import { TransactionPoller } from './TransactionPoller';
import type { TransactionControllerMessenger } from '../TransactionController';
import type { TransactionMeta } from '../types';
import { TransactionStatus } from '../types';

Expand Down Expand Up @@ -74,11 +75,25 @@ function createTransactionPollerMock(): jest.Mocked<TransactionPoller> {
} as unknown as jest.Mocked<TransactionPoller>;
}

/**
* Creates a mock messenger instance.
*
* @returns The mock messenger instance.
*/
function createMessengerMock(): jest.Mocked<TransactionControllerMessenger> {
return {
call: jest.fn().mockReturnValue({
remoteFeatureFlags: {},
}),
} as unknown as jest.Mocked<TransactionControllerMessenger>;
}

describe('PendingTransactionTracker', () => {
const queryMock = jest.mocked(query);
let blockTracker: jest.Mocked<BlockTracker>;
let pendingTransactionTracker: PendingTransactionTracker;
let transactionPoller: jest.Mocked<TransactionPoller>;
let messenger: jest.Mocked<TransactionControllerMessenger>;

let options: jest.Mocked<
ConstructorParameters<typeof PendingTransactionTracker>[0]
Expand Down Expand Up @@ -112,6 +127,7 @@ describe('PendingTransactionTracker', () => {
beforeEach(() => {
blockTracker = createBlockTrackerMock();
transactionPoller = createTransactionPollerMock();
messenger = createMessengerMock();

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

Expand All @@ -123,6 +139,7 @@ describe('PendingTransactionTracker', () => {
getTransactions: jest.fn(),
getGlobalLock: jest.fn(() => Promise.resolve(jest.fn())),
publishTransaction: jest.fn(),
messenger,
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import type {
BlockTracker,
NetworkClientId,
} from '@metamask/network-controller';
import type { Hex } from '@metamask/utils';
// This package purposefully relies on Node's EventEmitter module.
// eslint-disable-next-line import-x/no-nodejs-modules
import EventEmitter from 'events';
import { cloneDeep, merge } from 'lodash';

import { TransactionPoller } from './TransactionPoller';
import { createModuleLogger, projectLogger } from '../logger';
import type { TransactionControllerMessenger } from '../TransactionController';
import type { TransactionMeta, TransactionReceipt } from '../types';
import { TransactionStatus, TransactionType } from '../types';

Expand Down Expand Up @@ -101,15 +103,16 @@ export class PendingTransactionTracker {
blockTracker,
getChainId,
getEthQuery,
getGlobalLock,
getNetworkClientId,
getTransactions,
hooks,
isResubmitEnabled,
getGlobalLock,
messenger,
publishTransaction,
hooks,
}: {
blockTracker: BlockTracker;
getChainId: () => string;
getChainId: () => Hex;
getEthQuery: (networkClientId?: NetworkClientId) => EthQuery;
getNetworkClientId: () => string;
getTransactions: () => TransactionMeta[];
Expand All @@ -125,6 +128,7 @@ export class PendingTransactionTracker {
) => boolean;
beforePublish?: (transactionMeta: TransactionMeta) => boolean;
};
messenger: TransactionControllerMessenger;
}) {
this.hub = new EventEmitter() as PendingTransactionTrackerEventEmitter;

Expand All @@ -138,7 +142,11 @@ export class PendingTransactionTracker {
this.#getGlobalLock = getGlobalLock;
this.#publishTransaction = publishTransaction;
this.#running = false;
this.#transactionPoller = new TransactionPoller(blockTracker);
this.#transactionPoller = new TransactionPoller({
blockTracker,
chainId: getChainId(),
messenger,
});
this.#beforePublish = hooks?.beforePublish ?? (() => true);
this.#beforeCheckPendingTransaction =
hooks?.beforeCheckPendingTransaction ?? (() => true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import type { BlockTracker } from '@metamask/network-controller';

import { ACCELERATED_COUNT_MAX, TransactionPoller } from './TransactionPoller';
import { TransactionPoller } from './TransactionPoller';
import { flushPromises } from '../../../../tests/helpers';
import type { TransactionControllerMessenger } from '../TransactionController';
import type { TransactionMeta } from '../types';

jest.useFakeTimers();

const BLOCK_NUMBER_MOCK = '0x123';
const CHAIN_ID_MOCK = '0x1';
const DEFAULT_ACCELERATED_COUNT_MAX = 10;
const DEFAULT_ACCELERATED_POLLING_INTERVAL_MS = 3000;

const BLOCK_TRACKER_MOCK = {
getLatestBlock: jest.fn(),
on: jest.fn(),
removeListener: jest.fn(),
} as unknown as jest.Mocked<BlockTracker>;

const MESSENGER_MOCK = {
call: jest.fn().mockReturnValue({
remoteFeatureFlags: {},
}),
} as unknown as jest.Mocked<TransactionControllerMessenger>;

jest.mock('../utils/feature-flags', () => ({
getAcceleratedPollingParams: () => ({
countMax: DEFAULT_ACCELERATED_COUNT_MAX,
intervalMs: DEFAULT_ACCELERATED_POLLING_INTERVAL_MS,
}),
FEATURE_FLAG_TRANSACTIONS: 'confirmations_transactions',
}));

/**
* Creates a mock transaction metadata object.
*
Expand All @@ -32,7 +50,11 @@ describe('TransactionPoller', () => {

describe('Accelerated Polling', () => {
it('invokes listener after timeout', async () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

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

it('stops creating timeouts after max reached', async () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

const listener = jest.fn();
poller.start(listener);

for (let i = 0; i < ACCELERATED_COUNT_MAX * 3; i++) {
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX * 3; i++) {
jest.runOnlyPendingTimers();
await flushPromises();
}

expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX);
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX);
});

it('invokes listener with latest block number from block tracker', async () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

BLOCK_TRACKER_MOCK.getLatestBlock.mockResolvedValue(BLOCK_NUMBER_MOCK);

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

it('does not create timeout if stopped while listener being invoked', async () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

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

describe('Block Tracker Polling', () => {
it('invokes listener on block tracker update after accelerated limit reached', async () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

const listener = jest.fn();
poller.start(listener);

for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
jest.runOnlyPendingTimers();
await flushPromises();
}
Expand All @@ -106,16 +144,20 @@ describe('TransactionPoller', () => {
BLOCK_TRACKER_MOCK.on.mock.calls[0][1]();
await flushPromises();

expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX + 2);
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX + 2);
});

it('invokes listener with latest block number from event', async () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

const listener = jest.fn();
poller.start(listener);

for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
jest.runOnlyPendingTimers();
await flushPromises();
}
Expand All @@ -129,7 +171,11 @@ describe('TransactionPoller', () => {

describe('start', () => {
it('does nothing if already started', () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

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

describe('stop', () => {
it('removes timeout', () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

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

it('removes block tracker listener', async () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

const listener = jest.fn();
poller.start(listener);

for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
jest.runOnlyPendingTimers();
await flushPromises();
}

poller.stop();

expect(BLOCK_TRACKER_MOCK.removeListener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX);
expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX);
});

it('does nothing if not started', async () => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

poller.stop();

Expand All @@ -191,7 +249,11 @@ describe('TransactionPoller', () => {
])(
'resets accelerated count if transaction IDs %s',
async (_title, newPendingTransactions) => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

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

poller.setPendingTransactions(newPendingTransactions);

for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
jest.runOnlyPendingTimers();
await flushPromises();
}

expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX + 3);
expect(listener).toHaveBeenCalledTimes(
DEFAULT_ACCELERATED_COUNT_MAX + 3,
);
},
);

Expand All @@ -230,7 +294,11 @@ describe('TransactionPoller', () => {
])(
'resets to accelerated polling if transaction IDs added',
async (_title, newPendingTransactions) => {
const poller = new TransactionPoller(BLOCK_TRACKER_MOCK);
const poller = new TransactionPoller({
blockTracker: BLOCK_TRACKER_MOCK,
messenger: MESSENGER_MOCK,
chainId: CHAIN_ID_MOCK,
});

poller.setPendingTransactions([
createTransactionMetaMock('1'),
Expand All @@ -240,7 +308,7 @@ describe('TransactionPoller', () => {
const listener = jest.fn();
poller.start(listener);

for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
jest.runOnlyPendingTimers();
await flushPromises();
}
Expand All @@ -253,12 +321,14 @@ describe('TransactionPoller', () => {

poller.setPendingTransactions(newPendingTransactions);

for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) {
for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) {
jest.runOnlyPendingTimers();
await flushPromises();
}

expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX * 2 + 2);
expect(listener).toHaveBeenCalledTimes(
DEFAULT_ACCELERATED_COUNT_MAX * 2 + 2,
);
},
);
});
Expand Down
Loading
Loading