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
This commit is contained in:
Nicolas Gotchac 2016-12-27 16:23:58 +01:00 committed by Gav Wood
parent b27c809c64
commit 7581ac635f
3 changed files with 133 additions and 61 deletions

View File

@ -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"}] [{"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"}]

View File

@ -31,9 +31,13 @@ export default class BadgeReg {
this.contracts = {}; // by name this.contracts = {}; // by name
} }
getContract () {
return this._registry.getContract('badgereg');
}
certifierCount () { certifierCount () {
return this._registry return this
.getContract('badgereg') .getContract()
.then((badgeReg) => { .then((badgeReg) => {
return badgeReg.instance.badgeCount.call({}, []) return badgeReg.instance.badgeCount.call({}, [])
.then((count) => count.valueOf()); .then((count) => count.valueOf());
@ -45,8 +49,8 @@ export default class BadgeReg {
return Promise.resolve(this.certifiers[id]); return Promise.resolve(this.certifiers[id]);
} }
return this._registry return this
.getContract('badgereg') .getContract()
.then((badgeReg) => { .then((badgeReg) => {
return badgeReg.instance.badge.call({}, [ id ]); return badgeReg.instance.badge.call({}, [ id ]);
}) })
@ -70,8 +74,8 @@ export default class BadgeReg {
} }
fetchMeta (id) { fetchMeta (id) {
return this._registry return this
.getContract('badgereg') .getContract()
.then((badgeReg) => { .then((badgeReg) => {
return Promise.all([ return Promise.all([
badgeReg.instance.meta.call({}, [id, 'TITLE']), badgeReg.instance.meta.call({}, [id, 'TITLE']),

View File

@ -14,10 +14,9 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { uniq } from 'lodash'; import { uniq, range, debounce } from 'lodash';
import debounce from 'debounce';
import ABI from '~/contracts/abi/certifier.json'; import CertifierABI from '~/contracts/abi/certifier.json';
import Contract from '~/api/contract'; import Contract from '~/api/contract';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { addCertification, removeCertification } from './actions'; import { addCertification, removeCertification } from './actions';
@ -32,6 +31,7 @@ const updatableFilter = (api, onFilter) => {
api.eth.uninstallFilter(filterId); api.eth.uninstallFilter(filterId);
}); });
} }
filter = (filter || Promise.resolve()) filter = (filter || Promise.resolve())
.then(() => api.eth.newFilter({ .then(() => api.eth.newFilter({
fromBlock: 0, fromBlock: 0,
@ -46,7 +46,10 @@ const updatableFilter = (api, onFilter) => {
.catch((err) => { .catch((err) => {
console.error('Failed to create certifications filter:', err); console.error('Failed to create certifications filter:', err);
}); });
return filter;
}; };
return update; return update;
}; };
@ -54,12 +57,52 @@ export default class CertificationsMiddleware {
toMiddleware () { toMiddleware () {
const api = Contracts.get()._api; const api = Contracts.get()._api;
const badgeReg = Contracts.get().badgeReg; 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 Confirmed = contract.events.find((e) => e.name === 'Confirmed');
const Revoked = contract.events.find((e) => e.name === 'Revoked'); const Revoked = contract.events.find((e) => e.name === 'Revoked');
return (store) => { 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 = contract.parseEventLogs(logs);
logs.forEach((log) => { logs.forEach((log) => {
const certifier = certifiers.find((c) => c.address === log.address); const certifier = certifiers.find((c) => c.address === log.address);
@ -74,56 +117,63 @@ export default class CertificationsMiddleware {
store.dispatch(addCertification(log.params.who.value, id, name, title, icon)); 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) => { function _fetchChanges () {
filter = filterId; const method = filterChanged
api.eth.getFilterLogs(filterId) ? 'getFilterLogs'
.then(onLogs) : 'getFilterChanges';
filterChanged = false;
api.eth[method](badgeRegFilter)
.then(onBadgeRegLogs)
.catch((err) => { .catch((err) => {
console.error('Failed to fetch certifier events:', err); console.error('Failed to fetch badge reg events:', err);
}); })
}; .then(() => api.eth[method](filter))
const fetchChanges = debounce(() => {
api.eth.getFilterChanges(filter)
.then(onLogs) .then(onLogs)
.catch((err) => { .catch((err) => {
console.error('Failed to fetch new certifier events:', err); console.error('Failed to fetch new certifier events:', err);
}); });
}, 10 * 1000, true);
api.subscribe('eth_blockNumber', (err) => {
if (err) {
return;
} }
fetchChanges(); const shortFetchChanges = debounce(_fetchChanges, 0.5 * 1000, { leading: true });
}); const fetchChanges = debounce(shortFetchChanges, 10 * 1000, { leading: true });
const updateFilter = updatableFilter(api, onFilter); function fetchConfirmedEvents () {
let certifiers = []; return updateFilter(certifiers.map((c) => c.address), [
let accounts = []; // these are addresses
const fetchConfirmedEvents = () => {
updateFilter(certifiers.map((c) => c.address), [
[ Confirmed.signature, Revoked.signature ], [ Confirmed.signature, Revoked.signature ],
accounts addresses
]); ]).then(() => shortFetchChanges());
}; }
return (next) => (action) => { function fetchCertifiers (ids = []) {
switch (action.type) { if (fetchCertifiersPromise) {
case 'fetchCertifiers': return fetchCertifiersPromise;
badgeReg.certifierCount().then((count) => { }
new Array(+count).fill(null).forEach((_, id) => {
badgeReg.fetchCertifier(id) let fetchEvents = false;
const idsPromise = (certifiers.length === 0)
? badgeReg.certifierCount().then((count) => {
return range(count);
})
: Promise.resolve(ids);
fetchCertifiersPromise = idsPromise
.then((ids) => {
const promises = ids.map((id) => {
return badgeReg.fetchCertifier(id)
.then((cert) => { .then((cert) => {
if (!certifiers.some((c) => c.id === cert.id)) { if (!certifiers.some((c) => c.id === cert.id)) {
certifiers = certifiers.concat(cert); certifiers = certifiers.concat(cert);
fetchConfirmedEvents(); fetchEvents = true;
} }
}) })
.catch((err) => { .catch((err) => {
@ -134,21 +184,39 @@ export default class CertificationsMiddleware {
console.warn(`Could not fetch certifier ${id}:`, err); 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':
fetchConfirmedEvents();
break; break;
case 'fetchCertifications': case 'fetchCertifications':
const { address } = action; const { address } = action;
if (!accounts.includes(address)) { if (!addresses.includes(address)) {
accounts = accounts.concat(address); addresses = addresses.concat(address);
fetchConfirmedEvents(); fetchConfirmedEvents();
} }
break; break;
case 'setVisibleAccounts': case 'setVisibleAccounts':
const { addresses } = action; const _addresses = action.addresses || [];
accounts = uniq(accounts.concat(addresses)); addresses = uniq(addresses.concat(_addresses));
fetchConfirmedEvents(); fetchConfirmedEvents();
break; break;