refactor(@embark/ens): refactor and clean up embark-ens (#1900)

This commit is contained in:
Jonathan Rainville 2019-09-12 17:36:16 -04:00 committed by Iuri Matias
parent e282ae4974
commit 107e730fc3
6 changed files with 268 additions and 336 deletions

View File

@ -76,7 +76,7 @@ export default class BlockchainAPI {
this.events.setCommandHandler("blockchain:defaultAccount:get", (cb) => {
const getDefaultAccount = this.getRequestForBlockchain(blockchainName, "getDefaultAccount");
cb(getDefaultAccount);
cb(null, getDefaultAccount());
});
this.events.setCommandHandler("blockchain:defaultAccount:set", (account, cb) => {

View File

@ -0,0 +1,92 @@
import {__} from 'embark-i18n';
import ensJS from 'embarkjs-ens';
const ENSFunctions = ensJS.ENSFunctions;
const namehash = require('eth-ens-namehash');
class ensAPI {
constructor(embark, options) {
this.embark = embark;
this.ens = options.ens;
}
registerAPIs() {
this.embark.registerAPICall(
'get',
'/embark-api/ens/resolve',
(req, res) => {
ENSFunctions.resolveName(req.query.name, this.ens.ensContract, this.ens.createResolverContract.bind(this.ens), (error, address) => {
if (error) {
return res.send({error: error.message || error});
}
res.send({address});
}, namehash);
});
this.embark.registerAPICall(
'get',
'/embark-api/ens/lookup',
(req, res) => {
ENSFunctions.lookupAddress(req.query.address, this.ens.ensContract, namehash, this.ens.createResolverContract.bind(this.ens), (error, name) => {
if (error) {
return res.send({error: error.message || error});
}
res.send({name});
});
});
this.embark.registerAPICall(
'post',
'/embark-api/ens/register',
(req, res) => {
const {subdomain, address} = req.body;
this.ens.ensRegisterSubdomain(subdomain, address, (error) => {
if (error) {
return res.send({error: error.message || error});
}
res.send({
name: `${req.body.subdomain}.${this.embark.config.namesystemConfig.register.rootDomain}`,
address: req.body.address
});
});
}
);
}
registerConsoleCommands() {
this.embark.registerConsoleCommand({
usage: 'resolve [name]',
description: __('Resolves an ENS name'),
matches: (cmd) => cmd.split(' ')[0] === 'resolve',
process: (cmd, cb) => {
const [_cmdName, name] = cmd.split(' ');
ENSFunctions.resolveName(name, this.ens.ensContract, this.ens.createResolverContract.bind(this.ens), cb, namehash);
}
});
this.embark.registerConsoleCommand({
usage: 'lookup [address]',
description: __('Lookup an ENS address'),
matches: (cmd) => cmd.split(' ')[0] === 'lookup',
process: (cmd, cb) => {
const [_cmdName, address] = cmd.split(' ');
ENSFunctions.lookupAddress(address, this.ens.ensContract, namehash, this.ens.createResolverContract.bind(this.ens), cb);
}
});
this.embark.registerConsoleCommand({
usage: 'registerSubDomain [subDomain] [address]',
description: __('Register an ENS sub-domain'),
matches: (cmd) => cmd.split(' ')[0] === 'registerSubDomain',
process: (cmd, cb) => {
const [_cmdName, name, address] = cmd.split(' ');
this.embark.events.request("blockchain:defaultAccount:get", (_err, defaultAccount) => {
this.ens.safeRegisterSubDomain(name, address, defaultAccount, cb);
});
}
});
}
}
module.exports = ensAPI;

View File

@ -0,0 +1,42 @@
const MAINNET_ID = '1';
const ROPSTEN_ID = '3';
const RINKEBY_ID = '4';
export default {
[MAINNET_ID]: {
"ENSRegistry": {
"address": "0x314159265dd8dbb310642f98f50c066173c1259b",
"silent": true
},
"Resolver": {
"deploy": false
},
"FIFSRegistrar": {
"deploy": false
}
},
[ROPSTEN_ID]: {
"ENSRegistry": {
"address": "0x112234455c3a32fd11230c42e7bccd4a84e02010",
"silent": true
},
"Resolver": {
"deploy": false
},
"FIFSRegistrar": {
"deploy": false
}
},
[RINKEBY_ID]: {
"ENSRegistry": {
"address": "0xe7410170f87102DF0055eB195163A03B7F2Bff4A",
"silent": true
},
"Resolver": {
"deploy": false
},
"FIFSRegistrar": {
"deploy": false
}
}
};

View File

@ -1,10 +1,12 @@
import {__} from 'embark-i18n';
import {AddressUtils, dappPath, hashTo32ByteHexString, recursiveMerge} from 'embark-utils';
import {recursiveMerge} from 'embark-utils';
const namehash = require('eth-ens-namehash');
const async = require('async');
import {dappArtifacts, ens} from 'embark-core/constants.json';
import EmbarkJS, {Utils as embarkJsUtils} from 'embarkjs';
import {ens} from 'embark-core/constants.json';
import {Utils as embarkJsUtils} from 'embarkjs';
import ensJS from 'embarkjs-ens';
import ensContractAddresses from './ensContractAddresses';
import EnsAPI from './api';
const ENSFunctions = ensJS.ENSFunctions;
const Web3 = require('web3');
@ -12,55 +14,12 @@ const ensConfig = require('./ensContractConfigs');
const secureSend = embarkJsUtils.secureSend;
const reverseAddrSuffix = '.addr.reverse';
const {ZERO_ADDRESS} = AddressUtils;
const ENS_WHITELIST = ens.whitelist;
const NOT_REGISTERED_ERROR = 'Name not yet registered';
const MAINNET_ID = '1';
const ROPSTEN_ID = '3';
const RINKEBY_ID = '4';
// Price of ENS registration contract functions
const ENS_GAS_PRICE = 700000;
const ENS_CONTRACTS_CONFIG = {
[MAINNET_ID]: {
"ENSRegistry": {
"address": "0x314159265dd8dbb310642f98f50c066173c1259b",
"silent": true
},
"Resolver": {
"deploy": false
},
"FIFSRegistrar": {
"deploy": false
}
},
[ROPSTEN_ID]: {
"ENSRegistry": {
"address": "0x112234455c3a32fd11230c42e7bccd4a84e02010",
"silent": true
},
"Resolver": {
"deploy": false
},
"FIFSRegistrar": {
"deploy": false
}
},
[RINKEBY_ID]: {
"ENSRegistry": {
"address": "0xe7410170f87102DF0055eB195163A03B7F2Bff4A",
"silent": true
},
"Resolver": {
"deploy": false
},
"FIFSRegistrar": {
"deploy": false
}
}
};
class ENS {
constructor(embark, _options) {
this.env = embark.env;
@ -72,23 +31,13 @@ class ENS {
this.embark = embark;
this.ensConfig = ensConfig;
this.configured = false;
this.modulesPath = dappPath(embark.config.embarkConfig.generationDir, dappArtifacts.symlinkDir);
this.initated = false;
this.ensAPI = new EnsAPI(embark, {ens: this});
this.events.request("namesystem:node:register", "ens", (readyCb) => {
readyCb();
});
this.events.setCommandHandler("ens:resolve", this.ensResolve.bind(this));
this.events.setCommandHandler("ens:isENSName", (name, cb) => {
setImmediate(cb, this.isENSName(name));
});
this.events.on("blockchain:started", () => {
this._web3 = null;
});
this.init(() => {});
this.init(readyCb);
}, this.executeCommand.bind(this));
}
get web3() {
@ -122,73 +71,26 @@ class ENS {
this.enabled = true;
this.doSetENSProvider = this.config.namesystemConfig.provider === 'ens';
this.registerEvents();
this.registerConsoleCommands();
this.events.request2("runcode:whitelist", 'eth-ens-namehash');
this.registerActions();
this.ensAPI.registerConsoleCommands();
this.events.request("runcode:whitelist", 'eth-ens-namehash');
this.initated = true;
cb();
}
reset() {
this.configured = false;
}
registerConsoleCommands() {
this.embark.registerConsoleCommand({
usage: 'resolve [name]',
description: __('Resolves an ENS name'),
matches: (cmd) => {
let [cmdName] = cmd.split(' ');
return cmdName === 'resolve';
},
process: (cmd, cb) => {
let [_cmdName, domain] = cmd.split(' ');
EmbarkJS.Names.resolve(domain, cb);
}
});
this.embark.registerConsoleCommand({
usage: 'lookup [address]',
description: __('Lookup an ENS address'),
matches: (cmd) => {
let [cmdName] = cmd.split(' ');
return cmdName === 'lookup';
},
process: (cmd, cb) => {
let [_cmdName, address] = cmd.split(' ');
EmbarkJS.Names.lookup(address, cb);
}
});
this.embark.registerConsoleCommand({
usage: 'registerSubDomain [subDomain] [address]',
description: __('Register an ENS sub-domain'),
matches: (cmd) => {
let [cmdName] = cmd.split(' ');
return cmdName === 'registerSubDomain';
},
process: (cmd, cb) => {
let [_cmdName, name, address] = cmd.split(' ');
EmbarkJS.Names.registerSubDomain(name, address, cb);
}
});
}
registerEvents() {
if (this.eventsRegistered) {
registerActions() {
if (this.actionsRegistered) {
return;
}
this.eventsRegistered = true;
this.actionsRegistered = true;
this.embark.registerActionForEvent("deployment:deployContracts:beforeAll", this.configureContractsAndRegister.bind(this));
this.embark.registerActionForEvent('deployment:contract:beforeDeploy', this.modifyENSArguments.bind(this));
this.events.on('blockchain:reseted', this.reset.bind(this));
this.events.setCommandHandler("storage:ens:associate", this.associateStorageToEns.bind(this));
this.events.setCommandHandler("ens:config", this.getEnsConfig.bind(this));
this.embark.registerActionForEvent("deployment:deployContracts:afterAll", this.associateContractAddresses.bind(this));
this.embark.registerActionForEvent("pipeline:generateAll:before", this.addArtifactFile.bind(this));
}
getEnsConfig(cb) {
cb({
getEnsConfig() {
return {
env: this.env,
registration: this.config.namesystemConfig.register,
registryAbi: this.ensConfig.ENSRegistry.abiDefinition,
@ -197,26 +99,43 @@ class ENS {
registrarAddress: this.ensConfig.FIFSRegistrar.deployedAddress,
resolverAbi: this.ensConfig.Resolver.abiDefinition,
resolverAddress: this.ensConfig.Resolver.deployedAddress
});
};
}
setProviderAndRegisterDomains(cb = (() => {})) {
this.getEnsConfig(async (config) => {
if (this.doSetENSProvider) {
this.setupEmbarkJS(config);
}
executeCommand(command, args, cb) {
switch (command) {
case 'resolve': this.ensResolve(args[0], cb); break;
case 'lookup': this.ensLookup(args[0], cb); break;
case 'registerSubdomain': this.ensRegisterSubdomain(args[0], args[1], cb); break;
default: cb(__('Unknown command %s', command));
}
}
const web3 = await this.web3;
addArtifactFile(_params, cb) {
const config = this.getEnsConfig();
this.events.request("pipeline:register", {
path: [this.config.embarkConfig.generationDir, 'config'],
file: 'namesystem.json',
format: 'json',
content: Object.assign({}, this.embark.config.namesystemConfig, config)
}, cb);
}
const networkId = await web3.eth.net.getId();
const isKnownNetwork = Boolean(ENS_CONTRACTS_CONFIG[networkId]);
const shouldRegisterSubdomain = this.config.namesystemConfig.register && this.config.namesystemConfig.register.subdomains && Object.keys(this.config.namesystemConfig.register.subdomains).length;
if (isKnownNetwork || !shouldRegisterSubdomain) {
return cb();
}
async setProviderAndRegisterDomains(cb = (() => {})) {
const config = this.getEnsConfig();
if (this.doSetENSProvider) {
this.setupEmbarkJS(config);
}
this.registerConfigDomains(config, cb);
});
const web3 = await this.web3;
const networkId = await web3.eth.net.getId();
const isKnownNetwork = Boolean(ensContractAddresses[networkId]);
const shouldRegisterSubdomain = this.config.namesystemConfig.register && this.config.namesystemConfig.register.subdomains && Object.keys(this.config.namesystemConfig.register.subdomains).length;
if (isKnownNetwork || !shouldRegisterSubdomain) {
return cb();
}
this.registerConfigDomains(config, cb);
}
async setupEmbarkJS(config) {
@ -225,75 +144,6 @@ class ENS {
this.events.request("embarkjs:console:setProvider", 'names', 'ens', config);
}
associateStorageToEns(options, cb) {
const self = this;
// Code inspired by https://github.com/monkybrain/ipfs-to-ens
const {name, storageHash} = options;
if (!this.isENSName(name)) {
return cb(__('Invalid domain name: {{name}}\nValid extensions are: {{extenstions}}', {name, extenstions: ENS_WHITELIST.join(', ')}));
}
let hashedName = namehash.hash(name);
let contentHash;
try {
contentHash = hashTo32ByteHexString(storageHash);
} catch (e) {
return cb(__('Invalid IPFS hash'));
}
// Set content
async.waterfall([
function getRegistryABI(next) {
self.events.request('contracts:contract', "ENSRegistry", (contract) => {
next(null, contract);
});
},
function createRegistryContract(contract, next) {
self.events.request("blockchain:contract:create",
{abi: contract.abiDefinition, address: contract.deployedAddress},
(resolver) => {
next(null, resolver);
});
},
function getResolverForName(registry, next) {
registry.methods.resolver(hashedName).call((err, resolverAddress) => {
if (err) {
return cb(err);
}
if (resolverAddress === ZERO_ADDRESS) {
return cb(NOT_REGISTERED_ERROR);
}
next(null, resolverAddress);
});
},
function getResolverABI(resolverAddress, next) {
self.events.request('contracts:contract', "Resolver", (contract) => {
next(null, resolverAddress, contract);
});
},
function createResolverContract(resolverAddress, contract, next) {
self.events.request("blockchain:contract:create",
{abi: contract.abiDefinition, address: resolverAddress},
(resolver) => {
next(null, resolver);
});
},
function getDefaultAccount(resolver, next) {
self.events.request("blockchain:defaultAccount:get", (defaultAccount) => {
next(null, resolver, defaultAccount);
});
},
function setContent(resolver, defaultAccount, next) {
resolver.methods.setContent(hashedName, contentHash).send({from: defaultAccount}).then((transaction) => {
if (transaction.status !== "0x1" && transaction.status !== "0x01" && transaction.status !== true) {
return next('Association failed. Status: ' + transaction.status);
}
next();
}).catch(next);
}
], cb);
}
async registerConfigDomains(config, cb) {
const defaultAccount = await this.web3DefaultAccount;
async.each(Object.keys(this.config.namesystemConfig.register.subdomains), (subDomainName, eachCb) => {
@ -301,21 +151,28 @@ class ENS {
const directivesRegExp = new RegExp(/\$(\w+\[?\d?\]?)/g);
const directives = directivesRegExp.exec(address);
if (!directives || !directives.length) {
return this.safeRegisterSubDomain(subDomainName, address, defaultAccount, eachCb);
if (directives && directives.length) {
return eachCb();
}
this.safeRegisterSubDomain(subDomainName, address, defaultAccount, eachCb);
}, cb);
}
// Register as an afterAll to get the contract the directive is pointing to
this.embark.registerActionForEvent("contracts:deploy:afterAll", async (deployActionCb) => {
if (!this.config.namesystemConfig.enabled) {
// ENS was disabled
return deployActionCb();
}
async associateContractAddresses(params, cb) {
if (!this.config.namesystemConfig.enabled) {
return cb();
}
const currentDefaultAccount = await this.events.request2("blockchain:defaultAccount:get");
if (defaultAccount !== currentDefaultAccount) {
this.logger.trace(`Skipping registration of subdomain "${directives[1]}" as this action was registered for a previous configuration`);
return deployActionCb();
const defaultAccount = await this.web3DefaultAccount;
await Promise.all(Object.keys(this.config.namesystemConfig.register.subdomains).map((subDomainName) => {
return new Promise(async (resolve, _reject) => {
const address = this.config.namesystemConfig.register.subdomains[subDomainName];
const directivesRegExp = new RegExp(/\$(\w+\[?\d?\]?)/g);
const directives = directivesRegExp.exec(address);
if (!directives || !directives.length) {
return resolve();
}
const contract = await this.events.request2("contracts:contract", directives[1]);
@ -326,12 +183,18 @@ class ENS {
contractName: directives[1],
subdomain: subDomainName
}));
return deployActionCb();
return resolve();
}
this.safeRegisterSubDomain(subDomainName, contract.deployedAddress, defaultAccount, deployActionCb);
this.safeRegisterSubDomain(subDomainName, contract.deployedAddress, defaultAccount, (err) => {
if (err) {
this.logger.error(err);
}
resolve();
});
});
return eachCb();
}, cb);
}));
cb();
}
safeRegisterSubDomain(subDomainName, address, defaultAccount, callback) {
@ -346,7 +209,7 @@ class ENS {
}
const reverseNode = namehash.hash(address.toLowerCase().substr(2) + reverseAddrSuffix);
this.registerSubDomain(defaultAccount, subDomainName, reverseNode, address, secureSend, callback);
this.registerSubDomain(defaultAccount, subDomainName, reverseNode, address.toLowerCase(), secureSend, callback);
});
}
@ -356,73 +219,15 @@ class ENS {
subDomainName, this.config.namesystemConfig.register.rootDomain, reverseNode, address, this.logger, secureSend, cb, namehash);
}
createResolverContract(config, callback) {
createResolverContract(resolverAddress, callback) {
this.events.request("blockchain:contract:create", {
abi: config.resolverAbi,
address: config.resolverAddress
abi: this.ensConfig.Resolver.abiDefinition,
address: resolverAddress
}, (resolver) => {
callback(null, resolver);
});
}
registerAPI() {
let self = this;
const createInternalResolverContract = function (resolverAddress, callback) {
self.createResolverContract({resolverAbi: self.ensConfig.Resolver.abiDefinition, resolverAddress}, callback);
};
self.embark.registerAPICall(
'get',
'/embark-api/ens/resolve',
(req, res) => {
async.waterfall([
function (callback) {
ENSFunctions.resolveName(req.query.name, self.ensContract, createInternalResolverContract.bind(self), callback, namehash);
}
], function (error, address) {
if (error) {
return res.send({error: error.message || error});
}
res.send({address});
});
}
);
self.embark.registerAPICall(
'get',
'/embark-api/ens/lookup',
(req, res) => {
async.waterfall([
function (callback) {
ENSFunctions.lookupAddress(req.query.address, self.ensContract, namehash, createInternalResolverContract.bind(self), callback);
}
], function (error, name) {
if (error) {
return res.send({error: error.message || error});
}
res.send({name});
});
}
);
self.embark.registerAPICall(
'post',
'/embark-api/ens/register',
(req, res) => {
self.events.request("blockchain:defaultAccount:get", (defaultAccount) => {
const {subdomain, address} = req.body;
this.safeRegisterSubDomain(subdomain, address, defaultAccount, (error) => {
if (error) {
return res.send({error: error.message || error});
}
res.send({name: `${req.body.subdomain}.${self.config.namesystemConfig.register.rootDomain}`, address: req.body.address});
});
});
}
);
}
async configureContractsAndRegister(_options, cb) {
const NO_REGISTRATION = 'NO_REGISTRATION';
const self = this;
@ -434,8 +239,8 @@ class ENS {
const networkId = await web3.eth.net.getId();
if (ENS_CONTRACTS_CONFIG[networkId]) {
this.ensConfig = recursiveMerge(this.ensConfig, ENS_CONTRACTS_CONFIG[networkId]);
if (ensContractAddresses[networkId]) {
this.ensConfig = recursiveMerge(this.ensConfig, ensContractAddresses[networkId]);
}
this.ensConfig.ENSRegistry = await this.events.request2('contracts:add', this.ensConfig.ENSRegistry);
@ -540,7 +345,7 @@ class ENS {
self.logger.error(err.message || err);
return cb(err);
}
self.registerAPI();
self.ensAPI.registerAPIs();
self.setProviderAndRegisterDomains(cb);
});
}
@ -575,36 +380,18 @@ class ENS {
});
}
async ensResolve(name, cb) {
const self = this;
if (!self.enabled) {
return cb('ENS not enabled');
}
if (!self.configured) {
return cb('ENS not configured');
}
const hashedName = namehash.hash(name);
const web3 = await this.web3;
async.waterfall([
function getResolverAddress(next) {
self.ensContract.methods.resolver(hashedName).call((err, resolverAddress) => {
if (err) {
return next(err);
}
if (resolverAddress === ZERO_ADDRESS) {
return next(NOT_REGISTERED_ERROR);
}
next(null, resolverAddress);
});
},
function createResolverContract(resolverAddress, next) {
const resolverContract = new web3.eth.Contract(self.ensConfig.Resolver.abiDefinition, resolverAddress);
next(null, resolverContract);
},
function resolveName(resolverContract, next) {
resolverContract.methods.addr(hashedName).call(next);
}
], cb);
ensResolve(name, cb) {
ENSFunctions.resolveName(name, this.ensContract, this.createResolverContract.bind(this), cb, namehash);
}
ensLookup(address, cb) {
ENSFunctions.lookupAddress(address, this.ensContract, namehash, this.createResolverContract.bind(this), cb);
}
ensRegisterSubdomain(subdomain, address, cb) {
this.events.request("blockchain:defaultAccount:get", (_err, defaultAccount) => {
this.safeRegisterSubDomain(subdomain, address, defaultAccount, cb);
});
}
isENSName(name) {

View File

@ -100,8 +100,8 @@ class ListConfigs {
return stringReplaceAsync.seq(cmd, regex, (ensDomain) => {
ensDomain = ensDomain.slice(1, ensDomain.length - 1);
return (new Promise((resolve, reject) => {
this.events.request("ens:resolve", ensDomain, (err, address) => {
if(err) {
this.events.request("namesystem:resolve", ensDomain, (err, address) => {
if (err) {
return reject(new Error(err));
}
address = `'${address}'`;

View File

@ -1,43 +1,54 @@
import {__} from 'embark-i18n';
// import {canonicalHost, defaultHost} from 'embark-utils';
export default class Namesystem {
constructor(embark, _options) {
this.embark = embark;
this.events = this.embark.events;
this.embarkConfig = embark.config.embarkConfig;
this.namesystemConfig = this.embark.config.namesystemConfig;
this.namesystemNodes = {};
this.events.setCommandHandler("namesystem:node:register", (clientName, startCb) => {
this.namesystemNodes[clientName] = startCb;
this.registerCommandHandlers();
}
registerCommandHandlers() {
this.events.setCommandHandler("namesystem:node:register", (nodeName, startFunction, executeCommand) => {
this.namesystemNodes[nodeName] = {startFunction, executeCommand, started: false};
});
this.events.setCommandHandler("namesystem:node:start", (namesystemConfig, cb) => {
const clientName = namesystemConfig.provider;
const client = this.namesystemNodes[clientName];
if (!client) return cb(__("Namesystem client %s not found", clientName));
const nodeName = namesystemConfig.provider;
const client = this.namesystemNodes[nodeName];
if (!client) return cb(__("Namesystem client %s not found", nodeName));
client.apply(client, [
client.startFunction.apply(client, [
() => {
this.events.emit("namesystem:started", clientName);
client.started = true;
cb();
}
]);
});
embark.registerActionForEvent("pipeline:generateAll:before", this.addArtifactFile.bind(this));
}
async addArtifactFile(_params, cb) {
// FIXME this shouldn't be done as the stack component calls the plugins
// FIXME this will be refactored along with the ENS plugin refactor
this.events.request("ens:config", (config) => {
this.events.request("pipeline:register", {
path: [this.embarkConfig.generationDir, 'config'],
file: 'namesystem.json',
format: 'json',
content: Object.assign({}, this.namesystemConfig, config)
}, cb);
this.events.setCommandHandler("namesystem:resolve", (name, cb) => {
this.executeNodeCommand('resolve', [name], cb);
});
this.events.setCommandHandler("namesystem:lookup", (address, cb) => {
this.executeNodeCommand('lookup', [address], cb);
});
this.events.setCommandHandler("namesystem:registerSubdomain", (name, address, cb) => {
this.executeNodeCommand('registerSubdomain', [name, address], cb);
});
}
executeNodeCommand(command, args, cb) {
const startedNode = Object.values(this.namesystemNodes).find(node => node.started);
if (!startedNode) {
return cb(__("No namesystem client started"));
}
startedNode.executeCommand(command, args, cb);
}
}