From 7581ac635f49d5dfc1b2aebe3573e8fa2b334014 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Tue, 27 Dec 2016 16:23:58 +0100 Subject: [PATCH] Fetch certifiers only when needed (#3978) * Fetch certifiers once + on new Logs * Linting * BadgeReg First Query * Rightly fetching badges on page change * PR Grumbles * Only fetch certifiers onces --- js/src/contracts/abi/badgereg.json | 2 +- js/src/contracts/badgereg.js | 16 +- .../providers/certifications/middleware.js | 176 ++++++++++++------ 3 files changed, 133 insertions(+), 61 deletions(-) diff --git a/js/src/contracts/abi/badgereg.json b/js/src/contracts/abi/badgereg.json index 3d18ba393..6ae56393b 100644 --- a/js/src/contracts/abi/badgereg.json +++ b/js/src/contracts/abi/badgereg.json @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"fromName","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"badgeCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"badge","outputs":[{"name":"addr","type":"address"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"fromName","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"badgeCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_newAddr","type":"address"}],"name":"setAddress","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"badge","outputs":[{"name":"addr","type":"address"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index b3d949fed..370236a26 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -31,9 +31,13 @@ export default class BadgeReg { this.contracts = {}; // by name } + getContract () { + return this._registry.getContract('badgereg'); + } + certifierCount () { - return this._registry - .getContract('badgereg') + return this + .getContract() .then((badgeReg) => { return badgeReg.instance.badgeCount.call({}, []) .then((count) => count.valueOf()); @@ -45,8 +49,8 @@ export default class BadgeReg { return Promise.resolve(this.certifiers[id]); } - return this._registry - .getContract('badgereg') + return this + .getContract() .then((badgeReg) => { return badgeReg.instance.badge.call({}, [ id ]); }) @@ -70,8 +74,8 @@ export default class BadgeReg { } fetchMeta (id) { - return this._registry - .getContract('badgereg') + return this + .getContract() .then((badgeReg) => { return Promise.all([ badgeReg.instance.meta.call({}, [id, 'TITLE']), diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 840bd0b60..c81aa7e67 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -14,10 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { uniq } from 'lodash'; -import debounce from 'debounce'; +import { uniq, range, debounce } from 'lodash'; -import ABI from '~/contracts/abi/certifier.json'; +import CertifierABI from '~/contracts/abi/certifier.json'; import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { addCertification, removeCertification } from './actions'; @@ -32,6 +31,7 @@ const updatableFilter = (api, onFilter) => { api.eth.uninstallFilter(filterId); }); } + filter = (filter || Promise.resolve()) .then(() => api.eth.newFilter({ fromBlock: 0, @@ -46,7 +46,10 @@ const updatableFilter = (api, onFilter) => { .catch((err) => { console.error('Failed to create certifications filter:', err); }); + + return filter; }; + return update; }; @@ -54,12 +57,52 @@ export default class CertificationsMiddleware { toMiddleware () { const api = Contracts.get()._api; const badgeReg = Contracts.get().badgeReg; - const contract = new Contract(api, ABI); + + const contract = new Contract(api, CertifierABI); const Confirmed = contract.events.find((e) => e.name === 'Confirmed'); const Revoked = contract.events.find((e) => e.name === 'Revoked'); return (store) => { - const onLogs = (logs) => { + let certifiers = []; + let addresses = []; + let filterChanged = false; + let filter = null; + let badgeRegFilter = null; + let fetchCertifiersPromise = null; + + const updateFilter = updatableFilter(api, (filterId) => { + filterChanged = true; + filter = filterId; + }); + + const badgeRegUpdateFilter = updatableFilter(api, (filterId) => { + filterChanged = true; + badgeRegFilter = filterId; + }); + + badgeReg + .getContract() + .then((badgeRegContract) => { + return badgeRegUpdateFilter(badgeRegContract.address, [ + badgeRegContract.instance.Registered.signature, + badgeRegContract.instance.Unregistered.signature, + badgeRegContract.instance.MetaChanged.signature, + badgeRegContract.instance.AddressChanged.signature + ]); + }) + .then(() => { + shortFetchChanges(); + + api.subscribe('eth_blockNumber', (err) => { + if (err) { + return; + } + + fetchChanges(); + }); + }); + + function onLogs (logs) { logs = contract.parseEventLogs(logs); logs.forEach((log) => { const certifier = certifiers.find((c) => c.address === log.address); @@ -74,81 +117,106 @@ export default class CertificationsMiddleware { store.dispatch(addCertification(log.params.who.value, id, name, title, icon)); } }); - }; + } - let filter = null; + function onBadgeRegLogs (logs) { + const ids = logs.map((log) => log.params.id.value.toNumber()); + return fetchCertifiers(uniq(ids)); + } - const onFilter = (filterId) => { - filter = filterId; - api.eth.getFilterLogs(filterId) - .then(onLogs) + function _fetchChanges () { + const method = filterChanged + ? 'getFilterLogs' + : 'getFilterChanges'; + + filterChanged = false; + + api.eth[method](badgeRegFilter) + .then(onBadgeRegLogs) .catch((err) => { - console.error('Failed to fetch certifier events:', err); - }); - }; - - const fetchChanges = debounce(() => { - api.eth.getFilterChanges(filter) + console.error('Failed to fetch badge reg events:', err); + }) + .then(() => api.eth[method](filter)) .then(onLogs) .catch((err) => { console.error('Failed to fetch new certifier events:', err); }); - }, 10 * 1000, true); + } - api.subscribe('eth_blockNumber', (err) => { - if (err) { - return; + const shortFetchChanges = debounce(_fetchChanges, 0.5 * 1000, { leading: true }); + const fetchChanges = debounce(shortFetchChanges, 10 * 1000, { leading: true }); + + function fetchConfirmedEvents () { + return updateFilter(certifiers.map((c) => c.address), [ + [ Confirmed.signature, Revoked.signature ], + addresses + ]).then(() => shortFetchChanges()); + } + + function fetchCertifiers (ids = []) { + if (fetchCertifiersPromise) { + return fetchCertifiersPromise; } - fetchChanges(); - }); + let fetchEvents = false; - const updateFilter = updatableFilter(api, onFilter); - let certifiers = []; - let accounts = []; // these are addresses + const idsPromise = (certifiers.length === 0) + ? badgeReg.certifierCount().then((count) => { + return range(count); + }) + : Promise.resolve(ids); - const fetchConfirmedEvents = () => { - updateFilter(certifiers.map((c) => c.address), [ - [ Confirmed.signature, Revoked.signature ], - accounts - ]); - }; + fetchCertifiersPromise = idsPromise + .then((ids) => { + const promises = ids.map((id) => { + return badgeReg.fetchCertifier(id) + .then((cert) => { + if (!certifiers.some((c) => c.id === cert.id)) { + certifiers = certifiers.concat(cert); + fetchEvents = true; + } + }) + .catch((err) => { + if (/does not exist/.test(err.toString())) { + return console.warn(err.toString()); + } + + console.warn(`Could not fetch certifier ${id}:`, err); + }); + }); + + return Promise + .all(promises) + .then(() => { + fetchCertifiersPromise = null; + + if (fetchEvents) { + return fetchConfirmedEvents(); + } + }); + }); + + return fetchCertifiersPromise; + } return (next) => (action) => { switch (action.type) { case 'fetchCertifiers': - badgeReg.certifierCount().then((count) => { - new Array(+count).fill(null).forEach((_, id) => { - badgeReg.fetchCertifier(id) - .then((cert) => { - if (!certifiers.some((c) => c.id === cert.id)) { - certifiers = certifiers.concat(cert); - fetchConfirmedEvents(); - } - }) - .catch((err) => { - if (/does not exist/.test(err.toString())) { - return console.warn(err.toString()); - } - - console.warn(`Could not fetch certifier ${id}:`, err); - }); - }); - }); + fetchConfirmedEvents(); break; case 'fetchCertifications': const { address } = action; - if (!accounts.includes(address)) { - accounts = accounts.concat(address); + if (!addresses.includes(address)) { + addresses = addresses.concat(address); fetchConfirmedEvents(); } break; case 'setVisibleAccounts': - const { addresses } = action; - accounts = uniq(accounts.concat(addresses)); + const _addresses = action.addresses || []; + addresses = uniq(addresses.concat(_addresses)); fetchConfirmedEvents(); break;