implement embark-web3; add request2 to event bus with promise support

This commit is contained in:
Iuri Matias 2019-07-25 12:20:39 -04:00
parent dc2e68f0f2
commit f838739e17
16 changed files with 200 additions and 168 deletions

View File

@ -1,5 +1,6 @@
import {SimpleStorage} from '../../embarkArtifacts/contracts'; // import {SimpleStorage} from '../../embarkArtifacts/contracts';
import SimpleStorage from '../../embarkArtifacts/contracts/SimpleStorage.js';
window.SimpleStorage = SimpleStorage; window.SimpleStorage = SimpleStorage;

View File

@ -5,42 +5,34 @@ export { fs, VM };
import { Callback, Embark, Events, Logger } /* supplied by @types/embark in packages/embark-typings */ from "embark"; import { Callback, Embark, Events, Logger } /* supplied by @types/embark in packages/embark-typings */ from "embark";
// TODO: ideally shouldn't be needed or should be done through an API
import Web3 from "web3";
const EmbarkJS = require("embarkjs");
class CodeRunner { class CodeRunner {
private ready: boolean = false;
private blockchainConnected: boolean = false;
private logger: Logger; private logger: Logger;
private events: Events; private events: Events;
private vm: VM; private vm: VM;
private providerStates: { [key: string]: boolean } = {};
constructor(embark: Embark, _options: any) { constructor(embark: Embark, _options: any) {
this.logger = embark.logger; this.logger = embark.logger;
this.events = embark.events; this.events = embark.events;
EmbarkJS.environment = embark.env;
this.vm = new VM({ this.vm = new VM({
require: { require: {
mock: { mock: {
fs, fs,
}, },
}, },
// TODO: ideally shouldn't be needed or should be done through an API
sandbox: { sandbox: {
EmbarkJS, // TODO: can just use registerVar in the embarkjs plugin
Web3, // TODO: can just use registerVar in the web3.js plugin
}, },
}, this.logger); }, this.logger);
this.registerEvents(); this.registerEvents();
this.registerCommands(); this.registerCommands();
this.ready = true;
} }
private registerEvents() { private registerEvents() {
// TODO: remove this on once all runcode:register have been converted to commands
this.events.on("runcode:register", this.registerVar.bind(this)); this.events.on("runcode:register", this.registerVar.bind(this));
this.events.setCommandHandler("runcode:register", this.registerVar.bind(this));
this.events.setCommandHandler("runcode:whitelist", this.whitelistVar.bind(this));
} }
private registerCommands() { private registerCommands() {
@ -50,6 +42,11 @@ class CodeRunner {
this.events.setCommandHandler("runcode:eval", this.evalCode.bind(this)); this.events.setCommandHandler("runcode:eval", this.evalCode.bind(this));
} }
private whitelistVar(varName: string, cb = () => { }) {
// @ts-ignore
this.vm._options.require.external.push(varName); // @ts-ignore
}
private registerVar(varName: string, code: any, cb = () => { }) { private registerVar(varName: string, code: any, cb = () => { }) {
this.vm.registerVar(varName, code, cb); this.vm.registerVar(varName, code, cb);
} }
@ -61,8 +58,13 @@ class CodeRunner {
return cb(null, ""); return cb(null, "");
} }
console.dir("running");
console.dir(code);
this.vm.doEval(code, tolerateError, (err, result) => { this.vm.doEval(code, tolerateError, (err, result) => {
if (err) { if (err) {
console.dir("error")
console.dir(err)
return cb(err); return cb(err);
} }

View File

@ -29,7 +29,8 @@ class VM {
* Currently, all of the allowed external requires appear in the EmbarkJS scripts. If * Currently, all of the allowed external requires appear in the EmbarkJS scripts. If
* the requires change in any of the EmbarkJS scripts, they will need to be updated here. * the requires change in any of the EmbarkJS scripts, they will need to be updated here.
*/ */
private _options: NodeVMOptions = { // private _options: NodeVMOptions = {
public _options: NodeVMOptions = {
require: { require: {
builtin: ["path", "util"], builtin: ["path", "util"],
external: [ external: [
@ -37,17 +38,18 @@ class VM {
"@babel/runtime-corejs2/core-js/object/assign", "@babel/runtime-corejs2/core-js/object/assign",
"@babel/runtime-corejs2/core-js/promise", "@babel/runtime-corejs2/core-js/promise",
"@babel/runtime-corejs2/helpers/interopRequireDefault", "@babel/runtime-corejs2/helpers/interopRequireDefault",
"embark-utils", // "embark-utils",
// TODO: ideally this shouldnt' be needed/here or should be configurable by the modules themselves somehow // TODO: ideally this shouldnt' be needed/here or should be configurable by the modules themselves somehow
"embarkjs-ens", // "embarkjs-ens",
"embarkjs-ipfs", // "embarkjs-ipfs",
"embarkjs-swarm", // "embarkjs-swarm",
"embarkjs-whisper", // "embarkjs-whisper",
"eth-ens-namehash", // "eth-ens-namehash",
"ipfs-api", // "ipfs-api",
"rxjs", "rxjs",
"rxjs/operators", "rxjs/operators",
"swarm-api", // "web3",
// "swarm-api",
], ],
}, },
sandbox: { __dirname: dappPath() }, sandbox: { __dirname: dappPath() },

View File

@ -18,6 +18,9 @@ class Compiler {
} }
private compile_contracts(contractFiles: any[], cb: any) { private compile_contracts(contractFiles: any[], cb: any) {
console.dir("----- compile_contracts")
console.dir(contractFiles)
console.dir(cb)
if (contractFiles.length === 0) { if (contractFiles.length === 0) {
return cb(null, {}); return cb(null, {});
} }

View File

@ -26,7 +26,7 @@
}, },
"main": "./dist/index.js", "main": "./dist/index.js",
"scripts": { "scripts": {
"build": "cross-env BABEL_ENV=node babel src --extensions \".js\" --out-dir dist --root-mode upward --source-maps", "build": "cross-env BABEL_ENV=node babel src --extensions \".js\" --out-dir dist --root-mode upward --source-maps --copy-files",
"ci": "npm run qa", "ci": "npm run qa",
"clean": "npm run reset", "clean": "npm run reset",
"lint": "npm-run-all lint:*", "lint": "npm-run-all lint:*",
@ -46,7 +46,9 @@
"extends": "../../.eslintrc.json" "extends": "../../.eslintrc.json"
}, },
"dependencies": { "dependencies": {
"@babel/core": "7.2.2",
"@babel/runtime-corejs2": "7.3.1", "@babel/runtime-corejs2": "7.3.1",
"ejs": "2.6.1",
"embark-core": "^4.1.0-beta.5", "embark-core": "^4.1.0-beta.5",
"embark-utils": "^4.1.0-beta.5", "embark-utils": "^4.1.0-beta.5",
"embarkjs-web3": "^4.1.0-beta.4" "embarkjs-web3": "^4.1.0-beta.4"

View File

@ -0,0 +1,13 @@
const web3 = require("./web3_init.js")
let <%- className %>Abi = <%- abi %>;
let <%- className %> = new web3.eth.Contract(<%- className %>Abi);
<%- className %>.options.address = '<%- contract.deployedAddress %>';
<%- className %>.address = '<%- contract.deployedAddress %>';
<%- className %>.options.from = web3.eth.defaultAccount;
<% if (gasLimit != false) { %>
<%- className %>.options.gas = <%- gasLimit %>;
<%- className %>.options.gasLimit = <%- gasLimit %>;
<% } %>
module.exports = <%- className %>;

View File

@ -4,129 +4,89 @@ const { __ } = require('embark-i18n');
const { dappPath, embarkPath, normalizePath, toForwardSlashes } = require('embark-utils'); const { dappPath, embarkPath, normalizePath, toForwardSlashes } = require('embark-utils');
const constants = require('embark-core/constants'); const constants = require('embark-core/constants');
const path = require('path'); const path = require('path');
const Web3 = require('web3');
require('ejs');
const Templates = {
vanilla_contract: require('./vanilla-contract.js.ejs'),
contract_artifact: require('./contract-artifact.js.ejs'),
web3_init: require('./web3_init.js.ejs'),
};
class EmbarkWeb3 { class EmbarkWeb3 {
constructor(embark, _options) { constructor(embark, options) {
this.embarkConfig = embark.config.embarkConfig;
this.embark = embark; this.embark = embark;
this.logger = embark.logger; this.logger = embark.logger;
this.events = embark.events; this.events = embark.events;
this.fs = embark.fs; this.fs = embark.fs;
this.config = embark.config; this.config = embark.config;
this.modulesPath = dappPath(embark.config.embarkConfig.generationDir, constants.dappArtifacts.symlinkDir); let plugin = options.plugins.createPlugin('web3plugin', {});
// this.addWeb3ToEmbarkJS(); this.events.request("runcode:whitelist", 'web3', () => { });
this.events.on("blockchain:started", this.registerWeb3Object.bind(this));
plugin.registerActionForEvent("pipeline:generateAll:before", this.addWeb3Artifact.bind(this));
plugin.registerActionForEvent("deployment:contract:deployed", this.registerInVm.bind(this));
plugin.registerActionForEvent("deployment:contract:deployed", this.registerArtifact.bind(this));
this.registerWeb3Help()
} }
async addWeb3ToEmbarkJS() { async registerWeb3Object() {
let blockchainConnectorReady = false; const web3 = new Web3("ws://localhost:8556");
await this.events.request2("runcode:register", 'web3', web3);
code += "\nEmbarkJS.Blockchain.registerProvider('web3', embarkJSConnectorWeb3);";
// code += "\nEmbarkJS.Blockchain.setProvider('web3', {});";
code += "\nEmbarkJS.Blockchain.setProvider('web3', {web3});";
this.events.request('runcode:eval', code, (err) => {
if (err) {
return cb(err);
}
});
} }
registerWeb3Help() { async registerWeb3Help() {
this.events.request('console:register:helpCmd', { await this.events.request2('console:register:helpCmd', {
cmdName: "web3", cmdName: "web3",
cmdHelp: __("instantiated web3.js object configured to the current environment") cmdHelp: __("instantiated web3.js object configured to the current environment")
}, () => { })
}
// ===============
// ===============
// ===============
let web3Location = await web3LocationPromise;
web3Location = normalizePath(web3Location, true);
await this.registerVar('__Web3', require(web3Location));
const symlinkLocation = await this.generateSymlink(web3Location);
let code = `\nconst Web3 = global.__Web3 || require('${symlinkLocation}');`;
code += `\nglobal.Web3 = Web3;`;
let linkedModulePath = path.join(this.modulesPath, 'embarkjs-web3');
if (process.platform === 'win32') linkedModulePath = linkedModulePath.replace(/\\/g, '\\\\');
code += `\n
const __embarkWeb3 = require('${linkedModulePath}');
EmbarkJS.Blockchain.registerProvider('web3', __embarkWeb3.default || __embarkWeb3);
EmbarkJS.Blockchain.setProvider('web3', {});
`;
const configPath = toForwardSlashes(dappPath(this.config.embarkConfig.generationDir, constants.dappArtifacts.dir, constants.dappArtifacts.blockchain));
code += `\nif (!global.__Web3) {`; // Only connect when in the Dapp
code += `\n const web3ConnectionConfig = require('${configPath}');`;
code += `\n EmbarkJS.Blockchain.connect(web3ConnectionConfig, (err) => {if (err) { console.error(err); } });`;
code += `\n}`;
this.events.request('version:downloadIfNeeded', 'embarkjs-web3', (err, location) => {
if (err) {
this.logger.error(__('Error downloading embarkjs-web3'));
throw err;
}
this.embark.addProviderInit("blockchain", code, () => { return true; });
// Make sure that we use our web3 for the console and the tests
code += `if (typeof web3 === 'undefined') {
throw new Error('Global web3 is not present');
}
EmbarkJS.Blockchain.setProvider('web3', {web3});`;
this.embark.addConsoleProviderInit("blockchain", code, () => { return true; });
this.embark.addGeneratedCode((cb) => {
return cb(null, code, 'embarkjs-web3', location);
});
}); });
} }
getWeb3Location() { async registerInVm(params, cb) {
return new Promise((resolve, reject) => { let contract = params.contract;
this.events.request("version:get:web3", (web3Version) => { let abi = JSON.stringify(contract.abiDefinition);
if (web3Version === "1.0.0-beta") { let gasLimit = 6000000;
const nodePath = embarkPath('node_modules'); let contractCode = Templates.vanilla_contract({ className: contract.className, abi: abi, contract: contract, gasLimit: gasLimit });
const web3Path = require.resolve("web3", {paths: [nodePath]});
return resolve(web3Path); try {
} await this.events.request2('runcode:eval', contractCode);
this.events.request("version:getPackageLocation", "web3", web3Version, (err, location) => { await this.events.request2('runcode:eval', contract.className);
if (err) { await this.events.request2("runcode:register", contract.className, result);
return reject(err); cb();
} } catch (err) {
const locationPath = embarkPath(location); cb(err);
resolve(locationPath); }
});
});
});
} }
generateSymlink(location) { addWeb3Artifact(_params, cb) {
return new Promise((resolve, reject) => { let web3Code = Templates.web3_init({});
this.events.request('code-generator:symlink:generate', location, 'web3', (err, symlinkDest) => {
if (err) { this.events.request("pipeline:register", {
return reject(err); path: [this.embarkConfig.generationDir, 'contracts'],
} file: 'web3_init.js',
resolve(symlinkDest); format: 'js',
}); content: web3Code
}); }, cb);
} }
registerVar(name, code) { registerArtifact(params, cb) {
return new Promise((resolve) => { let contract = params.contract;
this.events.emit('runcode:register', name, code, () => { let abi = JSON.stringify(contract.abiDefinition);
resolve(); let gasLimit = 6000000;
});
}); let contractCode = Templates.contract_artifact({ className: contract.className, abi: abi, contract: contract, gasLimit: gasLimit });
this.events.request("pipeline:register", {
path: [this.embarkConfig.generationDir, 'contracts'],
file: contract.className + '.js',
format: 'js',
content: contractCode
}, cb);
} }
} }
module.exports = EmbarkWeb3; module.exports = EmbarkWeb3;

View File

@ -0,0 +1,3 @@
const Web3 = require('web3')
const web3 = new Web3("ws://localhost:8556");
module.exports = web3;

View File

@ -202,31 +202,40 @@ class EmbarkController {
// } // }
// engine.startService("fileWatcher"); // engine.startService("fileWatcher");
engine.startEngine(() => { engine.startEngine(async () => {
callback(); callback();
engine.events.request("webserver:start") engine.events.request("webserver:start")
engine.events.request("config:contractsFiles", (contractsFiles) => { let contractsFiles = await engine.events.request2("config:contractsFiles");
engine.events.request("compiler:contracts:compile", contractsFiles, (err, compiledContracts) => {
// engine.events.request("config:contractsFiles", (contractsFiles) => {
// engine.events.request("compiler:contracts:compile", contractsFiles, (err, compiledContracts) => {
let compiledContracts = await engine.events.request2("compiler:contracts:compile", contractsFiles);
console.dir("compilation done") console.dir("compilation done")
// console.dir(compiledContracts) console.dir(compiledContracts)
console.dir("requesting contracts configuration") console.dir("requesting contracts configuration")
engine.events.request("config:contractsConfig", (_contractsConfig) => { // engine.events.request("config:contractsConfig", (_contractsConfig) => {
let _contractsConfig = await engine.events.request2("config:contractsConfig");
console.dir(_contractsConfig); console.dir(_contractsConfig);
let contractsConfig = cloneDeep(_contractsConfig); let contractsConfig = cloneDeep(_contractsConfig);
engine.events.request("contracts:build", contractsConfig, compiledContracts, (err, contractsList, contractDependencies) => { // engine.events.request("contracts:build", contractsConfig, compiledContracts, (err, contractsList, contractDependencies) => {
console.dir("contracts config build done") let [contractsList, contractDependencies] = await engine.events.request2("contracts:build", contractsConfig, compiledContracts);
engine.events.request("deployment:contracts:deploy", contractsList, contractDependencies, () => { // let [contractsList, contractDependencies] = await engine.events.request2("contracts:build", contractsConfig, compiledContracts);
console.dir("deployment done") console.dir("contracts config build done")
}) console.dir(contractsList)
}) console.dir(contractDependencies)
})
}) // engine.events.request("deployment:contracts:deploy", contractsList, contractDependencies, () => {
}) await engine.events.request2("deployment:contracts:deploy", contractsList, contractDependencies);
console.dir("deployment done")
// })
// })
// })
// })
}); });
}, },

View File

@ -69,7 +69,7 @@ Config.prototype.registerEvents = function() {
}); });
this.events.setCommandHandler("config:contractsConfig", (cb) => { this.events.setCommandHandler("config:contractsConfig", (cb) => {
cb(this.contractsConfig); cb(null, this.contractsConfig);
}); });
this.events.setCommandHandler("config:contractsConfig:set", this.setConfig.bind(this, 'contractsConfig')); this.events.setCommandHandler("config:contractsConfig:set", this.setConfig.bind(this, 'contractsConfig'));
@ -79,7 +79,7 @@ Config.prototype.registerEvents = function() {
this.events.setCommandHandler("config:communicationConfig:set", this.setConfig.bind(this, 'communicationConfig')); this.events.setCommandHandler("config:communicationConfig:set", this.setConfig.bind(this, 'communicationConfig'));
this.events.setCommandHandler("config:contractsFiles", (cb) => { this.events.setCommandHandler("config:contractsFiles", (cb) => {
cb(this.contractsFiles); cb(null, this.contractsFiles);
}); });
// TODO: refactor this so reading the file can be done with a normal resolver or something that takes advantage of the plugin api // TODO: refactor this so reading the file can be done with a normal resolver or something that takes advantage of the plugin api

View File

@ -186,8 +186,8 @@ class Engine {
this.registerModule('blockchain-client'); this.registerModule('blockchain-client');
this.registerModule('ethereum-blockchain-client'); this.registerModule('ethereum-blockchain-client');
this.registerModule('web3', { plugins: this.plugins }); // this.registerModule('web3', { plugins: this.plugins });
this.registerModulePackage('embark-web3'); this.registerModulePackage('embark-web3', {plugins: this.plugins});
} }
startService(serviceName, _options) { startService(serviceName, _options) {

View File

@ -49,6 +49,59 @@ EventEmitter.prototype.setHandler = function(requestName, cb) {
return _setHandler.call(this, requestName, cb); return _setHandler.call(this, requestName, cb);
}; };
EventEmitter.prototype.get = function() {
let requestName = arguments[0];
let other_args = [].slice.call(arguments, 1);
log("get: ", requestName);
warnIfLegacy(requestName);
const listenerName = 'get:' + requestName;
let promise = new Promise((resolve, reject) => {
return this.emit(listenerName, ...other_args, (err, res) => {
if (err) return reject(err);
return resolve(res);
})
});
return promise;
};
EventEmitter.prototype.request2 = function() {
let requestName = arguments[0];
let other_args = [].slice.call(arguments, 1);
log("requesting: ", requestName);
console.log("requesting: " + requestName);
warnIfLegacy(requestName);
if (this._events && !this._events['request:' + requestName]) {
log("made request without listener: " + requestName)
console.log("made request without listener: " + requestName)
console.trace();
}
let promise = new Promise((resolve, reject) => {
console.dir("emitting... " + requestName)
other_args.push(
(err, ...res) => {
if (err) return reject(err);
if (res.length && res.length > 1) {
return resolve(res);
}
return resolve(res[0]);
}
)
console.dir("----- other_args")
console.dir(other_args)
this.emit('request:' + requestName, ...other_args)
});
return promise;
};
EventEmitter.prototype.request = function() { EventEmitter.prototype.request = function() {
let requestName = arguments[0]; let requestName = arguments[0];
let other_args = [].slice.call(arguments, 1); let other_args = [].slice.call(arguments, 1);

View File

@ -16,37 +16,12 @@ class Web3Plugin {
this.plugins = options.plugins; this.plugins = options.plugins;
let plugin = this.plugins.createPlugin('web3plugin', {}); let plugin = this.plugins.createPlugin('web3plugin', {});
// plugin.registerActionForEvent("deployment:contract:deployed", this.registerInVm.bind(this));
plugin.registerActionForEvent("deployment:contract:deployed", this.addContractJSONToPipeline.bind(this)); plugin.registerActionForEvent("deployment:contract:deployed", this.addContractJSONToPipeline.bind(this));
plugin.registerActionForEvent("deployment:contract:deployed", this.addContractFileToPipeline.bind(this)); plugin.registerActionForEvent("deployment:contract:deployed", this.addContractFileToPipeline.bind(this));
plugin.registerActionForEvent("pipeline:generateAll:before", this.addEmbarkJSNode.bind(this)); plugin.registerActionForEvent("pipeline:generateAll:before", this.addEmbarkJSNode.bind(this));
plugin.registerActionForEvent("pipeline:generateAll:before", this.addContractIndexToPipeline.bind(this)); plugin.registerActionForEvent("pipeline:generateAll:before", this.addContractIndexToPipeline.bind(this));
} }
registerInVm(params, cb) {
console.dir("-- registerInVm")
let contract = params.contract;
let abi = JSON.stringify(contract.abiDefinition);
let gasLimit = 6000000;
let contractCode = Templates.vanilla_contract({ className: contract.className, abi: abi, contract: contract, gasLimit: gasLimit });
this.events.request('runcode:eval', contractCode, (err) => {
if (err) {
console.dir("error!!!")
console.dir(err)
return cb(err);
}
this.events.request('runcode:eval', contract.className, (err, result) => {
if (err) {
console.dir("error!!!")
console.dir(err)
return cb(err);
}
this.events.emit("runcode:register", contract.className, result, () => { cb() });
});
});
}
addContractJSONToPipeline(params, cb) { addContractJSONToPipeline(params, cb) {
console.dir("-- addContractJSONToPipeline") console.dir("-- addContractJSONToPipeline")
// TODO: check if this is correct json object to generate // TODO: check if this is correct json object to generate

View File

@ -0,0 +1,9 @@
<%- className %>Abi = <%- abi %>;
<%- className %> = new web3.eth.Contract(<%- className %>Abi);
<%- className %>.options.address = '<%- contract.deployedAddress %>';
<%- className %>.address = '<%- contract.deployedAddress %>';
<%- className %>.options.from = web3.eth.defaultAccount;
<% if (gasLimit != false) { %>
<%- className %>.options.gas = <%- gasLimit %>;
<%- className %>.options.gasLimit = <%- gasLimit %>;
<% } %>

View File

@ -32,7 +32,7 @@ class Blockchain {
} }
addArtifactFile(_params, cb) { addArtifactFile(_params, cb) {
this.events.request("config:contractsConfig", (contractsConfig) => { this.events.request("config:contractsConfig", (_err, contractsConfig) => {
let config = { let config = {
dappConnection: contractsConfig.dappConnection, dappConnection: contractsConfig.dappConnection,
dappAutoEnable: contractsConfig.dappAutoEnable, dappAutoEnable: contractsConfig.dappAutoEnable,