Skip to content

Commit b272ea9

Browse files
committed
fix(wallet): one ledger-tip req per pollInterval
WHAT: replaced `delay` operator with `auditTime` operator, which starts a timer once the first isSettled$ emission, and emits the last value, ignoring previous ones. WHY: isSettled$ observable emissions triggered ledger-tip requests, delayed by pollingInterval. Flapping isSettled$ caused multiple requests to be done, because the flapping was delayed. Throttling is in place for the case where the ledger-tip request is not resolved yet.
1 parent b760734 commit b272ea9

File tree

2 files changed

+52
-6
lines changed

2 files changed

+52
-6
lines changed

packages/wallet/src/services/TipTracker.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import {
55
EMPTY,
66
Observable,
77
Subject,
8+
auditTime,
89
combineLatest,
910
concat,
10-
delay,
1111
distinctUntilChanged,
1212
exhaustMap,
1313
filter,
@@ -56,17 +56,19 @@ export class TipTracker extends PersistentDocumentTrackerSubject<Cardano.Tip> {
5656
// - if it's not settled for maxPollInterval
5757
combineLatest([
5858
triggerOrInterval$(syncStatus.isSettled$.pipe(filter((isSettled) => isSettled)), maxPollInterval).pipe(
59-
// trigger fetch after some delay once fully synced and online
60-
delay(minPollInterval),
59+
// Do not allow more than one fetch per minPollInterval.
60+
// The first syncStatus starts a minPollInterval timer. Only the latest emission from the minPollInterval
61+
// is emitted in case the syncStatus changes multiple times.
62+
auditTime(minPollInterval),
6163
// trigger fetch on start
6264
startWith(null)
6365
),
6466
connectionStatus$
6567
]).pipe(
66-
// Throttle syncing by interval, cancel ongoing request on external trigger
6768
tap(([, connectionStatus]) => {
6869
logger.debug(connectionStatus === ConnectionStatus.down ? 'Skipping fetch tip' : 'Fetching tip...');
6970
}),
71+
// Throttle syncing by interval, cancel ongoing request on external trigger
7072
exhaustMap(([, connectionStatus]) =>
7173
connectionStatus === ConnectionStatus.down
7274
? EMPTY

packages/wallet/test/services/TipTracker.test.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Cardano } from '@cardano-sdk/core';
22
import { ConnectionStatus, TipTracker } from '../../src/services';
33
import { InMemoryDocumentStore } from '../../src/persistence';
44
import { Milliseconds, SyncStatus } from '../../src';
5-
import { Observable, firstValueFrom, of } from 'rxjs';
5+
import { NEVER, Observable, firstValueFrom, of, take, takeUntil, timer } from 'rxjs';
66
import { createStubObservable, createTestScheduler } from '@cardano-sdk/util-dev';
77
import { dummyLogger } from 'ts-log';
88

@@ -15,6 +15,8 @@ const mockTips = {
1515
y: { hash: 'hy' }
1616
} as unknown as Record<string, Cardano.Tip>;
1717

18+
const trueFalse = { f: false, t: true };
19+
1820
describe('TipTracker', () => {
1921
const pollInterval: Milliseconds = 1; // delays emission after trigger
2022
let store: InMemoryDocumentStore<Cardano.Tip>;
@@ -26,6 +28,48 @@ describe('TipTracker', () => {
2628
connectionStatus$ = of(ConnectionStatus.up);
2729
});
2830

31+
it('calls the provider as soon as subscribed', () => {
32+
createTestScheduler().run(({ cold, expectObservable }) => {
33+
const provider$ = createStubObservable<Cardano.Tip>(cold('a|', mockTips));
34+
const tracker$ = new TipTracker({
35+
connectionStatus$,
36+
logger,
37+
maxPollInterval: Number.MAX_VALUE,
38+
minPollInterval: pollInterval,
39+
provider$,
40+
store,
41+
syncStatus: { isSettled$: NEVER } as unknown as SyncStatus
42+
});
43+
expectObservable(tracker$.asObservable().pipe(take(1))).toBe('(a|)', mockTips);
44+
});
45+
});
46+
47+
it('LW-11686 ignores multiple syncStatus emissions during pollInterval', () => {
48+
const poll: Milliseconds = 3;
49+
const sync = '-ttt-t----|';
50+
const tipT = 'a---b---c-|';
51+
// a-b--c-d|
52+
createTestScheduler().run(({ cold, expectObservable }) => {
53+
const syncStatus: Partial<SyncStatus> = { isSettled$: cold(sync, trueFalse) };
54+
const provider$ = createStubObservable<Cardano.Tip>(
55+
cold('(a|)', mockTips),
56+
cold('(b|)', mockTips),
57+
cold('(c|)', mockTips),
58+
cold('(d|)', mockTips)
59+
);
60+
const tracker$ = new TipTracker({
61+
connectionStatus$,
62+
logger,
63+
maxPollInterval: Number.MAX_VALUE,
64+
minPollInterval: poll,
65+
provider$,
66+
store,
67+
syncStatus: syncStatus as SyncStatus
68+
});
69+
expectObservable(tracker$.asObservable().pipe(takeUntil(timer(10)))).toBe(tipT, mockTips);
70+
});
71+
});
72+
2973
it('calls the provider immediately, only emitting distinct values, with throttling', () => {
3074
createTestScheduler().run(({ cold, expectObservable }) => {
3175
const syncStatus: Partial<SyncStatus> = { isSettled$: cold('---a---bc--d|') };
@@ -48,7 +92,7 @@ describe('TipTracker', () => {
4892
});
4993
});
5094

51-
it('starting offline, then coming online should subscribe to provider immediatelly for initial fetch', () => {
95+
it('starting offline, then coming online should subscribe to provider immediately for initial fetch', () => {
5296
createTestScheduler().run(({ cold, hot, expectObservable, expectSubscriptions }) => {
5397
const connectionStatusOffOn$ = hot('d--u----|', {
5498
d: ConnectionStatus.down,

0 commit comments

Comments
 (0)