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

Commit

Permalink
Smarter Tokens fetching (#3546)
Browse files Browse the repository at this point in the history
* Don't auto-subscribe for contracts #3240

* Smarter Balance : don't re-instantiate contracts, fetch tokens #3240

* Smarter Balance Tokens fetching (update image when needed) #3240

* Attaching to TokenReg Events instead of fetching for each block #3240

* Unsubscribe from shapeshit... #3240

* Unsubscribe from EthFilter if used once (resolved) #3240

* Remove warning

* PR review fix

* Typo

* Better contract JS API : one subscribe for all #3546

* Fixed test
  • Loading branch information
ngotchac authored and jacogr committed Nov 23, 2016
1 parent a969c00 commit 33dd491
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 150 deletions.
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
};
}
164 changes: 131 additions & 33 deletions js/src/api/contract/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ export default class Contract {
this._instance[fn.signature] = fn;
});

this._sendSubscriptionChanges();
this._subscribedToPendings = false;
this._pendingsSubscriptionId = null;

this._subscribedToBlock = false;
this._blockSubscriptionId = null;
}

get address () {
Expand Down Expand Up @@ -239,44 +243,71 @@ export default class Contract {
return event;
}

subscribe (eventName = null, options = {}, callback) {
return new Promise((resolve, reject) => {
let event = null;
_findEvent (eventName = null) {
const event = eventName
? this._events.find((evt) => evt.name === eventName)
: null;

if (eventName) {
event = this._events.find((evt) => evt.name === eventName);
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)`);
}

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;
}
}
return event;
}

return this._subscribe(event, options, callback).then(resolve).catch(reject);
_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
});

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++;
const options = Object.assign({}, _options, {
address: this._address,
topics: [event ? event.signature : null]
});
const { skipInitFetch } = _options;
delete _options['skipInitFetch'];

return this._api.eth
.newFilter(options)
return this
._createEthFilter(event, _options)
.then((filterId) => {
this._subscriptions[subscriptionId] = {
options: _options,
callback,
filterId
};

if (skipInitFetch) {
this._subscribeToChanges();
return subscriptionId;
}

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 @@ -285,19 +316,89 @@ export default class Contract {
unsubscribe (subscriptionId) {
return this._api.eth
.uninstallFilter(this._subscriptions[subscriptionId].filterId)
.then(() => {
delete this._subscriptions[subscriptionId];
})
.catch((error) => {
console.error('unsubscribe', error);
})
.then(() => {
delete this._subscriptions[subscriptionId];
this._unsubscribeFromChanges();
});
}

_sendSubscriptionChanges = () => {
_subscribeToChanges = () => {
const subscriptions = Object.values(this._subscriptions);
const timeout = () => setTimeout(this._sendSubscriptionChanges, 1000);

Promise
const pendingSubscriptions = subscriptions
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');

const otherSubscriptions = subscriptions
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));

if (pendingSubscriptions.length > 0 && !this._subscribedToPendings) {
this._subscribedToPendings = true;
this._subscribeToPendings();
}

if (otherSubscriptions.length > 0 && !this._subscribedToBlock) {
this._subscribedToBlock = true;
this._subscribeToBlock();
}
}

_unsubscribeFromChanges = () => {
const subscriptions = Object.values(this._subscriptions);

const pendingSubscriptions = subscriptions
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');

const otherSubscriptions = subscriptions
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));

if (pendingSubscriptions.length === 0 && this._subscribedToPendings) {
this._subscribedToPendings = false;
clearTimeout(this._pendingsSubscriptionId);
}

if (otherSubscriptions.length === 0 && this._subscribedToBlock) {
this._subscribedToBlock = false;
this._api.unsubscribe(this._blockSubscriptionId);
}
}

_subscribeToBlock = () => {
this._api
.subscribe('eth_blockNumber', (error) => {
if (error) {
console.error('::_subscribeToBlock', error, error && error.stack);
}

const subscriptions = Object.values(this._subscriptions)
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));

this._sendSubscriptionChanges(subscriptions);
})
.then((blockSubId) => {
this._blockSubscriptionId = blockSubId;
})
.catch((e) => {
console.error('::_subscribeToBlock', e, e && e.stack);
});
}

_subscribeToPendings = () => {
const subscriptions = Object.values(this._subscriptions)
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');

const timeout = () => setTimeout(() => this._subscribeFromPendings(), 1000);

this._sendSubscriptionChanges(subscriptions)
.then(() => {
this._pendingsSubscriptionId = timeout();
});
}

_sendSubscriptionChanges = (subscriptions) => {
return Promise
.all(
subscriptions.map((subscription) => {
return this._api.eth.getFilterChanges(subscription.filterId);
Expand All @@ -315,12 +416,9 @@ export default class Contract {
console.error('_sendSubscriptionChanges', error);
}
});

timeout();
})
.catch((error) => {
console.error('_sendSubscriptionChanges', error);
timeout();
});
}
}
15 changes: 10 additions & 5 deletions js/src/api/contract/contract.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ describe('api/contract/Contract', () => {
]
}
];

const logs = [{
address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
Expand All @@ -450,6 +451,7 @@ describe('api/contract/Contract', () => {
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: '0x0'
}];

const parsed = [{
address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
Expand All @@ -466,11 +468,13 @@ describe('api/contract/Contract', () => {
sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' }
},
topics: [
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', '0x0000000000000000000000000000000000000000000000000001000000004fe0'
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
],
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: new BigNumber(0)
}];

let contract;

beforeEach(() => {
Expand All @@ -496,34 +500,35 @@ describe('api/contract/Contract', () => {
scope = mockHttp([
{ method: 'eth_newFilter', reply: { result: '0x123' } },
{ method: 'eth_getFilterLogs', reply: { result: logs } },
{ method: 'eth_getFilterChanges', reply: { result: logs } },
{ method: 'eth_newFilter', reply: { result: '0x123' } },
{ method: 'eth_getFilterLogs', reply: { result: logs } }
]);
cbb = sinon.stub();
cbe = sinon.stub();

return contract.subscribe('Message', {}, cbb);
return contract.subscribe('Message', { toBlock: 'pending' }, cbb);
});

it('sets the subscriptionId returned', () => {
return contract
.subscribe('Message', {}, cbe)
.subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => {
expect(subscriptionId).to.equal(1);
});
});

it('creates a new filter and retrieves the logs on it', () => {
return contract
.subscribe('Message', {}, cbe)
.subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => {
expect(scope.isDone()).to.be.true;
});
});

it('returns the logs to the callback', () => {
return contract
.subscribe('Message', {}, cbe)
.subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => {
expect(cbe).to.have.been.calledWith(null, parsed);
});
Expand Down
16 changes: 11 additions & 5 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,14 +51,19 @@ export function inHash (hash) {
return inHex(hash);
}

export function pad (input, length) {
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);
}
// while (topics.length < 4) {
// topics.push(null);
// }

return topics;
}
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
Loading

0 comments on commit 33dd491

Please sign in to comment.