Skip to content

Commit e3e10e2

Browse files
authored
Merge pull request #1579 from input-output-hk/fix/sort-initially-fetched-txes
Fix/sort initially fetched txes - LW-12194
2 parents 02543e8 + 72d81c0 commit e3e10e2

File tree

2 files changed

+56
-27
lines changed

2 files changed

+56
-27
lines changed

packages/wallet/src/services/TransactionsTracker.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ const fetchInitialTransactions = async (
224224
if (transactions.length === 0) {
225225
return [];
226226
}
227-
228-
return transactions.slice(0, historicalTransactionsFetchLimit);
227+
// allTransactionsByAddresses returns in `asc` order
228+
return transactions.slice(-1 * historicalTransactionsFetchLimit);
229229
};
230230

231231
const findIntersectionAndUpdateTxStore = ({
@@ -335,11 +335,12 @@ const findIntersectionAndUpdateTxStore = ({
335335
localTx.blockHeader = newTx.blockHeader;
336336
}
337337
}
338-
// Skip overlapping transactions to avoid duplicates
338+
// Skip overlapping transactions to avoid duplicates.
339+
// Limit # of stored transactions to last `historicalTransactionsFetchLimit`
339340
localTransactions = deduplicateSortedArray(
340341
[...localTransactions, ...newTransactions.slice(localTxsFromSameBlock.length)],
341342
txEquals
342-
);
343+
).slice(-1 * historicalTransactionsFetchLimit);
343344
store.setAll(localTransactions);
344345
} else if (rollbackOcurred) {
345346
// This case handles rollbacks without new additions

packages/wallet/test/services/TransactionsTracker.test.ts

+51-23
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,12 @@ const generateRandomLetters = (length: number) => {
5454
return result;
5555
};
5656

57-
5857
const updateTransactionIds = (transactions: Cardano.HydratedTx[]) =>
5958
transactions.map((tx) => ({
6059
...tx,
6160
id: Cardano.TransactionId(`${generateRandomLetters(64)}`)
6261
}));
6362

64-
6563
describe('TransactionsTracker', () => {
6664
const logger = dummyLogger;
6765
const historicalTransactionsFetchLimit = 3;
@@ -97,7 +95,8 @@ describe('TransactionsTracker', () => {
9795
});
9896

9997
it('emits empty array if store is empty and ChainHistoryProvider does not return any transactions', async () => {
100-
chainHistoryProvider.transactionsByAddresses = jest.fn()
98+
chainHistoryProvider.transactionsByAddresses = jest
99+
.fn()
101100
.mockImplementation(() => delay(50).then(() => ({ pageResults: [], totalResultCount: 0 })));
102101
const provider$ = createAddressTransactionsProvider({
103102
addresses$: of(addresses),
@@ -112,27 +111,41 @@ describe('TransactionsTracker', () => {
112111
expect(store.setAll).toBeCalledTimes(0);
113112
});
114113

115-
it('if store is empty, stores and emits transactions resolved by ChainHistoryProvider', async () => {
114+
it('if store is empty, stores and emits last {historicalTransactionsFetchLimit} transactions resolved by ChainHistoryProvider', async () => {
115+
const lowerHistoricalTransactionsFetchLimit = 2;
116+
117+
chainHistoryProvider.transactionsByAddresses = jest.fn().mockImplementation(() =>
118+
delay(50).then(() => ({
119+
...queryTransactionsResult2,
120+
pageResults: [...queryTransactionsResult2.pageResults]
121+
}))
122+
);
116123
const provider$ = createAddressTransactionsProvider({
117124
addresses$: of(addresses),
118125
chainHistoryProvider,
119-
historicalTransactionsFetchLimit,
126+
historicalTransactionsFetchLimit: lowerHistoricalTransactionsFetchLimit,
120127
logger,
121128
retryBackoffConfig,
122129
store,
123130
tipBlockHeight$
124131
}).transactionsSource$;
125-
expect(await firstValueFrom(provider$)).toEqual(queryTransactionsResult.pageResults);
132+
const lastHistoricalTransactionsFetchLimitTransactions = queryTransactionsResult2.pageResults.slice(
133+
-1 * lowerHistoricalTransactionsFetchLimit
134+
);
135+
expect(await firstValueFrom(provider$)).toEqual(lastHistoricalTransactionsFetchLimitTransactions);
126136
expect(store.setAll).toBeCalledTimes(1);
127-
expect(store.setAll).toBeCalledWith(queryTransactionsResult.pageResults);
137+
expect(store.setAll).toBeCalledWith(lastHistoricalTransactionsFetchLimitTransactions);
128138
});
129139

130140
it('emits configured number of latest historical transactions', async () => {
131141
const totalTxsCount = PAGE_SIZE + 5;
132142

133143
const allTransactions = generateTxAlonzo(totalTxsCount);
134-
chainHistoryProvider.transactionsByAddresses = jest.fn().mockImplementation(
135-
(args: TransactionsByAddressesArgs) => filterAndPaginateTransactions(allTransactions, args));
144+
chainHistoryProvider.transactionsByAddresses = jest
145+
.fn()
146+
.mockImplementation((args: TransactionsByAddressesArgs) =>
147+
filterAndPaginateTransactions(allTransactions, args)
148+
);
136149

137150
const provider$ = createAddressTransactionsProvider({
138151
addresses$: of(addresses),
@@ -183,7 +196,8 @@ describe('TransactionsTracker', () => {
183196
await firstValueFrom(store.setAll([txId1, txId2]));
184197

185198
// ChainHistory is shorter by 1 tx: [1]
186-
chainHistoryProvider.transactionsByAddresses = jest.fn()
199+
chainHistoryProvider.transactionsByAddresses = jest
200+
.fn()
187201
// the mismatch will pop the single transaction found in the stored transactions
188202
.mockImplementationOnce(() => delay(50).then(() => ({ pageResults: [], totalResultCount: 0 })))
189203
// intersection is found, chain is shortened
@@ -221,7 +235,8 @@ describe('TransactionsTracker', () => {
221235
await firstValueFrom(store.setAll([txId1, txId2]));
222236

223237
// ChainHistory has one common and one different: [1, 3]
224-
chainHistoryProvider.transactionsByAddresses = jest.fn()
238+
chainHistoryProvider.transactionsByAddresses = jest
239+
.fn()
225240
// the mismatch will pop the single transaction found in the stored transactions
226241
.mockImplementationOnce(() => delay(50).then(() => ({ pageResults: [txId3], totalResultCount: 1 })))
227242
// intersection is found, and stored history is populated with the new transaction
@@ -263,7 +278,8 @@ describe('TransactionsTracker', () => {
263278

264279
it('queries ChainHistoryProvider again with blockRange lower bound from a previous transaction on rollback', async () => {
265280
await firstValueFrom(store.setAll(queryTransactionsResult.pageResults));
266-
chainHistoryProvider.transactionsByAddresses = jest.fn()
281+
chainHistoryProvider.transactionsByAddresses = jest
282+
.fn()
267283
.mockImplementationOnce(() => delay(50).then(() => ({ pageResults: [], totalResultCount: 0 })))
268284
.mockImplementationOnce(() => delay(50).then(() => ({ pageResults: [], totalResultCount: 0 })))
269285
.mockImplementationOnce(() =>
@@ -396,7 +412,10 @@ describe('TransactionsTracker', () => {
396412

397413
it('ignores duplicate transactions', async () => {
398414
// eslint-disable-next-line max-len
399-
const [txId1, txId2, txId3] = updateTransactionsBlockNo(queryTransactionsResult2.pageResults, Cardano.BlockNo(10_050));
415+
const [txId1, txId2, txId3] = updateTransactionsBlockNo(
416+
queryTransactionsResult2.pageResults,
417+
Cardano.BlockNo(10_050)
418+
);
400419

401420
txId1.blockHeader.slot = Cardano.Slot(10_050);
402421
txId2.blockHeader.slot = Cardano.Slot(10_051);
@@ -428,7 +447,7 @@ describe('TransactionsTracker', () => {
428447

429448
expect(await firstValueFrom(provider$.pipe(bufferCount(2)))).toEqual([
430449
[txId1, txId1, txId2], // from store
431-
[txId1, txId2, txId3] // chain history (fixes stored duplicates)
450+
[txId1, txId2, txId3] // chain history (fixes stored duplicates)
432451
]);
433452
expect(rollbacks.length).toBe(0);
434453
expect(store.setAll).toBeCalledTimes(2);
@@ -500,10 +519,19 @@ describe('TransactionsTracker', () => {
500519

501520
await firstValueFrom(store.setAll([txId1, txId2, txId3]));
502521

503-
chainHistoryProvider.transactionsByAddresses = jest.fn().mockImplementation(() => ({
504-
pageResults: [txId3OtherBlock, txId1OtherBlock, txId2OtherBlock],
505-
totalResultCount: 3
506-
}));
522+
chainHistoryProvider.transactionsByAddresses = jest
523+
.fn()
524+
.mockImplementationOnce(() => ({
525+
// asc
526+
pageResults: [txId3OtherBlock, txId1OtherBlock, txId2OtherBlock],
527+
totalResultCount: 3
528+
}))
529+
// detects a rollback and reverts all local transactions (all in the same block)
530+
// fetches from scratch - provider is called with 'desc' order
531+
.mockImplementationOnce(() => ({
532+
pageResults: [txId2OtherBlock, txId1OtherBlock, txId3OtherBlock],
533+
totalResultCount: 3
534+
}));
507535

508536
const { transactionsSource$: provider$, rollback$ } = createAddressTransactionsProvider({
509537
addresses$: of(addresses),
@@ -544,11 +572,11 @@ describe('TransactionsTracker', () => {
544572

545573
await firstValueFrom(store.setAll([txId1, txId2]));
546574

547-
chainHistoryProvider.transactionsByAddresses = jest.fn().mockImplementation(
548-
(args: TransactionsByAddressesArgs) => filterAndPaginateTransactions(
549-
[txId1OtherBlock, txId2OtherBlock, txId3OtherBlock]
550-
, args)
551-
);
575+
chainHistoryProvider.transactionsByAddresses = jest
576+
.fn()
577+
.mockImplementation((args: TransactionsByAddressesArgs) =>
578+
filterAndPaginateTransactions([txId1OtherBlock, txId2OtherBlock, txId3OtherBlock], args)
579+
);
552580

553581
const { transactionsSource$: provider$, rollback$ } = createAddressTransactionsProvider({
554582
addresses$: of(addresses),

0 commit comments

Comments
 (0)