diff --git a/src/lib/constants.json b/src/lib/constants.json index ebe87973d6..47bfa9a279 100644 --- a/src/lib/constants.json +++ b/src/lib/constants.json @@ -48,7 +48,12 @@ "rinkeby": 4 }, "gasAllowanceError": "Returned error: gas required exceeds allowance or always failing transaction", - "gasAllowanceErrorMessage": "Failing call, this could be because of invalid inputs or function guards that may have been triggered, or an unknown error." + "gasAllowanceErrorMessage": "Failing call, this could be because of invalid inputs or function guards that may have been triggered, or an unknown error.", + "transactionMethods": { + "eth_sendTransaction": "eth_sendTransaction", + "eth_sendRawTransaction": "eth_sendRawTransaction", + "eth_getTransactionReceipt": "eth_getTransactionReceipt" + } }, "storage": { "init": "init", diff --git a/src/lib/modules/blockchain_connector/index.js b/src/lib/modules/blockchain_connector/index.js index 9445c44be2..3152c9c2ce 100644 --- a/src/lib/modules/blockchain_connector/index.js +++ b/src/lib/modules/blockchain_connector/index.js @@ -128,7 +128,7 @@ class BlockchainConnector { let newParams = Object.assign({}, payload.params[0]); let newPayload = { id: payload.id + 1, - method: 'eth_sendTransaction', + method: constants.blockchain.transactionMethods.eth_sendTransaction, params: [newParams], jsonrpc: payload.jsonrpc }; diff --git a/src/lib/modules/blockchain_connector/provider.js b/src/lib/modules/blockchain_connector/provider.js index 77b7f569d3..145bbadf4a 100644 --- a/src/lib/modules/blockchain_connector/provider.js +++ b/src/lib/modules/blockchain_connector/provider.js @@ -121,7 +121,7 @@ class Provider { } cb(null, result); }); - } else if (payload.method === 'eth_sendRawTransaction') { + } else if (payload.method === constants.blockchain.transactionMethods.eth_sendRawTransaction) { return self.runTransaction.push({payload}, cb); } diff --git a/src/lib/modules/blockchain_process/blockchain.js b/src/lib/modules/blockchain_process/blockchain.js index d1dcc6f8b4..9758ecd470 100644 --- a/src/lib/modules/blockchain_process/blockchain.js +++ b/src/lib/modules/blockchain_process/blockchain.js @@ -7,7 +7,7 @@ const utils = require('../../utils/utils.js'); const GethClient = require('./gethClient.js'); const ParityClient = require('./parityClient.js'); const DevFunds = require('./dev_funds.js'); -const proxy = require('./proxy'); +const Proxy = require('./proxy'); const Ipc = require('../../core/ipc'); const {defaultHost, dockerHostSwap} = require('../../utils/host'); @@ -154,10 +154,10 @@ Blockchain.prototype.setupProxy = async function () { let wsProxy; if (this.config.wsRPC) { - wsProxy = proxy.serve(this.proxyIpc, this.config.wsHost, this.config.wsPort, true, this.config.wsOrigins, addresses, this.certOptions); + wsProxy = new Proxy(this.proxyIpc).serve(this.config.wsHost, this.config.wsPort, true, this.config.wsOrigins, addresses, this.certOptions); } - [this.rpcProxy, this.wsProxy] = await Promise.all([proxy.serve(this.proxyIpc, this.config.rpcHost, this.config.rpcPort, false, null, addresses, this.certOptions), wsProxy]); + [this.rpcProxy, this.wsProxy] = await Promise.all([new Proxy(this.proxyIpc).serve(this.config.rpcHost, this.config.rpcPort, false, null, addresses, this.certOptions), wsProxy]); }; Blockchain.prototype.shutdownProxy = function () { diff --git a/src/lib/modules/blockchain_process/proxy.js b/src/lib/modules/blockchain_process/proxy.js index 30823b3ee2..4c1b70c04c 100644 --- a/src/lib/modules/blockchain_process/proxy.js +++ b/src/lib/modules/blockchain_process/proxy.js @@ -13,6 +13,8 @@ const utils = require('../../utils/utils'); const WsParser = require('simples/lib/parsers/ws'); const WsWrapper = require('simples/lib/ws/wrapper'); const modifyResponse = require('node-http-proxy-json'); +const Transaction = require('ethereumjs-tx'); +const ethUtil = require('ethereumjs-util'); const METHODS_TO_MODIFY = {accounts: 'eth_accounts'}; @@ -48,28 +50,39 @@ const parseJsonMaybe = (string) => { return object; }; -exports.serve = async (ipc, host, port, ws, origin, accounts, certOptions={}) => { - const commList = {}; - const receipts = {}; - const transactions = {}; - const toModifyPayloads = {}; +class Proxy { + constructor(ipc) { + this.ipc = ipc; + this.commList = {}; + this.receipts = {}; + this.transactions = {}; + this.toModifyPayloads = {}; + } - const trackRequest = (req) => { + trackRequest(req) { if (!req) return; try { if (Object.values(METHODS_TO_MODIFY).includes(req.method)) { - toModifyPayloads[req.id] = req.method; + this.toModifyPayloads[req.id] = req.method; } - if (req.method === 'eth_sendTransaction') { - commList[req.id] = { + if (req.method === constants.blockchain.transactionMethods.eth_sendTransaction) { + this.commList[req.id] = { type: 'contract-log', address: req.params[0].to, data: req.params[0].data }; - } else if (req.method === 'eth_getTransactionReceipt') { - if (transactions[req.params[0]]) { - transactions[req.params[0]].receiptId = req.id; - receipts[req.id] = transactions[req.params[0]].commListId; + } else if (req.method === constants.blockchain.transactionMethods.eth_sendRawTransaction) { + const rawData = Buffer.from(ethUtil.stripHexPrefix(req.params[0]), 'hex'); + const tx = new Transaction(rawData, 'hex'); + this.commList[req.id] = { + type: 'contract-log', + address: '0x' + tx.to.toString('hex'), + data: '0x' + tx.data.toString('hex') + }; + } else if (req.method === constants.blockchain.transactionMethods.eth_getTransactionReceipt) { + if (this.transactions[req.params[0]]) { + this.transactions[req.params[0]].receiptId = req.id; + this.receipts[req.id] = this.transactions[req.params[0]].commListId; } } } catch (e) { @@ -77,151 +90,157 @@ exports.serve = async (ipc, host, port, ws, origin, accounts, certOptions={}) => `Proxy: Error tracking request message '${JSON.stringify(req)}'`, ); } - }; + } - const trackResponse = (res) => { + trackResponse(res) { if (!res) return; try { - if (commList[res.id]) { - commList[res.id].transactionHash = res.result; - transactions[res.result] = {commListId: res.id}; - } else if (receipts[res.id] && res.result && res.result.blockNumber) { + if (this.commList[res.id]) { + this.commList[res.id].transactionHash = res.result; + this.transactions[res.result] = { + commListId: res.id + }; + } else if (this.receipts[res.id] && res.result && res.result.blockNumber) { // TODO find out why commList[receipts[res.id]] is sometimes not defined - if (!commList[receipts[res.id]]) { - commList[receipts[res.id]] = {}; + if (!this.commList[this.receipts[res.id]]) { + this.commList[this.receipts[res.id]] = {}; } - commList[receipts[res.id]].blockNumber = res.result.blockNumber; - commList[receipts[res.id]].gasUsed = res.result.gasUsed; - commList[receipts[res.id]].status = res.result.status; + this.commList[this.receipts[res.id]].blockNumber = res.result.blockNumber; + this.commList[this.receipts[res.id]].gasUsed = res.result.gasUsed; + this.commList[this.receipts[res.id]].status = res.result.status; - if (ipc.connected && !ipc.connecting) { - ipc.request('log', commList[receipts[res.id]]); + if (this.ipc.connected && !this.ipc.connecting) { + this.ipc.request('log', this.commList[this.receipts[res.id]]); } else { - const message = commList[receipts[res.id]]; - ipc.connecting = true; - ipc.connect(() => { - ipc.connecting = false; - ipc.request('log', message); + const message = this.commList[this.receipts[res.id]]; + this.ipc.connecting = true; + this.ipc.connect(() => { + this.ipc.connecting = false; + this.ipc.request('log', message); }); } - delete transactions[commList[receipts[res.id]].transactionHash]; - delete commList[receipts[res.id]]; - delete receipts[res.id]; + delete this.transactions[this.commList[this.receipts[res.id]].transactionHash]; + delete this.commList[this.receipts[res.id]]; + delete this.receipts[res.id]; } } catch (e) { console.error( `Proxy: Error tracking response message '${JSON.stringify(res)}'` ); } - }; + } - const start = Date.now(); - await (function waitOnTarget() { - return new Promise(resolve => { - utils.pingEndpoint( - canonicalHost(host), - port, - ws ? 'ws': false, - 'http', - origin ? origin.split(',')[0] : undefined, - (err) => { - if (!err || (Date.now() - start > 10000)) { - resolve(); - } else { - utils.timer(250).then(waitOnTarget).then(resolve); + async serve(host, port, ws, origin, accounts, certOptions={}) { + const start = Date.now(); + await (function waitOnTarget() { + return new Promise(resolve => { + utils.pingEndpoint( + canonicalHost(host), + port, + ws ? 'ws': false, + 'http', + origin ? origin.split(',')[0] : undefined, + (err) => { + if (!err || (Date.now() - start > 10000)) { + resolve(); + } else { + utils.timer(250).then(waitOnTarget).then(resolve); + } } - } + ); + }); + }()); + + let proxy = httpProxy.createProxyServer({ + ssl: certOptions, + target: { + host: canonicalHost(host), + port: port + }, + ws: ws, + createWsServerTransformStream: (_req, _proxyReq, _proxyRes) => { + const parser = new WsParser(0, true); + parser.on('frame', ({data: buffer}) => { + let object = parseJsonMaybe(buffer.toString()); + if (object) { + object = modifyPayload(this.toModifyPayloads, object, accounts); + // track the modified response + this.trackResponse(object); + // send the modified response + WsWrapper.wrap( + {connection: dupl, masked: 0}, + Buffer.from(JSON.stringify(object)), + () => {} + ); + } + }); + const dupl = new Duplex({ + read(_size) {}, + write(chunk, encoding, callback) { + parser.write(chunk); + callback(); + } + }); + return dupl; + } + }); + + proxy.on('error', (err) => { + console.error( + __('Proxy: Error forwarding requests to blockchain/simulator'), + err.message ); }); - }()); - - let proxy = httpProxy.createProxyServer({ - ssl: certOptions, - target: { - host: canonicalHost(host), - port: port - }, - ws: ws, - createWsServerTransformStream: (_req, _proxyReq, _proxyRes) => { - const parser = new WsParser(0, true); - parser.on('frame', ({data: buffer}) => { - let object = parseJsonMaybe(buffer.toString()); - if (object) { - object = modifyPayload(toModifyPayloads, object, accounts); - // track the modified response - trackResponse(object); - // send the modified response - WsWrapper.wrap( - {connection: dupl, masked: 0}, - Buffer.from(JSON.stringify(object)), - () => {} - ); - } - }); - const dupl = new Duplex({ - read(_size) {}, - write(chunk, encoding, callback) { - parser.write(chunk); - callback(); + + proxy.on('proxyRes', (proxyRes, req, res) => { + modifyResponse(res, proxyRes, (body) => { + if (body) { + body = modifyPayload(this.toModifyPayloads, body, accounts); + this.trackResponse(body); } + return body; }); - return dupl; - } - }); - - proxy.on('error', (err) => { - console.error( - __('Proxy: Error forwarding requests to blockchain/simulator'), - err.message - ); - }); - - proxy.on('proxyRes', (proxyRes, req, res) => { - modifyResponse(res, proxyRes, (body) => { - if (body) { - body = modifyPayload(toModifyPayloads, body, accounts); - trackResponse(body); - } - return body; }); - }); - - const server = http.createServer((req, res) => { - if (req.method === 'POST') { - // messages TO the target - Asm.connectTo( - pump(req, jsonParser()) - ).on('done', ({current: object}) => { - trackRequest(object); - }); - } - if (!ws) { - proxy.web(req, res); - } - }); + const server = http.createServer((req, res) => { + if (req.method === 'POST') { + // messages TO the target + Asm.connectTo( + pump(req, jsonParser()) + ).on('done', ({current: object}) => { + this.trackRequest(object); + }); + } - if (ws) { - server.on('upgrade', (msg, socket, head) => { - proxy.ws(msg, socket, head); + if (!ws) { + proxy.web(req, res); + } }); - proxy.on('open', (_proxySocket) => { /* messages FROM the target */ }); + if (ws) { + server.on('upgrade', (msg, socket, head) => { + proxy.ws(msg, socket, head); + }); + + proxy.on('open', (_proxySocket) => { /* messages FROM the target */ }); - proxy.on('proxyReqWs', (_proxyReq, _req, socket) => { - // messages TO the target - pump(socket, new WsParser(0, false)).on('frame', ({data: buffer}) => { - const object = parseJsonMaybe(buffer.toString()); - trackRequest(object); + proxy.on('proxyReqWs', (_proxyReq, _req, socket) => { + // messages TO the target + pump(socket, new WsParser(0, false)).on('frame', ({data: buffer}) => { + const object = parseJsonMaybe(buffer.toString()); + this.trackRequest(object); + }); }); + } + + return new Promise(resolve => { + server.listen( + port - constants.blockchain.servicePortOnProxy, + defaultHost, + () => { resolve(server); } + ); }); } +} - return new Promise(resolve => { - server.listen( - port - constants.blockchain.servicePortOnProxy, - defaultHost, - () => { resolve(server); } - ); - }); -}; +module.exports = Proxy; diff --git a/src/lib/modules/blockchain_process/simulator.js b/src/lib/modules/blockchain_process/simulator.js index 395edda49f..bb7c64b4f1 100644 --- a/src/lib/modules/blockchain_process/simulator.js +++ b/src/lib/modules/blockchain_process/simulator.js @@ -1,7 +1,7 @@ const path = require('path'); const pkgUp = require('pkg-up'); let shelljs = require('shelljs'); -let proxy = require('./proxy'); +let Proxy = require('./proxy'); const Ipc = require('../../core/ipc'); const constants = require('../../constants.json'); const {defaultHost, dockerHostSwap} = require('../../utils/host'); @@ -86,7 +86,7 @@ class Simulator { if(useProxy){ let ipcObject = new Ipc({ipcRole: 'client'}); - proxy.serve(ipcObject, host, port, false, undefined); + new Proxy(ipcObject).serve(host, port, false); } } } diff --git a/src/lib/modules/console_listener/index.js b/src/lib/modules/console_listener/index.js index 6cfda686d7..e90aa0f3b5 100644 --- a/src/lib/modules/console_listener/index.js +++ b/src/lib/modules/console_listener/index.js @@ -14,7 +14,9 @@ class ConsoleListener { this.outputDone = false; this.logFile = fs.dappPath(".embark", "contractLogs.json"); - this._listenForLogRequests(); + if (this.ipc.ipcRole === 'server') { + this._listenForLogRequests(); + } this._registerAPI(); this.events.on("contracts:log", this._saveLog.bind(this)); @@ -23,7 +25,10 @@ class ConsoleListener { }); this.events.on("contractsDeployed", () => { this.contractsDeployed = true; - this._updateContractList(); + + this._getContractsList((contractsList) => { + this._updateContractList(contractsList); + }); }); this.writeLogFile = async.cargo((tasks, callback) => { @@ -42,44 +47,48 @@ class ConsoleListener { }); } - _updateContractList() { - this.events.request("contracts:list", (_err, contractsList) => { - if (_err) { + _getContractsList(callback) { + this.events.request("contracts:list", (err, contractsList) => { + if (err) { this.logger.error(__("no contracts found")); - return; + return callback(); + } + callback(contractsList); + }); + } + + _updateContractList(contractsList) { + if (!contractsList) return; + contractsList.forEach(contract => { + if (!contract.deployedAddress) return; + + let address = contract.deployedAddress.toLowerCase(); + if (!this.addressToContract[address]) { + let funcSignatures = {}; + contract.abiDefinition + .filter(func => func.type === "function") + .map(func => { + const name = func.name + + '(' + + (func.inputs ? func.inputs.map(input => input.type).join(',') : '') + + ')'; + funcSignatures[utils.sha3(name).substring(0, 10)] = { + name, + abi: func, + functionName: func.name + }; + }); + + this.addressToContract[address] = { + name: contract.className, + functions: funcSignatures, + silent: contract.silent + }; } - contractsList.forEach(contract => { - if (!contract.deployedAddress) return; - - let address = contract.deployedAddress.toLowerCase(); - if (!this.addressToContract[address]) { - let funcSignatures = {}; - contract.abiDefinition - .filter(func => func.type === "function") - .map(func => { - const name = func.name + - '(' + - (func.inputs ? func.inputs.map(input => input.type).join(',') : '') + - ')'; - funcSignatures[utils.sha3(name).substring(0, 10)] = { - name, - abi: func, - functionName: func.name - }; - }); - - this.addressToContract[address] = { - name: contract.className, - functions: funcSignatures, - silent: contract.silent - }; - } - }); }); } _listenForLogRequests() { - if (this.ipc.ipcRole !== 'server') return; this.events.on('deploy:contract:receipt', receipt => { this.events.emit('contracts:log', { name: receipt.className, @@ -94,47 +103,52 @@ class ConsoleListener { }); this.ipc.on('log', (request) => { - if (request.type !== 'contract-log') { - return this.logger.info(JSON.stringify(request)); - } + this._onIpcLogRequest(request); + }); + } - if (!this.contractsDeployed) return; + _onIpcLogRequest(request) { - let {address, data, transactionHash, blockNumber, gasUsed, status} = request; - const contract = this.addressToContract[address]; + if (request.type !== 'contract-log') { + return this.logger.info(JSON.stringify(request)); + } - if (!contract) { - this._updateContractList(); - return; - } + if (!this.contractsDeployed) return; - const {name, silent} = contract; - if (silent && !this.outputDone) { - return; - } + let {address, data, transactionHash, blockNumber, gasUsed, status} = request; + const contract = this.addressToContract[address]; - const func = contract.functions[data.substring(0, 10)]; - const functionName = func.functionName; + if (!contract) { + this.logger.info(`Contract log for unknown contract: ${JSON.stringify(request)}`); + return this._getContractsList((contractsList) => { + this._updateContractList(contractsList); + }); + } + const {name, silent} = contract; + if (silent && !this.outputDone) { + return; + } - const decodedParameters = utils.decodeParams(func.abi.inputs, data.substring(10)); - let paramString = ""; - if (func.abi.inputs) { - func.abi.inputs.forEach((input) => { - let quote = input.type.indexOf("int") === -1 ? '"' : ''; - paramString += quote + decodedParameters[input.name] + quote + ", "; - }); - paramString = paramString.substring(0, paramString.length - 2); - } + const func = contract.functions[data.substring(0, 10)]; + const functionName = func.functionName; - gasUsed = utils.hexToNumber(gasUsed); - blockNumber = utils.hexToNumber(blockNumber); + const decodedParameters = utils.decodeParams(func.abi.inputs, data.substring(10)); + let paramString = ""; + if (func.abi.inputs) { + func.abi.inputs.forEach((input) => { + let quote = input.type.indexOf("int") === -1 ? '"' : ''; + paramString += quote + decodedParameters[input.name] + quote + ", "; + }); + paramString = paramString.substring(0, paramString.length - 2); + } - const log = Object.assign({}, request, {name, functionName, paramString, gasUsed, blockNumber}); - this.events.emit('contracts:log', log); + gasUsed = utils.hexToNumber(gasUsed); + blockNumber = utils.hexToNumber(blockNumber); - this.logger.info(`Blockchain>`.underline + ` ${name}.${functionName}(${paramString})`.bold + ` | ${transactionHash} | gas:${gasUsed} | blk:${blockNumber} | status:${status}`); - this.events.emit('blockchain:tx', { name: name, functionName: functionName, paramString: paramString, transactionHash: transactionHash, gasUsed: gasUsed, blockNumber: blockNumber, status: status }); - }); + const log = Object.assign({}, request, {name, functionName, paramString, gasUsed, blockNumber}); + this.events.emit('contracts:log', log); + this.logger.info(`Blockchain>`.underline + ` ${name}.${functionName}(${paramString})`.bold + ` | ${transactionHash} | gas:${gasUsed} | blk:${blockNumber} | status:${status}`); + this.events.emit('blockchain:tx', {name: name, functionName: functionName, paramString: paramString, transactionHash: transactionHash, gasUsed: gasUsed, blockNumber: blockNumber, status: status}); } _registerAPI() { @@ -143,7 +157,7 @@ class ConsoleListener { 'ws', apiRoute, (ws, _req) => { - this.events.on('contracts:log', function(log) { + this.events.on('contracts:log', function (log) { ws.send(JSON.stringify(log), () => {}); }); } @@ -171,7 +185,7 @@ class ConsoleListener { fs.ensureFileSync(this.logFile); try { return JSON.parse(fs.readFileSync(this.logFile)); - } catch(_error) { + } catch (_error) { return {}; } } diff --git a/src/test/modules/console_listener.js b/src/test/modules/console_listener.js new file mode 100644 index 0000000000..c3ac23fdb8 --- /dev/null +++ b/src/test/modules/console_listener.js @@ -0,0 +1,262 @@ +/*globals describe, it, beforeEach*/ +const {expect} = require('chai'); +const sinon = require('sinon'); +const Events = require('../../lib/core/events'); +const Logger = require('../../lib/core/logger'); +const ConsoleListener = require('../../lib/modules/console_listener'); +const IPC = require('../../lib/core/ipc.js'); +require('colors'); + +let events, + logger, + consoleListener, + ipc, + embark, + loggerErrors = [], + loggerInfos = [], + eventsEmitted = [], + ipcRequest, + contractsList; + +function resetTest() { + loggerErrors = []; + loggerInfos = []; + eventsEmitted = []; + ipcRequest = { + type: 'contract-log', + address: "0x12345", + data: "0x6d4ce63c", + transactionHash: "hash", + blockNumber: "0x0", + gasUsed: "0x0", + status: "yay" + }; + contractsList = [{ + abiDefinition: [ + { + "constant": true, + "inputs": [], + "name": "storedData", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "x", + "type": "uint256" + } + ], + "name": "set", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "get", + "outputs": [ + { + "name": "retVal", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "initialValue", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + } + ], + deployedAddress: "0x12345", + className: "SimpleStorage", + silent: true + }]; + + events = new Events(); + logger = new Logger(events); + ipc = new IPC({ipcRole: 'none'}); + embark = { + events, + logger, + config: { + contractsConfig: {} + }, + registerAPICall: () => {} + }; + + // setup ConsoleListener + consoleListener = new ConsoleListener(embark, {ipc}); + consoleListener.contractsDeployed = true; + consoleListener.outputDone = true; + + sinon.stub(events, 'emit').callsFake((eventName, data) => { + eventsEmitted.push({eventName, data}); + return true; + }); + sinon.stub(logger, 'info').callsFake((args) => { + loggerInfos.push(args); + }); +} + +describe('Console Listener', function () { + beforeEach('reset test', function (done) { + resetTest(); + done(); + }); + + describe('#updateContractList', function () { + it('should not update contracts list', function (done) { + contractsList.deployedAddress = undefined; + consoleListener._updateContractList(contractsList); + + expect(consoleListener.addressToContract.length).to.be.equal(0); + done(); + }); + + it('should update contracts list', function (done) { + consoleListener._updateContractList(contractsList); + + expect(consoleListener.addressToContract["0x12345"]).to.deep.equal({ + name: "SimpleStorage", + functions: { + "0x2a1afcd9": { + "abi": { + "constant": true, + "inputs": [], + "name": "storedData", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + "functionName": "storedData", + "name": "storedData()" + }, + "0x60fe47b1": { + "abi": { + "constant": false, + "inputs": [ + { + "name": "x", + "type": "uint256" + } + ], + "name": "set", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "functionName": "set", + "name": "set(uint256)" + }, + "0x6d4ce63c": { + "abi": { + "constant": true, + "inputs": [], + "name": "get", + "outputs": [ + { + "name": "retVal", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + "functionName": "get", + "name": "get()" + } + }, + silent: true + }); + done(); + }); + }); + + describe('#listenForLogRequests', function () { + it('should emit the correct contracts logs', function (done) { + consoleListener._updateContractList(contractsList); + consoleListener._onIpcLogRequest(ipcRequest); + + const expectedContractLog = { + address: "0x12345", + blockNumber: 0, + data: "0x6d4ce63c", + functionName: "get", + gasUsed: 0, + name: "SimpleStorage", + paramString: "", + status: "yay", + transactionHash: "hash", + type: "contract-log" + }; + const {name, functionName, paramString, transactionHash, gasUsed, blockNumber, status} = expectedContractLog; + + const contractLogEmitted = eventsEmitted.find(event => event.eventName === 'contracts:log'); + expect(contractLogEmitted).to.deep.equal({ + eventName: 'contracts:log', + data: expectedContractLog + }); + + const blockchainTxLogEmitted = eventsEmitted.find(event => event.eventName === 'blockchain:tx'); + expect(blockchainTxLogEmitted).to.deep.equal({ + eventName: 'blockchain:tx', + data: { + name, + functionName, + paramString, + transactionHash, + gasUsed, + blockNumber, + status + } + }); + + expect(loggerInfos[0]).to.be.equal(`Blockchain>`.underline + ` ${name}.${functionName}(${paramString})`.bold + ` | ${transactionHash} | gas:${gasUsed} | blk:${blockNumber} | status:${status}`); + done(); + }); + + it('should emit a log for a non-contract log', function (done) { + ipcRequest.type = 'something-other-than-contract-log'; + consoleListener._updateContractList(contractsList); + consoleListener._onIpcLogRequest(ipcRequest); + + expect(loggerInfos[0]).to.be.equal(JSON.stringify(ipcRequest)); + done(); + }); + + it('should emit an "unknown contract" log message', function (done) { + consoleListener._onIpcLogRequest(ipcRequest); + + expect(loggerInfos[0]).to.be.equal(`Contract log for unknown contract: ${JSON.stringify(ipcRequest)}`); + done(); + }); + }); +}); diff --git a/src/test/proxy.js b/src/test/proxy.js new file mode 100644 index 0000000000..1fca794cbc --- /dev/null +++ b/src/test/proxy.js @@ -0,0 +1,156 @@ +/*globals describe, it, before*/ +const { + expect +} = require('chai'); +const sinon = require('sinon'); +let IPC = require('../lib/core/ipc.js'); +let Proxy = require('../lib/modules/blockchain_process/proxy'); +const constants = require('../lib/constants'); + +describe('embark.Proxy', function () { + let ipc, proxy, ipcRequests; + before(function () { + ipc = new IPC({ + ipcRole: 'none' + }); + ipcRequests = []; + + ipc.connected = true; + sinon.stub(ipc, 'request').callsFake((...args) => { + ipcRequests.push(args); + }); + + proxy = new Proxy(ipc); + }); + + describe('#trackRequest', function () { + it('should handle eth_sendTransaction', function (done) { + const to = "to"; + const data = "data"; + + proxy.trackRequest({ + id: 1, + method: constants.blockchain.transactionMethods.eth_sendTransaction, + params: [{ + to: to, + data: data + }] + }); + + expect(proxy.commList[1]).to.deep.equal({ + type: 'contract-log', + address: to, + data: data + }); + done(); + }); + + it('should handle eth_sendRawTransaction', function (done) { + const to = "0x2e6242a07ea1c4e79ecc5c69a2dccae19878a280"; + const data = "0x60fe47b1000000000000000000000000000000000000000000000000000000000000115c"; + + proxy.trackRequest({ + id: 1, + method: constants.blockchain.transactionMethods.eth_sendRawTransaction, + params: ["0xf8852701826857942e6242a07ea1c4e79ecc5c69a2dccae19878a28080a460fe47b1000000000000000000000000000000000000000000000000000000000000115c820a96a04d6e3cbb86d80a75cd51da02bf8f0cc9893d64ca7956ce21b350cc143aa8a023a05878a850e4e7810d08093add07df405298280fdd901ecbabb74e73422cb5e0b0"] + }); + + expect(proxy.commList[1]).to.deep.equal({ + type: 'contract-log', + address: to, + data: data + }); + done(); + }); + }); + + describe('#trackResponse', function () { + describe('when the response is a transaction', function () { + before(function () { + proxy.trackRequest({ + id: 1, + method: constants.blockchain.transactionMethods.eth_sendTransaction, + params: [{to: "to", data: "data" }] + }); + }); + + it('should populate the transaction when it is a known id', function (done) { + proxy.trackResponse({ + id: 1, + result: 1 + }); + + expect(proxy.transactions[1]).to.deep.equal({ + commListId: 1 + }); + done(); + }); + + it('should not populate the transaction when it is a unknown id', function (done) { + proxy.trackResponse({ + id: 2 + }); + + expect(proxy.transactions[2]).to.be.equal(undefined); + done(); + }); + }); + + describe('when the response is a receipt', function () { + it('should make an ipc call', function (done) { + proxy.trackRequest({ + id: 3, + method: constants.blockchain.transactionMethods.eth_sendTransaction, + params: [{ + to: "to", + data: "data" + }] + }); + + proxy.trackResponse({ + id: 3, + result: { + to: "to", + data: "data" + } + }); + + proxy.trackRequest({ + id: 4, + method: constants.blockchain.transactionMethods.eth_getTransactionReceipt, + params: [{ + to: "to", + data: "data" + }] + }); + + expect(proxy.receipts[4]).to.be.equal(3); + + proxy.trackResponse({ + id: 4, + result: { + blockNumber: 666, + gasUsed: 122, + status: 'ok' + } + }); + + expect(ipcRequests[0]).to.deep.equal([ + "log", { + "address": "to", + "blockNumber": 666, + "data": "data", + "gasUsed": 122, + "status": "ok", + "transactionHash": { + "data": "data", + "to": "to" + }, + "type": "contract-log" + } + ]); + done(); + }); + }); + }); +});