Merge pull request #633 from embark-framework/fund_accounts

Changes for 3.1 & --dev mode
This commit is contained in:
Iuri Matias 2018-07-19 11:16:20 +03:00 committed by GitHub
commit f5d82d222c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 3102 additions and 2916 deletions

View File

@ -6,6 +6,7 @@ const fs = require('../../core/fs.js');
const constants = require('../../constants.json'); const constants = require('../../constants.json');
const GethCommands = require('./geth_commands.js'); const GethCommands = require('./geth_commands.js');
const DevFunds = require('./dev_funds.js');
/*eslint complexity: ["error", 36]*/ /*eslint complexity: ["error", 36]*/
var Blockchain = function(options) { var Blockchain = function(options) {
@ -13,12 +14,17 @@ var Blockchain = function(options) {
this.env = options.env || 'development'; this.env = options.env || 'development';
this.client = options.client; this.client = options.client;
this.isDev = options.isDev; this.isDev = options.isDev;
this.onReadyCallback = options.onReadyCallback; this.onReadyCallback = options.onReadyCallback || (() => {});
if ((this.blockchainConfig === {} || JSON.stringify(this.blockchainConfig) === '{"enabled":true}') && this.env !== 'development') { if ((this.blockchainConfig === {} || JSON.stringify(this.blockchainConfig) === '{"enabled":true}') && this.env !== 'development') {
console.log("===> " + __("warning: running default config on a non-development environment")); console.log("===> " + __("warning: running default config on a non-development environment"));
} }
let defaultWsApi = ['eth', 'web3', 'net', 'shh', 'debug'];
if (this.isDev) {
defaultWsApi.push('personal');
}
this.config = { this.config = {
geth_bin: this.blockchainConfig.geth_bin || 'geth', geth_bin: this.blockchainConfig.geth_bin || 'geth',
networkType: this.blockchainConfig.networkType || 'custom', networkType: this.blockchainConfig.networkType || 'custom',
@ -41,7 +47,7 @@ var Blockchain = function(options) {
wsHost: this.blockchainConfig.wsHost || 'localhost', wsHost: this.blockchainConfig.wsHost || 'localhost',
wsPort: this.blockchainConfig.wsPort || 8546, wsPort: this.blockchainConfig.wsPort || 8546,
wsOrigins: this.blockchainConfig.wsOrigins || false, wsOrigins: this.blockchainConfig.wsOrigins || false,
wsApi: (this.blockchainConfig.wsApi || ['eth', 'web3', 'net', 'shh', 'debug']), wsApi: (this.blockchainConfig.wsApi || defaultWsApi),
vmdebug: this.blockchainConfig.vmdebug || false, vmdebug: this.blockchainConfig.vmdebug || false,
targetGasLimit: this.blockchainConfig.targetGasLimit || false, targetGasLimit: this.blockchainConfig.targetGasLimit || false,
syncMode: this.blockchainConfig.syncMode, syncMode: this.blockchainConfig.syncMode,
@ -162,6 +168,13 @@ Blockchain.prototype.run = function() {
self.child.stderr.on('data', (data) => { self.child.stderr.on('data', (data) => {
data = data.toString(); data = data.toString();
if (self.onReadyCallback && !self.readyCalled && data.indexOf('WebSocket endpoint opened') > -1) { if (self.onReadyCallback && !self.readyCalled && data.indexOf('WebSocket endpoint opened') > -1) {
if (self.isDev) {
self.createFundAndUnlockAccounts((err) => {
if(err) console.error('Error creating, unlocking, and funding accounts', err);
//self.readyCalled = true;
//self.onReadyCallback();
});
}
self.readyCalled = true; self.readyCalled = true;
self.onReadyCallback(); self.onReadyCallback();
} }
@ -175,6 +188,13 @@ Blockchain.prototype.run = function() {
}); });
}; };
Blockchain.prototype.createFundAndUnlockAccounts = function(cb) {
let devFunds = new DevFunds(this.config);
devFunds.createFundAndUnlockAccounts((err) => {
cb(err);
});
};
Blockchain.prototype.kill = function() { Blockchain.prototype.kill = function() {
if (this.child) { if (this.child) {
this.child.kill(); this.child.kill();

View File

@ -0,0 +1,124 @@
const async = require('async');
const Web3 = require('web3');
const {getWeiBalanceFromString, buildUrl} = require('../../utils/utils.js');
const {readFileSync, dappPath} = require('../../core/fs');
class DevFunds {
constructor(blockchainConfig) {
// TODO: temporary to avoid nasty suprises, should be removed
try {
this.web3 = null;
this.blockchainConfig = blockchainConfig;
this.accounts = [];
this.numAccounts = this.blockchainConfig.account.numAccounts || 0;
this.password = readFileSync(dappPath(blockchainConfig.account.password), 'utf8').replace('\n', '');
this.web3 = new Web3();
this.networkId = null;
this.balance = Web3.utils.toWei("1", "ether");
if (this.blockchainConfig.account.balance) {
this.balance = getWeiBalanceFromString(this.blockchainConfig.account.balance, this.web3);
}
} catch(_err) {
}
}
_sendTx() {
if (this.networkId !== 1337) {
return;
}
this.web3.eth.sendTransaction({value: "1000000000000000", to: "0xA2817254cb8E7b6269D1689c3E0eBadbB78889d1", from: this.web3.eth.defaultAccount});
}
// trigger regular txs due to a bug in geth and stuck transactions in --dev mode
regularTxs(cb) {
const self = this;
self.web3.eth.net.getId().then((networkId) => {
self.networkId = networkId;
if (self.networkId !== 1337) {
return;
}
setInterval(function() { self._sendTx(); }, 1500);
if (cb) {
cb();
}
});
}
regularUnlocks() {
const self = this;
setInterval(function() { self.unlockAccounts(self.password, () => {}); }, 20000);
}
connectToNode(cb) {
this.web3.setProvider(new Web3.providers.WebsocketProvider(buildUrl('ws', this.blockchainConfig.wsHost, this.blockchainConfig.wsPort), {headers: {Origin: "http://localhost:8000"}}));
this.web3.eth.getAccounts().then((accounts) => {
this.web3.eth.defaultAccount = accounts[0];
if (accounts.length > 1) {
this.accounts = accounts.slice(1);
}
cb();
});
}
createAccounts(numAccounts, password, cb) {
const numAccountsToCreate = numAccounts - (this.accounts.length + 1);
if (numAccountsToCreate === 0) return cb();
async.timesLimit(numAccountsToCreate, 1, (_, next) => {
this.web3.eth.personal.newAccount(password, next);
}, (err, accounts) => {
if (err) return cb(err);
this.accounts = accounts;
cb();
});
}
unlockAccounts(password, cb) {
async.each(this.accounts, (account, next) => {
this.web3.eth.personal.unlockAccount(account, password).then((_result) => {
next();
}).catch(next);
}, cb);
}
fundAccounts(balance, cb) {
async.each(this.accounts, (account, next) => {
this.web3.eth.getBalance(account).then(currBalance => {
const remainingBalance = balance - currBalance;
if (remainingBalance <= 0) return next();
this.web3.eth.sendTransaction({to: account, value: remainingBalance}).then((_result) => {
next();
}).catch(next);
}, cb);
});
}
createFundAndUnlockAccounts(cb) {
if (!this.web3) {
return cb();
}
async.waterfall([
(next) => {
this.connectToNode(next);
},
(next) => {
this.createAccounts(this.numAccounts, this.password, next);
},
(next) => {
this.unlockAccounts(this.password, next);
},
(next) => {
this.regularTxs();
this.regularUnlocks();
this.fundAccounts(this.balance, next);
}
], cb);
}
}
module.exports = DevFunds;

View File

@ -123,7 +123,13 @@ class GethCommands {
let self = this; let self = this;
let config = this.config; let config = this.config;
let rpc_api = (this.config.rpcApi || ['eth', 'web3', 'net', 'debug']); let rpc_api = (this.config.rpcApi || ['eth', 'web3', 'net', 'debug']);
let ws_api = (this.config.wsApi || ['eth', 'web3', 'net', 'debug']);
let defaultWsApi = ['eth', 'web3', 'net', 'shh', 'debug'];
if (this.isDev) {
defaultWsApi.push('personal');
}
let ws_api = (this.config.wsApi || defaultWsApi);
let args = []; let args = [];

View File

@ -1,6 +1,7 @@
const bip39 = require("bip39"); const bip39 = require("bip39");
const hdkey = require('ethereumjs-wallet/hdkey'); const hdkey = require('ethereumjs-wallet/hdkey');
const fs = require('../core/fs'); const fs = require('../core/fs');
const {getHexBalanceFromString} = require('../utils/utils');
class AccountParser { class AccountParser {
static parseAccountsConfig(accountsConfig, web3, logger) { static parseAccountsConfig(accountsConfig, web3, logger) {
@ -21,31 +22,14 @@ class AccountParser {
return accounts; return accounts;
} }
static getHexBalance(balanceString, web3) {
if (!balanceString) {
return 0xFFFFFFFFFFFFFFFFFF;
}
if (web3.utils.isHexStrict(balanceString)) {
return balanceString;
}
const match = balanceString.match(/([0-9]+) ?([a-zA-Z]*)/);
if (!match) {
throw new Error(__('Unrecognized balance string "%s"', balanceString));
}
if (!match[2]) {
return web3.utils.toHex(parseInt(match[1], 10));
}
return web3.utils.toHex(web3.utils.toWei(match[1], match[2]));
}
static getAccount(accountConfig, web3, logger) { static getAccount(accountConfig, web3, logger) {
if (!logger) { if (!logger) {
logger = console; logger = console;
} }
let hexBalance = null; let hexBalance = null;
if (accountConfig.balance) { if (accountConfig.balance) {
hexBalance = AccountParser.getHexBalance(accountConfig.balance, web3); hexBalance = getHexBalanceFromString(accountConfig.balance, web3);
//hexBalance = getHexBalanceFromString(accountConfig.balance, web3);
} }
if (accountConfig.privateKey) { if (accountConfig.privateKey) {
if (!accountConfig.privateKey.startsWith('0x')) { if (!accountConfig.privateKey.startsWith('0x')) {

View File

@ -36,6 +36,7 @@ class Blockchain {
cb = function(){}; cb = function(){};
} }
if (this.isWeb3Ready) { if (this.isWeb3Ready) {
this.events.emit(WEB3_READY);
return cb(); return cb();
} }
const self = this; const self = this;
@ -50,7 +51,6 @@ class Blockchain {
const protocol = (this.contractsConfig.deployment.type === "rpc") ? this.contractsConfig.deployment.protocol : 'ws'; const protocol = (this.contractsConfig.deployment.type === "rpc") ? this.contractsConfig.deployment.protocol : 'ws';
let provider; let provider;
this.web3Endpoint = utils.buildUrl(protocol, this.contractsConfig.deployment.host, this.contractsConfig.deployment.port);//`${protocol}://${this.contractsConfig.deployment.host}:${this.contractsConfig.deployment.port}`; this.web3Endpoint = utils.buildUrl(protocol, this.contractsConfig.deployment.host, this.contractsConfig.deployment.port);//`${protocol}://${this.contractsConfig.deployment.host}:${this.contractsConfig.deployment.port}`;
const providerOptions = { const providerOptions = {
web3: this.web3, web3: this.web3,
accountsConfig: this.contractsConfig.deployment.accounts, accountsConfig: this.contractsConfig.deployment.accounts,
@ -87,6 +87,7 @@ class Blockchain {
provider.startWeb3Provider(next); provider.startWeb3Provider(next);
}, },
function fundAccountsIfNeeded(next) { function fundAccountsIfNeeded(next) {
self.isWeb3Ready = true;
provider.fundAccounts(next); provider.fundAccounts(next);
} }
], (err) => { ], (err) => {

View File

@ -199,6 +199,12 @@ class ContractDeployer {
function applyBeforeDeploy(next) { function applyBeforeDeploy(next) {
self.plugins.emitAndRunActionsForEvent('deploy:contract:beforeDeploy', {contract: contract}, next); self.plugins.emitAndRunActionsForEvent('deploy:contract:beforeDeploy', {contract: contract}, next);
}, },
function getGasPriceForNetwork(next) {
self.events.request("blockchain:gasPrice", (gasPrice) => {
contract.gasPrice = contract.gasPrice || gasPrice;
next();
});
},
function createDeployObject(next) { function createDeployObject(next) {
let contractObject = self.blockchain.ContractObject({abi: contract.abiDefinition}); let contractObject = self.blockchain.ContractObject({abi: contract.abiDefinition});

View File

@ -90,10 +90,7 @@ class ContractsManager {
callback(); callback();
}, },
function getGasPriceForNetwork(callback) { function getGasPriceForNetwork(callback) {
if (self.contractsConfig.gasPrice) { return callback(null, self.contractsConfig.gasPrice);
return callback(null, self.contractsConfig.gasPrice);
}
self.events.request("blockchain:gasPrice", callback);
}, },
function prepareContractsFromCompilation(gasPrice, callback) { function prepareContractsFromCompilation(gasPrice, callback) {
let className, compiledContract, contractConfig, contract; let className, compiledContract, contractConfig, contract;

View File

@ -83,9 +83,7 @@ class DeployManager {
// TODO: could be implemented as an event (beforeDeployAll) // TODO: could be implemented as an event (beforeDeployAll)
function checkIsConnectedToBlockchain(callback) { function checkIsConnectedToBlockchain(callback) {
self.blockchain.onReady(() => { self.blockchain.onReady(() => {
self.blockchain.assertNodeConnection((err) => { self.blockchain.assertNodeConnection(callback);
callback(err);
});
}); });
}, },

View File

@ -45,7 +45,7 @@ class Embark {
blockchain(env, client) { blockchain(env, client) {
this.context = [constants.contexts.blockchain]; this.context = [constants.contexts.blockchain];
return require('./cmds/blockchain/blockchain.js')(this.config.blockchainConfig, client, env, this.isDev(env)).run(); return require('./cmds/blockchain/blockchain.js')(this.config.blockchainConfig, client, env, this.isDev(env), () => {}).run();
} }
simulator(options) { simulator(options) {

View File

@ -1,6 +1,8 @@
let http = require('follow-redirects').http; let http = require('follow-redirects').http;
let https = require('follow-redirects').https; let https = require('follow-redirects').https;
const balanceRegex = /([0-9]+) ?([a-zA-Z]*)/;
function joinPath() { function joinPath() {
const path = require('path'); const path = require('path');
return path.join.apply(path.join, arguments); return path.join.apply(path.join, arguments);
@ -297,6 +299,45 @@ function buildUrlFromConfig (configObj){
return this.buildUrl(configObj.protocol, configObj.host, configObj.port); return this.buildUrl(configObj.protocol, configObj.host, configObj.port);
} }
function getWeiBalanceFromString(balanceString, web3){
if(!web3){
throw new Error(__('[utils.getWeiBalanceFromString]: Missing parameter \'web3\''));
}
if (!balanceString) {
return 0;
}
const match = balanceString.match(balanceRegex);
if (!match) {
throw new Error(__('Unrecognized balance string "%s"', balanceString));
}
if (!match[2]) {
return web3.utils.toHex(parseInt(match[1], 10));
}
return web3.utils.toWei(match[1], match[2]);
}
function getHexBalanceFromString(balanceString, web3) {
if(!web3){
throw new Error(__('[utils.getWeiBalanceFromString]: Missing parameter \'web3\''));
}
if (!balanceString) {
return 0xFFFFFFFFFFFFFFFFFF;
}
if (web3.utils.isHexStrict(balanceString)) {
return balanceString;
}
const match = balanceString.match(balanceRegex);
if (!match) {
throw new Error(__('Unrecognized balance string "%s"', balanceString));
}
if (!match[2]) {
return web3.utils.toHex(parseInt(match[1], 10));
}
return web3.utils.toHex(web3.utils.toWei(match[1], match[2]));
}
module.exports = { module.exports = {
joinPath: joinPath, joinPath: joinPath,
filesMatchingPattern: filesMatchingPattern, filesMatchingPattern: filesMatchingPattern,
@ -323,5 +364,7 @@ module.exports = {
sha3: sha3, sha3: sha3,
normalizeInput, normalizeInput,
buildUrl, buildUrl,
buildUrlFromConfig buildUrlFromConfig,
getWeiBalanceFromString,
getHexBalanceFromString
}; };

5760
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
const assert = require('assert'); const assert = require('assert');
const sinon = require('sinon'); const sinon = require('sinon');
const AccountParser = require('../lib/contracts/accountParser'); const AccountParser = require('../lib/contracts/accountParser');
const utils = require('../lib/utils/utils');
let TestLogger = require('../lib/tests/test_logger.js'); let TestLogger = require('../lib/tests/test_logger.js');
const Web3 = require('web3'); const Web3 = require('web3');
const i18n = require('../lib/i18n/i18n.js'); const i18n = require('../lib/i18n/i18n.js');
@ -75,38 +76,38 @@ describe('embark.AccountParser', function () {
describe('getHexBalance', () => { describe('getHexBalance', () => {
it('should return default if no balance', () => { it('should return default if no balance', () => {
const hexBalance = AccountParser.getHexBalance(null, Web3); const hexBalance = utils.getHexBalanceFromString(null, Web3);
assert.strictEqual(hexBalance, 0xFFFFFFFFFFFFFFFFFF); assert.strictEqual(hexBalance, 0xFFFFFFFFFFFFFFFFFF);
}); });
it('should return the balance string if already hexadecimal', () => { it('should return the balance string if already hexadecimal', () => {
const hexBalance = AccountParser.getHexBalance('0xFFF', Web3); const hexBalance = utils.getHexBalanceFromString('0xFFF', Web3);
assert.strictEqual(hexBalance, '0xFFF'); assert.strictEqual(hexBalance, '0xFFF');
}); });
it('should convert to hex when decimal', () => { it('should convert to hex when decimal', () => {
const hexBalance = AccountParser.getHexBalance('500', Web3); const hexBalance = utils.getHexBalanceFromString('500', Web3);
assert.strictEqual(hexBalance, '0x1f4'); assert.strictEqual(hexBalance, '0x1f4');
}); });
it('should convert to hex with eth string', () => { it('should convert to hex with eth string', () => {
const hexBalance = AccountParser.getHexBalance('4ether', Web3); const hexBalance = utils.getHexBalanceFromString('4ether', Web3);
assert.strictEqual(hexBalance, '0x3782dace9d900000'); assert.strictEqual(hexBalance, '0x3782dace9d900000');
}); });
it('should convert to hex with eth string with space', () => { it('should convert to hex with eth string with space', () => {
const hexBalance = AccountParser.getHexBalance('673 shannon', Web3); const hexBalance = utils.getHexBalanceFromString('673 shannon', Web3);
assert.strictEqual(hexBalance, '0x9cb1ed0a00'); assert.strictEqual(hexBalance, '0x9cb1ed0a00');
}); });
it('should fail when string is not good', () => { it('should fail when string is not good', () => {
try { try {
AccountParser.getHexBalance('nogood', Web3); utils.getHexBalanceFromString('nogood', Web3);
assert.fail('Should have failed at getHexBalance'); assert.fail('Should have failed at getHexBalance');
} catch (e) { } catch (e) {
// Ok // Ok

View File

@ -13,6 +13,8 @@
"rpcPort": 8545, "rpcPort": 8545,
"rpcCorsDomain": "auto", "rpcCorsDomain": "auto",
"account": { "account": {
"numAccounts": 3,
"balance": "5 ether",
"password": "config/development/password" "password": "config/development/password"
}, },
"targetGasLimit": 8000000, "targetGasLimit": 8000000,