Fix tokens and badges (#6725)
* Update new token fetching * Working Certifications Monitoring * Update on Certification / Revoke * Fix none-fetched tokens value display * Fix tests
This commit is contained in:
committed by
Jaco Greeff
parent
edbbd42d80
commit
3e7b775961
342
js/src/redux/providers/certifications/certifiers.monitor.js
Normal file
342
js/src/redux/providers/certifications/certifiers.monitor.js
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { range } from 'lodash';
|
||||
|
||||
import { addCertification, removeCertification } from './actions';
|
||||
|
||||
import { getLogger, LOG_KEYS } from '~/config';
|
||||
import Contract from '~/api/contract';
|
||||
import { bytesToHex, hexToAscii } from '~/api/util/format';
|
||||
import Contracts from '~/contracts';
|
||||
import CertifierABI from '~/contracts/abi/certifier.json';
|
||||
import { querier } from './enhanced-querier';
|
||||
|
||||
const log = getLogger(LOG_KEYS.CertificationsMiddleware);
|
||||
|
||||
let self = null;
|
||||
|
||||
export default class CertifiersMonitor {
|
||||
constructor (api, store) {
|
||||
this._api = api;
|
||||
this._name = 'Certifiers';
|
||||
this._store = store;
|
||||
|
||||
this._contract = new Contract(this.api, CertifierABI);
|
||||
this._contractEvents = [ 'Confirmed', 'Revoked' ]
|
||||
.map((name) => this.contract.events.find((e) => e.name === name));
|
||||
|
||||
this.certifiers = {};
|
||||
this.fetchedAccounts = {};
|
||||
|
||||
this.load();
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
self = new CertifiersMonitor();
|
||||
return self;
|
||||
}
|
||||
|
||||
static init (api, store) {
|
||||
if (!self) {
|
||||
self = new CertifiersMonitor(api, store);
|
||||
}
|
||||
}
|
||||
|
||||
get api () {
|
||||
return this._api;
|
||||
}
|
||||
|
||||
get contract () {
|
||||
return this._contract;
|
||||
}
|
||||
|
||||
get contractEvents () {
|
||||
return this._contractEvents;
|
||||
}
|
||||
|
||||
get name () {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
get store () {
|
||||
return this._store;
|
||||
}
|
||||
|
||||
get registry () {
|
||||
return this._registry;
|
||||
}
|
||||
|
||||
get registryEvents () {
|
||||
return this._registryEvents;
|
||||
}
|
||||
|
||||
checkFilters () {
|
||||
this.checkCertifiersFilter();
|
||||
this.checkRegistryFilter();
|
||||
}
|
||||
|
||||
checkCertifiersFilter () {
|
||||
if (!this.certifiersFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.api.eth.getFilterChanges(this.certifiersFilter)
|
||||
.then((logs) => {
|
||||
if (logs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedLogs = this.contract.parseEventLogs(logs).filter((log) => log.params);
|
||||
|
||||
log.debug('received certifiers logs', parsedLogs);
|
||||
|
||||
const promises = parsedLogs.map((log) => {
|
||||
const account = log.params.who.value;
|
||||
const certifier = Object.values(this.certifiers).find((c) => c.address === log.address);
|
||||
|
||||
if (!certifier) {
|
||||
log.warn('could not find the certifier', { certifiers: this.certifiers, log });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.fetchAccount(account, { ids: [ certifier.id ] });
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
checkRegistryFilter () {
|
||||
if (!this.registryFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.api.eth.getFilterChanges(this.registryFilter)
|
||||
.then((logs) => {
|
||||
if (logs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedLogs = this.contract.parseEventLogs(logs).filter((log) => log.params);
|
||||
const indexes = parsedLogs.map((log) => log.params && log.params.id.value.toNumber());
|
||||
|
||||
log.debug('received registry logs', parsedLogs);
|
||||
return this.fetchElements(indexes);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial load of the Monitor.
|
||||
* Fetch the contract from the Registry, and
|
||||
* load the elements addresses
|
||||
*/
|
||||
load () {
|
||||
const badgeReg = Contracts.get().badgeReg;
|
||||
|
||||
log.debug(`loading the ${this.name} monitor...`);
|
||||
return badgeReg.getContract()
|
||||
.then((registryContract) => {
|
||||
this._registry = registryContract;
|
||||
this._registryEvents = [ 'Registered', 'Unregistered', 'MetaChanged', 'AddressChanged' ]
|
||||
.map((name) => this.registry.events.find((e) => e.name === name));
|
||||
|
||||
return this.registry.instance.badgeCount.call({});
|
||||
})
|
||||
.then((count) => {
|
||||
log.debug(`found ${count.toFormat()} registered contracts for ${this.name}`);
|
||||
return this.fetchElements(range(count.toNumber()));
|
||||
})
|
||||
.then(() => {
|
||||
return this.setRegistryFilter();
|
||||
})
|
||||
.then(() => {
|
||||
// Listen for new blocks
|
||||
return this.api.subscribe('eth_blockNumber', (err) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkFilters();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
log.debug(`loaded the ${this.name} monitor!`, this.certifiers);
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the given registered element
|
||||
*/
|
||||
fetchElements (indexes) {
|
||||
const badgeReg = Contracts.get().badgeReg;
|
||||
const { instance } = this.registry;
|
||||
|
||||
const sorted = indexes.sort();
|
||||
const from = sorted[0];
|
||||
const last = sorted[sorted.length - 1];
|
||||
const limit = last - from + 1;
|
||||
|
||||
// Fetch the address, name and owner in one batch
|
||||
return querier(this.api, { address: instance.address, from, limit }, instance.badge)
|
||||
.then((results) => {
|
||||
this.certifiers = results
|
||||
.map(([ address, name, owner ], index) => ({
|
||||
address, owner,
|
||||
id: index + from,
|
||||
name: hexToAscii(bytesToHex(name).replace(/(00)+$/, ''))
|
||||
}))
|
||||
.reduce((certifiers, certifier) => {
|
||||
const { id } = certifier;
|
||||
|
||||
if (!/^(0x)?0+$/.test(certifier.address)) {
|
||||
certifiers[id] = certifier;
|
||||
} else if (certifiers[id]) {
|
||||
delete certifiers[id];
|
||||
}
|
||||
|
||||
return certifiers;
|
||||
}, {});
|
||||
|
||||
// Fetch the meta-data in serie
|
||||
return Object.values(this.certifiers).reduce((promise, certifier) => {
|
||||
return promise.then(() => badgeReg.fetchMeta(certifier.id))
|
||||
.then((meta) => {
|
||||
this.certifiers[certifier.id] = { ...certifier, ...meta };
|
||||
});
|
||||
}, Promise.resolve());
|
||||
})
|
||||
.then(() => log.debug('fetched certifiers', { certifiers: this.certifiers }))
|
||||
// Fetch the know accounts in case it's an update of the certifiers
|
||||
.then(() => this.fetchAccounts(Object.keys(this.fetchedAccounts), { ids: indexes, force: true }));
|
||||
}
|
||||
|
||||
fetchAccounts (addresses, { ids = null, force = false } = {}) {
|
||||
const newAddresses = force
|
||||
? addresses
|
||||
: addresses.filter((address) => !this.fetchedAccounts[address]);
|
||||
|
||||
if (newAddresses.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
log.debug(`fetching values for "${addresses.join(' ; ')}" in ${this.name}...`);
|
||||
return newAddresses
|
||||
.reduce((promise, address) => {
|
||||
return promise.then(() => this.fetchAccount(address, { ids }));
|
||||
}, Promise.resolve())
|
||||
.then(() => {
|
||||
log.debug(`fetched values for "${addresses.join(' ; ')}" in ${this.name}!`);
|
||||
})
|
||||
.then(() => this.setCertifiersFilter());
|
||||
}
|
||||
|
||||
fetchAccount (address, { ids = null } = {}) {
|
||||
let certifiers = Object.values(this.certifiers);
|
||||
|
||||
// Only fetch values for the givens ids, if any
|
||||
if (ids) {
|
||||
certifiers = certifiers.filter((certifier) => ids.includes(certifier.id));
|
||||
}
|
||||
|
||||
certifiers
|
||||
.reduce((promise, certifier) => {
|
||||
return promise
|
||||
.then(() => {
|
||||
return this.contract.at(certifier.address).instance.certified.call({}, [ address ]);
|
||||
})
|
||||
.then((certified) => {
|
||||
const { id, title, icon, name } = certifier;
|
||||
|
||||
this.fetchedAccounts[address] = true;
|
||||
|
||||
if (!certified) {
|
||||
return this.store.dispatch(removeCertification(address, id));
|
||||
}
|
||||
|
||||
log.debug('seen as certified', { address, id, name, icon });
|
||||
this.store.dispatch(addCertification(address, id, name, title, icon));
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
setCertifiersFilter () {
|
||||
const accounts = Object.keys(this.fetchedAccounts);
|
||||
const addresses = Object.values(this.certifiers).map((c) => c.address);
|
||||
// The events have as first indexed data the account address
|
||||
const topics = [
|
||||
this.contractEvents.map((event) => '0x' + event.signature),
|
||||
accounts
|
||||
];
|
||||
|
||||
if (accounts.length === 0 || addresses.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const promise = this.certifiersFilter
|
||||
? this.api.eth.uninstallFilter(this.certifiersFilter)
|
||||
: Promise.resolve();
|
||||
|
||||
log.debug('setting up registry filter', { topics, accounts, addresses });
|
||||
|
||||
return promise
|
||||
.then(() => this.api.eth.newFilter({
|
||||
fromBlock: 'latest',
|
||||
toBlock: 'latest',
|
||||
address: addresses,
|
||||
topics
|
||||
}))
|
||||
.then((filterId) => {
|
||||
this.certifiersFilter = filterId;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
setRegistryFilter () {
|
||||
const { address } = this.registry.instance;
|
||||
const topics = [ this.registryEvents.map((event) => '0x' + event.signature) ];
|
||||
|
||||
log.debug('setting up registry filter', { topics, address });
|
||||
|
||||
return this.api.eth
|
||||
.newFilter({
|
||||
fromBlock: 'latest',
|
||||
toBlock: 'latest',
|
||||
address, topics
|
||||
})
|
||||
.then((filterId) => {
|
||||
this.registryFilter = filterId;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user