From a27d230c4ca6d452e7deb1a17b5329b08b9bc6fd Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Wed, 10 Oct 2018 15:31:04 -0400 Subject: [PATCH] Added account options --- gas-relayer/config/config.js | 8 ++- gas-relayer/package.json | 2 + gas-relayer/src/account-parser.js | 70 +++++++++++++++++++ gas-relayer/src/message-processor.js | 46 +++++++----- gas-relayer/src/service.js | 19 ++++- .../src/strategy/AvailabilityStrategy.js | 2 +- gas-relayer/src/strategy/BaseStrategy.js | 2 +- gas-relayer/src/strategy/IdentityStrategy.js | 2 +- gas-relayer/src/strategy/SNTStrategy.js | 19 +++-- 9 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 gas-relayer/src/account-parser.js diff --git a/gas-relayer/config/config.js b/gas-relayer/config/config.js index 6ae86f6..995e7fa 100644 --- a/gas-relayer/config/config.js +++ b/gas-relayer/config/config.js @@ -11,7 +11,13 @@ module.exports = { "port": 8545 }, "blockchain": { - "account": "0xb8d851486d1c953e31a44374aca11151d49b8bb3" + // privateKey: "your_private_key", + + // privateKeyFile: "path/to/file" + + // mnemonic: "12 word mnemonic", + // addressIndex: "0", // Optionnal. The index to start getting the address + // hdpath: "m/44'/60'/0'/0/" // Optionnal. HD derivation path }, "whisper": { "symKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b", diff --git a/gas-relayer/package.json b/gas-relayer/package.json index b7f854b..9797b8e 100644 --- a/gas-relayer/package.json +++ b/gas-relayer/package.json @@ -22,8 +22,10 @@ }, "dependencies": { "axios": "^0.18.0", + "bip39": "^2.5.0", "consola": "^1.4.3", "daemonize2": "^0.4.2", + "ethereumjs-wallet": "^0.6.2", "ganache-cli": "^6.1.0", "jsum": "^0.1.4", "memory-cache": "^0.2.0", diff --git a/gas-relayer/src/account-parser.js b/gas-relayer/src/account-parser.js new file mode 100644 index 0000000..5865d3c --- /dev/null +++ b/gas-relayer/src/account-parser.js @@ -0,0 +1,70 @@ +const bip39 = require("bip39"); +const hdkey = require('ethereumjs-wallet/hdkey'); +const ethereumjsWallet = require('ethereumjs-wallet'); +const path = require('path'); +const fs = require('fs'); + + +class AccountParser { + static get(accountConfig, web3) { + if (accountConfig.privateKey) { + if (!accountConfig.privateKey.startsWith('0x')) { + accountConfig.privateKey = '0x' + accountConfig.privateKey; + } + if (!web3.utils.isHexStrict(accountConfig.privateKey)) { + console.error(`Private key ending with ${accountConfig.privateKey.substr(accountConfig.privateKey.length - 5)} is not a HEX string`); + return null; + } + return web3.eth.accounts.privateKeyToAccount(accountConfig.privateKey); + } + + + if (accountConfig.privateKeyFile) { + let privateKeyFile = path.resolve(accountConfig.privateKeyFile); + let fileContent = fs.readFileSync(privateKeyFile).toString(); + if (accountConfig.password) { + try { + fileContent = JSON.parse(fileContent); + if (!ethereumjsWallet['fromV' + fileContent.version]) { + console.error(`Key file ${accountConfig.privateKeyFile} is not a valid keystore file`); + return null; + } + const wallet = ethereumjsWallet['fromV' + fileContent.version](fileContent, accountConfig.password); + return web3.eth.accounts.privateKeyToAccount('0x' + wallet.getPrivateKey().toString('hex')); + } catch (e) { + console.error('Private key file is not a keystore JSON file but a password was provided'); + console.error(e.message || e); + return null; + } + } + + fileContent = fileContent.trim().split(/[,;]/); + return fileContent.map((key, index) => { + if (!key.startsWith('0x')) { + key = '0x' + key; + } + if (!web3.utils.isHexStrict(key)) { + console.error(`Private key is not a HEX string in file ${accountConfig.privateKeyFile} at index ${index}`); + return null; + } + return web3.eth.accounts.privateKeyToAccount(key); + }); + } + + if (accountConfig.mnemonic) { + const hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(accountConfig.mnemonic.trim())); + const addressIndex = accountConfig.addressIndex || 0; + const wallet_hdpath = accountConfig.hdpath || "m/44'/60'/0'/0/"; + const wallet = hdwallet.derivePath(wallet_hdpath + addressIndex).getWallet(); + return web3.eth.accounts.privateKeyToAccount('0x' + wallet.getPrivateKey().toString('hex')); + } + + console.error('Unsupported account configuration: ' + JSON.stringify(accountConfig)); + console.error('Try using one of those: ' + + '{ "privateKey": "your-private-key", "privateKeyFile": "path/to/file/containing/key", "mnemonic": "12 word mnemonic" }'); + + return null; + } +} + +module.exports = AccountParser; diff --git a/gas-relayer/src/message-processor.js b/gas-relayer/src/message-processor.js index b54b782..e8c823e 100644 --- a/gas-relayer/src/message-processor.js +++ b/gas-relayer/src/message-processor.js @@ -92,29 +92,37 @@ class MessageProcessor { * @param {function} reply - function to reply a message * @returns {undefined} */ - async processTransaction(contract, input, reply, cb){ + async processTransaction(contract, input, reply, account, cb){ const validationResult = await this.processStrategy(contract, input, reply); + const {toHex} = this.web3.utils; + if(!validationResult.success) return; const {toBN} = this.web3.utils; + const gasPrice = toBN(await this.web3.eth.getGasPrice()).add(toBN(this.config.gasPrice.modifier)).toString(); - let p = { - from: this.config.node.blockchain.account, - to: input.contract, - value: 0, - data: input.payload, - gasPrice - }; + const nonce = await this.web3.eth.getTransactionCount(this.config.node.blockchain.account.address); if(!validationResult.estimatedGas){ validationResult.estimatedGas = await this.web3.eth.estimateGas(p); } - p.gas = Math.floor(parseInt(validationResult.estimatedGas, 10)); // Tune this + const estimatedGas = parseInt(validationResult.estimatedGas, 10); - const nodeBalance = await this.web3.eth.getBalance(this.config.node.blockchain.account); + let p = { + from: this.config.node.blockchain.account.address, + to: input.contract, + value: "0x00", + data: input.payload, + nonce: toHex(nonce), + gasPrice: toHex(parseInt(gasPrice, 10)), + gasLimit: toHex(estimatedGas) // Tune this, + }; + + + const nodeBalance = await this.web3.eth.getBalance(this.config.node.blockchain.account.address); if(nodeBalance < p.gas){ reply("Relayer unavailable"); @@ -122,14 +130,16 @@ class MessageProcessor { this.events.emit('exit'); } else { try { - this.web3.eth.sendTransaction(p) - .on('transactionHash', function(hash){ - reply("Transaction broadcasted: " + hash); - cb(); - }) - .on('receipt', function(receipt){ - reply("Transaction mined", receipt); - }); + const signedTrx = await account.signTransaction(p); + + this.web3.eth.sendSignedTransaction(signedTrx.rawTransaction) + .on('transactionHash', function(hash){ + reply("Transaction broadcasted: " + hash); + cb(); + }) + .on('receipt', function(receipt){ + reply("Transaction mined", receipt); + }); } catch(err){ reply("Couldn't mine transaction: " + err.message); diff --git a/gas-relayer/src/service.js b/gas-relayer/src/service.js index 7b3439c..80f7de3 100644 --- a/gas-relayer/src/service.js +++ b/gas-relayer/src/service.js @@ -6,7 +6,8 @@ const MessageProcessor = require('./message-processor'); const JSum = require('jsum'); const logger = require('consola'); const winston = require('winston'); -var cache = require('memory-cache'); +const cache = require('memory-cache'); +const accountParser = require('./account-parser'); // Setting up logging const wLogger = winston.createLogger({ @@ -28,6 +29,8 @@ const events = new EventEmitter(); const connectionURL = `${config.node.local.protocol}://${config.node.local.host}:${config.node.local.port}`; const wsProvider = new Web3.providers.WebsocketProvider(connectionURL, {headers: {Origin: "gas-relayer"}}); const web3 = new Web3(wsProvider); +let account; + web3.eth.net.isListening() .then(() => events.emit('web3:connected', connectionURL)) @@ -39,6 +42,15 @@ web3.eth.net.isListening() events.on('web3:connected', connURL => { logger.info("Connected to '" + connURL + "'"); + + + account = accountParser.get(config.node.blockchain, web3); + if(!account) { + process.exit(1); + } else { + config.node.blockchain.account = account; + } + let settings = new ContractSettings(config, web3, events, logger); settings.process(); }); @@ -50,10 +62,10 @@ const shhOptions = { }; const verifyBalance = async (exitSubs) => { - const nodeBalance = await web3.eth.getBalance(config.node.blockchain.account); + const nodeBalance = await web3.eth.getBalance(config.node.blockchain.account.address); if(web3.utils.toBN(nodeBalance).lte(web3.utils.toBN(100000))){ // TODO: tune minimum amount required for transactions logger.info("Not enough balance available for processing transactions"); - logger.info("> Account: " + config.node.blockchain.account); + logger.info("> Account: " + config.node.blockchain.account.address); logger.info("> Balance: " + nodeBalance); if(exitSubs){ @@ -171,6 +183,7 @@ events.on('server:listen', (shhOptions, settings) => { processor.processTransaction(settings.getContractByTopic(message.topic), input, reply, + account, () => { cache.put(inputCheckSum, (new Date().getTime()), 3600000); } diff --git a/gas-relayer/src/strategy/AvailabilityStrategy.js b/gas-relayer/src/strategy/AvailabilityStrategy.js index 2558e7c..0c3f436 100644 --- a/gas-relayer/src/strategy/AvailabilityStrategy.js +++ b/gas-relayer/src/strategy/AvailabilityStrategy.js @@ -32,7 +32,7 @@ class AvailabilityStrategy extends Strategy { success: true, message: { message: "Available", - address: this.config.node.blockchain.account, + address: this.config.node.blockchain.account.address, minGasPrice: gasPrices.inTokens.toString(), gasPriceETH: gasPrices.inEther.add(toBN(this.config.gasPrice.modifier)).toString() } diff --git a/gas-relayer/src/strategy/BaseStrategy.js b/gas-relayer/src/strategy/BaseStrategy.js index a697be1..d947d02 100644 --- a/gas-relayer/src/strategy/BaseStrategy.js +++ b/gas-relayer/src/strategy/BaseStrategy.js @@ -84,7 +84,7 @@ class BaseStrategy { */ async _estimateGas(input){ let p = { - from: this.config.node.blockchain.account, + from: this.config.node.blockchain.account.address, to: input.contract, data: input.payload }; diff --git a/gas-relayer/src/strategy/IdentityStrategy.js b/gas-relayer/src/strategy/IdentityStrategy.js index 0d505d8..e66037f 100644 --- a/gas-relayer/src/strategy/IdentityStrategy.js +++ b/gas-relayer/src/strategy/IdentityStrategy.js @@ -82,7 +82,7 @@ class IdentityStrategy extends Strategy { if(exc.message.indexOf("revert") > -1) return {success: false, message: "Transaction will revert"}; else { console.error(exc); - return {success: false, message: "Couldn't process transaction"}; + return {success: false, message: "Transaction will fail"}; } } diff --git a/gas-relayer/src/strategy/SNTStrategy.js b/gas-relayer/src/strategy/SNTStrategy.js index eb5b9b9..6eb53a7 100644 --- a/gas-relayer/src/strategy/SNTStrategy.js +++ b/gas-relayer/src/strategy/SNTStrategy.js @@ -23,11 +23,20 @@ class SNTStrategy extends Strategy { const balance = await this.getBalance(input.address, token); - const estimatedGas = await this.web3.eth.estimateGas({ - data: input.payload, - from: this.config.node.blockchain.account, - to: input.contract - }); + let estimatedGas; + try { + estimatedGas = await this.web3.eth.estimateGas({ + data: input.payload, + from: this.config.node.blockchain.account.address, + to: input.contract + }); + } catch(exc){ + if(exc.message.indexOf("revert") > -1) return {success: false, message: "Transaction will revert"}; + else { + console.error(exc); + return {success: false, message: "Transaction will fail"}; + } + } let tokenRate = await this.getTokenRate(token, cache); if(!tokenRate){