aboutsummaryrefslogtreecommitdiffstats
path: root/packages/0x.js/src/order_watcher/expiration_watcher.ts
blob: 717edaad7e17e2be1388f0a46f2a712464a410d7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import * as _ from 'lodash';
import {BigNumber} from 'bignumber.js';
import {RBTree} from 'bintrees';
import {utils} from '../utils/utils';
import {intervalUtils} from '../utils/interval_utils';
import {SignedOrder, ZeroExError} from '../types';
import {ZeroEx} from '../0x';

const DEFAULT_EXPIRATION_MARGIN_MS = 0;
const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50;

/**
 * This class includes the functionality to detect expired orders.
 * It stores them in a min heap by expiration time and checks for expired ones every `orderExpirationCheckingIntervalMs`
 */
export class ExpirationWatcher {
    private orderHashByExpirationRBTree: RBTree<string>;
    private expiration: {[orderHash: string]: BigNumber} = {};
    private orderExpirationCheckingIntervalMs: number;
    private expirationMarginMs: number;
    private orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer;
    constructor(expirationMarginIfExistsMs?: number,
                orderExpirationCheckingIntervalIfExistsMs?: number) {
        this.expirationMarginMs = expirationMarginIfExistsMs ||
                                  DEFAULT_EXPIRATION_MARGIN_MS;
        this.orderExpirationCheckingIntervalMs = expirationMarginIfExistsMs ||
                                                 DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS;
        const scoreFunction = (orderHash: string) => this.expiration[orderHash].toNumber();
        const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs);
        this.orderHashByExpirationRBTree = new RBTree(comparator);
    }
    public subscribe(callbackAsync: (orderHash: string) => Promise<void>): void {
        if (!_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists)) {
            throw new Error(ZeroExError.SubscriptionAlreadyPresent);
        }
        this.orderExpirationCheckingIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
            this.pruneExpiredOrdersAsync.bind(this, callbackAsync), this.orderExpirationCheckingIntervalMs,
        );
    }
    public unsubscribe(): void {
        if (_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists)) {
            throw new Error(ZeroExError.SubscriptionNotFound);
        }
        intervalUtils.clearAsyncExcludingInterval(this.orderExpirationCheckingIntervalIdIfExists);
        delete this.orderExpirationCheckingIntervalIdIfExists;
    }
    public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void {
        this.expiration[orderHash] = expirationUnixTimestampMs;
        this.orderHashByExpirationRBTree.insert(orderHash);
    }
    public removeOrder(orderHash: string): void {
        this.orderHashByExpirationRBTree.remove(orderHash);
        delete this.expiration[orderHash];
    }
    private async pruneExpiredOrdersAsync(callbackAsync: (orderHash: string) => Promise<void>): Promise<void> {
        const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs();
        while (true) {
            const hasTrakedOrders = this.orderHashByExpirationRBTree.size === 0;
            if (hasTrakedOrders) {
                break;
            }
            const nextOrderHashToExpire = this.orderHashByExpirationRBTree.min();
            const hasNoExpiredOrders = this.expiration[nextOrderHashToExpire].greaterThan(
                currentUnixTimestampMs.plus(this.expirationMarginMs),
            );
            const isSubscriptionActive = _.isUndefined(this.orderExpirationCheckingIntervalIdIfExists);
            if (hasNoExpiredOrders || isSubscriptionActive) {
                break;
            }
            const orderHash = this.orderHashByExpirationRBTree.min();
            this.orderHashByExpirationRBTree.remove(orderHash);
            delete this.expiration[orderHash];
            await callbackAsync(orderHash);
        }
    }
}