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-08-22 22:32:43 +00:00
|
|
|
const utils = require('../../utils/utils');
|
2018-08-23 15:36:08 +00:00
|
|
|
const constants = require('../../constants');
|
|
|
|
const embarkJsUtils = require('embarkjs').Utils;
|
2018-05-23 13:33:32 +00:00
|
|
|
|
2018-10-10 20:34:04 +00:00
|
|
|
const WEB3_READY = 'blockchain:ready';
|
2018-05-18 20:51:03 +00:00
|
|
|
|
2018-08-22 22:32:43 +00:00
|
|
|
// TODO: consider another name, this is the blockchain connector
|
|
|
|
class BlockchainConnector {
|
|
|
|
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;
|
2018-10-12 15:23:12 +00:00
|
|
|
this.config = embark.config;
|
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-10-09 20:43:38 +00:00
|
|
|
this.wait = options.wait;
|
2018-10-09 13:19:58 +00:00
|
|
|
this.contractsSubscriptions = [];
|
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-08-22 22:32:43 +00:00
|
|
|
self.events.setCommandHandler("blockchain:object", (cb) => {
|
|
|
|
cb(self);
|
|
|
|
});
|
|
|
|
|
2018-10-09 13:19:58 +00:00
|
|
|
embark.registerActionForEvent("contracts:deploy:afterAll", this.subscribeToContractEvents.bind(this));
|
|
|
|
|
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-10-09 20:43:38 +00:00
|
|
|
|
2018-07-20 12:21:23 +00:00
|
|
|
this.registerServiceCheck();
|
2018-05-19 01:08:32 +00:00
|
|
|
this.registerRequests();
|
2018-07-31 14:46:19 +00:00
|
|
|
this.registerAPIRequests();
|
2018-05-23 15:16:56 +00:00
|
|
|
this.registerWeb3Object();
|
2018-07-20 12:21:23 +00:00
|
|
|
this.registerEvents();
|
2018-08-23 15:36:08 +00:00
|
|
|
this.subscribeToPendingTransactions();
|
2018-07-19 20:31:28 +00:00
|
|
|
}
|
|
|
|
|
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-10-09 20:43:38 +00:00
|
|
|
if (self.wait) {
|
2018-10-12 15:23:12 +00:00
|
|
|
self.wait = false;
|
2018-10-09 20:43:38 +00:00
|
|
|
return cb();
|
|
|
|
}
|
|
|
|
|
2018-10-12 15:23:12 +00:00
|
|
|
let {type, host, port, accounts, protocol, coverage} = this.config.contractsConfig.deployment;
|
2018-08-30 19:48:50 +00:00
|
|
|
if (!protocol) {
|
|
|
|
protocol = (type === "rpc") ? 'http' : 'ws';
|
|
|
|
}
|
2018-10-09 20:43:38 +00:00
|
|
|
|
|
|
|
if (!BlockchainConnector.ACCEPTED_TYPES.includes(type)) {
|
|
|
|
this.logger.error(__("contracts config error: unknown deployment type %s", type));
|
|
|
|
this.logger.error(__("Accepted types are: %s", BlockchainConnector.ACCEPTED_TYPES.join(', ')));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type === 'vm') {
|
2018-10-10 20:34:04 +00:00
|
|
|
const sim = self._getSimulator();
|
2018-10-12 15:23:12 +00:00
|
|
|
self.provider = sim.provider(self.config.contractsConfig.deployment);
|
2018-10-10 20:34:04 +00:00
|
|
|
|
2018-10-12 15:23:12 +00:00
|
|
|
if (coverage) {
|
2018-10-10 20:34:04 +00:00
|
|
|
// Here we patch the sendAsync method on the provider. The goal behind this is to force pure/constant/view calls to become
|
|
|
|
// transactions, so that we can pull in execution traces and account for those executions in code coverage.
|
|
|
|
//
|
|
|
|
// Instead of a simple call, here's what happens:
|
|
|
|
//
|
|
|
|
// 1) A transaction is sent with the same payload, and a pre-defined gas price;
|
|
|
|
// 2) We wait for the transaction to be mined by asking for the receipt;
|
|
|
|
// 3) Once we get the receipt back, we dispatch the real call and pass the original callback;
|
|
|
|
//
|
|
|
|
// This will still allow tests to get the return value from the call and run contracts unmodified.
|
|
|
|
self.provider.realSendAsync = self.provider.sendAsync.bind(self.provider);
|
|
|
|
self.provider.sendAsync = function(payload, cb) {
|
|
|
|
if(payload.method !== 'eth_call') {
|
|
|
|
return self.provider.realSendAsync(payload, cb);
|
|
|
|
}
|
|
|
|
self.events.request('reporter:toggleGasListener');
|
|
|
|
let newParams = Object.assign({}, payload.params[0], {gasPrice: '0x77359400'});
|
|
|
|
let newPayload = {
|
|
|
|
id: payload.id + 1,
|
|
|
|
method: 'eth_sendTransaction',
|
|
|
|
params: [newParams],
|
|
|
|
jsonrpc: payload.jsonrpc
|
|
|
|
};
|
|
|
|
|
|
|
|
self.provider.realSendAsync(newPayload, (_err, response) => {
|
|
|
|
let txHash = response.result;
|
|
|
|
self.web3.eth.getTransactionReceipt(txHash, (_err, _res) => {
|
|
|
|
self.events.request('reporter:toggleGasListener');
|
|
|
|
self.provider.realSendAsync(payload, cb);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
self.web3.setProvider(self.provider);
|
|
|
|
self._emitWeb3Ready();
|
2018-10-10 15:34:56 +00:00
|
|
|
return cb();
|
2018-05-18 21:15:49 +00:00
|
|
|
}
|
2018-06-15 18:35:50 +00:00
|
|
|
|
2018-10-09 20:43:38 +00:00
|
|
|
protocol = (type === "rpc") ? protocol : 'ws';
|
2018-07-19 20:31:28 +00:00
|
|
|
|
2018-10-09 20:43:38 +00:00
|
|
|
this.web3Endpoint = utils.buildUrl(protocol, host, port);
|
2018-07-19 20:31:28 +00:00
|
|
|
|
|
|
|
const providerOptions = {
|
|
|
|
web3: this.web3,
|
2018-10-09 20:43:38 +00:00
|
|
|
accountsConfig: accounts,
|
2018-10-12 15:23:12 +00:00
|
|
|
blockchainConfig: this.config.blockchainConfig,
|
2018-07-19 20:31:28 +00:00
|
|
|
logger: this.logger,
|
|
|
|
isDev: this.isDev,
|
2018-10-09 20:43:38 +00:00
|
|
|
type: type,
|
2018-07-19 20:31:28 +00:00
|
|
|
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-09-28 10:09:23 +00:00
|
|
|
this.getNetworkId()
|
2018-07-31 13:59:27 +00:00
|
|
|
.then(id => {
|
2018-10-12 15:23:12 +00:00
|
|
|
let networkId = self.config.blockchainConfig.networkId;
|
|
|
|
if (!networkId && constants.blockchain.networkIds[self.config.blockchainConfig.networkType]) {
|
|
|
|
networkId = constants.blockchain.networkIds[self.config.blockchainConfig.networkType];
|
2018-07-31 13:59:27 +00:00
|
|
|
}
|
2018-08-23 14:29:39 +00:00
|
|
|
if (networkId && 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(() => {
|
2018-08-03 11:08:42 +00:00
|
|
|
self.isWeb3Ready = true;
|
|
|
|
self.events.emit(WEB3_READY);
|
|
|
|
self.registerWeb3Object();
|
2018-07-19 20:31:28 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-09 20:43:38 +00:00
|
|
|
_emitWeb3Ready() {
|
|
|
|
this.isWeb3Ready = true;
|
|
|
|
this.events.emit(WEB3_READY);
|
|
|
|
this.registerWeb3Object();
|
2018-10-10 20:34:04 +00:00
|
|
|
this.subscribeToPendingTransactions();
|
2018-10-09 20:43:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_getSimulator() {
|
|
|
|
try {
|
|
|
|
return require('ganache-cli');
|
|
|
|
} catch (e) {
|
|
|
|
const moreInfo = 'For more information see https://github.com/trufflesuite/ganache-cli';
|
|
|
|
if (e.code === 'MODULE_NOT_FOUND') {
|
|
|
|
this.logger.error(__('Simulator not found; Please install it with "%s"', 'npm install ganache-cli --save'));
|
|
|
|
this.logger.error(moreInfo);
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
this.logger.error("==============");
|
|
|
|
this.logger.error(__("Tried to load Ganache CLI (testrpc), but an error occurred. This is a problem with Ganache CLI"));
|
|
|
|
this.logger.error(moreInfo);
|
|
|
|
this.logger.error("==============");
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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', () => {
|
2018-09-25 16:41:22 +00:00
|
|
|
self.logger.warn('Ethereum went offline: stopping web3 provider...');
|
2018-07-26 17:11:38 +00:00
|
|
|
self.provider.stop();
|
|
|
|
|
|
|
|
// once the node goes back online, we can restart the provider
|
|
|
|
self.events.once('check:backOnline:Ethereum', () => {
|
2018-09-25 16:41:22 +00:00
|
|
|
self.logger.warn('Ethereum back online: starting web3 provider...');
|
2018-07-26 17:11:38 +00:00
|
|
|
self.provider.startWeb3Provider(() => {
|
2018-09-25 16:41:22 +00:00
|
|
|
self.logger.warn('web3 provider restarted after ethereum node came back online');
|
2018-07-26 17:11:38 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
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-08-27 20:22:53 +00:00
|
|
|
this.events.request("services:register", 'Ethereum', function(cb) {
|
2018-05-23 12:40:09 +00:00
|
|
|
async.waterfall([
|
|
|
|
function checkNodeConnection(next) {
|
2018-10-17 10:27:12 +00:00
|
|
|
if (!self.provider || !self.provider.connected()) {
|
2018-07-20 12:21:23 +00:00
|
|
|
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-09-21 21:51:53 +00:00
|
|
|
return next(null, {name: "Ethereum node not found", status: 'off'});
|
2018-05-23 12:40:09 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2018-10-12 15:23:12 +00:00
|
|
|
this.events.setCommandHandler("blockchain:reset", function(cb) {
|
|
|
|
self.isWeb3Ready = false;
|
|
|
|
self.initWeb3(cb);
|
|
|
|
});
|
|
|
|
|
2018-10-05 17:15:01 +00:00
|
|
|
this.events.setCommandHandler("blockchain:get", function(cb) {
|
|
|
|
cb(self.web3);
|
|
|
|
});
|
|
|
|
|
2018-05-18 22:31:47 +00:00
|
|
|
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
|
|
|
|
2018-10-10 16:09:56 +00:00
|
|
|
this.events.setCommandHandler("blockchain:getAccounts", function(cb) {
|
|
|
|
self.getAccounts(cb);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.events.setCommandHandler("blockchain:getBalance", function(address, cb) {
|
|
|
|
self.getBalance(address, 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-09-28 10:09:23 +00:00
|
|
|
this.events.setCommandHandler("blockchain:networkId", function(cb) {
|
|
|
|
self.getNetworkId().then(cb);
|
|
|
|
});
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2018-07-31 14:46:19 +00:00
|
|
|
registerAPIRequests() {
|
|
|
|
const self = this;
|
|
|
|
|
|
|
|
let plugin = self.plugins.createPlugin('blockchain', {});
|
|
|
|
plugin.registerAPICall(
|
|
|
|
'get',
|
2018-08-01 09:28:25 +00:00
|
|
|
'/embark-api/blockchain/accounts',
|
2018-07-31 14:46:19 +00:00
|
|
|
(req, res) => {
|
2018-08-03 15:51:15 +00:00
|
|
|
self.getAccountsWithTransactionCount(res.send.bind(res));
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
plugin.registerAPICall(
|
|
|
|
'get',
|
|
|
|
'/embark-api/blockchain/accounts/:address',
|
|
|
|
(req, res) => {
|
|
|
|
self.getAccount(req.params.address, res.send.bind(res));
|
2018-07-31 14:46:19 +00:00
|
|
|
}
|
|
|
|
);
|
2018-08-02 10:27:33 +00:00
|
|
|
|
|
|
|
plugin.registerAPICall(
|
|
|
|
'get',
|
|
|
|
'/embark-api/blockchain/blocks',
|
|
|
|
(req, res) => {
|
2018-08-02 11:40:25 +00:00
|
|
|
let from = parseInt(req.query.from, 10);
|
2018-08-02 10:27:33 +00:00
|
|
|
let limit = req.query.limit || 10;
|
2018-08-02 13:40:13 +00:00
|
|
|
self.getBlocks(from, limit, false, res.send.bind(res));
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2018-08-03 15:51:15 +00:00
|
|
|
plugin.registerAPICall(
|
|
|
|
'get',
|
|
|
|
'/embark-api/blockchain/blocks/:blockNumber',
|
|
|
|
(req, res) => {
|
2018-08-06 12:39:00 +00:00
|
|
|
self.getBlock(req.params.blockNumber, (err, block) => {
|
2018-08-27 20:22:53 +00:00
|
|
|
if (err) {
|
2018-08-06 12:39:00 +00:00
|
|
|
self.logger.error(err);
|
|
|
|
}
|
2018-08-03 15:51:15 +00:00
|
|
|
res.send(block);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2018-08-02 13:40:13 +00:00
|
|
|
plugin.registerAPICall(
|
|
|
|
'get',
|
|
|
|
'/embark-api/blockchain/transactions',
|
|
|
|
(req, res) => {
|
|
|
|
let blockFrom = parseInt(req.query.blockFrom, 10);
|
|
|
|
let blockLimit = req.query.blockLimit || 10;
|
|
|
|
self.getTransactions(blockFrom, blockLimit, res.send.bind(res));
|
|
|
|
}
|
|
|
|
);
|
2018-08-03 11:08:42 +00:00
|
|
|
|
2018-08-03 15:51:15 +00:00
|
|
|
plugin.registerAPICall(
|
|
|
|
'get',
|
|
|
|
'/embark-api/blockchain/transactions/:hash',
|
|
|
|
(req, res) => {
|
2018-08-06 12:39:00 +00:00
|
|
|
self.getTransaction(req.params.hash, (err, transaction) => {
|
2018-08-27 20:22:53 +00:00
|
|
|
if (err) {
|
2018-08-06 12:39:00 +00:00
|
|
|
self.logger.error(err);
|
|
|
|
}
|
2018-08-03 15:51:15 +00:00
|
|
|
res.send(transaction);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
2018-08-03 11:08:42 +00:00
|
|
|
|
|
|
|
plugin.registerAPICall(
|
|
|
|
'ws',
|
|
|
|
'/embark-api/blockchain/blockHeader',
|
|
|
|
(ws) => {
|
2018-08-31 17:16:02 +00:00
|
|
|
self.events.on('block:header', (block) => {
|
2018-08-03 11:08:42 +00:00
|
|
|
ws.send(JSON.stringify({block: block}), () => {});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
2018-08-02 13:40:13 +00:00
|
|
|
}
|
|
|
|
|
2018-08-03 15:51:15 +00:00
|
|
|
getAccountsWithTransactionCount(callback) {
|
|
|
|
let self = this;
|
|
|
|
self.getAccounts((err, addresses) => {
|
|
|
|
let accounts = [];
|
|
|
|
async.eachOf(addresses, (address, index, eachCb) => {
|
|
|
|
let account = {address, index};
|
|
|
|
async.waterfall([
|
|
|
|
function(callback) {
|
|
|
|
self.getTransactionCount(address, (err, count) => {
|
|
|
|
if (err) {
|
|
|
|
self.logger.error(err);
|
|
|
|
account.transactionCount = 0;
|
|
|
|
} else {
|
|
|
|
account.transactionCount = count;
|
|
|
|
}
|
|
|
|
callback(null, account);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function(account, callback) {
|
|
|
|
self.getBalance(address, (err, balance) => {
|
|
|
|
if (err) {
|
|
|
|
self.logger.error(err);
|
|
|
|
account.balance = 0;
|
|
|
|
} else {
|
|
|
|
account.balance = self.web3.utils.fromWei(balance);
|
|
|
|
}
|
|
|
|
callback(null, account);
|
|
|
|
});
|
|
|
|
}
|
2018-08-27 20:22:53 +00:00
|
|
|
], function(_err, account) {
|
2018-08-03 15:51:15 +00:00
|
|
|
accounts.push(account);
|
|
|
|
eachCb();
|
|
|
|
});
|
2018-08-27 20:22:53 +00:00
|
|
|
}, function() {
|
2018-08-03 15:51:15 +00:00
|
|
|
callback(accounts);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getAccount(address, callback) {
|
|
|
|
let self = this;
|
|
|
|
async.waterfall([
|
|
|
|
function(next) {
|
2018-08-06 09:03:52 +00:00
|
|
|
self.getAccountsWithTransactionCount((accounts) => {
|
2018-08-03 15:51:15 +00:00
|
|
|
let account = accounts.find((a) => a.address === address);
|
2018-08-27 20:22:53 +00:00
|
|
|
if (!account) {
|
2018-08-06 11:54:11 +00:00
|
|
|
return next("No account found with this address");
|
2018-08-03 15:51:15 +00:00
|
|
|
}
|
|
|
|
next(null, account);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function(account, next) {
|
|
|
|
self.getBlockNumber((err, blockNumber) => {
|
|
|
|
if (err) {
|
|
|
|
self.logger.error(err);
|
|
|
|
next(err);
|
|
|
|
} else {
|
|
|
|
next(null, blockNumber, account);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function(blockNumber, account, next) {
|
|
|
|
self.getTransactions(blockNumber, blockNumber, (transactions) => {
|
|
|
|
account.transactions = transactions.filter((transaction) => transaction.from === address);
|
|
|
|
next(null, account);
|
|
|
|
});
|
|
|
|
}
|
2018-08-27 20:22:53 +00:00
|
|
|
], function(err, result) {
|
2018-08-03 15:51:15 +00:00
|
|
|
if (err) {
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
callback(result);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-02 13:40:13 +00:00
|
|
|
getTransactions(blockFrom, blockLimit, callback) {
|
|
|
|
this.getBlocks(blockFrom, blockLimit, true, (blocks) => {
|
2018-08-31 20:47:58 +00:00
|
|
|
let transactions = blocks.reduce((acc, block) => {
|
|
|
|
if (!block || !block.transactions) {
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
return acc.concat(block.transactions);
|
|
|
|
}, []);
|
2018-08-02 13:40:13 +00:00
|
|
|
callback(transactions);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getBlocks(from, limit, returnTransactionObjects, callback) {
|
|
|
|
let self = this;
|
|
|
|
let blocks = [];
|
|
|
|
async.waterfall([
|
|
|
|
function(next) {
|
|
|
|
if (!isNaN(from)) {
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
self.getBlockNumber((err, blockNumber) => {
|
|
|
|
if (err) {
|
|
|
|
self.logger.error(err);
|
|
|
|
from = 0;
|
|
|
|
} else {
|
|
|
|
from = blockNumber;
|
2018-08-02 10:27:33 +00:00
|
|
|
}
|
2018-08-02 13:40:13 +00:00
|
|
|
next();
|
2018-08-02 10:27:33 +00:00
|
|
|
});
|
2018-08-02 13:40:13 +00:00
|
|
|
},
|
|
|
|
function(next) {
|
|
|
|
async.times(limit, function(n, eachCb) {
|
|
|
|
self.web3.eth.getBlock(from - n, returnTransactionObjects, function(err, block) {
|
2018-08-27 20:22:53 +00:00
|
|
|
if (err) {
|
2018-08-02 13:40:13 +00:00
|
|
|
self.logger.error(err);
|
|
|
|
return eachCb();
|
|
|
|
}
|
|
|
|
blocks.push(block);
|
|
|
|
eachCb();
|
|
|
|
});
|
|
|
|
}, next);
|
2018-08-02 10:27:33 +00:00
|
|
|
}
|
2018-08-27 20:22:53 +00:00
|
|
|
], function() {
|
2018-08-02 13:40:13 +00:00
|
|
|
callback(blocks);
|
|
|
|
});
|
2018-07-31 14:46:19 +00:00
|
|
|
}
|
|
|
|
|
2018-05-18 22:31:47 +00:00
|
|
|
defaultAccount() {
|
|
|
|
return this.web3.eth.defaultAccount;
|
|
|
|
}
|
|
|
|
|
2018-08-02 10:27:33 +00:00
|
|
|
getBlockNumber(cb) {
|
|
|
|
return this.web3.eth.getBlockNumber(cb);
|
|
|
|
}
|
|
|
|
|
2018-05-18 22:31:47 +00:00
|
|
|
setDefaultAccount(account) {
|
|
|
|
this.web3.eth.defaultAccount = account;
|
|
|
|
}
|
|
|
|
|
|
|
|
getAccounts(cb) {
|
|
|
|
this.web3.eth.getAccounts(cb);
|
|
|
|
}
|
|
|
|
|
2018-07-31 14:46:19 +00:00
|
|
|
getTransactionCount(address, cb) {
|
|
|
|
this.web3.eth.getTransactionCount(address, cb);
|
|
|
|
}
|
|
|
|
|
2018-10-10 16:09:56 +00:00
|
|
|
getBalance(address, cb) {
|
|
|
|
this.web3.eth.getBalance(address, 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) {
|
2018-08-03 15:51:15 +00:00
|
|
|
this.web3.eth.getBlock(blockNumber, true, cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
getTransaction(hash, cb) {
|
|
|
|
this.web3.eth.getTransaction(hash, cb);
|
2018-05-18 22:50:20 +00:00
|
|
|
}
|
|
|
|
|
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-09-28 10:09:23 +00:00
|
|
|
getNetworkId() {
|
|
|
|
return this.web3.eth.net.getId();
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-08-13 17:04:42 +00:00
|
|
|
deployContractFromObject(deployContractObject, params, cb) {
|
2018-08-27 20:22:53 +00:00
|
|
|
embarkJsUtils.secureSend(this.web3, deployContractObject, {
|
2018-05-19 00:26:21 +00:00
|
|
|
from: params.from, gas: params.gas, gasPrice: params.gasPrice
|
2018-08-27 20:22:53 +00:00
|
|
|
}, true, cb);
|
2018-05-20 13:08:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
determineDefaultAccount(cb) {
|
|
|
|
const self = this;
|
2018-08-27 20:22:53 +00:00
|
|
|
self.getAccounts(function(err, accounts) {
|
2018-05-20 13:08:03 +00:00
|
|
|
if (err) {
|
|
|
|
self.logger.error(err);
|
|
|
|
return cb(new Error(err));
|
|
|
|
}
|
2018-10-12 15:23:12 +00:00
|
|
|
let accountConfig = self.config.blockchainConfig.account;
|
2018-05-20 13:08:03 +00:00
|
|
|
let selectedAccount = accountConfig && accountConfig.address;
|
2018-10-17 05:14:55 +00:00
|
|
|
const defaultAccount = selectedAccount || accounts[0];
|
|
|
|
self.setDefaultAccount(defaultAccount);
|
|
|
|
cb(null, defaultAccount);
|
2018-05-20 13:08:03 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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
|
2018-08-29 10:03:06 +00:00
|
|
|
this.events.emit("runcode:register", "web3", this.web3, false);
|
2018-05-23 15:16:56 +00:00
|
|
|
}
|
2018-08-27 20:22:53 +00:00
|
|
|
|
|
|
|
subscribeToPendingTransactions() {
|
|
|
|
const self = this;
|
|
|
|
this.onReady(() => {
|
|
|
|
if (self.logsSubscription) {
|
|
|
|
self.logsSubscription.unsubscribe();
|
|
|
|
}
|
|
|
|
self.logsSubscription = self.web3.eth
|
|
|
|
.subscribe('newBlockHeaders', () => {})
|
|
|
|
.on("data", function (blockHeader) {
|
|
|
|
self.events.emit('block:header', blockHeader);
|
|
|
|
});
|
2018-08-29 19:46:17 +00:00
|
|
|
|
|
|
|
if (self.pendingSubscription) {
|
|
|
|
self.pendingSubscription.unsubscribe();
|
|
|
|
}
|
|
|
|
self.pendingSubscription = self.web3.eth
|
|
|
|
.subscribe('pendingTransactions', function(error, transaction){
|
|
|
|
if (!error) {
|
|
|
|
self.events.emit('block:pending:transaction', transaction);
|
|
|
|
}
|
|
|
|
});
|
2018-08-27 20:22:53 +00:00
|
|
|
});
|
|
|
|
}
|
2018-10-09 13:19:58 +00:00
|
|
|
|
|
|
|
subscribeToContractEvents(callback) {
|
|
|
|
this.contractsSubscriptions.forEach((eventEmitter) => {
|
|
|
|
eventEmitter.unsubscribe();
|
|
|
|
});
|
|
|
|
this.contractsSubscriptions = [];
|
|
|
|
this.events.request("contracts:list", (_err, contractsList) => {
|
|
|
|
contractsList.forEach(contractObject => {
|
|
|
|
if (!contractObject.address){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const contract = this.ContractObject({abi: contractObject.abiDefinition, address: contractObject.address});
|
|
|
|
const eventEmitter = contract.events.allEvents();
|
|
|
|
this.contractsSubscriptions.push(eventEmitter);
|
|
|
|
eventEmitter.on('data', (data) => {
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
2018-05-18 20:51:03 +00:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:43:38 +00:00
|
|
|
BlockchainConnector.ACCEPTED_TYPES = ['rpc', 'ws', 'vm'];
|
2018-07-27 19:36:16 +00:00
|
|
|
module.exports = BlockchainConnector;
|