Skip to content

Commit c380575

Browse files
authored
Merge pull request #1535 from input-output-hk/fix/transaction-tracker-process-transactions-in-correct-order
fix(wallet): transaction tracker now process transaction in chronological order
2 parents 0492223 + 40b87ce commit c380575

File tree

2 files changed

+72
-9
lines changed

2 files changed

+72
-9
lines changed

packages/wallet/src/services/TransactionsTracker.ts

+17-6
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ export interface TransactionsTrackerInternalsProps {
7777
// Temporarily hardcoded. Will be replaced with ChainHistoryProvider 'maxPageSize' value once ADP-2249 is implemented
7878
export const PAGE_SIZE = 25;
7979

80+
/**
81+
* Sorts the given HydratedTx by slot.
82+
*
83+
* @param lhs The left-hand side of the comparison operation.
84+
* @param rhs The left-hand side of the comparison operation.
85+
*/
86+
const sortTxBySlot = (lhs: Cardano.HydratedTx, rhs: Cardano.HydratedTx) => lhs.blockHeader.slot - rhs.blockHeader.slot;
87+
8088
const allTransactionsByAddresses = async (
8189
chainHistoryProvider: ChainHistoryProvider,
8290
{ addresses, blockRange }: { addresses: Cardano.PaymentAddress[]; blockRange: Range<Cardano.BlockNo> }
@@ -102,7 +110,7 @@ const allTransactionsByAddresses = async (
102110
} while (pageResults.length === PAGE_SIZE);
103111
}
104112

105-
return response;
113+
return response.sort(sortTxBySlot);
106114
};
107115

108116
const getLastTransactionsAtBlock = (
@@ -120,7 +128,9 @@ const getLastTransactionsAtBlock = (
120128
}
121129
}
122130

123-
return txsFromSameBlock;
131+
// Since we are traversing the array in reverse to find the transactions for this block,
132+
// we must reverse the result.
133+
return txsFromSameBlock.reverse();
124134
};
125135

