Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Smarter Tokens fetching #3546

Merged
merged 13 commits into from
Nov 23, 2016
26 changes: 22 additions & 4 deletions js/src/3rdparty/shapeshift/shapeshift.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

export default function (rpc) {
const subscriptions = [];
let subscriptions = [];
let pollStatusIntervalId = null;

function getCoins () {
return rpc.get('getcoins');
Expand Down Expand Up @@ -45,6 +46,24 @@ export default function (rpc) {
callback,
idx
});

// Only poll if there are subscriptions...
if (!pollStatusIntervalId) {
pollStatusIntervalId = setInterval(_pollStatus, 2000);
}
}

function unsubscribe (depositAddress) {
const newSubscriptions = []
.concat(subscriptions)
.filter((sub) => sub.depositAddress !== depositAddress);

subscriptions = newSubscriptions;

if (subscriptions.length === 0) {
clearInterval(pollStatusIntervalId);
pollStatusIntervalId = null;
}
}

function _getSubscriptionStatus (subscription) {
Expand Down Expand Up @@ -81,13 +100,12 @@ export default function (rpc) {
subscriptions.forEach(_getSubscriptionStatus);
}

setInterval(_pollStatus, 2000);

return {
getCoins,
getMarketInfo,
getStatus,
shift,
subscribe
subscribe,
unsubscribe
};
}
114 changes: 92 additions & 22 deletions js/src/api/contract/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export default class Contract {
this._instance[fn.signature] = fn;
});

this._sendSubscriptionChanges();
this._subscribedToChanges = false;
this._subscribedToChangesId = null;
}

get address () {
Expand Down Expand Up @@ -232,51 +233,102 @@ export default class Contract {
return this._subscribe(event, options, callback);
};

event.register = (options = {}, callback) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, not 100% sure I get the difference between event.register and event.subscribe - not from an API user POV.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The register method actually calls eth_getFilterChanges every second, which is unnecessary for getting token meta data updates (will be max a change for every block). We could actually call it every minute or so if needed...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, still unsure about how to explain this since it sits on the API interface. Obviously for any filter when pending is not included (toBlock) just calling the new filter on new blocks is more than enough. (Something to probably think about and enhance)

The case here is that we have 2 external APIs now - how does anybody decide what to use, seems we are making the external interface very complex now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess for filters querying pending blocks too it could be useful to query every second (not sure though). True from the API perspective, but didn't want to change the whole behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. As a dapp developer - what do I use? subscribe or register? As it stands the naming is almost interchangeable. We want things simple so people don't need to overthink things, but still allow them to do anything.

So to accomplish this, would have -

  1. not have yet another external interface event
  2. only polled for rapid changes when pending is the toBlock
  3. poll for filter changes on new blocks only when latest is toBlock
  4. when we poll for tokens, specify latest

... all wrapped in contracts.js. This way anybody using the API doesn't need to care about times and polling, the API just does the right thing, as asked. (Plus no confusion)

Copy link
Contributor Author

@ngotchac ngotchac Nov 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this. First I wanted to call it manualSubscribe or something like that. Will fix

return this._register(event, options, callback);
};

event.unsubscribe = (subscriptionId) => {
return this.unsubscribe(subscriptionId);
};

return event;
}

subscribe (eventName = null, options = {}, callback) {
return new Promise((resolve, reject) => {
let event = null;
_register (event = null, _options, callback) {
return this
._createEthFilter(event, _options)
.then((filterId) => {
return {
fetch: () => {
return this._api.eth
.getFilterChanges(filterId)
.then((logs) => {
if (!logs || !logs.length) {
return;
}

try {
callback(null, this.parseEventLogs(logs));
} catch (error) {
callback(error);
}
});
},

unsubscribe: () => {
return this._api.eth.uninstallFilter(filterId);
}
};
});
}

if (eventName) {
event = this._events.find((evt) => evt.name === eventName);
_findEvent (eventName = null) {
const event = eventName
? this._events.find((evt) => evt.name === eventName)
: null;

if (!event) {
const events = this._events.map((evt) => evt.name).join(', ');
reject(new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`));
return;
}
}
if (eventName && !event) {
const events = this._events.map((evt) => evt.name).join(', ');
throw new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`);
}

return this._subscribe(event, options, callback).then(resolve).catch(reject);
});
return event;
}

_subscribe (event = null, _options, callback) {
const subscriptionId = nextSubscriptionId++;
_createEthFilter (event = null, _options) {
const optionTopics = _options.topics || [];
const signature = event && event.signature || null;

// If event provided, remove the potential event signature
// as the first element of the topics
const topics = signature
? [ signature ].concat(optionTopics.filter((t, idx) => idx > 0 || t !== signature))
: optionTopics;

const options = Object.assign({}, _options, {
address: this._address,
topics: [event ? event.signature : null]
topics
});

return this._api.eth
.newFilter(options)
return this._api.eth.newFilter(options);
}

subscribe (eventName = null, options = {}, callback) {
try {
const event = this._findEvent(eventName);
return this._subscribe(event, options, callback);
} catch (e) {
return Promise.reject(e);
}
}

_subscribe (event = null, _options, callback) {
const subscriptionId = nextSubscriptionId++;

return this
._createEthFilter(event, _options)
.then((filterId) => {
return this._api.eth
.getFilterLogs(filterId)
.then((logs) => {
callback(null, this.parseEventLogs(logs));

this._subscriptions[subscriptionId] = {
options,
callback,
filterId
};

this._subscribeToChanges();
return subscriptionId;
});
});
Expand All @@ -287,12 +339,30 @@ export default class Contract {
.uninstallFilter(this._subscriptions[subscriptionId].filterId)
.then(() => {
delete this._subscriptions[subscriptionId];

if (Object.keys(this._subscriptions).length === 0) {
this._unsubscribeToChanges();
}
})
.catch((error) => {
console.error('unsubscribe', error);
});
}

_subscribeToChanges = () => {
if (this._subscribedToChanges) {
return;
}

this._subscribedToChanges = true;
this._sendSubscriptionChanges();
}

_unsubscribeToChanges = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsubscribeFromChanges? (;

this._subscribedToChanges = false;
clearTimeout(this._subscribedToChangesId);
}

_sendSubscriptionChanges = () => {
const subscriptions = Object.values(this._subscriptions);
const timeout = () => setTimeout(this._sendSubscriptionChanges, 1000);
Expand All @@ -316,11 +386,11 @@ export default class Contract {
}
});

timeout();
this._subscribedToChangesId = timeout();
})
.catch((error) => {
console.error('_sendSubscriptionChanges', error);
timeout();
this._subscribedToChangesId = timeout();
});
}
}
10 changes: 8 additions & 2 deletions js/src/api/format/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

