aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--circle.yml2
-rw-r--r--package-lock.json2
-rw-r--r--package.json2
-rw-r--r--src/0x.ts14
-rw-r--r--src/order_watcher/event_watcher.ts48
-rw-r--r--src/order_watcher/order_state_watcher.ts46
-rw-r--r--src/types.ts7
-rw-r--r--test/event_watcher_test.ts12
-rw-r--r--test/order_state_watcher_test.ts6
-rw-r--r--test/token_wrapper_test.ts4
11 files changed, 96 insertions, 51 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 156fb1222..029144b5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+v0.22.6 - _November 10, 2017_
+------------------------
+ * Add a timeout parameter to transaction awaiting (#206)
+
v0.22.5 - _November 7, 2017_
------------------------
* Re-publish v0.22.4 to fix publishing issue
diff --git a/circle.yml b/circle.yml
index 5a8d340f0..3dc00bd03 100644
--- a/circle.yml
+++ b/circle.yml
@@ -2,7 +2,7 @@ machine:
node:
version: 6.5.0
environment:
- CONTRACTS_COMMIT_HASH: '35053f9'
+ CONTRACTS_COMMIT_HASH: '78fe8dd'
PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
dependencies:
diff --git a/package-lock.json b/package-lock.json
index c3c4962e7..6b4a97d87 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "0x.js",
- "version": "0.22.5",
+ "version": "0.22.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 4b23bf6e1..e7e21bdce 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "0x.js",
- "version": "0.22.5",
+ "version": "0.22.6",
"description": "A javascript library for interacting with the 0x protocol",
"keywords": [
"0x.js",
diff --git a/src/0x.ts b/src/0x.ts
index 59688e948..a1841eaa8 100644
--- a/src/0x.ts
+++ b/src/0x.ts
@@ -285,13 +285,24 @@ export class ZeroEx {
* Waits for a transaction to be mined and returns the transaction receipt.
* @param txHash Transaction hash
* @param pollingIntervalMs How often (in ms) should we check if the transaction is mined.
+ * @param timeoutMs How long (in ms) to poll for transaction mined until aborting.
* @return Transaction receipt with decoded log args.
*/
public async awaitTransactionMinedAsync(
- txHash: string, pollingIntervalMs: number = 1000): Promise<TransactionReceiptWithDecodedLogs> {
+ txHash: string, pollingIntervalMs = 1000, timeoutMs?: number): Promise<TransactionReceiptWithDecodedLogs> {
+ let timeoutExceeded = false;
+ if (timeoutMs) {
+ setTimeout(() => timeoutExceeded = true, timeoutMs);
+ }
+
const txReceiptPromise = new Promise(
(resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
const intervalId = intervalUtils.setAsyncExcludingInterval(async () => {
+ if (timeoutExceeded) {
+ intervalUtils.clearAsyncExcludingInterval(intervalId);
+ return reject(ZeroExError.TransactionMiningTimeout);
+ }
+
const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
if (!_.isNull(transactionReceipt)) {
intervalUtils.clearAsyncExcludingInterval(intervalId);
@@ -307,6 +318,7 @@ export class ZeroEx {
}
}, pollingIntervalMs);
});
+
return txReceiptPromise;
}
/*
diff --git a/src/order_watcher/event_watcher.ts b/src/order_watcher/event_watcher.ts
index 2a1b6dacf..c9e72281c 100644
--- a/src/order_watcher/event_watcher.ts
+++ b/src/order_watcher/event_watcher.ts
@@ -1,42 +1,58 @@
import * as Web3 from 'web3';
import * as _ from 'lodash';
import {Web3Wrapper} from '../web3_wrapper';
-import {BlockParamLiteral, EventCallback, EventWatcherCallback} from '../types';
+import {
+ BlockParamLiteral,
+ EventCallback,
+ EventWatcherCallback,
+ ZeroExError,
+} from '../types';
import {AbiDecoder} from '../utils/abi_decoder';
import {intervalUtils} from '../utils/interval_utils';
import {assert} from '../utils/assert';
+import {utils} from '../utils/utils';
const DEFAULT_EVENT_POLLING_INTERVAL = 200;
+enum LogEventState {
+ Removed,
+ Added,
+}
+
+/*
+ * The EventWatcher watches for blockchain events at the specified block confirmation
+ * depth.
+ */
export class EventWatcher {
private _web3Wrapper: Web3Wrapper;
private _pollingIntervalMs: number;
private _intervalIdIfExists?: NodeJS.Timer;
private _lastEvents: Web3.LogEntry[] = [];
- private _callbackIfExistsAsync?: EventWatcherCallback;
private _numConfirmations: number;
constructor(web3Wrapper: Web3Wrapper, pollingIntervalMs: undefined|number, numConfirmations: number) {
this._web3Wrapper = web3Wrapper;
this._numConfirmations = numConfirmations;
this._pollingIntervalMs = _.isUndefined(pollingIntervalMs) ?
- DEFAULT_EVENT_POLLING_INTERVAL :
- pollingIntervalMs;
+ DEFAULT_EVENT_POLLING_INTERVAL :
+ pollingIntervalMs;
}
public subscribe(callback: EventWatcherCallback): void {
assert.isFunction('callback', callback);
- this._callbackIfExistsAsync = callback;
+ if (!_.isUndefined(this._intervalIdIfExists)) {
+ throw new Error(ZeroExError.SubscriptionAlreadyPresent);
+ }
this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
- this._pollForMempoolEventsAsync.bind(this), this._pollingIntervalMs,
+ this._pollForBlockchainEventsAsync.bind(this, callback), this._pollingIntervalMs,
);
}
public unsubscribe(): void {
- delete this._callbackIfExistsAsync;
this._lastEvents = [];
if (!_.isUndefined(this._intervalIdIfExists)) {
intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists);
+ delete this._intervalIdIfExists;
}
}
- private async _pollForMempoolEventsAsync(): Promise<void> {
+ private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> {
const pendingEvents = await this._getEventsAsync();
if (pendingEvents.length === 0) {
// HACK: Sometimes when node rebuilds the pending block we get back the empty result.
@@ -46,10 +62,8 @@ export class EventWatcher {
}
const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify);
const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify);
- let isRemoved = true;
- await this._emitDifferencesAsync(removedEvents, isRemoved);
- isRemoved = false;
- await this._emitDifferencesAsync(newEvents, isRemoved);
+ await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback);
+ await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback);
this._lastEvents = pendingEvents;
}
private async _getEventsAsync(): Promise<Web3.LogEntry[]> {
@@ -67,14 +81,16 @@ export class EventWatcher {
const events = await this._web3Wrapper.getLogsAsync(eventFilter);
return events;
}
- private async _emitDifferencesAsync(logs: Web3.LogEntry[], isRemoved: boolean): Promise<void> {
+ private async _emitDifferencesAsync(
+ logs: Web3.LogEntry[], logEventState: LogEventState, callback: EventWatcherCallback,
+ ): Promise<void> {
for (const log of logs) {
const logEvent = {
- removed: isRemoved,
+ removed: logEventState === LogEventState.Removed,
...log,
};
- if (!_.isUndefined(this._callbackIfExistsAsync)) {
- await this._callbackIfExistsAsync(logEvent);
+ if (!_.isUndefined(this._intervalIdIfExists)) {
+ await callback(logEvent);
}
}
}
diff --git a/src/order_watcher/order_state_watcher.ts b/src/order_watcher/order_state_watcher.ts
index 303ec8bd3..4866f8409 100644
--- a/src/order_watcher/order_state_watcher.ts
+++ b/src/order_watcher/order_state_watcher.ts
@@ -35,9 +35,15 @@ interface OrderByOrderHash {
[orderHash: string]: SignedOrder;
}
+/**
+ * This class includes all the functionality related to watching a set of orders
+ * for potential changes in order validity/fillability. The orderWatcher notifies
+ * the subscriber of these changes so that a final decison can be made on whether
+ * the order should be deemed invalid.
+ */
export class OrderStateWatcher {
- private _orders: OrderByOrderHash;
- private _dependentOrderHashes: DependentOrderHashes;
+ private _orderByOrderHash: OrderByOrderHash = {};
+ private _dependentOrderHashes: DependentOrderHashes = {};
private _web3Wrapper: Web3Wrapper;
private _callbackIfExistsAsync?: OnOrderStateChangeCallback;
private _eventWatcher: EventWatcher;
@@ -49,8 +55,6 @@ export class OrderStateWatcher {
config?: OrderStateWatcherConfig,
) {
this._web3Wrapper = web3Wrapper;
- this._orders = {};
- this._dependentOrderHashes = {};
const eventPollingIntervalMs = _.isUndefined(config) ? undefined : config.pollingIntervalMs;
this._numConfirmations = _.isUndefined(config) ?
DEFAULT_NUM_CONFIRMATIONS
@@ -62,14 +66,15 @@ export class OrderStateWatcher {
this._orderStateUtils = orderStateUtils;
}
/**
- * Add an order to the orderStateWatcher
+ * Add an order to the orderStateWatcher. Before the order is added, it's
+ * signature is verified.
* @param signedOrder The order you wish to start watching.
*/
public addOrder(signedOrder: SignedOrder): void {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
- this._orders[orderHash] = signedOrder;
+ this._orderByOrderHash[orderHash] = signedOrder;
this.addToDependentOrderHashes(signedOrder, orderHash);
}
/**
@@ -78,22 +83,18 @@ export class OrderStateWatcher {
*/
public removeOrder(orderHash: string): void {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
- const signedOrder = this._orders[orderHash];
+ const signedOrder = this._orderByOrderHash[orderHash];
if (_.isUndefined(signedOrder)) {
return; // noop
}
- delete this._orders[orderHash];
- this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].delete(orderHash);
- // We currently do not remove the maker/makerToken keys from the mapping when all orderHashes removed
+ delete this._orderByOrderHash[orderHash];
+ this.removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash);
}
/**
* Starts an orderStateWatcher subscription. The callback will be called every time a watched order's
* backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order.
* @param callback Receives the orderHash of the order that should be re-validated, together
* with all the order-relevant blockchain state needed to re-validate the order.
- * @param numConfirmations Number of confirmed blocks deeps you want to run the orderWatcher from. Passing
- * is 0 will watch the backing node's mempool, 3 will emit events when blockchain
- * state relevant to a watched order changed 3 blocks ago.
*/
public subscribe(callback: OnOrderStateChangeCallback): void {
assert.isFunction('callback', callback);
@@ -112,10 +113,12 @@ export class OrderStateWatcher {
}
private async _onEventWatcherCallbackAsync(log: LogEvent): Promise<void> {
const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log);
- const isDecodedLog = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event);
- if (!isDecodedLog) {
+ const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event);
+ if (!isLogDecoded) {
return; // noop
}
+ // Unfortunately blockNumber is returned as a hex-encoded string, so we
+ // convert it to a number here.
const blockNumberBuff = ethUtil.toBuffer(maybeDecodedLog.blockNumber);
const blockNumber = ethUtil.bufferToInt(blockNumberBuff);
@@ -147,7 +150,7 @@ export class OrderStateWatcher {
case ExchangeEvents.LogFill:
case ExchangeEvents.LogCancel:
const orderHash = decodedLog.args.orderHash;
- const isOrderWatched = !_.isUndefined(this._orders[orderHash]);
+ const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
if (isOrderWatched) {
await this._emitRevalidateOrdersAsync([orderHash], blockNumber);
}
@@ -169,7 +172,7 @@ export class OrderStateWatcher {
};
for (const orderHash of orderHashes) {
- const signedOrder = this._orders[orderHash] as SignedOrder;
+ const signedOrder = this._orderByOrderHash[orderHash] as SignedOrder;
const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder, methodOpts);
if (!_.isUndefined(this._callbackIfExistsAsync)) {
await this._callbackIfExistsAsync(orderState);
@@ -187,4 +190,13 @@ export class OrderStateWatcher {
}
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash);
}
+ private removeFromDependentOrderHashes(makerAddress: string, makerTokenAddress: string, orderHash: string) {
+ this._dependentOrderHashes[makerAddress][makerTokenAddress].delete(orderHash);
+ if (this._dependentOrderHashes[makerAddress][makerTokenAddress].size === 0) {
+ delete this._dependentOrderHashes[makerAddress][makerTokenAddress];
+ }
+ if (_.isEmpty(this._dependentOrderHashes[makerAddress])) {
+ delete this._dependentOrderHashes[makerAddress];
+ }
+ }
}
diff --git a/src/types.ts b/src/types.ts
index 13867dac8..160b71fda 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -17,6 +17,7 @@ export enum ZeroExError {
NoNetworkId = 'NO_NETWORK_ID',
SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND',
SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT',
+ TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT',
}
export enum InternalZeroExError {
@@ -508,6 +509,6 @@ export interface OrderStateInvalid {
export type OrderState = OrderStateValid|OrderStateInvalid;
-export type OnOrderStateChangeCallback = (
- orderState: OrderState,
-) => void;
+export type OnOrderStateChangeCallbackSync = (orderState: OrderState) => void;
+export type OnOrderStateChangeCallbackAsync = (orderState: OrderState) => Promise<void>;
+export type OnOrderStateChangeCallback = OnOrderStateChangeCallbackAsync|OnOrderStateChangeCallbackSync;
diff --git a/test/event_watcher_test.ts b/test/event_watcher_test.ts
index 36153c207..98dab93b5 100644
--- a/test/event_watcher_test.ts
+++ b/test/event_watcher_test.ts
@@ -24,7 +24,7 @@ describe('EventWatcher', () => {
let eventWatcher: EventWatcher;
let web3Wrapper: Web3Wrapper;
const numConfirmations = 0;
- const logA = {
+ const logA: Web3.LogEntry = {
address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5',
blockHash: null,
blockNumber: null,
@@ -32,9 +32,9 @@ describe('EventWatcher', () => {
logIndex: null,
topics: [],
transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17',
- transactionIndex: null,
+ transactionIndex: 0,
};
- const logB = {
+ const logB: Web3.LogEntry = {
address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819',
blockHash: null,
blockNumber: null,
@@ -42,9 +42,9 @@ describe('EventWatcher', () => {
logIndex: null,
topics: [ '0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567' ],
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
- transactionIndex: null,
+ transactionIndex: 0,
};
- const logC = {
+ const logC: Web3.LogEntry = {
address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5',
blockHash: null,
blockNumber: null,
@@ -52,7 +52,7 @@ describe('EventWatcher', () => {
logIndex: null,
topics: [ '0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567' ],
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
- transactionIndex: null,
+ transactionIndex: 0,
};
before(async () => {
web3 = web3Factory.create();
diff --git a/test/order_state_watcher_test.ts b/test/order_state_watcher_test.ts
index 269956400..d8ac0af49 100644
--- a/test/order_state_watcher_test.ts
+++ b/test/order_state_watcher_test.ts
@@ -67,17 +67,17 @@ describe('OrderStateWatcher', () => {
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
zeroEx.orderStateWatcher.addOrder(signedOrder);
- expect((zeroEx.orderStateWatcher as any)._orders).to.include({
+ expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({
[orderHash]: signedOrder,
});
let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash);
zeroEx.orderStateWatcher.removeOrder(orderHash);
- expect((zeroEx.orderStateWatcher as any)._orders).to.not.include({
+ expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({
[orderHash]: signedOrder,
});
dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
- expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.not.have.keys(orderHash);
+ expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined();
});
it('should no-op when removing a non-existing order', async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
diff --git a/test/token_wrapper_test.ts b/test/token_wrapper_test.ts
index 2f6f126c1..23020c47a 100644
--- a/test/token_wrapper_test.ts
+++ b/test/token_wrapper_test.ts
@@ -162,7 +162,7 @@ describe('TokenWrapper', () => {
const token = tokens[0];
const ownerAddress = coinbase;
const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress);
- const expectedBalance = new BigNumber('100000000000000000000000000');
+ const expectedBalance = new BigNumber('1000000000000000000000000000');
return expect(balance).to.be.bignumber.equal(expectedBalance);
});
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
@@ -190,7 +190,7 @@ describe('TokenWrapper', () => {
const token = tokens[0];
const ownerAddress = coinbase;
const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress);
- const expectedBalance = new BigNumber('100000000000000000000000000');
+ const expectedBalance = new BigNumber('1000000000000000000000000000');
return expect(balance).to.be.bignumber.equal(expectedBalance);
});
});