126136
export const revertLastBlock = (
@@ -176,7 +186,7 @@ const findIntersectionAndUpdateTxStore = ({
176186
combinator: exhaustMap,
177187
equals: transactionsEquals,
178188
onFatalError,
179-
// eslint-disable-next-line sonarjs/cognitive-complexity
189+
// eslint-disable-next-line sonarjs/cognitive-complexity,complexity
180190
provider: async () => {
181191
let rollbackOcurred = false;
182192
// eslint-disable-next-line no-constant-condition
@@ -219,12 +229,13 @@ const findIntersectionAndUpdateTxStore = ({
219229

220230
const localTxsFromSameBlock = getLastTransactionsAtBlock(localTransactions, lowerBound);
221231
const firstSegmentOfNewTransactions = newTransactions.slice(0, localTxsFromSameBlock.length);
232+
const hasSameLength = localTxsFromSameBlock.length === firstSegmentOfNewTransactions.length;
222233

223234
// The first segment of new transaction should match exactly (same txs and same order) our last know TXs. Otherwise
224235
// roll them back and re-apply in new order.
225-
const sameTxAndOrder = localTxsFromSameBlock.every(
226-
(tx, index) => tx.id === firstSegmentOfNewTransactions[index].id
227-
);
236+
const sameTxAndOrder =
237+
hasSameLength &&
238+
localTxsFromSameBlock.every((tx, index) => tx.id === firstSegmentOfNewTransactions[index].id);
228239

229240
if (!sameTxAndOrder) {
230241
localTransactions = revertLastBlock(localTransactions, lowerBound, rollback$, newTransactions, logger);

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

+55-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const { generateTxAlonzo, mockChainHistoryProvider, queryTransactionsResult, que
3131
const updateTransactionsBlockNo = (transactions: Cardano.HydratedTx[], blockNo = Cardano.BlockNo(10_050)) =>
3232
transactions.map((tx) => ({
3333
...tx,
34-
blockHeader: { ...tx.blockHeader, blockNo }
34+
blockHeader: { ...tx.blockHeader, blockNo, slot: Cardano.Slot(0) }
3535
}));
3636

3737
const updateTransactionIds = (transactions: Cardano.HydratedTx[], tailPattern = 'aaa') =>
@@ -343,6 +343,13 @@ describe('TransactionsTracker', () => {
343343
Cardano.BlockNo(10_051)
344344
);
345345

346+
txId1.blockHeader.slot = Cardano.Slot(10_050);
347+
txId2.blockHeader.slot = Cardano.Slot(10_051);
348+
txId3.blockHeader.slot = Cardano.Slot(10_052);
349+
350+
txId1OtherBlock.blockHeader.slot = Cardano.Slot(10_050);
351+
txId2OtherBlock.blockHeader.slot = Cardano.Slot(10_051);
352+
346353
await firstValueFrom(store.setAll([txId1, txId2]));
347354

348355
chainHistoryProvider.transactionsByAddresses = jest.fn(() => ({
@@ -364,11 +371,11 @@ describe('TransactionsTracker', () => {
364371

365372
expect(await firstValueFrom(provider$.pipe(bufferCount(2)))).toEqual([
366373
[txId1, txId2], // from store
367-
[txId1OtherBlock, txId2OtherBlock, txId3] // chain history
374+
[txId1, txId2, txId3] // chain history
368375
]);
369376
expect(rollbacks.length).toBe(0);
370377
expect(store.setAll).toBeCalledTimes(2);
371-
expect(store.setAll).nthCalledWith(2, [txId1OtherBlock, txId2OtherBlock, txId3]);
378+
expect(store.setAll).nthCalledWith(2, [txId1, txId2, txId3]);
372379
});
373380

374381
// latestStoredBlock <1 2 3>
@@ -380,8 +387,16 @@ describe('TransactionsTracker', () => {
380387
queryTransactionsResult2.pageResults,
381388
Cardano.BlockNo(10_050)
382389
);
390+
391+
txId1.blockHeader.slot = Cardano.Slot(10_050);
392+
txId2.blockHeader.slot = Cardano.Slot(10_051);
393+
txId3.blockHeader.slot = Cardano.Slot(10_052);
394+
383395
const [txId1OtherBlock, txId2OtherBlock] = updateTransactionsBlockNo([txId1, txId2], Cardano.BlockNo(10_051));
384396

397+
txId1OtherBlock.blockHeader.slot = Cardano.Slot(10_051);
398+
txId2OtherBlock.blockHeader.slot = Cardano.Slot(10_052);
399+
385400
await firstValueFrom(store.setAll([txId1, txId2, txId3]));
386401

387402
chainHistoryProvider.transactionsByAddresses = jest.fn(() => ({
@@ -496,6 +511,43 @@ describe('TransactionsTracker', () => {
496511
expect(store.setAll).nthCalledWith(2, [txId3OtherBlock, txId2OtherBlock, txId1OtherBlock]);
497512
});
498513

514+
it('process transactions in the right order (sorted by slot ASC) regardless of transaction order in the backend response', async () => {
515+
const [txId1, txId2, txId3] = updateTransactionsBlockNo(
516+
queryTransactionsResult2.pageResults,
517+
Cardano.BlockNo(10_000)
518+
);
519+
520+
txId1.blockHeader.slot = Cardano.Slot(10_000);
521+
txId2.blockHeader.slot = Cardano.Slot(10_001);
522+
txId3.blockHeader.slot = Cardano.Slot(10_002);
523+
524+
await firstValueFrom(store.setAll([txId1, txId2, txId3]));
525+
526+
chainHistoryProvider.transactionsByAddresses = jest.fn(() => ({
527+
pageResults: [txId3, txId2, txId1],
528+
totalResultCount: 3
529+
}));
530+
531+
const { transactionsSource$: provider$, rollback$ } = createAddressTransactionsProvider({
532+
addresses$: of(addresses),
533+
chainHistoryProvider,
534+
logger,
535+
retryBackoffConfig,
536+
store,
537+
tipBlockHeight$
538+
});
539+
540+
const rollbacks: Cardano.HydratedTx[] = [];
541+
rollback$.subscribe((tx) => rollbacks.push(tx));
542+
543+
expect(await firstValueFrom(provider$.pipe(bufferCount(1)))).toEqual([
544+
[txId1, txId2, txId3] // chain history
545+
]);
546+
expect(rollbacks.length).toBe(0);
547+
expect(store.setAll).toBeCalledTimes(1);
548+
expect(store.setAll).nthCalledWith(1, [txId1, txId2, txId3]);
549+
});
550+
499551
// latestStoredBlock <1 2 3>
500552
// newBlock <1 2 3>
501553
// rollback$ none

0 commit comments

Comments
 (0)