import BigNumber from 'bignumber.js';
import { range } from 'lodash';

import { isArray, isHex, isInstanceOf, isString } from '../util/types';

Expand Down Expand Up @@ -50,10 +51,15 @@ export function inHash (hash) {
return inHex(hash);
}

export function pad (input, length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have this function already? (Seems duplicated)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pads the topics on the right

const value = inHex(input).substr(2, length * 2);
return '0x' + value + range(length * 2 - value.length).map(() => '0').join('');
}

export function inTopics (_topics) {
let topics = (_topics || [])
.filter((topic) => topic)
.map(inHex);
.filter((topic) => topic === null || topic)
.map((topic) => topic === null ? null : pad(topic, 32));

while (topics.length < 4) {
topics.push(null);
Expand Down
4 changes: 4 additions & 0 deletions js/src/api/util/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ export function hex2Ascii (_hex) {

return str;
}

export function asciiToHex (string) {
return '0x' + string.split('').map((s) => s.charCodeAt(0).toString(16)).join('');
}
3 changes: 2 additions & 1 deletion js/src/api/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
import { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
import { bytesToHex, hex2Ascii } from './format';
import { bytesToHex, hex2Ascii, asciiToHex } from './format';
import { fromWei, toWei } from './wei';
import { sha3 } from './sha3';
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
Expand All @@ -31,6 +31,7 @@ export default {
isString,
bytesToHex,
hex2Ascii,
asciiToHex,
createIdentityImg,
decodeCallData,
decodeMethodInput,
Expand Down
37 changes: 27 additions & 10 deletions js/src/contracts/sms-verification.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,35 @@ export const checkIfVerified = (contract, account) => {
};

export const checkIfRequested = (contract, account) => {
let subId = null;
let resolved = false;

return new Promise((resolve, reject) => {
contract.subscribe('Requested', {
fromBlock: 0, toBlock: 'pending'
}, (err, logs) => {
if (err) {
return reject(err);
}
const e = logs.find((l) => {
return l.type === 'mined' && l.params.who && l.params.who.value === account;
contract
.subscribe('Requested', {
fromBlock: 0, toBlock: 'pending'
}, (err, logs) => {
if (err) {
return reject(err);
}
const e = logs.find((l) => {
return l.type === 'mined' && l.params.who && l.params.who.value === account;
});

resolve(e ? e.transactionHash : false);
resolved = true;

if (subId) {
contract.unsubscribe(subId);
}
})
.then((_subId) => {
subId = _subId;

if (resolved) {
contract.unsubscribe(subId);
}
});
resolve(e ? e.transactionHash : false);
});
});
};

Expand Down
14 changes: 14 additions & 0 deletions js/src/modals/Shapeshift/shapeshift.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ export default class Shapeshift extends Component {
this.retrieveCoins();
}

componentWillUnmount () {
this.unsubscribe();
}

unsubscribe () {
// Unsubscribe from Shapeshit
const { depositAddress } = this.state;
shapeshift.unsubscribe(depositAddress);
}

render () {
const { error, stage } = this.state;

Expand Down Expand Up @@ -205,6 +215,10 @@ export default class Shapeshift extends Component {
console.log('onShift', result);
const depositAddress = result.deposit;

if (this.state.depositAddress) {
this.unsubscribe();
}

shapeshift.subscribe(depositAddress, this.onExchangeInfo);
this.setState({ depositAddress });
})
Expand Down
Loading