From 88d020f9f2e78a1df76e93aa4d190100414c73cb Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 15 Nov 2017 14:54:56 -0600 Subject: Add initial implementation of expiration watcher --- .../0x.js/src/order_watcher/expiration_watcher.ts | 58 ++++++++++++++ .../0x.js/src/order_watcher/order_state_watcher.ts | 25 +++++- packages/0x.js/src/types.ts | 2 + packages/0x.js/src/utils/heap.ts | 90 ++++++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 packages/0x.js/src/order_watcher/expiration_watcher.ts create mode 100644 packages/0x.js/src/utils/heap.ts 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..1a9e9a418 --- /dev/null +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -0,0 +1,58 @@ +import * as _ from 'lodash'; +import {BigNumber} from 'bignumber.js'; +import {utils} from '../utils/utils'; +import {intervalUtils} from '../utils/interval_utils'; +import {SignedOrder} from '../types'; +import {Heap} from '../utils/heap'; +import {ZeroEx} from '../0x'; + +// Order prunning is very fast +const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; + +/** + * This class includes all the functionality related to prunning expired orders + */ +export class ExpirationWatcher { + private orderHashHeapByExpiration: Heap; + private expiration: {[orderHash: string]: BigNumber}; + private callbackIfExists?: (orderHash: string) => void; + private orderExpirationCheckingIntervalMs: number; + private orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; + constructor(orderExpirationCheckingIntervalMs?: number) { + this.orderExpirationCheckingIntervalMs = orderExpirationCheckingIntervalMs || + DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; + this.expiration = {}; + const scoreFunction = ((orderHash: string) => { + return this.expiration[orderHash].toNumber(); + }).bind(this); + this.orderHashHeapByExpiration = new Heap(scoreFunction); + } + public subscribe(callback: (orderHash: string) => void): void { + this.callbackIfExists = callback; + this.orderExpirationCheckingIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( + this.pruneExpiredOrders.bind(this), this.orderExpirationCheckingIntervalMs, + ); + } + public unsubscribe(): void { + intervalUtils.clearAsyncExcludingInterval(this.orderExpirationCheckingIntervalIdIfExists as NodeJS.Timer); + delete this.callbackIfExists; + } + public addOrder(orderHash: string, expirationUnixTimestampSec: BigNumber): void { + this.expiration[orderHash] = expirationUnixTimestampSec; + // We don't remove hashes on order remove because it's slow (linear). + // We just skip them later if the order was already removed from the order watcher. + this.orderHashHeapByExpiration.push(orderHash); + } + private pruneExpiredOrders(): void { + const currentUnixTimestampSec = utils.getCurrentUnixTimestamp(); + while ( + this.orderHashHeapByExpiration.size() !== 0 && + this.expiration[this.orderHashHeapByExpiration.head()].lessThan(currentUnixTimestampSec) && + !_.isUndefined(this.callbackIfExists) + ) { + const orderHash = this.orderHashHeapByExpiration.pop(); + delete this.expiration[orderHash]; + this.callbackIfExists(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 2b9d7997e..f21ad0bbe 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; @@ -72,6 +74,10 @@ export class OrderStateWatcher { this._orderStateUtils = new OrderStateUtils( this._balanceAndProxyAllowanceLazyStore, this._orderFilledCancelledLazyStore, ); + const orderExpirationCheckingIntervalMs = _.isUndefined(config) ? + undefined : + config.orderExpirationCheckingIntervalMs; + this._expirationWatcher = new ExpirationWatcher(orderExpirationCheckingIntervalMs); } /** * Add an order to the orderStateWatcher. Before the order is added, it's @@ -84,6 +90,8 @@ export class OrderStateWatcher { assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); this._orderByOrderHash[orderHash] = signedOrder; this.addToDependentOrderHashes(signedOrder, orderHash); + // We don't remove orders from expirationWatcher because heap removal is linear. We just skip it later + this._expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); } /** * Removes an order from the orderStateWatcher @@ -111,6 +119,7 @@ export class OrderStateWatcher { } this._callbackIfExistsAsync = callback; this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this)); + this._expirationWatcher.subscribe(this._onOrderExpired.bind(this)); } /** * Ends an orderStateWatcher subscription. @@ -123,6 +132,18 @@ export class OrderStateWatcher { this._orderFilledCancelledLazyStore.deleteAll(); delete this._callbackIfExistsAsync; this._eventWatcher.unsubscribe(); + this._expirationWatcher.unsubscribe(); + } + private _onOrderExpired(orderHash: string): void { + const orderState: OrderState = { + isValid: false, + orderHash, + error: ExchangeContractErrs.OrderFillExpired, + }; + if (!_.isUndefined(this._orderByOrderHash[orderHash])) { + // We need this check because we never remove the orders from expiration watcher + (this._callbackIfExistsAsync as OnOrderStateChangeCallback)(orderState); + } } private async _onEventWatcherCallbackAsync(log: LogEvent): Promise { const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log); diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 11b5d8569..45b49e148 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -397,9 +397,11 @@ export interface JSONRPCPayload { } /* + * orderExpirationCheckingIntervalMs: How often to check for expired orders * eventPollingIntervalMs: How often to poll the Ethereum node for new events */ export interface OrderStateWatcherConfig { + orderExpirationCheckingIntervalMs?: number; eventPollingIntervalMs?: number; } diff --git a/packages/0x.js/src/utils/heap.ts b/packages/0x.js/src/utils/heap.ts new file mode 100644 index 000000000..aaa17e719 --- /dev/null +++ b/packages/0x.js/src/utils/heap.ts @@ -0,0 +1,90 @@ +// Based on Original JavaScript Code from Marijn Haverbeke (http://eloquentjavascript.net/1st_edition/appendix2.html) +export class Heap { + private content: T[]; + private scoreFunction: (x: T) => number; + constructor(scoreFunction: (x: T) => number) { + this.content = []; + this.scoreFunction = scoreFunction; + } + public push(element: T) { + this.content.push(element); + this.bubbleUp(this.content.length - 1); + } + public size(): number { + const size = this.content.length; + return size; + } + public head(): T { + const head = this.content[0]; + return head; + } + public pop(): T { + const head = this.content[0]; + const end = this.content.pop(); + if (this.content.length > 0) { + this.content[0] = end as T; + this.sinkDown(0); + } + return head; + } + private bubbleUp(n: number) { + // Fetch the element that has to be moved. + const element = this.content[n]; + const score = this.scoreFunction(element); + // When at 0, an element can not go up any further. + while (n > 0) { + // Compute the parent element's index, and fetch it. + const parentN = Math.floor((n + 1) / 2) - 1; + const parent = this.content[parentN]; + // If the parent has a lesser score, things are in order and we + // are done. + if (score >= this.scoreFunction(parent)) { + break; + } + + // Otherwise, swap the parent with the current element and + // continue. + this.content[parentN] = element; + this.content[n] = parent; + n = parentN; + } + } + + private sinkDown(n: number) { + // Look up the target element and its score. + const length = this.content.length; + const element = this.content[n]; + const elemScore = this.scoreFunction(element); + + while (true) { + // Compute the indices of the child elements. + const child2N = (n + 1) * 2; + const child1N = child2N - 1; + // This is used to store the new position of the element, if any. + let swap = n; + let child1Score; + let child2Score; + // If the first child exists (is inside the array)... + if (child1N < length) { + // Look it up and compute its score. + const child1 = this.content[child1N]; + child1Score = this.scoreFunction(child1); + // If the score is less than our element's, we need to swap. + if (child1Score < elemScore) { + swap = child1N; + } + // Do the same checks for the other child. + if (child2N < length) { + const child2 = this.content[child2N]; + child2Score = this.scoreFunction(child2); + if (child2Score < (swap == null ? elemScore : child1Score)) { + swap = child2N; + } + } + } + this.content[n] = this.content[swap]; + this.content[swap] = element; + n = swap; + } + } +} -- cgit v1.2.3 From b7585318c7015dd81516f498aa59b86ec2ee5671 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 17 Nov 2017 13:02:17 -0600 Subject: Fix heap implementation --- packages/0x.js/src/utils/heap.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/0x.js/src/utils/heap.ts b/packages/0x.js/src/utils/heap.ts index aaa17e719..1135c76b9 100644 --- a/packages/0x.js/src/utils/heap.ts +++ b/packages/0x.js/src/utils/heap.ts @@ -82,6 +82,9 @@ export class Heap { } } } + if (swap === n) { + break; + } this.content[n] = this.content[swap]; this.content[swap] = element; n = swap; -- cgit v1.2.3 From 34926eb66ae6ea105f7f2e029c65c4d44025a655 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 17 Nov 2017 13:02:37 -0600 Subject: Add tests for expirationWatcher --- packages/0x.js/test/expiration_watcher_test.ts | 137 +++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 packages/0x.js/test/expiration_watcher_test.ts diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts new file mode 100644 index 000000000..47071416e --- /dev/null +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -0,0 +1,137 @@ +import 'mocha'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import * as Sinon from 'sinon'; +import * as Web3 from 'web3'; +import BigNumber from 'bignumber.js'; +import {chaiSetup} from './utils/chai_setup'; +import {web3Factory} from './utils/web3_factory'; +import {utils} from '../src/utils/utils'; +import {Web3Wrapper} from '../src/web3_wrapper'; +import {TokenUtils} from './utils/token_utils'; +import {ExpirationWatcher} from '../src/order_watcher/expiration_watcher'; +import { + ZeroEx, + Token, +} from '../src'; +import {FillScenarios} from './utils/fill_scenarios'; +import {DoneCallback} from '../src/types'; +import {reportCallbackErrors} from './utils/report_callback_errors'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('ExpirationWatcher', () => { + let web3: Web3; + let zeroEx: ZeroEx; + let tokenUtils: TokenUtils; + let tokens: Token[]; + let userAddresses: string[]; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const fillableAmount = new BigNumber(5); + let currentUnixTimestampSec: BigNumber; + let timer: Sinon.SinonFakeTimers; + let expirationWatcher: ExpirationWatcher; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider); + exchangeContractAddress = await zeroEx.exchange.getContractAddressAsync(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(() => { + const sinonTimerConfig = {shouldAdvanceTime: true} as any; + // This constructor has incorrect types + timer = Sinon.useFakeTimers(sinonTimerConfig); + currentUnixTimestampSec = utils.getCurrentUnixTimestamp(); + expirationWatcher = new ExpirationWatcher(); + }); + afterEach(() => { + timer.restore(); + expirationWatcher.unsubscribe(); + }); + it('correctly emits events when order expires', (done: DoneCallback) => { + (async () => { + const orderLifetime = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetime); + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, + expirationUnixTimestampSec, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); + const callback = reportCallbackErrors(done)((hash: string) => { + expect(hash).to.be.equal(orderHash); + expect(utils.getCurrentUnixTimestamp()).to.be.bignumber.above(expirationUnixTimestampSec); + done(); + }); + expirationWatcher.subscribe(callback); + timer.tick(orderLifetime * 1000); + })().catch(done); + }); + it('doesn\'t emit events before order expires', (done: DoneCallback) => { + (async () => { + const orderLifetime = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetime); + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, + expirationUnixTimestampSec, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); + const callback = reportCallbackErrors(done)((hash: string) => { + done(new Error('Emited expiration vent before the order actually expired')); + }); + expirationWatcher.subscribe(callback); + const notEnoughTime = orderLifetime - 1; + timer.tick(notEnoughTime * 1000); + done(); + })().catch(done); + }); + it('emits events in correct order', (done: DoneCallback) => { + (async () => { + const order1Lifetime = 60; + const order2Lifetime = 120; + const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime); + const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime); + const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, + order1ExpirationUnixTimestampSec, + ); + const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, + order2ExpirationUnixTimestampSec, + ); + const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1); + const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec); + const expirationOrder = [orderHash1, orderHash2]; + const callback = reportCallbackErrors(done)((hash: string) => { + const orderHash = expirationOrder.shift(); + expect(hash).to.be.equal(orderHash); + if (_.isEmpty(expirationOrder)) { + done(); + } + }); + expirationWatcher.subscribe(callback); + timer.tick(order2Lifetime * 1000); + })().catch(done); + }); +}); -- cgit v1.2.3 From d3e03744cdbc12bd1956fd5d921fc2331e4ee5cf Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 17 Nov 2017 13:06:10 -0600 Subject: Improve the comment --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 1a9e9a418..5153cd8b9 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -10,7 +10,8 @@ import {ZeroEx} from '../0x'; const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; /** - * This class includes all the functionality related to prunning expired orders + * 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 orderHashHeapByExpiration: Heap; -- cgit v1.2.3 From e755ed927cef8352900187b5487881a3fa4847f1 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 17 Nov 2017 17:25:58 -0600 Subject: Remove order on expiration --- packages/0x.js/src/order_watcher/order_state_watcher.ts | 1 + 1 file changed, 1 insertion(+) 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 f21ad0bbe..bc61e415c 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -142,6 +142,7 @@ export class OrderStateWatcher { }; if (!_.isUndefined(this._orderByOrderHash[orderHash])) { // We need this check because we never remove the orders from expiration watcher + this.removeOrder(orderHash); (this._callbackIfExistsAsync as OnOrderStateChangeCallback)(orderState); } } -- cgit v1.2.3 From c4d10a6f2d14f23367500d4eb46de7097b00d209 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 11:05:37 -0600 Subject: Do simple inits inline --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 5153cd8b9..b211ee826 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -15,14 +15,13 @@ const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; */ export class ExpirationWatcher { private orderHashHeapByExpiration: Heap; - private expiration: {[orderHash: string]: BigNumber}; + private expiration: {[orderHash: string]: BigNumber} = {}; private callbackIfExists?: (orderHash: string) => void; private orderExpirationCheckingIntervalMs: number; private orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; constructor(orderExpirationCheckingIntervalMs?: number) { this.orderExpirationCheckingIntervalMs = orderExpirationCheckingIntervalMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; - this.expiration = {}; const scoreFunction = ((orderHash: string) => { return this.expiration[orderHash].toNumber(); }).bind(this); -- cgit v1.2.3 From 2c193a1244d9f288c7c01142d42e442a687ce114 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 11:06:32 -0600 Subject: Remove redundant bind --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index b211ee826..acc7e7e5d 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -22,9 +22,7 @@ export class ExpirationWatcher { constructor(orderExpirationCheckingIntervalMs?: number) { this.orderExpirationCheckingIntervalMs = orderExpirationCheckingIntervalMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; - const scoreFunction = ((orderHash: string) => { - return this.expiration[orderHash].toNumber(); - }).bind(this); + const scoreFunction = (orderHash: string) => this.expiration[orderHash].toNumber(); this.orderHashHeapByExpiration = new Heap(scoreFunction); } public subscribe(callback: (orderHash: string) => void): void { -- cgit v1.2.3 From 856a5c3369c0248ec32924b25ec77dce6b4e1071 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 11:10:25 -0600 Subject: Throw when subscription is already present --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index acc7e7e5d..6d9556336 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -2,7 +2,7 @@ import * as _ from 'lodash'; import {BigNumber} from 'bignumber.js'; import {utils} from '../utils/utils'; import {intervalUtils} from '../utils/interval_utils'; -import {SignedOrder} from '../types'; +import {SignedOrder, ZeroExError} from '../types'; import {Heap} from '../utils/heap'; import {ZeroEx} from '../0x'; @@ -26,6 +26,9 @@ export class ExpirationWatcher { this.orderHashHeapByExpiration = new Heap(scoreFunction); } public subscribe(callback: (orderHash: string) => void): void { + if (!_.isUndefined(this.callbackIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } this.callbackIfExists = callback; this.orderExpirationCheckingIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( this.pruneExpiredOrders.bind(this), this.orderExpirationCheckingIntervalMs, -- cgit v1.2.3 From 20449a0cb293649d9c301781dd89e7c26cc7a6b6 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 11:13:54 -0600 Subject: Throw when subscription is already removed --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 6d9556336..f9c6571db 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -35,7 +35,10 @@ export class ExpirationWatcher { ); } public unsubscribe(): void { - intervalUtils.clearAsyncExcludingInterval(this.orderExpirationCheckingIntervalIdIfExists as NodeJS.Timer); + if (_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + intervalUtils.clearAsyncExcludingInterval(this.orderExpirationCheckingIntervalIdIfExists); delete this.callbackIfExists; } public addOrder(orderHash: string, expirationUnixTimestampSec: BigNumber): void { -- cgit v1.2.3 From a37f0198065571421eb2772c96241e0d57007bd3 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 11:15:01 -0600 Subject: Delete orderExpirationCheckingIntervalIdIfExists --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index f9c6571db..0f73d088a 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -40,6 +40,7 @@ export class ExpirationWatcher { } intervalUtils.clearAsyncExcludingInterval(this.orderExpirationCheckingIntervalIdIfExists); delete this.callbackIfExists; + delete this.orderExpirationCheckingIntervalIdIfExists; } public addOrder(orderHash: string, expirationUnixTimestampSec: BigNumber): void { this.expiration[orderHash] = expirationUnixTimestampSec; -- cgit v1.2.3 From fa2c4160b59e88a6816436855db306c9ce4a8da1 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 11:15:48 -0600 Subject: Remove new line --- packages/0x.js/src/utils/heap.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/0x.js/src/utils/heap.ts b/packages/0x.js/src/utils/heap.ts index 1135c76b9..e262266d4 100644 --- a/packages/0x.js/src/utils/heap.ts +++ b/packages/0x.js/src/utils/heap.ts @@ -49,7 +49,6 @@ export class Heap { n = parentN; } } - private sinkDown(n: number) { // Look up the target element and its score. const length = this.content.length; -- cgit v1.2.3 From 67ad07020de52eccc27b088ad51094cb319db946 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 11:16:40 -0600 Subject: Remove comment --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 0f73d088a..cd73f1d1f 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -6,7 +6,6 @@ import {SignedOrder, ZeroExError} from '../types'; import {Heap} from '../utils/heap'; import {ZeroEx} from '../0x'; -// Order prunning is very fast const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; /** -- cgit v1.2.3 From 86d19657b14f7ac7afcb04ae952425f094a02b14 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 11:17:53 -0600 Subject: Improve the comment --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index cd73f1d1f..d699b7a51 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -43,7 +43,7 @@ export class ExpirationWatcher { } public addOrder(orderHash: string, expirationUnixTimestampSec: BigNumber): void { this.expiration[orderHash] = expirationUnixTimestampSec; - // We don't remove hashes on order remove because it's slow (linear). + // We don't remove hashes from the heap on order remove because it's slow (linear). // We just skip them later if the order was already removed from the order watcher. this.orderHashHeapByExpiration.push(orderHash); } -- cgit v1.2.3 From c068ec5231a97752a914eaa0479750c05b5c6342 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 12:49:55 -0600 Subject: Add ifExists suffix --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 4 ++-- packages/0x.js/src/order_watcher/order_state_watcher.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index d699b7a51..cf0222e3c 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -18,8 +18,8 @@ export class ExpirationWatcher { private callbackIfExists?: (orderHash: string) => void; private orderExpirationCheckingIntervalMs: number; private orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; - constructor(orderExpirationCheckingIntervalMs?: number) { - this.orderExpirationCheckingIntervalMs = orderExpirationCheckingIntervalMs || + constructor(orderExpirationCheckingIntervalMsIfExists?: number) { + this.orderExpirationCheckingIntervalMs = orderExpirationCheckingIntervalMsIfExists || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; const scoreFunction = (orderHash: string) => this.expiration[orderHash].toNumber(); this.orderHashHeapByExpiration = new Heap(scoreFunction); 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 bc61e415c..40007805f 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -74,10 +74,10 @@ export class OrderStateWatcher { this._orderStateUtils = new OrderStateUtils( this._balanceAndProxyAllowanceLazyStore, this._orderFilledCancelledLazyStore, ); - const orderExpirationCheckingIntervalMs = _.isUndefined(config) ? - undefined : - config.orderExpirationCheckingIntervalMs; - this._expirationWatcher = new ExpirationWatcher(orderExpirationCheckingIntervalMs); + const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) ? + undefined : + config.orderExpirationCheckingIntervalMs; + this._expirationWatcher = new ExpirationWatcher(orderExpirationCheckingIntervalMsIfExists); } /** * Add an order to the orderStateWatcher. Before the order is added, it's -- cgit v1.2.3 From 49d926013c07d1343ce2d69b413acc7a83bfbe97 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 12:53:16 -0600 Subject: Reference types directly --- packages/0x.js/test/expiration_watcher_test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index 47071416e..629fa7ba6 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -10,10 +10,8 @@ import {utils} from '../src/utils/utils'; import {Web3Wrapper} from '../src/web3_wrapper'; import {TokenUtils} from './utils/token_utils'; import {ExpirationWatcher} from '../src/order_watcher/expiration_watcher'; -import { - ZeroEx, - Token, -} from '../src'; +import {Token} from '../src/types'; +import {ZeroEx} from '../src'; import {FillScenarios} from './utils/fill_scenarios'; import {DoneCallback} from '../src/types'; import {reportCallbackErrors} from './utils/report_callback_errors'; -- cgit v1.2.3 From 83a2abeee4d68020085a5b9f15c806cd6d3b9e20 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 12:53:49 -0600 Subject: Rename orderLifetime to orderLifetimeS --- packages/0x.js/test/expiration_watcher_test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index 629fa7ba6..1b4ba0bf5 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -66,8 +66,8 @@ describe('ExpirationWatcher', () => { }); it('correctly emits events when order expires', (done: DoneCallback) => { (async () => { - const orderLifetime = 60; - const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetime); + const orderLifetimeS = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeS); const signedOrder = await fillScenarios.createFillableSignedOrderAsync( makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, expirationUnixTimestampSec, @@ -80,13 +80,13 @@ describe('ExpirationWatcher', () => { done(); }); expirationWatcher.subscribe(callback); - timer.tick(orderLifetime * 1000); + timer.tick(orderLifetimeS * 1000); })().catch(done); }); it('doesn\'t emit events before order expires', (done: DoneCallback) => { (async () => { - const orderLifetime = 60; - const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetime); + const orderLifetimeS = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeS); const signedOrder = await fillScenarios.createFillableSignedOrderAsync( makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, expirationUnixTimestampSec, @@ -97,7 +97,7 @@ describe('ExpirationWatcher', () => { done(new Error('Emited expiration vent before the order actually expired')); }); expirationWatcher.subscribe(callback); - const notEnoughTime = orderLifetime - 1; + const notEnoughTime = orderLifetimeS - 1; timer.tick(notEnoughTime * 1000); done(); })().catch(done); -- cgit v1.2.3 From 35668fe2259323c84a792b575d99df74702ee620 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 12:54:22 -0600 Subject: Fix typos --- packages/0x.js/test/expiration_watcher_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index 1b4ba0bf5..31e01dba6 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -94,7 +94,7 @@ describe('ExpirationWatcher', () => { const orderHash = ZeroEx.getOrderHashHex(signedOrder); expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); const callback = reportCallbackErrors(done)((hash: string) => { - done(new Error('Emited expiration vent before the order actually expired')); + done(new Error('Emitted expiration went before the order actually expired')); }); expirationWatcher.subscribe(callback); const notEnoughTime = orderLifetimeS - 1; -- cgit v1.2.3 From a613c3b7e7654728cb56cceb67566ab75313e16f Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 13:02:24 -0600 Subject: Add defaults --- packages/0x.js/src/order_watcher/event_watcher.ts | 10 +++++----- packages/0x.js/src/order_watcher/order_state_watcher.ts | 4 ++-- packages/0x.js/src/types.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts index 81529a98c..da824bd09 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/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts index 40007805f..27f4c7d29 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -67,8 +67,8 @@ 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( diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 45b49e148..37e80e6bc 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -397,8 +397,8 @@ export interface JSONRPCPayload { } /* - * orderExpirationCheckingIntervalMs: How often to check for expired orders - * 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 */ export interface OrderStateWatcherConfig { orderExpirationCheckingIntervalMs?: number; -- cgit v1.2.3 From 71475d3ceafd8f1a4a76d1e5b49ff0d186bacd9b Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 13:47:09 -0600 Subject: Add expirationMarginMs --- .../0x.js/src/order_watcher/expiration_watcher.ts | 19 +++++++++++++------ .../0x.js/src/order_watcher/order_state_watcher.ts | 10 ++++++++-- packages/0x.js/src/types.ts | 4 ++++ packages/0x.js/src/utils/utils.ts | 7 +++++-- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index cf0222e3c..71199e75f 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -6,6 +6,7 @@ import {SignedOrder, ZeroExError} from '../types'; import {Heap} from '../utils/heap'; import {ZeroEx} from '../0x'; +const DEFAULT_EXPIRATION_MARGIN_MS = 0; const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; /** @@ -17,9 +18,13 @@ export class ExpirationWatcher { private expiration: {[orderHash: string]: BigNumber} = {}; private callbackIfExists?: (orderHash: string) => void; private orderExpirationCheckingIntervalMs: number; + private expirationMarginMs: number; private orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; - constructor(orderExpirationCheckingIntervalMsIfExists?: number) { - this.orderExpirationCheckingIntervalMs = orderExpirationCheckingIntervalMsIfExists || + constructor(expirationMarginIfExistsMs?: number, + orderExpirationCheckingIntervalIfExistsMs?: number) { + this.expirationMarginMs = expirationMarginIfExistsMs || + DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; + this.orderExpirationCheckingIntervalMs = expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; const scoreFunction = (orderHash: string) => this.expiration[orderHash].toNumber(); this.orderHashHeapByExpiration = new Heap(scoreFunction); @@ -41,17 +46,19 @@ export class ExpirationWatcher { delete this.callbackIfExists; delete this.orderExpirationCheckingIntervalIdIfExists; } - public addOrder(orderHash: string, expirationUnixTimestampSec: BigNumber): void { - this.expiration[orderHash] = expirationUnixTimestampSec; + public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void { + this.expiration[orderHash] = expirationUnixTimestampMs; // We don't remove hashes from the heap on order remove because it's slow (linear). // We just skip them later if the order was already removed from the order watcher. this.orderHashHeapByExpiration.push(orderHash); } private pruneExpiredOrders(): void { - const currentUnixTimestampSec = utils.getCurrentUnixTimestamp(); + const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); while ( this.orderHashHeapByExpiration.size() !== 0 && - this.expiration[this.orderHashHeapByExpiration.head()].lessThan(currentUnixTimestampSec) && + this.expiration[this.orderHashHeapByExpiration.head()].lessThan( + currentUnixTimestampMs.plus(this.expirationMarginMs), + ) && !_.isUndefined(this.callbackIfExists) ) { const orderHash = this.orderHashHeapByExpiration.pop(); 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 27f4c7d29..3659fc6e2 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -77,7 +77,12 @@ export class OrderStateWatcher { const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) ? undefined : config.orderExpirationCheckingIntervalMs; - this._expirationWatcher = new ExpirationWatcher(orderExpirationCheckingIntervalMsIfExists); + 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 @@ -91,7 +96,8 @@ export class OrderStateWatcher { this._orderByOrderHash[orderHash] = signedOrder; this.addToDependentOrderHashes(signedOrder, orderHash); // We don't remove orders from expirationWatcher because heap removal is linear. We just skip it later - this._expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); + const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000); + this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); } /** * Removes an order from the orderStateWatcher diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 37e80e6bc..13a46659d 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -396,14 +396,18 @@ export interface JSONRPCPayload { method: string; } +// tslint:disable:max-line-length /* * 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; } +// tslint:enable:max-line-length /* * gasPrice: Gas price to use with every transaction 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()); }, }; -- cgit v1.2.3 From 5788b90c522b63b53f5e8480f2b357ee24bb08a3 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 14:40:53 -0600 Subject: Remove custom heap and use bintrees --- packages/0x.js/package.json | 4 +- .../0x.js/src/order_watcher/expiration_watcher.ts | 22 +++--- .../0x.js/src/order_watcher/order_state_watcher.ts | 2 +- packages/0x.js/src/utils/heap.ts | 92 ---------------------- yarn.lock | 8 ++ 5 files changed, 25 insertions(+), 103 deletions(-) delete mode 100644 packages/0x.js/src/utils/heap.ts diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 6839e6513..539a78f37 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -49,6 +49,7 @@ "node": ">=6.0.0" }, "devDependencies": { + "@types/bintrees": "^1.0.2", "@types/jsonschema": "^1.1.1", "@types/lodash": "^4.14.64", "@types/mocha": "^2.2.41", @@ -87,9 +88,10 @@ "webpack": "^3.1.0" }, "dependencies": { - "@0xproject/assert": "0.0.3", "0x-json-schemas": "^0.6.1", + "@0xproject/assert": "0.0.3", "bignumber.js": "~4.1.0", + "bintrees": "^1.0.2", "compare-versions": "^3.0.1", "es6-promisify": "^5.0.0", "ethereumjs-abi": "^0.6.4", diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 71199e75f..7d6f8df65 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -1,9 +1,9 @@ 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 {Heap} from '../utils/heap'; import {ZeroEx} from '../0x'; const DEFAULT_EXPIRATION_MARGIN_MS = 0; @@ -14,7 +14,7 @@ const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; * It stores them in a min heap by expiration time and checks for expired ones every `orderExpirationCheckingIntervalMs` */ export class ExpirationWatcher { - private orderHashHeapByExpiration: Heap; + private orderHashRBTreeByExpiration: RBTree; private expiration: {[orderHash: string]: BigNumber} = {}; private callbackIfExists?: (orderHash: string) => void; private orderExpirationCheckingIntervalMs: number; @@ -27,7 +27,8 @@ export class ExpirationWatcher { this.orderExpirationCheckingIntervalMs = expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; const scoreFunction = (orderHash: string) => this.expiration[orderHash].toNumber(); - this.orderHashHeapByExpiration = new Heap(scoreFunction); + const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs); + this.orderHashRBTreeByExpiration = new RBTree(comparator); } public subscribe(callback: (orderHash: string) => void): void { if (!_.isUndefined(this.callbackIfExists)) { @@ -48,20 +49,23 @@ export class ExpirationWatcher { } public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void { this.expiration[orderHash] = expirationUnixTimestampMs; - // We don't remove hashes from the heap on order remove because it's slow (linear). - // We just skip them later if the order was already removed from the order watcher. - this.orderHashHeapByExpiration.push(orderHash); + this.orderHashRBTreeByExpiration.insert(orderHash); + } + public removeOrder(orderHash: string): void { + this.orderHashRBTreeByExpiration.remove(orderHash); + delete this.expiration[orderHash]; } private pruneExpiredOrders(): void { const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); while ( - this.orderHashHeapByExpiration.size() !== 0 && - this.expiration[this.orderHashHeapByExpiration.head()].lessThan( + this.orderHashRBTreeByExpiration.size !== 0 && + this.expiration[this.orderHashRBTreeByExpiration.min()].lessThan( currentUnixTimestampMs.plus(this.expirationMarginMs), ) && !_.isUndefined(this.callbackIfExists) ) { - const orderHash = this.orderHashHeapByExpiration.pop(); + const orderHash = this.orderHashRBTreeByExpiration.min(); + this.orderHashRBTreeByExpiration.remove(orderHash); delete this.expiration[orderHash]; this.callbackIfExists(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 3659fc6e2..975679f57 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -95,7 +95,6 @@ export class OrderStateWatcher { assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); this._orderByOrderHash[orderHash] = signedOrder; this.addToDependentOrderHashes(signedOrder, orderHash); - // We don't remove orders from expirationWatcher because heap removal is linear. We just skip it later const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000); this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); } @@ -111,6 +110,7 @@ export class OrderStateWatcher { } delete this._orderByOrderHash[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 diff --git a/packages/0x.js/src/utils/heap.ts b/packages/0x.js/src/utils/heap.ts deleted file mode 100644 index e262266d4..000000000 --- a/packages/0x.js/src/utils/heap.ts +++ /dev/null @@ -1,92 +0,0 @@ -// Based on Original JavaScript Code from Marijn Haverbeke (http://eloquentjavascript.net/1st_edition/appendix2.html) -export class Heap { - private content: T[]; - private scoreFunction: (x: T) => number; - constructor(scoreFunction: (x: T) => number) { - this.content = []; - this.scoreFunction = scoreFunction; - } - public push(element: T) { - this.content.push(element); - this.bubbleUp(this.content.length - 1); - } - public size(): number { - const size = this.content.length; - return size; - } - public head(): T { - const head = this.content[0]; - return head; - } - public pop(): T { - const head = this.content[0]; - const end = this.content.pop(); - if (this.content.length > 0) { - this.content[0] = end as T; - this.sinkDown(0); - } - return head; - } - private bubbleUp(n: number) { - // Fetch the element that has to be moved. - const element = this.content[n]; - const score = this.scoreFunction(element); - // When at 0, an element can not go up any further. - while (n > 0) { - // Compute the parent element's index, and fetch it. - const parentN = Math.floor((n + 1) / 2) - 1; - const parent = this.content[parentN]; - // If the parent has a lesser score, things are in order and we - // are done. - if (score >= this.scoreFunction(parent)) { - break; - } - - // Otherwise, swap the parent with the current element and - // continue. - this.content[parentN] = element; - this.content[n] = parent; - n = parentN; - } - } - private sinkDown(n: number) { - // Look up the target element and its score. - const length = this.content.length; - const element = this.content[n]; - const elemScore = this.scoreFunction(element); - - while (true) { - // Compute the indices of the child elements. - const child2N = (n + 1) * 2; - const child1N = child2N - 1; - // This is used to store the new position of the element, if any. - let swap = n; - let child1Score; - let child2Score; - // If the first child exists (is inside the array)... - if (child1N < length) { - // Look it up and compute its score. - const child1 = this.content[child1N]; - child1Score = this.scoreFunction(child1); - // If the score is less than our element's, we need to swap. - if (child1Score < elemScore) { - swap = child1N; - } - // Do the same checks for the other child. - if (child2N < length) { - const child2 = this.content[child2N]; - child2Score = this.scoreFunction(child2); - if (child2Score < (swap == null ? elemScore : child1Score)) { - swap = child2N; - } - } - } - if (swap === n) { - break; - } - this.content[n] = this.content[swap]; - this.content[swap] = element; - n = swap; - } - } -} diff --git a/yarn.lock b/yarn.lock index 7eb98bc97..0c87f3257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,6 +9,10 @@ jsonschema "^1.2.0" lodash.values "^4.3.0" +"@types/bintrees@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/bintrees/-/bintrees-1.0.2.tgz#0dfdce4eeebdf90427bd35b0e79dc248b3d157a6" + "@types/fs-extra@^4.0.0": version "4.0.4" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.4.tgz#72947e108f2cbeda5ab288a927399fdf6d02bd42" @@ -830,6 +834,10 @@ bindings@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" +bintrees@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" + bip39@^2.2.0: version "2.4.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.4.0.tgz#a0b8adbf163f53495f00f05d9ede7c25369ccf13" -- cgit v1.2.3 From 9745d5348c7cbef0d0b16fafa2453acfc6cb2c1f Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 16:14:40 -0600 Subject: Pass callback down --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 7d6f8df65..933cb6f1d 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -16,7 +16,6 @@ const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; export class ExpirationWatcher { private orderHashRBTreeByExpiration: RBTree; private expiration: {[orderHash: string]: BigNumber} = {}; - private callbackIfExists?: (orderHash: string) => void; private orderExpirationCheckingIntervalMs: number; private expirationMarginMs: number; private orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; @@ -31,12 +30,11 @@ export class ExpirationWatcher { this.orderHashRBTreeByExpiration = new RBTree(comparator); } public subscribe(callback: (orderHash: string) => void): void { - if (!_.isUndefined(this.callbackIfExists)) { + if (!_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists)) { throw new Error(ZeroExError.SubscriptionAlreadyPresent); } - this.callbackIfExists = callback; this.orderExpirationCheckingIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( - this.pruneExpiredOrders.bind(this), this.orderExpirationCheckingIntervalMs, + this.pruneExpiredOrders.bind(this, callback), this.orderExpirationCheckingIntervalMs, ); } public unsubscribe(): void { @@ -44,7 +42,6 @@ export class ExpirationWatcher { throw new Error(ZeroExError.SubscriptionNotFound); } intervalUtils.clearAsyncExcludingInterval(this.orderExpirationCheckingIntervalIdIfExists); - delete this.callbackIfExists; delete this.orderExpirationCheckingIntervalIdIfExists; } public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void { @@ -55,19 +52,18 @@ export class ExpirationWatcher { this.orderHashRBTreeByExpiration.remove(orderHash); delete this.expiration[orderHash]; } - private pruneExpiredOrders(): void { + private pruneExpiredOrders(callback: (orderHash: string) => void): void { const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); while ( this.orderHashRBTreeByExpiration.size !== 0 && this.expiration[this.orderHashRBTreeByExpiration.min()].lessThan( currentUnixTimestampMs.plus(this.expirationMarginMs), - ) && - !_.isUndefined(this.callbackIfExists) + ) ) { const orderHash = this.orderHashRBTreeByExpiration.min(); this.orderHashRBTreeByExpiration.remove(orderHash); delete this.expiration[orderHash]; - this.callbackIfExists(orderHash); + callback(orderHash); } } } -- cgit v1.2.3 From b01a4af99ed246cda6307ea10303ad25d2cadbe0 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 16:17:23 -0600 Subject: Rename --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 933cb6f1d..7d6ce6bdd 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -14,7 +14,7 @@ const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; * It stores them in a min heap by expiration time and checks for expired ones every `orderExpirationCheckingIntervalMs` */ export class ExpirationWatcher { - private orderHashRBTreeByExpiration: RBTree; + private orderHashByExpirationRBTree: RBTree; private expiration: {[orderHash: string]: BigNumber} = {}; private orderExpirationCheckingIntervalMs: number; private expirationMarginMs: number; @@ -27,7 +27,7 @@ export class ExpirationWatcher { 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.orderHashRBTreeByExpiration = new RBTree(comparator); + this.orderHashByExpirationRBTree = new RBTree(comparator); } public subscribe(callback: (orderHash: string) => void): void { if (!_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists)) { @@ -46,22 +46,22 @@ export class ExpirationWatcher { } public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void { this.expiration[orderHash] = expirationUnixTimestampMs; - this.orderHashRBTreeByExpiration.insert(orderHash); + this.orderHashByExpirationRBTree.insert(orderHash); } public removeOrder(orderHash: string): void { - this.orderHashRBTreeByExpiration.remove(orderHash); + this.orderHashByExpirationRBTree.remove(orderHash); delete this.expiration[orderHash]; } private pruneExpiredOrders(callback: (orderHash: string) => void): void { const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); while ( - this.orderHashRBTreeByExpiration.size !== 0 && - this.expiration[this.orderHashRBTreeByExpiration.min()].lessThan( + this.orderHashByExpirationRBTree.size !== 0 && + this.expiration[this.orderHashByExpirationRBTree.min()].lessThan( currentUnixTimestampMs.plus(this.expirationMarginMs), ) ) { - const orderHash = this.orderHashRBTreeByExpiration.min(); - this.orderHashRBTreeByExpiration.remove(orderHash); + const orderHash = this.orderHashByExpirationRBTree.min(); + this.orderHashByExpirationRBTree.remove(orderHash); delete this.expiration[orderHash]; callback(orderHash); } -- cgit v1.2.3 From 3bc3666215d4fdaa6d9db87e065ca5e762df9b25 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 16:25:51 -0600 Subject: Check if callback exists --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 8 ++++---- packages/0x.js/src/order_watcher/order_state_watcher.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 7d6ce6bdd..efedd3cf8 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -29,12 +29,12 @@ export class ExpirationWatcher { const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs); this.orderHashByExpirationRBTree = new RBTree(comparator); } - public subscribe(callback: (orderHash: string) => void): void { + public subscribe(callbackAsync: (orderHash: string) => Promise): void { if (!_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists)) { throw new Error(ZeroExError.SubscriptionAlreadyPresent); } this.orderExpirationCheckingIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( - this.pruneExpiredOrders.bind(this, callback), this.orderExpirationCheckingIntervalMs, + this.pruneExpiredOrdersAsync.bind(this, callbackAsync), this.orderExpirationCheckingIntervalMs, ); } public unsubscribe(): void { @@ -52,7 +52,7 @@ export class ExpirationWatcher { this.orderHashByExpirationRBTree.remove(orderHash); delete this.expiration[orderHash]; } - private pruneExpiredOrders(callback: (orderHash: string) => void): void { + private async pruneExpiredOrdersAsync(callbackAsync: (orderHash: string) => Promise): Promise { const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); while ( this.orderHashByExpirationRBTree.size !== 0 && @@ -63,7 +63,7 @@ export class ExpirationWatcher { const orderHash = this.orderHashByExpirationRBTree.min(); this.orderHashByExpirationRBTree.remove(orderHash); delete this.expiration[orderHash]; - callback(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 579fa388a..84f2128c0 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -128,7 +128,7 @@ export class OrderStateWatcher { } this._callbackIfExists = callback; this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this)); - this._expirationWatcher.subscribe(this._onOrderExpired.bind(this)); + this._expirationWatcher.subscribe(this._onOrderExpiredAsync.bind(this)); } /** * Ends an orderStateWatcher subscription. @@ -143,7 +143,7 @@ export class OrderStateWatcher { this._eventWatcher.unsubscribe(); this._expirationWatcher.unsubscribe(); } - private _onOrderExpired(orderHash: string): void { + private async _onOrderExpiredAsync(orderHash: string): Promise { const orderState: OrderState = { isValid: false, orderHash, @@ -151,8 +151,10 @@ export class OrderStateWatcher { }; if (!_.isUndefined(this._orderByOrderHash[orderHash])) { // We need this check because we never remove the orders from expiration watcher - this.removeOrder(orderHash); - (this._callbackIfExistsAsync as OnOrderStateChangeCallback)(orderState); + await this.removeOrderAsync(orderHash); + if (!_.isUndefined(this._callbackIfExists)) { + this._callbackIfExists(orderState); + } } } private async _onEventWatcherCallbackAsync(log: LogEvent): Promise { -- cgit v1.2.3 From 3e0371685fe16b7dc96f5f900c0d4531b16370d4 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 16:39:34 -0600 Subject: Fix async callbacks --- packages/0x.js/src/utils/order_validation_utils.ts | 4 ++-- packages/0x.js/test/expiration_watcher_test.ts | 19 +++++++++---------- packages/0x.js/test/utils/report_callback_errors.ts | 6 +++--- 3 files changed, 14 insertions(+), 15 deletions(-) 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/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index 31e01dba6..a384658e7 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -10,10 +10,9 @@ import {utils} from '../src/utils/utils'; import {Web3Wrapper} from '../src/web3_wrapper'; import {TokenUtils} from './utils/token_utils'; import {ExpirationWatcher} from '../src/order_watcher/expiration_watcher'; -import {Token} from '../src/types'; +import {Token, DoneCallback} from '../src/types'; import {ZeroEx} from '../src'; import {FillScenarios} from './utils/fill_scenarios'; -import {DoneCallback} from '../src/types'; import {reportCallbackErrors} from './utils/report_callback_errors'; chaiSetup.configure(); @@ -57,7 +56,7 @@ describe('ExpirationWatcher', () => { const sinonTimerConfig = {shouldAdvanceTime: true} as any; // This constructor has incorrect types timer = Sinon.useFakeTimers(sinonTimerConfig); - currentUnixTimestampSec = utils.getCurrentUnixTimestamp(); + currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); expirationWatcher = new ExpirationWatcher(); }); afterEach(() => { @@ -74,12 +73,12 @@ describe('ExpirationWatcher', () => { ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); - const callback = reportCallbackErrors(done)((hash: string) => { + const callbackAsync = reportCallbackErrors(done)(async (hash: string) => { expect(hash).to.be.equal(orderHash); - expect(utils.getCurrentUnixTimestamp()).to.be.bignumber.above(expirationUnixTimestampSec); + expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.above(expirationUnixTimestampSec); done(); }); - expirationWatcher.subscribe(callback); + expirationWatcher.subscribe(callbackAsync); timer.tick(orderLifetimeS * 1000); })().catch(done); }); @@ -93,10 +92,10 @@ describe('ExpirationWatcher', () => { ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); - const callback = reportCallbackErrors(done)((hash: string) => { + const callbackAsync = reportCallbackErrors(done)(async (hash: string) => { done(new Error('Emitted expiration went before the order actually expired')); }); - expirationWatcher.subscribe(callback); + expirationWatcher.subscribe(callbackAsync); const notEnoughTime = orderLifetimeS - 1; timer.tick(notEnoughTime * 1000); done(); @@ -121,14 +120,14 @@ describe('ExpirationWatcher', () => { expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec); expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec); const expirationOrder = [orderHash1, orderHash2]; - const callback = reportCallbackErrors(done)((hash: string) => { + const callbackAsync = reportCallbackErrors(done)(async (hash: string) => { const orderHash = expirationOrder.shift(); expect(hash).to.be.equal(orderHash); if (_.isEmpty(expirationOrder)) { done(); } }); - expirationWatcher.subscribe(callback); + expirationWatcher.subscribe(callbackAsync); timer.tick(order2Lifetime * 1000); })().catch(done); }); diff --git a/packages/0x.js/test/utils/report_callback_errors.ts b/packages/0x.js/test/utils/report_callback_errors.ts index d471b2af2..4f9517704 100644 --- a/packages/0x.js/test/utils/report_callback_errors.ts +++ b/packages/0x.js/test/utils/report_callback_errors.ts @@ -1,10 +1,10 @@ import { DoneCallback } from '../../src/types'; export const reportCallbackErrors = (done: DoneCallback) => { - return (f: (...args: any[]) => void) => { - const wrapped = (...args: any[]) => { + return (fAsync: (...args: any[]) => void|Promise) => { + const wrapped = async (...args: any[]) => { try { - f(...args); + await fAsync(...args); } catch (err) { done(err); } -- cgit v1.2.3 From 3fc8645d92f293fcfd3a10affcabdbd75b8714b5 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 16:52:53 -0600 Subject: Remove old comment --- packages/0x.js/src/order_watcher/order_state_watcher.ts | 1 - 1 file changed, 1 deletion(-) 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 84f2128c0..fd7496699 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -150,7 +150,6 @@ export class OrderStateWatcher { error: ExchangeContractErrs.OrderFillExpired, }; if (!_.isUndefined(this._orderByOrderHash[orderHash])) { - // We need this check because we never remove the orders from expiration watcher await this.removeOrderAsync(orderHash); if (!_.isUndefined(this._callbackIfExists)) { this._callbackIfExists(orderState); -- cgit v1.2.3 From 3ad6020e192e73ce276cc090751cedf2036e6a06 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 16:55:53 -0600 Subject: Address nits --- packages/0x.js/src/types.ts | 5 ++--- packages/0x.js/test/expiration_watcher_test.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 39c60695e..c3aabfd86 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -391,18 +391,17 @@ export interface JSONRPCPayload { method: string; } -// tslint:disable:max-line-length /* * 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 + * 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; } -// tslint:enable:max-line-length /* * gasPrice: Gas price to use with every transaction diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index a384658e7..19c08a811 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -11,7 +11,7 @@ import {Web3Wrapper} from '../src/web3_wrapper'; import {TokenUtils} from './utils/token_utils'; import {ExpirationWatcher} from '../src/order_watcher/expiration_watcher'; import {Token, DoneCallback} from '../src/types'; -import {ZeroEx} from '../src'; +import {ZeroEx} from '../src/0x'; import {FillScenarios} from './utils/fill_scenarios'; import {reportCallbackErrors} from './utils/report_callback_errors'; @@ -65,8 +65,8 @@ describe('ExpirationWatcher', () => { }); it('correctly emits events when order expires', (done: DoneCallback) => { (async () => { - const orderLifetimeS = 60; - const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeS); + const orderLifetimeSec = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); const signedOrder = await fillScenarios.createFillableSignedOrderAsync( makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, expirationUnixTimestampSec, @@ -79,13 +79,13 @@ describe('ExpirationWatcher', () => { done(); }); expirationWatcher.subscribe(callbackAsync); - timer.tick(orderLifetimeS * 1000); + timer.tick(orderLifetimeSec * 1000); })().catch(done); }); it('doesn\'t emit events before order expires', (done: DoneCallback) => { (async () => { - const orderLifetimeS = 60; - const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeS); + const orderLifetimeSec = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); const signedOrder = await fillScenarios.createFillableSignedOrderAsync( makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, expirationUnixTimestampSec, @@ -96,7 +96,7 @@ describe('ExpirationWatcher', () => { done(new Error('Emitted expiration went before the order actually expired')); }); expirationWatcher.subscribe(callbackAsync); - const notEnoughTime = orderLifetimeS - 1; + const notEnoughTime = orderLifetimeSec - 1; timer.tick(notEnoughTime * 1000); done(); })().catch(done); -- cgit v1.2.3 From c4669013abe477e555745c10c16194d47f523f9e Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 17:24:30 -0600 Subject: Check if transactionReceipt exists before normalizing it --- packages/0x.js/src/web3_wrapper.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/0x.js/src/web3_wrapper.ts b/packages/0x.js/src/web3_wrapper.ts index c937f9288..12d7caaf8 100644 --- a/packages/0x.js/src/web3_wrapper.ts +++ b/packages/0x.js/src/web3_wrapper.ts @@ -39,7 +39,9 @@ export class Web3Wrapper { } public async getTransactionReceiptAsync(txHash: string): Promise { 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 { -- cgit v1.2.3 From cfcbf34305cf1dffeb677761138a93403d9b82be Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 20 Nov 2017 17:46:53 -0600 Subject: Fix test:circleci command --- packages/0x.js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 4fe11a8dc..36c3b8c8e 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -17,7 +17,7 @@ "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", "upload_docs_json": "aws s3 cp docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json", "lint": "tslint src/**/*.ts test/**/*.ts", - "test:circleci": "run-s test:coverage report_test_coverage; if [ $CIRCLE_BRANCH = \"development\" ]; then yarn test:umd; fi", + "test:circleci": "run-s test:coverage report_test_coverage && if [ $CIRCLE_BRANCH = \"development\" ]; then yarn test:umd; fi", "test": "run-s clean test:commonjs", "test:umd": "./scripts/test_umd.sh", "test:coverage": "nyc npm run test --all", -- cgit v1.2.3 From 4dc1962f9ec2c537b7944246842d934c3aee186d Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 21 Nov 2017 11:59:34 -0600 Subject: Fix a typo --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index efedd3cf8..21b239be1 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -22,7 +22,7 @@ export class ExpirationWatcher { constructor(expirationMarginIfExistsMs?: number, orderExpirationCheckingIntervalIfExistsMs?: number) { this.expirationMarginMs = expirationMarginIfExistsMs || - DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; + DEFAULT_EXPIRATION_MARGIN_MS; this.orderExpirationCheckingIntervalMs = expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; const scoreFunction = (orderHash: string) => this.expiration[orderHash].toNumber(); -- cgit v1.2.3 From 351b7557b65e4cdef2177585d52021ee5a0f3e42 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 21 Nov 2017 12:21:49 -0600 Subject: Fix tests --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 3 ++- packages/0x.js/test/expiration_watcher_test.ts | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 21b239be1..5aa8b3d17 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -58,7 +58,8 @@ export class ExpirationWatcher { this.orderHashByExpirationRBTree.size !== 0 && this.expiration[this.orderHashByExpirationRBTree.min()].lessThan( currentUnixTimestampMs.plus(this.expirationMarginMs), - ) + ) && + !_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists) ) { const orderHash = this.orderHashByExpirationRBTree.min(); this.orderHashByExpirationRBTree.remove(orderHash); diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index 19c08a811..0d5ebb909 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -72,10 +72,10 @@ describe('ExpirationWatcher', () => { expirationUnixTimestampSec, ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); const callbackAsync = reportCallbackErrors(done)(async (hash: string) => { expect(hash).to.be.equal(orderHash); - expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.above(expirationUnixTimestampSec); + expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec); done(); }); expirationWatcher.subscribe(callbackAsync); @@ -91,7 +91,7 @@ describe('ExpirationWatcher', () => { expirationUnixTimestampSec, ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); const callbackAsync = reportCallbackErrors(done)(async (hash: string) => { done(new Error('Emitted expiration went before the order actually expired')); }); @@ -117,8 +117,8 @@ describe('ExpirationWatcher', () => { ); const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1); const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2); - expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec); - expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); const expirationOrder = [orderHash1, orderHash2]; const callbackAsync = reportCallbackErrors(done)(async (hash: string) => { const orderHash = expirationOrder.shift(); -- cgit v1.2.3 From 3fc0eae4c0d66ff2cf9bffc8bc646b5aeb0c13b8 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 22 Nov 2017 11:44:34 -0600 Subject: Refactor while condition --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 5aa8b3d17..862714cc5 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -54,13 +54,19 @@ export class ExpirationWatcher { } private async pruneExpiredOrdersAsync(callbackAsync: (orderHash: string) => Promise): Promise { const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); - while ( - this.orderHashByExpirationRBTree.size !== 0 && - this.expiration[this.orderHashByExpirationRBTree.min()].lessThan( + while (true) { + const noOrdersLeft = this.orderHashByExpirationRBTree.size === 0; + if (noOrdersLeft) { + break; + } + const nextOrderHashToExpire = this.orderHashByExpirationRBTree.min(); + const noExpiredOrdersLeft = this.expiration[nextOrderHashToExpire].greaterThan( currentUnixTimestampMs.plus(this.expirationMarginMs), - ) && - !_.isUndefined(this.orderExpirationCheckingIntervalIdIfExists) - ) { + ); + const noActiveSubscription = _.isUndefined(this.orderExpirationCheckingIntervalIdIfExists); + if (noExpiredOrdersLeft || noActiveSubscription) { + break; + } const orderHash = this.orderHashByExpirationRBTree.min(); this.orderHashByExpirationRBTree.remove(orderHash); delete this.expiration[orderHash]; -- cgit v1.2.3 From ac2c723ec9044589ad6495f4fd915db1f9eda3a7 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 22 Nov 2017 11:56:34 -0600 Subject: Last renames --- packages/0x.js/src/order_watcher/expiration_watcher.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 862714cc5..717edaad7 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -55,16 +55,16 @@ export class ExpirationWatcher { private async pruneExpiredOrdersAsync(callbackAsync: (orderHash: string) => Promise): Promise { const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); while (true) { - const noOrdersLeft = this.orderHashByExpirationRBTree.size === 0; - if (noOrdersLeft) { + const hasTrakedOrders = this.orderHashByExpirationRBTree.size === 0; + if (hasTrakedOrders) { break; } const nextOrderHashToExpire = this.orderHashByExpirationRBTree.min(); - const noExpiredOrdersLeft = this.expiration[nextOrderHashToExpire].greaterThan( + const hasNoExpiredOrders = this.expiration[nextOrderHashToExpire].greaterThan( currentUnixTimestampMs.plus(this.expirationMarginMs), ); - const noActiveSubscription = _.isUndefined(this.orderExpirationCheckingIntervalIdIfExists); - if (noExpiredOrdersLeft || noActiveSubscription) { + const isSubscriptionActive = _.isUndefined(this.orderExpirationCheckingIntervalIdIfExists); + if (hasNoExpiredOrders || isSubscriptionActive) { break; } const orderHash = this.orderHashByExpirationRBTree.min(); -- cgit v1.2.3