From 031ccc37ee6e5cc5909201d46237989ee2d1cfe6 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Thu, 9 Aug 2018 15:33:07 -0400 Subject: [PATCH] add contract logs --- embark-ui/src/actions/index.js | 7 + embark-ui/src/api/index.js | 4 + embark-ui/src/components/ContractLayout.js | 10 + embark-ui/src/components/ContractLogger.js | 47 ++++ .../src/containers/ContractLoggerContainer.js | 42 ++++ embark-ui/src/reducers/index.js | 4 + embark-ui/src/reducers/selectors.js | 5 + embark-ui/src/sagas/index.js | 8 +- lib/contracts/proxy.js | 204 +++++++++--------- lib/modules/console_listener/index.js | 16 ++ 10 files changed, 245 insertions(+), 102 deletions(-) create mode 100644 embark-ui/src/components/ContractLogger.js create mode 100644 embark-ui/src/containers/ContractLoggerContainer.js diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index 7cc92cd26..9fe035060 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -76,6 +76,13 @@ export const processLogs = { failure: (error) => action(PROCESS_LOGS[FAILURE], {error}) }; +export const CONTRACT_LOGS = createRequestTypes('CONTRACT_LOGS'); +export const contractLogs = { + request: () => action(CONTRACT_LOGS[REQUEST]), + success: (contractLogs) => action(CONTRACT_LOGS[SUCCESS], {contractLogs}), + failure: (error) => action(CONTRACT_LOGS[FAILURE], {error}) +}; + export const CONTRACTS = createRequestTypes('CONTRACTS'); export const contracts = { request: () => action(CONTRACTS[REQUEST]), diff --git a/embark-ui/src/api/index.js b/embark-ui/src/api/index.js index 48dbbda18..a4c6b3f9d 100644 --- a/embark-ui/src/api/index.js +++ b/embark-ui/src/api/index.js @@ -56,6 +56,10 @@ export function fetchProcessLogs(payload) { return get(`/process-logs/${payload.processName}`); } +export function fetchContractLogs() { + return get(`/contracts/logs`); +} + export function fetchContracts() { return get('/contracts'); } diff --git a/embark-ui/src/components/ContractLayout.js b/embark-ui/src/components/ContractLayout.js index e6afe1fbe..5a4dd9bc0 100644 --- a/embark-ui/src/components/ContractLayout.js +++ b/embark-ui/src/components/ContractLayout.js @@ -8,6 +8,7 @@ import { } from "tabler-react"; import ContractContainer from '../containers/ContractContainer'; +import ContractLoggerContainer from '../containers/ContractLoggerContainer'; import ContractProfileContainer from '../containers/ContractProfileContainer'; const ContractLayout = (props) => ( @@ -48,11 +49,20 @@ const ContractLayout = (props) => ( > Profile + + Logger + + diff --git a/embark-ui/src/components/ContractLogger.js b/embark-ui/src/components/ContractLogger.js new file mode 100644 index 000000000..8a1bfd73d --- /dev/null +++ b/embark-ui/src/components/ContractLogger.js @@ -0,0 +1,47 @@ +import PropTypes from "prop-types"; +import React from 'react'; +import { + Page, + Grid, Table +} from "tabler-react"; + +const ContractLogger = ({contractName, contractLogs}) => ( + + + + { + return ([ + {content: log.functionName}, + {content: log.paramString}, + {content: log.transactionHash}, + {content: log.gasUsed}, + {content: log.blockNumber}, + {content: log.status} + ]); + }) + } + /> + + + +); + +ContractLogger.propTypes = { + contractName: PropTypes.string.isRequired, + contractLogs: PropTypes.array.isRequired +}; + +export default ContractLogger; + diff --git a/embark-ui/src/containers/ContractLoggerContainer.js b/embark-ui/src/containers/ContractLoggerContainer.js new file mode 100644 index 000000000..33dd5bb8c --- /dev/null +++ b/embark-ui/src/containers/ContractLoggerContainer.js @@ -0,0 +1,42 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import PropTypes from 'prop-types'; +import {withRouter} from 'react-router-dom'; +import {contractLogs as contractLogsAction} from '../actions'; + +import ContractLogger from '../components/ContractLogger'; +// import DataWrapper from "../components/DataWrapper"; +import {getContractLogsByContract} from "../reducers/selectors"; + +class ContractProfileContainer extends Component { + componentDidMount() { + if (this.props.contractLogs.length === 0) { + this.props.fetchContractLogs(this.props.match.params.contractName); + } + } + + render() { + return ( + + ); + } +} + +function mapStateToProps(state, props) { + return { + contractLogs: getContractLogsByContract(state, props.match.params.contractName) + }; +} + +ContractProfileContainer.propTypes = { + contractLogs: PropTypes.array, + fetchContractLogs: PropTypes.func, + match: PropTypes.object +}; + +export default withRouter(connect( + mapStateToProps, + { + fetchContractLogs: contractLogsAction.request + } +)(ContractProfileContainer)); diff --git a/embark-ui/src/reducers/index.js b/embark-ui/src/reducers/index.js index d62f0c6d5..19275d675 100644 --- a/embark-ui/src/reducers/index.js +++ b/embark-ui/src/reducers/index.js @@ -11,6 +11,7 @@ const entitiesDefaultState = { processLogs: [], contracts: [], contractProfiles: [], + contractLogs: [], commands: [], messages: [], messageChannels: [], @@ -27,6 +28,9 @@ const sorter = { processLogs: function(a, b) { return a.timestamp - b.timestamp; }, + contractLogs: function(a, b) { + return a.timestamp - b.timestamp; + }, messages: function(a, b) { return a.time - b.time; } diff --git a/embark-ui/src/reducers/selectors.js b/embark-ui/src/reducers/selectors.js index fb98d86a9..f39e0277c 100644 --- a/embark-ui/src/reducers/selectors.js +++ b/embark-ui/src/reducers/selectors.js @@ -46,6 +46,11 @@ export function getProcessLogsByProcess(state, processName) { return state.entities.processLogs.filter((processLog => processLog.name === processName)); } +export function getContractLogsByContract(state, contractName) { + return state.entities.contractLogs; + // return state.entities.processLogs.filter((processLog => processLog.name === processName)); +} + export function getContracts(state) { return state.entities.contracts; } diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index 6929c0c5f..0044eebbb 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -4,7 +4,7 @@ import {eventChannel} from 'redux-saga'; import {all, call, fork, put, takeEvery, take} from 'redux-saga/effects'; const {account, accounts, block, blocks, transaction, transactions, processes, commands, processLogs, - contracts, contract, contractProfile, messageSend, messageVersion, messageListen} = actions; + contracts, contract, contractProfile, messageSend, messageVersion, messageListen, contractLogs} = actions; function *doRequest(entity, apiFn, payload) { const {response, error} = yield call(apiFn, payload); @@ -24,6 +24,7 @@ export const fetchTransactions = doRequest.bind(null, transactions, api.fetchTra export const fetchProcesses = doRequest.bind(null, processes, api.fetchProcesses); export const postCommand = doRequest.bind(null, commands, api.postCommand); export const fetchProcessLogs = doRequest.bind(null, processLogs, api.fetchProcessLogs); +export const fetchContractLogs = doRequest.bind(null, contractLogs, api.fetchContractLogs); export const fetchContracts = doRequest.bind(null, contracts, api.fetchContracts); export const fetchContract = doRequest.bind(null, contract, api.fetchContract); export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile); @@ -64,6 +65,10 @@ export function *watchFetchProcessLogs() { yield takeEvery(actions.PROCESS_LOGS[actions.REQUEST], fetchProcessLogs); } +export function *watchFetchContractLogs() { + yield takeEvery(actions.CONTRACT_LOGS[actions.REQUEST], fetchContractLogs); +} + export function *watchFetchContract() { yield takeEvery(actions.CONTRACT[actions.REQUEST], fetchContract); } @@ -146,6 +151,7 @@ export default function *root() { fork(watchFetchAccount), fork(watchFetchProcesses), fork(watchFetchProcessLogs), + fork(watchFetchContractLogs), fork(watchListenToProcessLogs), fork(watchFetchBlock), fork(watchFetchTransactions), diff --git a/lib/contracts/proxy.js b/lib/contracts/proxy.js index ce3ed4b18..db001c2fa 100644 --- a/lib/contracts/proxy.js +++ b/lib/contracts/proxy.js @@ -8,120 +8,122 @@ let receipts = {}; const {canonicalHost, defaultHost} = require('../utils/host'); -const parseRequest = function(reqBody){ - let jsonO; - try { - jsonO = JSON.parse(reqBody); - } catch(e){ - return; // Request is not a json. Do nothing - } - if(jsonO.method === "eth_sendTransaction"){ - commList[jsonO.id] = { - type: 'contract-log', - address: jsonO.params[0].to, - data: jsonO.params[0].data - }; - } else if(jsonO.method === "eth_getTransactionReceipt"){ - if(transactions[jsonO.params[0]]){ - transactions[jsonO.params[0]].receiptId = jsonO.id; - receipts[jsonO.id] = transactions[jsonO.params[0]].commListId; - } +const parseRequest = function(reqBody) { + let jsonO; + try { + jsonO = JSON.parse(reqBody); + } catch (e) { + return; // Request is not a json. Do nothing + } + if (jsonO.method === "eth_sendTransaction") { + commList[jsonO.id] = { + type: 'contract-log', + address: jsonO.params[0].to, + data: jsonO.params[0].data + }; + } else if (jsonO.method === "eth_getTransactionReceipt") { + if (transactions[jsonO.params[0]]) { + transactions[jsonO.params[0]].receiptId = jsonO.id; + receipts[jsonO.id] = transactions[jsonO.params[0]].commListId; } + } }; -const parseResponse = function(ipc, resBody){ - let jsonO; - try { - jsonO = JSON.parse(resBody); - } catch(e) { - return; // Response is not a json. Do nothing +const parseResponse = function(ipc, resBody) { + let jsonO; + try { + jsonO = JSON.parse(resBody); + } catch (e) { + return; // Response is not a json. Do nothing + } + + if (commList[jsonO.id]) { + commList[jsonO.id].transactionHash = jsonO.result; + transactions[jsonO.result] = {commListId: jsonO.id}; + } else if (receipts[jsonO.id] && jsonO.result && jsonO.result.blockNumber) { + commList[receipts[jsonO.id]].blockNumber = jsonO.result.blockNumber; + commList[receipts[jsonO.id]].gasUsed = jsonO.result.gasUsed; + commList[receipts[jsonO.id]].status = jsonO.result.status; + + if (ipc.connected && !ipc.connecting) { + ipc.request('log', commList[receipts[jsonO.id]]); + } else { + ipc.connecting = true; + ipc.connect(() => { + ipc.connecting = false; + }); } - if(commList[jsonO.id]){ - commList[jsonO.id].transactionHash = jsonO.result; - transactions[jsonO.result] = {commListId: jsonO.id}; - } else if(receipts[jsonO.id] && jsonO.result && jsonO.result.blockNumber){ - commList[receipts[jsonO.id]].blockNumber = jsonO.result.blockNumber; - commList[receipts[jsonO.id]].gasUsed = jsonO.result.gasUsed; - commList[receipts[jsonO.id]].status = jsonO.result.status; - - if(ipc.connected && !ipc.connecting){ - ipc.request('log', commList[receipts[jsonO.id]]); - } else { - ipc.connecting = true; - ipc.connect(() => { - ipc.connecting = false; - }); - } - - delete transactions[commList[receipts[jsonO.id]].transactionHash]; - delete receipts[jsonO.id]; - delete commList[jsonO.id]; - } + delete transactions[commList[receipts[jsonO.id]].transactionHash]; + delete receipts[jsonO.id]; + delete commList[jsonO.id]; + } }; -exports.serve = function(ipc, host, port, ws){ - let proxy = httpProxy.createProxyServer({ - target: { - host: canonicalHost(host), - port: port + constants.blockchain.servicePortOnProxy - }, - ws: ws +exports.serve = function(ipc, host, port, ws) { + let proxy = httpProxy.createProxyServer({ + target: { + host: canonicalHost(host), + port: port + constants.blockchain.servicePortOnProxy + }, + ws: ws + }); + + proxy.on('error', function(e) { + console.error(__("Error forwarding requests to blockchain/simulator"), e.message); + }); + + proxy.on('proxyRes', (proxyRes) => { + let resBody = []; + proxyRes.on('data', (b) => resBody.push(b)); + proxyRes.on('end', function() { + resBody = Buffer.concat(resBody).toString(); + if (resBody) { + parseResponse(ipc, resBody); + } }); + }); - proxy.on('error', function (e) { - console.error(__("Error forwarding requests to blockchain/simulator"), e.message); - }); - - proxy.on('proxyRes', (proxyRes) => { - let resBody = []; - proxyRes.on('data', (b) => resBody.push(b)); - proxyRes.on('end', function () { - resBody = Buffer.concat(resBody).toString(); - if(resBody){ - parseResponse(ipc, resBody); - } - }); - }); - - let server = http.createServer((req, res) => { - let reqBody = []; - req.on('data', (b) => { reqBody.push(b); }) - .on('end', () => { - reqBody = Buffer.concat(reqBody).toString(); - if(reqBody){ - parseRequest(reqBody); - } - }); - - if(!ws){ - proxy.web(req, res); + let server = http.createServer((req, res) => { + let reqBody = []; + req.on('data', (b) => { + reqBody.push(b); + }) + .on('end', () => { + reqBody = Buffer.concat(reqBody).toString(); + if (reqBody) { + parseRequest(reqBody); } + }); + + if (!ws) { + proxy.web(req, res); + } + }); + + if (ws) { + const WsParser = require('simples/lib/parsers/ws'); // npm install simples + + server.on('upgrade', function(req, socket, head) { + proxy.ws(req, socket, head); }); - if(ws){ - const WsParser = require('simples/lib/parsers/ws'); // npm install simples + proxy.on('open', (proxySocket) => { + proxySocket.on('data', (data) => { + parseResponse(ipc, data.toString().substr(data.indexOf("{"))); + }); + }); - server.on('upgrade', function (req, socket, head) { - proxy.ws(req, socket, head); - }); + proxy.on('proxyReqWs', (proxyReq, req, socket) => { + var parser = new WsParser(0, false); + socket.pipe(parser); + parser.on('frame', function(frame) { + parseRequest(frame.data); + }); - proxy.on('open', (proxySocket) => { - proxySocket.on('data', (data) => { - parseResponse(ipc, data.toString().substr(data.indexOf("{"))); - }); - }); + }); + } - proxy.on('proxyReqWs', (proxyReq, req, socket) => { - var parser = new WsParser(0, false); - socket.pipe(parser); - parser.on('frame', function (frame) { - parseRequest(frame.data); - }); - - }); - } - - server.listen(port, defaultHost); - return server; + server.listen(port, defaultHost); + return server; }; diff --git a/lib/modules/console_listener/index.js b/lib/modules/console_listener/index.js index 6a6e557e9..971105f9b 100644 --- a/lib/modules/console_listener/index.js +++ b/lib/modules/console_listener/index.js @@ -2,10 +2,12 @@ const utils = require('../../utils/utils.js'); class ConsoleListener { constructor(embark, options) { + this.embark = embark; this.logger = embark.logger; this.ipc = options.ipc; this.events = embark.events; this.addressToContract = []; + this.logs = []; this.contractsConfig = embark.config.contractsConfig; this.contractsDeployed = false; this.outputDone = false; @@ -13,6 +15,7 @@ class ConsoleListener { this.events.on('outputDone', () => { this.outputDone = true; }); + this._registerAPI(); this.events.on("contractsDeployed", () => { this.contractsDeployed = true; @@ -91,12 +94,25 @@ class ConsoleListener { gasUsed = utils.hexToNumber(gasUsed); blockNumber = utils.hexToNumber(blockNumber); + this.logs.push(Object.assign({}, request, {name, functionName, paramString})); + this.logger.info(`Blockchain>`.underline + ` ${name}.${functionName}(${paramString})`.bold + ` | ${transactionHash} | gas:${gasUsed} | blk:${blockNumber} | status:${status}`); } else { this.logger.info(JSON.stringify(request)); } }); } + + _registerAPI() { + const apiRoute = '/embark-api/contracts/logs'; + this.embark.registerAPICall( + 'get', + apiRoute, + (req, res) => { + res.send(JSON.stringify(this.logs)); + } + ); + } } module.exports = ConsoleListener;