2018-05-18 21:15:49 +00:00
|
|
|
const Web3 = require('web3');
|
2018-05-23 12:40:09 +00:00
|
|
|
const async = require('async');
|
2018-05-18 18:58:05 +00:00
|
|
|
const Provider = require('./provider.js');
|
2018-07-27 20:54:33 +00:00
|
|
|
const utils = require('../../utils/utils');
|
2018-07-31 13:59:27 +00:00
|
|
|
const constants = require('../../constants');
|
2018-05-23 13:33:32 +00:00
|
|
|
|
|
|
|
const WEB3_READY = 'web3Ready';
|
2018-05-18 20:51:03 +00:00
|
|
|
|
2018-07-20 16:37:50 +00:00
|
|
|
// TODO: consider another name, this is the blockchain connector
|
2018-07-27 19:36:16 +00:00
|
|
|
class BlockchainConnector {
|
2018-07-27 20:54:33 +00:00
|
|
|
constructor(embark, options) {
|
2018-07-20 12:21:23 +00:00
|
|
|
const self = this;
|
2018-05-18 20:51:03 +00:00
|
|
|
this.plugins = options.plugins;
|
2018-07-27 20:54:33 +00:00
|
|
|
this.logger = embark.logger;
|
|
|
|
this.events = embark.events;
|
|
|
|
this.contractsConfig = embark.config.contractsConfig;
|
|
|
|
this.blockchainConfig = embark.config.blockchainConfig;
|
2018-05-18 21:15:49 +00:00
|
|
|
this.web3 = options.web3;
|
2018-05-23 15:52:07 +00:00
|
|
|
this.isDev = options.isDev;
|
2018-05-23 13:54:13 +00:00
|
|
|
this.web3Endpoint = '';
|
2018-05-23 13:33:32 +00:00
|
|
|
this.isWeb3Ready = false;
|
2018-05-18 18:58:05 +00:00
|
|
|
|
2018-07-20 12:21:23 +00:00
|
|
|
self.events.setCommandHandler("blockchain:web3:isReady", (cb) => {
|
|
|
|
cb(self.isWeb3Ready);
|
|
|
|
});
|
2018-07-19 20:31:28 +00:00
|
|
|
|
2018-07-27 20:03:15 +00:00
|
|
|
self.events.setCommandHandler("blockchain:object", (cb) => {
|
|
|
|
cb(self);
|
|
|
|
});
|
|
|
|
|
2018-05-18 21:15:49 +00:00
|
|
|
if (!this.web3) {
|
|
|
|
this.initWeb3();
|
2018-05-23 13:54:13 +00:00
|
|
|
} else {
|
|
|
|
this.isWeb3Ready = true;
|
2018-05-18 21:15:49 +00:00
|
|
|
}
|
2018-07-20 12:21:23 +00:00
|
|
|
this.registerServiceCheck();
|
2018-05-19 01:08:32 +00:00
|
|
|
this.registerRequests();
|
2018-05-23 15:16:56 +00:00
|
|
|
this.registerWeb3Object();
|
2018-07-20 12:21:23 +00:00
|
|
|
this.registerEvents();
|
2018-07-19 20:31:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//initWeb3() {
|
2018-05-22 21:34:54 +00:00
|
|
|
initWeb3(cb) {
|
2018-05-23 13:33:32 +00:00
|
|
|
if (!cb) {
|
|
|
|
cb = function(){};
|
|
|
|
}
|
|
|
|
if (this.isWeb3Ready) {
|
2018-07-17 13:13:12 +00:00
|
|
|
this.events.emit(WEB3_READY);
|
2018-05-23 13:33:32 +00:00
|
|
|
return cb();
|
|
|
|
}
|
2018-07-19 20:31:28 +00:00
|
|
|
|
2018-05-18 18:58:05 +00:00
|
|
|
const self = this;
|
2018-05-18 21:15:49 +00:00
|
|
|
this.web3 = new Web3();
|
2018-05-18 18:58:05 +00:00
|
|
|
|
2018-06-15 18:35:50 +00:00
|
|
|
if (this.contractsConfig.deployment.type !== "rpc" && this.contractsConfig.deployment.type !== "ws") {
|
|
|
|
const message = __("contracts config error: unknown deployment type %s", this.contractsConfig.deployment.type);
|
|
|
|
this.logger.error(message);
|
2018-05-18 21:15:49 +00:00
|
|
|
}
|
2018-06-15 18:35:50 +00:00
|
|
|
|
2018-06-19 04:43:55 +00:00
|
|
|
const protocol = (this.contractsConfig.deployment.type === "rpc") ? this.contractsConfig.deployment.protocol : 'ws';
|
2018-07-19 20:31:28 +00:00
|
|
|
|
|
|
|
this.web3Endpoint = utils.buildUrl(protocol, this.contractsConfig.deployment.host, this.contractsConfig.deployment.port);//`${protocol}://${this.contractsConfig.deployment.host}:${this.contractsConfig.deployment.port}`;
|
|
|
|
|
|
|
|
const providerOptions = {
|
|
|
|
web3: this.web3,
|
|
|
|
accountsConfig: this.contractsConfig.deployment.accounts,
|
|
|
|
blockchainConfig: this.blockchainConfig,
|
|
|
|
logger: this.logger,
|
|
|
|
isDev: this.isDev,
|
|
|
|
type: this.contractsConfig.deployment.type,
|
|
|
|
web3Endpoint: self.web3Endpoint
|
|
|
|
};
|
|
|
|
this.provider = new Provider(providerOptions);
|
|
|
|
|
2018-07-20 12:21:23 +00:00
|
|
|
self.events.request("processes:launch", "blockchain", () => {
|
|
|
|
self.provider.startWeb3Provider(() => {
|
2018-07-31 13:59:27 +00:00
|
|
|
this.web3.eth.net.getId()
|
|
|
|
.then(id => {
|
|
|
|
let networkId = self.blockchainConfig.networkId;
|
|
|
|
if (!networkId && constants.blockchain.networkIds[self.blockchainConfig.networkType]) {
|
|
|
|
networkId = constants.blockchain.networkIds[self.blockchainConfig.networkType];
|
|
|
|
}
|
2018-08-07 19:04:58 +00:00
|
|
|
if (id.toString() !== networkId.toString()) {
|
2018-07-31 13:59:27 +00:00
|
|
|
self.logger.warn(__('Connected to a blockchain node on network {{realId}} while your config specifies {{configId}}', {realId: id, configId: networkId}));
|
|
|
|
self.logger.warn(__('Make sure you started the right blockchain node'));
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(console.error);
|
2018-07-20 12:21:23 +00:00
|
|
|
self.provider.fundAccounts(() => {
|
|
|
|
self.isWeb3Ready = true;
|
|
|
|
self.events.emit(WEB3_READY);
|
|
|
|
self.registerWeb3Object();
|
2018-07-19 20:31:28 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-07-20 12:21:23 +00:00
|
|
|
registerEvents() {
|
2018-07-26 17:11:38 +00:00
|
|
|
const self = this;
|
|
|
|
self.events.on('check:wentOffline:Ethereum', () => {
|
|
|
|
self.logger.trace('Ethereum went offline: stopping web3 provider...');
|
|
|
|
self.provider.stop();
|
|
|
|
|
|
|
|
// once the node goes back online, we can restart the provider
|
|
|
|
self.events.once('check:backOnline:Ethereum', () => {
|
|
|
|
self.logger.trace('Ethereum back online: starting web3 provider...');
|
|
|
|
self.provider.startWeb3Provider(() => {
|
|
|
|
self.logger.trace('web3 provider restarted after ethereum node came back online');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2018-05-18 21:15:49 +00:00
|
|
|
}
|
|
|
|
|
2018-05-23 13:33:32 +00:00
|
|
|
onReady(callback) {
|
|
|
|
if (this.isWeb3Ready) {
|
|
|
|
return callback();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.events.once(WEB3_READY, () => {
|
2018-06-22 12:52:15 +00:00
|
|
|
callback();
|
|
|
|
});
|
2018-05-23 13:33:32 +00:00
|
|
|
}
|
|
|
|
|
2018-05-18 21:15:49 +00:00
|
|
|
registerServiceCheck() {
|
|
|
|
const self = this;
|
2018-05-30 15:02:01 +00:00
|
|
|
const NO_NODE = 'noNode';
|
2018-05-18 21:15:49 +00:00
|
|
|
|
2018-06-01 15:58:11 +00:00
|
|
|
this.events.request("services:register", 'Ethereum', function (cb) {
|
2018-05-23 12:40:09 +00:00
|
|
|
async.waterfall([
|
|
|
|
function checkNodeConnection(next) {
|
2018-07-20 12:21:23 +00:00
|
|
|
if (!self.web3.currentProvider) {
|
|
|
|
return next(NO_NODE, {name: "No Blockchain node found", status: 'off'});
|
|
|
|
}
|
|
|
|
next();
|
2018-05-23 12:40:09 +00:00
|
|
|
},
|
|
|
|
function checkVersion(next) {
|
|
|
|
// TODO: web3_clientVersion method is currently not implemented in web3.js 1.0
|
|
|
|
self.web3._requestManager.send({method: 'web3_clientVersion', params: []}, (err, version) => {
|
|
|
|
if (err) {
|
2018-07-20 12:21:23 +00:00
|
|
|
self.isWeb3Ready = false;
|
2018-05-23 12:40:09 +00:00
|
|
|
return next(null, {name: "Ethereum node (version unknown)", status: 'on'});
|
|
|
|
}
|
|
|
|
if (version.indexOf("/") < 0) {
|
2018-07-20 12:21:23 +00:00
|
|
|
self.events.emit(WEB3_READY);
|
|
|
|
self.isWeb3Ready = true;
|
2018-05-23 12:40:09 +00:00
|
|
|
return next(null, {name: version, status: 'on'});
|
|
|
|
}
|
|
|
|
let nodeName = version.split("/")[0];
|
|
|
|
let versionNumber = version.split("/")[1].split("-")[0];
|
|
|
|
let name = nodeName + " " + versionNumber + " (Ethereum)";
|
|
|
|
|
2018-07-20 12:21:23 +00:00
|
|
|
self.events.emit(WEB3_READY);
|
|
|
|
self.isWeb3Ready = true;
|
2018-05-23 12:40:09 +00:00
|
|
|
return next(null, {name: name, status: 'on'});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
], (err, statusObj) => {
|
2018-05-30 15:02:01 +00:00
|
|
|
if (err && err !== NO_NODE) {
|
|
|
|
return cb(err);
|
2018-05-18 21:15:49 +00:00
|
|
|
}
|
2018-05-23 12:40:09 +00:00
|
|
|
cb(statusObj);
|
2018-05-18 21:15:49 +00:00
|
|
|
});
|
2018-05-30 16:00:44 +00:00
|
|
|
}, 5000, 'off');
|
2018-05-18 20:51:03 +00:00
|
|
|
}
|
2018-05-18 21:15:49 +00:00
|
|
|
|
2018-05-19 01:08:32 +00:00
|
|
|
registerRequests() {
|
2018-05-18 22:31:47 +00:00
|
|
|
const self = this;
|
|
|
|
|
|
|
|
this.events.setCommandHandler("blockchain:defaultAccount:get", function(cb) {
|
2018-07-13 20:41:15 +00:00
|
|
|
cb(self.defaultAccount());
|
2018-05-18 22:31:47 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
this.events.setCommandHandler("blockchain:defaultAccount:set", function(account, cb) {
|
|
|
|
self.setDefaultAccount(account);
|
|
|
|
cb();
|
|
|
|
});
|
2018-05-19 01:08:32 +00:00
|
|
|
|
|
|
|
this.events.setCommandHandler("blockchain:block:byNumber", function(blockNumber, cb) {
|
|
|
|
self.getBlock(blockNumber, cb);
|
|
|
|
});
|
|
|
|
|
2018-06-14 19:22:50 +00:00
|
|
|
this.events.setCommandHandler("blockchain:gasPrice", function(cb) {
|
2018-07-18 12:08:32 +00:00
|
|
|
self.getGasPrice(cb);
|
2018-06-14 19:22:50 +00:00
|
|
|
});
|
|
|
|
|
2018-07-13 20:41:15 +00:00
|
|
|
this.events.setCommandHandler("blockchain:contract:create", function(params, cb) {
|
|
|
|
cb(self.ContractObject(params));
|
|
|
|
});
|
2018-05-18 22:31:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defaultAccount() {
|
|
|
|
return this.web3.eth.defaultAccount;
|
|
|
|
}
|
|
|
|
|
|
|
|
setDefaultAccount(account) {
|
|
|
|
this.web3.eth.defaultAccount = account;
|
|
|
|
}
|
|
|
|
|
|
|
|
getAccounts(cb) {
|
|
|
|
this.web3.eth.getAccounts(cb);
|
|
|
|
}
|
|
|
|
|
2018-05-18 22:41:04 +00:00
|
|
|
getCode(address, cb) {
|
|
|
|
this.web3.eth.getCode(address, cb);
|
|
|
|
}
|
|
|
|
|
2018-05-18 22:50:20 +00:00
|
|
|
getBlock(blockNumber, cb) {
|
|
|
|
this.web3.eth.getBlock(blockNumber, cb);
|
|
|
|
}
|
|
|
|
|
2018-06-14 19:22:50 +00:00
|
|
|
getGasPrice(cb) {
|
2018-07-20 12:21:23 +00:00
|
|
|
const self = this;
|
|
|
|
this.onReady(() => {
|
|
|
|
self.web3.eth.getGasPrice(cb);
|
|
|
|
});
|
2018-06-14 19:22:50 +00:00
|
|
|
}
|
|
|
|
|
2018-05-18 23:00:36 +00:00
|
|
|
ContractObject(params) {
|
2018-07-13 20:41:15 +00:00
|
|
|
return new this.web3.eth.Contract(params.abi, params.address);
|
2018-05-18 23:00:36 +00:00
|
|
|
}
|
|
|
|
|
2018-05-19 00:26:21 +00:00
|
|
|
deployContractObject(contractObject, params) {
|
|
|
|
return contractObject.deploy({arguments: params.arguments, data: params.data});
|
|
|
|
}
|
|
|
|
|
|
|
|
estimateDeployContractGas(deployObject, cb) {
|
|
|
|
return deployObject.estimateGas().then((gasValue) => {
|
|
|
|
cb(null, gasValue);
|
|
|
|
}).catch(cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
deployContractFromObject(deployContractObject, params, cb) {
|
2018-07-09 20:50:38 +00:00
|
|
|
const self = this;
|
|
|
|
let hash;
|
|
|
|
let calledBacked = false;
|
|
|
|
|
2018-07-11 12:47:12 +00:00
|
|
|
function callback(err, receipt) {
|
2018-07-09 20:50:38 +00:00
|
|
|
if (calledBacked) {
|
|
|
|
return;
|
|
|
|
}
|
2018-07-11 12:47:12 +00:00
|
|
|
if (!err && !receipt.contractAddress) {
|
|
|
|
return; // Not deployed yet. Need to wait
|
|
|
|
}
|
2018-07-09 20:50:38 +00:00
|
|
|
if (interval) {
|
|
|
|
clearInterval(interval);
|
|
|
|
}
|
|
|
|
calledBacked = true;
|
2018-07-11 12:47:12 +00:00
|
|
|
cb(err, receipt);
|
2018-07-09 20:50:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This interval is there to compensate for the event that sometimes doesn't get triggered when using WebSocket
|
|
|
|
// FIXME The issue somehow only happens when the blockchain node is started in the same terminal
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
if (!hash) {
|
|
|
|
return; // Wait until we receive the hash
|
|
|
|
}
|
|
|
|
self.web3.eth.getTransactionReceipt(hash, (err, receipt) => {
|
|
|
|
if (!err && !receipt) {
|
|
|
|
return; // Transaction is not yet complete
|
|
|
|
}
|
|
|
|
callback(err, receipt);
|
|
|
|
});
|
|
|
|
}, 500);
|
|
|
|
|
2018-05-19 00:26:21 +00:00
|
|
|
deployContractObject.send({
|
|
|
|
from: params.from, gas: params.gas, gasPrice: params.gasPrice
|
2018-07-09 20:50:38 +00:00
|
|
|
}, function (err, transactionHash) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
2018-05-19 00:26:21 +00:00
|
|
|
}
|
2018-07-09 20:50:38 +00:00
|
|
|
hash = transactionHash;
|
2018-07-20 12:21:23 +00:00
|
|
|
}).on('receipt', function (receipt) {
|
|
|
|
if (receipt.contractAddress !== undefined) {
|
|
|
|
callback(null, receipt);
|
2018-05-23 12:40:09 +00:00
|
|
|
}
|
2018-07-20 12:21:23 +00:00
|
|
|
}).then(function (_contract) {
|
|
|
|
if (!hash) {
|
|
|
|
return; // Somehow we didn't get the receipt yet... Interval will catch it
|
2018-05-20 13:08:03 +00:00
|
|
|
}
|
2018-07-20 12:21:23 +00:00
|
|
|
self.web3.eth.getTransactionReceipt(hash, callback);
|
|
|
|
}).catch(callback);
|
2018-05-20 13:08:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
determineDefaultAccount(cb) {
|
|
|
|
const self = this;
|
|
|
|
self.getAccounts(function (err, accounts) {
|
|
|
|
if (err) {
|
|
|
|
self.logger.error(err);
|
|
|
|
return cb(new Error(err));
|
|
|
|
}
|
|
|
|
let accountConfig = self.blockchainConfig.account;
|
|
|
|
let selectedAccount = accountConfig && accountConfig.address;
|
|
|
|
self.setDefaultAccount(selectedAccount || accounts[0]);
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-23 15:16:56 +00:00
|
|
|
registerWeb3Object() {
|
|
|
|
// doesn't feel quite right, should be a cmd or plugin method
|
|
|
|
// can just be a command without a callback
|
|
|
|
this.events.emit("runcode:register", "web3", this.web3);
|
|
|
|
}
|
2018-05-18 20:51:03 +00:00
|
|
|
}
|
|
|
|
|
2018-07-27 19:36:16 +00:00
|
|
|
module.exports = BlockchainConnector;
|
2018-05-18 20:51:03 +00:00
|
|
|
|