aboutsummaryrefslogtreecommitdiffstats
path: root/packages/0x.js/src
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2017-11-23 04:22:22 +0800
committerFabio Berger <me@fabioberger.com>2017-11-23 04:22:22 +0800
commitf74408f390199b239ce707ef6db3773045c9863d (patch)
treea87fb0f5312dad68b816c597eeb58d411e97bbcd /packages/0x.js/src
parentf5e0fd8de5a3201ac17a8dde68aec3314674f1c4 (diff)
parent0e03ef385b03f5b2f916f38001553b1c1c18c383 (diff)
downloaddexon-sol-tools-f74408f390199b239ce707ef6db3773045c9863d.tar
dexon-sol-tools-f74408f390199b239ce707ef6db3773045c9863d.tar.gz
dexon-sol-tools-f74408f390199b239ce707ef6db3773045c9863d.tar.bz2
dexon-sol-tools-f74408f390199b239ce707ef6db3773045c9863d.tar.lz
dexon-sol-tools-f74408f390199b239ce707ef6db3773045c9863d.tar.xz
dexon-sol-tools-f74408f390199b239ce707ef6db3773045c9863d.tar.zst
dexon-sol-tools-f74408f390199b239ce707ef6db3773045c9863d.zip
Merge branch 'development' into addWebsite
* development: (33 commits) Last renames Refactor while condition Fix tests Fix tests Fix a typo Fix test:circleci command Check if transactionReceipt exists before normalizing it Address nits Remove old comment Fix async callbacks Check if callback exists Rename Pass callback down Remove custom heap and use bintrees Add expirationMarginMs Add defaults Fix typos Rename orderLifetime to orderLifetimeS Reference types directly Add ifExists suffix ...
Diffstat (limited to 'packages/0x.js/src')
-rw-r--r--packages/0x.js/src/order_watcher/event_watcher.ts10
-rw-r--r--packages/0x.js/src/order_watcher/expiration_watcher.ts76
-rw-r--r--packages/0x.js/src/order_watcher/order_state_watcher.ts37
-rw-r--r--packages/0x.js/src/types.ts7
-rw-r--r--packages/0x.js/src/utils/order_validation_utils.ts4
-rw-r--r--packages/0x.js/src/utils/utils.ts7
-rw-r--r--packages/0x.js/src/web3_wrapper.ts4
7 files changed, 130 insertions, 15 deletions
diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts
index c39431f6d..ecbab0cd5 100644
--- a/packages/0x.js/src/order_watcher/event_watcher.ts
+++ b/packages/0x.js/src/order_watcher/event_watcher.ts
@@ -12,7 +12,7 @@ import {intervalUtils} from '../utils/interval_utils';
import {assert} from '../utils/assert';
import {utils} from '../utils/utils';
-const DEFAULT_EVENT_POLLING_INTERVAL = 200;
+const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200;
enum LogEventState {
Removed,
@@ -28,11 +28,11 @@ export class EventWatcher {
private _pollingIntervalMs: number;
private _intervalIdIfExists?: NodeJS.Timer;
private _lastEvents: Web3.LogEntry[] = [];
- constructor(web3Wrapper: Web3Wrapper, pollingIntervalMs: undefined|number) {
+ constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined|number) {
this._web3Wrapper = web3Wrapper;
- this._pollingIntervalMs = _.isUndefined(pollingIntervalMs) ?
- DEFAULT_EVENT_POLLING_INTERVAL :
- pollingIntervalMs;
+ this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs) ?
+ DEFAULT_EVENT_POLLING_INTERVAL_MS :
+ pollingIntervalIfExistsMs;
}
public subscribe(callback: EventWatcherCallback): void {
assert.isFunction('callback', callback);
diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts
new file mode 100644
index 000000000..717edaad7
--- /dev/null
+++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts
@@ -0,0 +1,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);
+ }
+ }
+}
diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts
index 33fa69b1c..fd7496699 100644
--- a/packages/0x.js/src/order_watcher/order_state_watcher.ts
+++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts
@@ -6,6 +6,7 @@ import {assert} from '../utils/assert';
import {utils} from '../utils/utils';
import {artifacts} from '../artifacts';
import {AbiDecoder} from '../utils/abi_decoder';
+import {intervalUtils} from '../utils/interval_utils';
import {OrderStateUtils} from '../utils/order_state_utils';
import {
LogEvent,
@@ -24,14 +25,14 @@ import {
ExchangeEvents,
TokenEvents,
ZeroExError,
+ ExchangeContractErrs,
} from '../types';
import {Web3Wrapper} from '../web3_wrapper';
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
import {OrderFilledCancelledLazyStore} from '../stores/order_filled_cancelled_lazy_store';
import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';
-
-const DEFAULT_NUM_CONFIRMATIONS = 0;
+import {ExpirationWatcher} from './expiration_watcher';
interface DependentOrderHashes {
[makerAddress: string]: {
@@ -56,6 +57,7 @@ export class OrderStateWatcher {
private _eventWatcher: EventWatcher;
private _web3Wrapper: Web3Wrapper;
private _abiDecoder: AbiDecoder;
+ private _expirationWatcher: ExpirationWatcher;
private _orderStateUtils: OrderStateUtils;
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
@@ -65,13 +67,22 @@ export class OrderStateWatcher {
) {
this._abiDecoder = abiDecoder;
this._web3Wrapper = web3Wrapper;
- const eventPollingIntervalMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
- this._eventWatcher = new EventWatcher(web3Wrapper, eventPollingIntervalMs);
+ const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
+ this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs);
this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(token);
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange);
this._orderStateUtils = new OrderStateUtils(
this._balanceAndProxyAllowanceLazyStore, this._orderFilledCancelledLazyStore,
);
+ const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) ?
+ undefined :
+ config.orderExpirationCheckingIntervalMs;
+ const expirationMarginIfExistsMs = _.isUndefined(config) ?
+ undefined :
+ config.expirationMarginMs;
+ this._expirationWatcher = new ExpirationWatcher(
+ expirationMarginIfExistsMs, orderExpirationCheckingIntervalMsIfExists,
+ );
}
/**
* Add an order to the orderStateWatcher. Before the order is added, it's
@@ -84,6 +95,8 @@ export class OrderStateWatcher {
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
this._orderByOrderHash[orderHash] = signedOrder;
await this.addToDependentOrderHashesAsync(signedOrder, orderHash);
+ const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000);
+ this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs);
}
/**
* Removes an order from the orderStateWatcher
@@ -100,6 +113,7 @@ export class OrderStateWatcher {
const zrxTokenAddress = await exchange.getZRXTokenAddressAsync();
this.removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash);
this.removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash);
+ this._expirationWatcher.removeOrder(orderHash);
}
/**
* Starts an orderStateWatcher subscription. The callback will be called every time a watched order's
@@ -114,6 +128,7 @@ export class OrderStateWatcher {
}
this._callbackIfExists = callback;
this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this));
+ this._expirationWatcher.subscribe(this._onOrderExpiredAsync.bind(this));
}
/**
* Ends an orderStateWatcher subscription.
@@ -126,6 +141,20 @@ export class OrderStateWatcher {
this._orderFilledCancelledLazyStore.deleteAll();
delete this._callbackIfExists;
this._eventWatcher.unsubscribe();
+ this._expirationWatcher.unsubscribe();
+ }
+ private async _onOrderExpiredAsync(orderHash: string): Promise<void> {
+ const orderState: OrderState = {
+ isValid: false,
+ orderHash,
+ error: ExchangeContractErrs.OrderFillExpired,
+ };
+ if (!_.isUndefined(this._orderByOrderHash[orderHash])) {
+ await this.removeOrderAsync(orderHash);
+ if (!_.isUndefined(this._callbackIfExists)) {
+ this._callbackIfExists(orderState);
+ }
+ }
}
private async _onEventWatcherCallbackAsync(log: LogEvent): Promise<void> {
const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log);
diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts
index 39e5fa9f2..c3aabfd86 100644
--- a/packages/0x.js/src/types.ts
+++ b/packages/0x.js/src/types.ts
@@ -392,10 +392,15 @@ export interface JSONRPCPayload {
}
/*
- * eventPollingIntervalMs: How often to poll the Ethereum node for new events
+ * orderExpirationCheckingIntervalMs: How often to check for expired orders. Default: 50
+ * eventPollingIntervalMs: How often to poll the Ethereum node for new events. Defaults: 200
+ * expirationMarginMs: Amount of time before order expiry that you'd like to be notified
+ * of an orders expiration. Defaults: 0
*/
export interface OrderStateWatcherConfig {
+ orderExpirationCheckingIntervalMs?: number;
eventPollingIntervalMs?: number;
+ expirationMarginMs?: number;
}
/*
diff --git a/packages/0x.js/src/utils/order_validation_utils.ts b/packages/0x.js/src/utils/order_validation_utils.ts
index f03703c4e..ed723e3d4 100644
--- a/packages/0x.js/src/utils/order_validation_utils.ts
+++ b/packages/0x.js/src/utils/order_validation_utils.ts
@@ -102,7 +102,7 @@ export class OrderValidationUtils {
if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
}
- const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderCancelExpired);
}
@@ -150,7 +150,7 @@ export class OrderValidationUtils {
}
}
private validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) {
- const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderFillExpired);
}
diff --git a/packages/0x.js/src/utils/utils.ts b/packages/0x.js/src/utils/utils.ts
index 280f3e979..5370c3b4b 100644
--- a/packages/0x.js/src/utils/utils.ts
+++ b/packages/0x.js/src/utils/utils.ts
@@ -49,7 +49,10 @@ export const utils = {
const hashHex = ethUtil.bufferToHex(hashBuff);
return hashHex;
},
- getCurrentUnixTimestamp(): BigNumber {
- return new BigNumber(Date.now() / 1000);
+ getCurrentUnixTimestampSec(): BigNumber {
+ return new BigNumber(Date.now() / 1000).round();
+ },
+ getCurrentUnixTimestampMs(): BigNumber {
+ return new BigNumber(Date.now());
},
};
diff --git a/packages/0x.js/src/web3_wrapper.ts b/packages/0x.js/src/web3_wrapper.ts
index f6b6ca09a..7bd8ea093 100644
--- a/packages/0x.js/src/web3_wrapper.ts
+++ b/packages/0x.js/src/web3_wrapper.ts
@@ -50,7 +50,9 @@ export class Web3Wrapper {
}
public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
const transactionReceipt = await promisify(this.web3.eth.getTransactionReceipt)(txHash);
- transactionReceipt.status = this.normalizeTxReceiptStatus(transactionReceipt.status);
+ if (!_.isNull(transactionReceipt)) {
+ transactionReceipt.status = this.normalizeTxReceiptStatus(transactionReceipt.status);
+ }
return transactionReceipt;
}
public getCurrentProvider(): Web3.Provider {