mirror of https://github.com/embarklabs/embark.git
feat(@embark/core): Disable regular txs until needed
Regular transactions (aka “dev funds”) exist in embark as a workaround to a known bug in geth when using metamask. The workaround is to send a transaction at a regular interval (1.5s), which pushes through any transactions that were stuck. The problem is that the transaction logs and trace logs become cluttered and difficult to parse visually. This PR disables regular transactions until the following conditions are met: 1. Embark is running geth 2. The user is running metamask in their browser 3. The user authenticates to the cockpit with `enableRegularTxs=1|true` in the query string. A console warning is show in large letters in the browser with a link to the cockpit URL that includes the special query string to enable regular txs. This could be extended later to have a button in the cockpit that start/stops regular txs. Or at least extended to allow disabling of regular txs once started. Support standalone blockchain process.
This commit is contained in:
parent
8efa8895aa
commit
135fde0a85
|
@ -426,6 +426,13 @@ export const removeEditorTabs = {
|
|||
failure: () => action(REMOVE_EDITOR_TABS[FAILURE])
|
||||
};
|
||||
|
||||
export const INIT_REGULAR_TXS = createRequestTypes('INIT_REGULAR_TXS');
|
||||
export const initRegularTxs = {
|
||||
request: () => action(INIT_REGULAR_TXS[REQUEST], {mode: 'on'}),
|
||||
success: () => action(INIT_REGULAR_TXS[SUCCESS]),
|
||||
failure: () => action(INIT_REGULAR_TXS[FAILURE])
|
||||
};
|
||||
|
||||
// Web Socket
|
||||
export const WATCH_NEW_PROCESS_LOGS = 'WATCH_NEW_PROCESS_LOGS';
|
||||
export const STOP_NEW_PROCESS_LOGS = 'STOP_NEW_PROCESS_LOGS';
|
||||
|
|
|
@ -6,7 +6,7 @@ import routes from '../routes';
|
|||
import Login from '../components/Login';
|
||||
import Layout from "../components/Layout";
|
||||
import {DEFAULT_HOST} from '../constants';
|
||||
import {getQueryToken, stripQueryToken} from '../utils/utils';
|
||||
import {getQueryToken, stripQueryToken, getQueryParam, stripQueryParam} from '../utils/utils';
|
||||
import {Helmet} from "react-helmet";
|
||||
|
||||
import {
|
||||
|
@ -16,6 +16,7 @@ import {
|
|||
plugins as pluginsAction,
|
||||
listenToServices as listenToServicesAction,
|
||||
listenToContracts as listenToContractsAction,
|
||||
initRegularTxs as initRegularTxsAction,
|
||||
changeTheme, fetchTheme
|
||||
} from '../actions';
|
||||
|
||||
|
@ -25,6 +26,8 @@ import {
|
|||
getCredentials, getAuthenticationError, getProcesses, getTheme
|
||||
} from '../reducers/selectors';
|
||||
|
||||
const ENABLE_REGULAR_TXS = 'enableRegularTxs';
|
||||
|
||||
class AppContainer extends Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchCredentials();
|
||||
|
@ -50,26 +53,28 @@ class AppContainer extends Component {
|
|||
|
||||
const queryToken = getQueryToken(this.props.location);
|
||||
if (queryToken && !(queryToken === this.props.credentials.token &&
|
||||
this.props.credentials.host === DEFAULT_HOST)) {
|
||||
this.props.credentials.host === DEFAULT_HOST)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.props.credentials.authenticated &&
|
||||
this.props.credentials.host &&
|
||||
this.props.credentials.token) {
|
||||
this.props.credentials.host &&
|
||||
this.props.credentials.token) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(){
|
||||
componentDidUpdate() {
|
||||
if (this.requireAuthentication()) {
|
||||
this.doAuthenticate();
|
||||
}
|
||||
|
||||
if (getQueryToken(this.props.location) &&
|
||||
(!this.props.credentials.authenticating ||
|
||||
const enableRegularTxs = !!getQueryParam(this.props.location, ENABLE_REGULAR_TXS);
|
||||
|
||||
if (getQueryToken(this.props.location) &&
|
||||
(!this.props.credentials.authenticating ||
|
||||
this.props.credentials.authenticated)) {
|
||||
this.props.history.replace(stripQueryToken(this.props.location));
|
||||
}
|
||||
|
@ -80,6 +85,10 @@ class AppContainer extends Component {
|
|||
this.props.listenToServices();
|
||||
this.props.fetchPlugins();
|
||||
this.props.listenToContracts();
|
||||
if (enableRegularTxs) {
|
||||
this.props.initRegularTxs();
|
||||
this.props.history.replace(stripQueryParam(this.props.location, ENABLE_REGULAR_TXS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,18 +108,18 @@ class AppContainer extends Component {
|
|||
renderBody() {
|
||||
if (this.shouldRenderLogin()) {
|
||||
return (
|
||||
<Login credentials={this.props.credentials}
|
||||
authenticate={this.props.authenticate}
|
||||
error={this.props.authenticationError} />
|
||||
<Login credentials={this.props.credentials}
|
||||
authenticate={this.props.authenticate}
|
||||
error={this.props.authenticationError} />
|
||||
);
|
||||
} else if (this.props.credentials.authenticating) {
|
||||
return <React.Fragment/>;
|
||||
return <React.Fragment />;
|
||||
}
|
||||
return (
|
||||
<Layout location={this.props.location}
|
||||
logout={this.props.logout}
|
||||
toggleTheme={() => this.toggleTheme()}
|
||||
currentTheme={this.props.theme}>
|
||||
logout={this.props.logout}
|
||||
toggleTheme={() => this.toggleTheme()}
|
||||
currentTheme={this.props.theme}>
|
||||
<React.Fragment>{routes}</React.Fragment>
|
||||
</Layout>
|
||||
);
|
||||
|
@ -148,7 +157,8 @@ AppContainer.propTypes = {
|
|||
fetchTheme: PropTypes.func,
|
||||
history: PropTypes.object,
|
||||
listenToServices: PropTypes.func,
|
||||
listenToContracts: PropTypes.func
|
||||
listenToContracts: PropTypes.func,
|
||||
initRegularTxs: PropTypes.func
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
@ -173,6 +183,7 @@ export default withRouter(connect(
|
|||
fetchPlugins: pluginsAction.request,
|
||||
changeTheme: changeTheme.request,
|
||||
fetchTheme: fetchTheme.request,
|
||||
listenToContracts: listenToContractsAction
|
||||
listenToContracts: listenToContractsAction,
|
||||
initRegularTxs: initRegularTxsAction.request
|
||||
},
|
||||
)(AppContainer));
|
||||
|
|
|
@ -79,6 +79,7 @@ export const debugStepIntoForward = doRequest.bind(null, actions.debugStepIntoFo
|
|||
export const debugStepIntoBackward = doRequest.bind(null, actions.debugStepIntoBackward, api.debugStepIntoBackward);
|
||||
export const toggleBreakpoint = doRequest.bind(null, actions.toggleBreakpoint, api.toggleBreakpoint);
|
||||
export const authenticate = doRequest.bind(null, actions.authenticate, api.authenticate);
|
||||
export const initRegularTxs = doRequest.bind(null, actions.initRegularTxs, api.initRegularTxs);
|
||||
|
||||
export const fetchCredentials = doRequest.bind(null, actions.fetchCredentials, storage.fetchCredentials);
|
||||
export const saveCredentials = doRequest.bind(null, actions.saveCredentials, storage.saveCredentials);
|
||||
|
@ -343,6 +344,10 @@ export function *watchRemoveEditorTabsSuccess() {
|
|||
yield takeEvery(actions.REMOVE_EDITOR_TABS[actions.SUCCESS], fetchEditorTabs);
|
||||
}
|
||||
|
||||
export function *watchInitRegularTxs() {
|
||||
yield takeEvery(actions.INIT_REGULAR_TXS[actions.REQUEST], initRegularTxs);
|
||||
}
|
||||
|
||||
function createChannel(socket) {
|
||||
return eventChannel(emit => {
|
||||
socket.onmessage = ((message) => {
|
||||
|
@ -585,6 +590,7 @@ export default function *root() {
|
|||
fork(watchRemoveEditorTabsSuccess),
|
||||
fork(watchPostFileSuccess),
|
||||
fork(watchPostFolderSuccess),
|
||||
fork(watchListenContracts)
|
||||
fork(watchListenContracts),
|
||||
fork(watchInitRegularTxs)
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -228,6 +228,10 @@ export function toggleBreakpoint(payload) {
|
|||
return post('/debugger/breakpoint', {params: payload, credentials: payload.credentials});
|
||||
}
|
||||
|
||||
export function initRegularTxs(payload) {
|
||||
return get('/regular-txs', {params: payload, credentials: payload.credentials});
|
||||
}
|
||||
|
||||
export function listenToDebugger(credentials) {
|
||||
return websocket(credentials, '/debugger');
|
||||
}
|
||||
|
|
|
@ -23,8 +23,12 @@ export function ansiToHtml(text) {
|
|||
return convert.toHtml(text.replace(/\n/g,'<br>'));
|
||||
}
|
||||
|
||||
export function getQueryParam(location, param) {
|
||||
return qs.parse(location.search, {ignoreQueryPrefix: true})[param];
|
||||
}
|
||||
|
||||
export function getQueryToken(location) {
|
||||
return qs.parse(location.search, {ignoreQueryPrefix: true}).token;
|
||||
return getQueryParam(location, 'token');
|
||||
}
|
||||
|
||||
export function getDebuggerTransactionHash(location) {
|
||||
|
@ -32,9 +36,13 @@ export function getDebuggerTransactionHash(location) {
|
|||
}
|
||||
|
||||
export function stripQueryToken(location) {
|
||||
return stripQueryParam(location, 'token');
|
||||
}
|
||||
|
||||
export function stripQueryParam(location, param) {
|
||||
const _location = Object.assign({}, location);
|
||||
_location.search = _location.search.replace(
|
||||
/(\?|&?)(token=[\w-]*)(&?)/,
|
||||
new RegExp(`(\\?|&?)(${param}=[\\w-]*)(&?)`),
|
||||
(_, p1, p2, p3) => (p2 ? (p3 === '&' ? p1 : '') : '')
|
||||
);
|
||||
return _location;
|
||||
|
|
|
@ -53,7 +53,9 @@
|
|||
"eth_sendTransaction": "eth_sendTransaction",
|
||||
"eth_sendRawTransaction": "eth_sendRawTransaction",
|
||||
"eth_getTransactionReceipt": "eth_getTransactionReceipt"
|
||||
}
|
||||
},
|
||||
"startRegularTxs": "startRegularTxs",
|
||||
"stopRegularTxs": "stopRegularTxs"
|
||||
},
|
||||
"storage": {
|
||||
"init": "init",
|
||||
|
|
|
@ -26,6 +26,9 @@ class BlockchainListener {
|
|||
|
||||
if (this.ipc.isServer()) {
|
||||
this._listenToBlockchainLogs();
|
||||
this._listenToCommands();
|
||||
this._registerConsoleCommands();
|
||||
this._registerApiEndpoint();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +43,43 @@ class BlockchainListener {
|
|||
this.processLogsApi.logHandler.handleLog({logLevel, message});
|
||||
});
|
||||
}
|
||||
|
||||
_registerConsoleCommands() {
|
||||
this.embark.registerConsoleCommand({
|
||||
description: 'Toggles regular transactions used to prevent transactions from getting stuck when using Geth and Metamask',
|
||||
matches: ['regularTxs on', 'regularTxs off'],
|
||||
usage: "regularTxs on/off",
|
||||
process: (cmd, callback) => {
|
||||
const eventCmd = `regularTxs:${cmd.trim().endsWith('on') ? 'start' : 'stop'}`;
|
||||
this.events.request(eventCmd, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_registerApiEndpoint() {
|
||||
this.embark.registerAPICall(
|
||||
'get',
|
||||
'/embark-api/regular-txs',
|
||||
(req, _res) => {
|
||||
this.events.request(`regularTxs:${req.query.mode === 'on' ? 'start' : 'stop'}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_listenToCommands() {
|
||||
|
||||
this.events.setCommandHandler('regularTxs:start', (cb) => {
|
||||
this.events.emit('regularTxs:start');
|
||||
this.ipc.broadcast('regularTxs', 'start');
|
||||
return cb(null, 'Enabling regular transactions');
|
||||
});
|
||||
|
||||
this.events.setCommandHandler('regularTxs:stop', (cb) => {
|
||||
this.events.emit('regularTxs:stop');
|
||||
this.ipc.broadcast('regularTxs', 'stop');
|
||||
return cb(null, 'Disabling regular transactions');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlockchainListener;
|
||||
|
|
|
@ -65,6 +65,8 @@ var Blockchain = function(userConfig, clientClass) {
|
|||
proxy: this.userConfig.proxy
|
||||
};
|
||||
|
||||
this.devFunds = null;
|
||||
|
||||
if (this.userConfig.accounts) {
|
||||
const nodeAccounts = this.userConfig.accounts.find(account => account.nodeAccounts);
|
||||
if (nodeAccounts) {
|
||||
|
@ -120,7 +122,7 @@ Blockchain.prototype.initStandaloneProcess = function () {
|
|||
if (this.isStandalone) {
|
||||
// on every log logged in logger (say that 3x fast), send the log
|
||||
// to the IPC serve listening (only if we're connected of course)
|
||||
this.events.on('log', (logLevel, message) => {
|
||||
this.logger.events.on('log', (logLevel, message) => {
|
||||
if (this.ipc.connected) {
|
||||
this.ipc.request('blockchain:log', {logLevel, message});
|
||||
}
|
||||
|
@ -133,7 +135,14 @@ Blockchain.prototype.initStandaloneProcess = function () {
|
|||
// `embark run` without restarting `embark blockchain`)
|
||||
setInterval(() => {
|
||||
if (!this.ipc.connected) {
|
||||
this.ipc.connect(() => {});
|
||||
this.ipc.connect(() => {
|
||||
if (this.ipc.connected) {
|
||||
this.ipc.listenTo('regularTxs', (mode) => {
|
||||
if(mode === 'start') this.startRegularTxs(() => {});
|
||||
else if (mode === 'stop') this.stopRegularTxs(() => {});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, IPC_CONNECT_INTERVAL);
|
||||
}
|
||||
|
@ -244,11 +253,6 @@ Blockchain.prototype.run = function () {
|
|||
data = data.toString();
|
||||
if (!self.readyCalled && self.client.isReady(data)) {
|
||||
self.readyCalled = true;
|
||||
if (self.isDev) {
|
||||
self.fundAccounts((err) => {
|
||||
if (err) this.logger.error('Error funding accounts', err);
|
||||
});
|
||||
}
|
||||
if (self.config.proxy) {
|
||||
await self.setupProxy();
|
||||
}
|
||||
|
@ -280,14 +284,50 @@ Blockchain.prototype.run = function () {
|
|||
};
|
||||
|
||||
Blockchain.prototype.fundAccounts = function(cb) {
|
||||
DevFunds.new({blockchainConfig: this.config}).then(devFunds => {
|
||||
devFunds.fundAccounts(this.client.needKeepAlive(), (err) => {
|
||||
if(this.isDev && this.devFunds){
|
||||
this.devFunds.fundAccounts((err) => {
|
||||
cb(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockchain.prototype.startRegularTxs = function(cb) {
|
||||
if (this.client.needKeepAlive() && this.devFunds){
|
||||
return this.devFunds.startRegularTxs(() => {
|
||||
this.logger.info('Regular transactions have been enabled.');
|
||||
cb();
|
||||
});
|
||||
}
|
||||
cb();
|
||||
};
|
||||
|
||||
Blockchain.prototype.stopRegularTxs = function(cb) {
|
||||
if (this.client.needKeepAlive() && this.devFunds){
|
||||
return this.devFunds.stopRegularTxs(() => {
|
||||
this.logger.info('Regular transactions have been disabled.');
|
||||
cb();
|
||||
});
|
||||
}
|
||||
cb();
|
||||
};
|
||||
|
||||
Blockchain.prototype.readyCallback = function () {
|
||||
if (this.isDev) {
|
||||
if(!this.devFunds) {
|
||||
DevFunds.new({blockchainConfig: this.config}).then(devFunds => {
|
||||
this.devFunds = devFunds;
|
||||
this.fundAccounts((err) => {
|
||||
if (err) this.logger.error('Error funding accounts', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.fundAccounts((err) => {
|
||||
if (err) this.logger.error('Error funding accounts', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.onReadyCallback) {
|
||||
this.onReadyCallback();
|
||||
}
|
||||
|
@ -450,7 +490,7 @@ Blockchain.prototype.initChainAndGetAddress = function (callback) {
|
|||
});
|
||||
};
|
||||
|
||||
var BlockchainClient = function(userConfig, clientName, env, certOptions, onReadyCallback, onExitCallback, logger, _events, _isStandalone) {
|
||||
var BlockchainClient = function(userConfig, clientName, env, certOptions, onReadyCallback, onExitCallback, logger, _events, isStandalone) {
|
||||
if ((userConfig === {} || JSON.stringify(userConfig) === '{"enabled":true}') && env !== 'development') {
|
||||
logger.info("===> " + __("warning: running default config on a non-development environment"));
|
||||
}
|
||||
|
@ -478,6 +518,7 @@ var BlockchainClient = function(userConfig, clientName, env, certOptions, onRead
|
|||
userConfig.onExitCallback = onExitCallback;
|
||||
userConfig.logger = logger;
|
||||
userConfig.certOptions = certOptions;
|
||||
userConfig.isStandalone = isStandalone;
|
||||
return new Blockchain(userConfig, clientClass);
|
||||
};
|
||||
|
||||
|
|
|
@ -52,4 +52,10 @@ process.on('message', (msg) => {
|
|||
blockchainProcess = new BlockchainProcess(msg.options);
|
||||
return blockchainProcess.send({result: constants.blockchain.initiated});
|
||||
}
|
||||
else if(msg.action === constants.blockchain.startRegularTxs){
|
||||
blockchainProcess.blockchain.startRegularTxs(() => {});
|
||||
}
|
||||
else if(msg.action === constants.blockchain.stopRegularTxs){
|
||||
blockchainProcess.blockchain.stopRegularTxs(() => {});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -38,7 +38,8 @@ class BlockchainProcessLauncher {
|
|||
env: this.env,
|
||||
isDev: this.isDev,
|
||||
locale: this.locale,
|
||||
certOptions: this.embark.config.webServerConfig.certOptions
|
||||
certOptions: this.embark.config.webServerConfig.certOptions,
|
||||
events: this.events
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -62,6 +63,14 @@ class BlockchainProcessLauncher {
|
|||
this.events.on('logs:ethereum:disable', () => {
|
||||
this.blockchainProcess.silent = true;
|
||||
});
|
||||
|
||||
this.events.on('regularTxs:start', () => {
|
||||
this.blockchainProcess.send({action: constants.blockchain.startRegularTxs});
|
||||
});
|
||||
|
||||
this.events.on('regularTxs:stop', () => {
|
||||
this.blockchainProcess.send({action: constants.blockchain.stopRegularTxs});
|
||||
});
|
||||
|
||||
this.events.on('exit', () => {
|
||||
this.blockchainProcess.send('exit');
|
||||
|
|
|
@ -60,20 +60,28 @@ class DevFunds {
|
|||
this.web3.eth.sendTransaction({value: "1000000000000000", to: "0xA2817254cb8E7b6269D1689c3E0eBadbB78889d1", from: this.web3.eth.defaultAccount});
|
||||
}
|
||||
|
||||
_regularTxs(cb) {
|
||||
startRegularTxs(cb) {
|
||||
const self = this;
|
||||
self.web3.eth.net.getId().then((networkId) => {
|
||||
self.networkId = networkId;
|
||||
if (self.networkId !== 1337) {
|
||||
return;
|
||||
}
|
||||
setInterval(function() { self._sendTx(); }, 1500);
|
||||
this.regularTxsInt = setInterval(function() { self._sendTx(); }, 1500);
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopRegularTxs(cb) {
|
||||
if(this.regularTxsInt) {
|
||||
clearInterval(this.regularTxsInt);
|
||||
return cb();
|
||||
}
|
||||
cb('Regular txs not enabled.');
|
||||
}
|
||||
|
||||
_fundAccounts(balance, cb) {
|
||||
async.each(this.accounts, (account, next) => {
|
||||
this.web3.eth.getBalance(account).then(currBalance => {
|
||||
|
@ -113,13 +121,12 @@ class DevFunds {
|
|||
}, cb);
|
||||
}
|
||||
|
||||
fundAccounts(pingForever = false, cb) {
|
||||
fundAccounts(cb) {
|
||||
if (!this.web3) {
|
||||
return cb();
|
||||
}
|
||||
async.waterfall([
|
||||
(next) => {
|
||||
if (pingForever) this._regularTxs();
|
||||
this._fundAccounts(this.balance, next);
|
||||
}
|
||||
], cb);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
EmbarkJS.Blockchain.autoEnable = <%= autoEnable %>;
|
||||
EmbarkJS.Blockchain.connect(<%- connectionList %>, {warnAboutMetamask: <%= warnAboutMetamask %>}, function(err) {
|
||||
EmbarkJS.Blockchain.connect(<%- connectionList %>, {warnAboutMetamask: <%= warnAboutMetamask %>, blockchainClient: "<%= blockchainClient %>"}, function(err) {
|
||||
<%- done %>
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ class CodeGenerator {
|
|||
this.storageConfig = embark.config.storageConfig || {};
|
||||
this.communicationConfig = embark.config.communicationConfig || {};
|
||||
this.namesystemConfig = embark.config.namesystemConfig || {};
|
||||
this.webServerConfig = embark.config.webServerConfig || {};
|
||||
this.env = options.env || 'development';
|
||||
this.plugins = options.plugins;
|
||||
this.events = embark.events;
|
||||
|
@ -124,7 +125,8 @@ class CodeGenerator {
|
|||
autoEnable: this.contractsConfig.dappAutoEnable,
|
||||
connectionList: connectionList,
|
||||
done: 'done(err);',
|
||||
warnAboutMetamask: isDev
|
||||
warnAboutMetamask: isDev,
|
||||
blockchainClient: this.blockchainConfig.ethereumClientName
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ describe('embark.DevFunds', function() {
|
|||
// provider.injectResult('0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe'); // send tx response
|
||||
});
|
||||
|
||||
devFunds.fundAccounts(devFunds.balance, (errFundAccounts) => {
|
||||
devFunds.fundAccounts((errFundAccounts) => {
|
||||
assert.equal(errFundAccounts, null);
|
||||
|
||||
// inject response for web3.eth.getAccounts
|
||||
|
|
Loading…
Reference in New Issue